This document describes the implementation of package-aware metadata management in ObjectStack, ensuring that:
- Every metadata item belongs to a package
- Code-loaded packages are read-only
- Database packages are mutable
- Package Ownership: All metadata (objects, views, flows, etc.) must belong to a package
- Source-Based Mutability:
- Filesystem/Code packages → Read-only (scope='system', source='filesystem')
- Database packages → Mutable (scope='platform'/'user', source='database')
- Conversation Context: AI tools track active package per conversation
- Overlay Pattern: Code metadata can have database overlays for customization
The MetadataRecordSchema in packages/spec/src/system/metadata-persistence.zod.ts already includes:
{
packageId: string | undefined; // Package ownership
managedBy: 'package' | 'platform' | 'user'; // Lifecycle management
scope: 'system' | 'platform' | 'user'; // Mutability scope
source: 'filesystem' | 'database' | 'api' | 'migration'; // Origin
}Created 5 new AI tools for package management:
-
list_packages(list-packages.tool.ts)- Lists all installed packages
- Supports filtering by status and enabled state
- Returns package metadata (id, name, version, type, status)
-
get_package(get-package.tool.ts)- Gets detailed information about a specific package
- Returns full manifest, dependencies, namespaces
-
create_package(create-package.tool.ts)- Creates a new package with manifest
- Validates reverse domain notation for package ID
- Auto-derives namespace from package ID
- Automatically sets as active package in conversation
-
get_active_package(get-active-package.tool.ts)- Retrieves the currently active package from conversation context
- Returns null if no active package is set
-
set_active_package(set-active-package.tool.ts)- Sets the active package for the conversation
- All subsequent metadata operations use this package
Handler Implementation: package-tools.ts
- Implements
IPackageRegistryinterface for package CRUD - Implements
IConversationServiceinterface for context tracking - Validates package IDs (reverse domain notation)
- Validates namespaces (snake_case)
- Validates versions (semver)
Completed:
-
Updated
MetadataToolContextinterface to include:conversationService- for tracking active packageconversationId- current conversation contextpackageRegistry- for validating packages and checking read-only status
-
Added
packageIdparameter tocreate_objecttool- Optional parameter
- Falls back to active package from conversation
- Provides clear error message if no package context available
Remaining Work:
-
Update
createObjectHandlerto:- Resolve package ID (explicit > active > error)
- Check if package is read-only
- Attach package metadata to object definition
- Return package info in success response
-
Update other metadata tools (
add_field,modify_field,delete_field)- Add
packageIdparameter where appropriate - Implement read-only validation
- Add
Objectives:
- Store
activePackageIdin conversation metadata - Persist across conversation turns
- Clear on conversation end
Implementation Plan:
// In conversation service
interface ConversationMetadata {
activePackageId?: string;
lastPackageOperation?: string;
createdAt?: string;
}
// Store in database table: ai_conversation_metadata
{
conversation_id: string;
metadata: JSON; // Contains activePackageId
updated_at: timestamp;
}Objectives:
- Prevent modification of code-based metadata
- Allow database metadata modifications
- Support customization overlays for code metadata
Implementation Plan:
- Add source tracking to metadata registration:
// In metadata service
async register(type: string, name: string, data: unknown, options?: {
packageId?: string;
scope?: 'system' | 'platform' | 'user';
source?: 'filesystem' | 'database' | 'api';
}): Promise<void>- Implement read-only check:
async register(type: string, name: string, data: unknown, options) {
const existing = await this.get(type, name);
if (existing) {
const metadata = existing as MetadataRecord;
// Block if trying to modify code-based metadata
if (metadata.scope === 'system' || metadata.source === 'filesystem') {
throw new Error(
`Cannot modify ${type} "${name}" - it is code-based metadata. ` +
`Use overlay customization instead via saveOverlay().`
);
}
}
// Proceed with registration for database metadata
await this.storage.save(type, name, { ...data, ...options });
}- Support overlay pattern:
// Allow customization of code metadata via overlays
await metadataService.saveOverlay({
type: 'object',
name: 'account',
scope: 'platform', // or 'user'
overlay: {
fields: {
custom_field: { type: 'text', label: 'Custom Field' }
}
}
});
// Runtime serves merged result:
// base (from code) + platform overlay + user overlay
const effective = await metadataService.getEffective('object', 'account', context);Unit Tests Needed:
- Package tool validation (reverse domain, semver, snake_case)
- Package CRUD operations
- Active package resolution logic
- Read-only package detection
- Metadata service write protection
Integration Tests Needed:
- End-to-end package creation workflow
- Metadata creation with package context
- Read-only enforcement for code packages
- Overlay application and merging
Documentation Needed:
- Package-first development workflow guide
- AI agent integration examples
- Package naming conventions
- Customization overlay patterns
- Migration guide for existing metadata
// User: "Create a new CRM application"
// AI uses: create_package
{
id: "com.acme.crm",
name: "CRM Application",
version: "1.0.0",
type: "application"
}
// AI automatically sets as active package
// Now all metadata creation uses this package
// User: "Create an Account object with name and email fields"
// AI uses: create_object (packageId is implicit from active package)
{
name: "account",
label: "Account",
fields: [
{ name: "account_name", type: "text", label: "Account Name" },
{ name: "email", type: "text", label: "Email" }
]
}
// Object is created with packageId="com.acme.crm"// Code-based package (loaded from filesystem)
// packages/my-plugin/metadata/objects/user.object.ts
export default defineObject({
name: 'user',
label: 'User',
fields: { ... }
});
// At runtime, this is registered with:
// scope='system', source='filesystem', packageId='com.example.myplugin'
// User tries: "Add a custom_field to the user object"
// AI uses: add_field
{
objectName: "user",
name: "custom_field",
type: "text"
}
// Metadata service blocks:
// "Cannot modify object 'user' - it is code-based metadata.
// Use overlay customization instead."
// AI suggests alternative:
// "I see 'user' is a system object. I can create a customization overlay instead.
// Would you like me to add the field as a platform-level customization?"-
Package Naming:
- Use reverse domain notation:
com.company.product - Examples:
com.acme.crm,org.nonprofit.fundraising
- Use reverse domain notation:
-
Namespace Derivation:
- Auto-derived from last part of package ID
com.acme.crm→ namespace:crm- Can be explicitly overridden if needed
-
Scope Selection:
system: Platform/framework code (read-only)platform: Admin-configured (mutable, applies to all users)user: User-configured (mutable, personal customizations)
-
Source Tracking:
filesystem: Loaded from code files (read-only)database: Stored in database (mutable)api: Loaded from external APImigration: Created during migration
- Complete Phase 2: Finish enhancing all metadata tool handlers
- Implement Phase 3: Conversation context persistence
- Implement Phase 4: Metadata service write protection
- Write comprehensive tests (Phase 5)
- Update AI agent system prompts with package-first instructions
- Create user documentation and migration guide
packages/services/service-ai/src/tools/list-packages.tool.tspackages/services/service-ai/src/tools/get-package.tool.tspackages/services/service-ai/src/tools/create-package.tool.tspackages/services/service-ai/src/tools/get-active-package.tool.tspackages/services/service-ai/src/tools/set-active-package.tool.tspackages/services/service-ai/src/tools/package-tools.ts
packages/services/service-ai/src/index.ts- Added package tool exportspackages/services/service-ai/src/tools/create-object.tool.ts- Added packageId parameterpackages/services/service-ai/src/tools/metadata-tools.ts- Enhanced context interface
packages/spec/src/system/metadata-persistence.zod.ts- MetadataRecordSchemapackages/spec/src/kernel/package-registry.zod.ts- InstalledPackageSchemapackages/spec/src/kernel/manifest.zod.ts- ManifestSchemapackages/spec/src/api/package-api.zod.ts- Package API contractspackages/spec/src/contracts/metadata-service.ts- IMetadataService interface
The foundation for package-aware metadata management has been established. The package management tools are complete and ready for use. The next phases will complete the integration with metadata tools and enforce read-only protection for code-based packages.
This implementation aligns with industry best practices from Salesforce, ServiceNow, and other enterprise low-code platforms, ensuring metadata governance, version control compatibility, and safe upgrade paths.