Skip to content

Commit bc21aad

Browse files
authored
Merge pull request #1169 from objectstack-ai/claude/create-new-package-for-metadata
feat(service-ai): add package management tools and package-aware metadata
2 parents 08559b1 + 11d64be commit bc21aad

13 files changed

+1037
-4
lines changed

PACKAGE_METADATA_IMPLEMENTATION.md

Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
1+
# Package-Aware Metadata Management Implementation
2+
3+
## Overview
4+
5+
This document describes the implementation of package-aware metadata management in ObjectStack, ensuring that:
6+
1. Every metadata item belongs to a package
7+
2. Code-loaded packages are read-only
8+
3. Database packages are mutable
9+
10+
## Architecture
11+
12+
### Core Principles
13+
14+
1. **Package Ownership**: All metadata (objects, views, flows, etc.) must belong to a package
15+
2. **Source-Based Mutability**:
16+
- **Filesystem/Code packages** → Read-only (scope='system', source='filesystem')
17+
- **Database packages** → Mutable (scope='platform'/'user', source='database')
18+
3. **Conversation Context**: AI tools track active package per conversation
19+
4. **Overlay Pattern**: Code metadata can have database overlays for customization
20+
21+
### Schema Support (Already Exists)
22+
23+
The `MetadataRecordSchema` in `packages/spec/src/system/metadata-persistence.zod.ts` already includes:
24+
25+
```typescript
26+
{
27+
packageId: string | undefined; // Package ownership
28+
managedBy: 'package' | 'platform' | 'user'; // Lifecycle management
29+
scope: 'system' | 'platform' | 'user'; // Mutability scope
30+
source: 'filesystem' | 'database' | 'api' | 'migration'; // Origin
31+
}
32+
```
33+
34+
## Implementation Progress
35+
36+
### Phase 1: Package Management Tools ✅ COMPLETE
37+
38+
Created 5 new AI tools for package management:
39+
40+
1. **`list_packages`** (`list-packages.tool.ts`)
41+
- Lists all installed packages
42+
- Supports filtering by status and enabled state
43+
- Returns package metadata (id, name, version, type, status)
44+
45+
2. **`get_package`** (`get-package.tool.ts`)
46+
- Gets detailed information about a specific package
47+
- Returns full manifest, dependencies, namespaces
48+
49+
3. **`create_package`** (`create-package.tool.ts`)
50+
- Creates a new package with manifest
51+
- Validates reverse domain notation for package ID
52+
- Auto-derives namespace from package ID
53+
- Automatically sets as active package in conversation
54+
55+
4. **`get_active_package`** (`get-active-package.tool.ts`)
56+
- Retrieves the currently active package from conversation context
57+
- Returns null if no active package is set
58+
59+
5. **`set_active_package`** (`set-active-package.tool.ts`)
60+
- Sets the active package for the conversation
61+
- All subsequent metadata operations use this package
62+
63+
**Handler Implementation**: `package-tools.ts`
64+
- Implements `IPackageRegistry` interface for package CRUD
65+
- Implements `IConversationService` interface for context tracking
66+
- Validates package IDs (reverse domain notation)
67+
- Validates namespaces (snake_case)
68+
- Validates versions (semver)
69+
70+
### Phase 2: Enhanced Metadata Tools ⏳ IN PROGRESS
71+
72+
**Completed:**
73+
- Updated `MetadataToolContext` interface to include:
74+
- `conversationService` - for tracking active package
75+
- `conversationId` - current conversation context
76+
- `packageRegistry` - for validating packages and checking read-only status
77+
78+
- Added `packageId` parameter to `create_object` tool
79+
- Optional parameter
80+
- Falls back to active package from conversation
81+
- Provides clear error message if no package context available
82+
83+
**Remaining Work:**
84+
- Update `createObjectHandler` to:
85+
- Resolve package ID (explicit > active > error)
86+
- Check if package is read-only
87+
- Attach package metadata to object definition
88+
- Return package info in success response
89+
90+
- Update other metadata tools (`add_field`, `modify_field`, `delete_field`)
91+
- Add `packageId` parameter where appropriate
92+
- Implement read-only validation
93+
94+
### Phase 3: Conversation Context Management (TODO)
95+
96+
**Objectives:**
97+
- Store `activePackageId` in conversation metadata
98+
- Persist across conversation turns
99+
- Clear on conversation end
100+
101+
**Implementation Plan:**
102+
```typescript
103+
// In conversation service
104+
interface ConversationMetadata {
105+
activePackageId?: string;
106+
lastPackageOperation?: string;
107+
createdAt?: string;
108+
}
109+
110+
// Store in database table: ai_conversation_metadata
111+
{
112+
conversation_id: string;
113+
metadata: JSON; // Contains activePackageId
114+
updated_at: timestamp;
115+
}
116+
```
117+
118+
### Phase 4: Metadata Service Write Protection (TODO)
119+
120+
**Objectives:**
121+
- Prevent modification of code-based metadata
122+
- Allow database metadata modifications
123+
- Support customization overlays for code metadata
124+
125+
**Implementation Plan:**
126+
127+
1. **Add source tracking to metadata registration:**
128+
```typescript
129+
// In metadata service
130+
async register(type: string, name: string, data: unknown, options?: {
131+
packageId?: string;
132+
scope?: 'system' | 'platform' | 'user';
133+
source?: 'filesystem' | 'database' | 'api';
134+
}): Promise<void>
135+
```
136+
137+
2. **Implement read-only check:**
138+
```typescript
139+
async register(type: string, name: string, data: unknown, options) {
140+
const existing = await this.get(type, name);
141+
142+
if (existing) {
143+
const metadata = existing as MetadataRecord;
144+
145+
// Block if trying to modify code-based metadata
146+
if (metadata.scope === 'system' || metadata.source === 'filesystem') {
147+
throw new Error(
148+
`Cannot modify ${type} "${name}" - it is code-based metadata. ` +
149+
`Use overlay customization instead via saveOverlay().`
150+
);
151+
}
152+
}
153+
154+
// Proceed with registration for database metadata
155+
await this.storage.save(type, name, { ...data, ...options });
156+
}
157+
```
158+
159+
3. **Support overlay pattern:**
160+
```typescript
161+
// Allow customization of code metadata via overlays
162+
await metadataService.saveOverlay({
163+
type: 'object',
164+
name: 'account',
165+
scope: 'platform', // or 'user'
166+
overlay: {
167+
fields: {
168+
custom_field: { type: 'text', label: 'Custom Field' }
169+
}
170+
}
171+
});
172+
173+
// Runtime serves merged result:
174+
// base (from code) + platform overlay + user overlay
175+
const effective = await metadataService.getEffective('object', 'account', context);
176+
```
177+
178+
### Phase 5: Testing & Documentation (TODO)
179+
180+
**Unit Tests Needed:**
181+
- Package tool validation (reverse domain, semver, snake_case)
182+
- Package CRUD operations
183+
- Active package resolution logic
184+
- Read-only package detection
185+
- Metadata service write protection
186+
187+
**Integration Tests Needed:**
188+
- End-to-end package creation workflow
189+
- Metadata creation with package context
190+
- Read-only enforcement for code packages
191+
- Overlay application and merging
192+
193+
**Documentation Needed:**
194+
- Package-first development workflow guide
195+
- AI agent integration examples
196+
- Package naming conventions
197+
- Customization overlay patterns
198+
- Migration guide for existing metadata
199+
200+
## Usage Examples
201+
202+
### Creating a Package and Objects via AI
203+
204+
```typescript
205+
// User: "Create a new CRM application"
206+
// AI uses: create_package
207+
{
208+
id: "com.acme.crm",
209+
name: "CRM Application",
210+
version: "1.0.0",
211+
type: "application"
212+
}
213+
214+
// AI automatically sets as active package
215+
// Now all metadata creation uses this package
216+
217+
// User: "Create an Account object with name and email fields"
218+
// AI uses: create_object (packageId is implicit from active package)
219+
{
220+
name: "account",
221+
label: "Account",
222+
fields: [
223+
{ name: "account_name", type: "text", label: "Account Name" },
224+
{ name: "email", type: "text", label: "Email" }
225+
]
226+
}
227+
228+
// Object is created with packageId="com.acme.crm"
229+
```
230+
231+
### Handling Read-Only Packages
232+
233+
```typescript
234+
// Code-based package (loaded from filesystem)
235+
// packages/my-plugin/metadata/objects/user.object.ts
236+
export default defineObject({
237+
name: 'user',
238+
label: 'User',
239+
fields: { ... }
240+
});
241+
242+
// At runtime, this is registered with:
243+
// scope='system', source='filesystem', packageId='com.example.myplugin'
244+
245+
// User tries: "Add a custom_field to the user object"
246+
// AI uses: add_field
247+
{
248+
objectName: "user",
249+
name: "custom_field",
250+
type: "text"
251+
}
252+
253+
// Metadata service blocks:
254+
// "Cannot modify object 'user' - it is code-based metadata.
255+
// Use overlay customization instead."
256+
257+
// AI suggests alternative:
258+
// "I see 'user' is a system object. I can create a customization overlay instead.
259+
// Would you like me to add the field as a platform-level customization?"
260+
```
261+
262+
## Best Practices
263+
264+
1. **Package Naming**:
265+
- Use reverse domain notation: `com.company.product`
266+
- Examples: `com.acme.crm`, `org.nonprofit.fundraising`
267+
268+
2. **Namespace Derivation**:
269+
- Auto-derived from last part of package ID
270+
- `com.acme.crm` → namespace: `crm`
271+
- Can be explicitly overridden if needed
272+
273+
3. **Scope Selection**:
274+
- `system`: Platform/framework code (read-only)
275+
- `platform`: Admin-configured (mutable, applies to all users)
276+
- `user`: User-configured (mutable, personal customizations)
277+
278+
4. **Source Tracking**:
279+
- `filesystem`: Loaded from code files (read-only)
280+
- `database`: Stored in database (mutable)
281+
- `api`: Loaded from external API
282+
- `migration`: Created during migration
283+
284+
## Next Steps
285+
286+
1. Complete Phase 2: Finish enhancing all metadata tool handlers
287+
2. Implement Phase 3: Conversation context persistence
288+
3. Implement Phase 4: Metadata service write protection
289+
4. Write comprehensive tests (Phase 5)
290+
5. Update AI agent system prompts with package-first instructions
291+
6. Create user documentation and migration guide
292+
293+
## Related Files
294+
295+
### New Files Created
296+
- `packages/services/service-ai/src/tools/list-packages.tool.ts`
297+
- `packages/services/service-ai/src/tools/get-package.tool.ts`
298+
- `packages/services/service-ai/src/tools/create-package.tool.ts`
299+
- `packages/services/service-ai/src/tools/get-active-package.tool.ts`
300+
- `packages/services/service-ai/src/tools/set-active-package.tool.ts`
301+
- `packages/services/service-ai/src/tools/package-tools.ts`
302+
303+
### Modified Files
304+
- `packages/services/service-ai/src/index.ts` - Added package tool exports
305+
- `packages/services/service-ai/src/tools/create-object.tool.ts` - Added packageId parameter
306+
- `packages/services/service-ai/src/tools/metadata-tools.ts` - Enhanced context interface
307+
308+
### Existing Schema Files (Used)
309+
- `packages/spec/src/system/metadata-persistence.zod.ts` - MetadataRecordSchema
310+
- `packages/spec/src/kernel/package-registry.zod.ts` - InstalledPackageSchema
311+
- `packages/spec/src/kernel/manifest.zod.ts` - ManifestSchema
312+
- `packages/spec/src/api/package-api.zod.ts` - Package API contracts
313+
- `packages/spec/src/contracts/metadata-service.ts` - IMetadataService interface
314+
315+
## Conclusion
316+
317+
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.
318+
319+
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.

packages/services/service-ai/src/index.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,19 @@ export {
4343
describeObjectTool,
4444
} from './tools/metadata-tools.js';
4545

46+
// Package tools
47+
export { registerPackageTools, PACKAGE_TOOL_DEFINITIONS } from './tools/package-tools.js';
48+
export type { PackageToolContext, IPackageRegistry, IConversationService } from './tools/package-tools.js';
49+
50+
// Individual package tool metadata
51+
export {
52+
listPackagesTool,
53+
getPackageTool,
54+
createPackageTool,
55+
getActivePackageTool,
56+
setActivePackageTool,
57+
} from './tools/package-tools.js';
58+
4659
// Agent runtime
4760
export { AgentRuntime } from './agent-runtime.js';
4861
export type { AgentChatContext } from './agent-runtime.js';

packages/services/service-ai/src/tools/add-field.tool.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ export const addFieldTool = defineTool({
2020
parameters: {
2121
type: 'object',
2222
properties: {
23+
packageId: {
24+
type: 'string',
25+
description: 'Package ID that owns the target object (e.g., com.acme.crm). If not provided, uses the active package from conversation context.',
26+
},
2327
objectName: {
2428
type: 'string',
2529
description: 'Target object machine name (snake_case)',

packages/services/service-ai/src/tools/create-object.tool.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ export const createObjectTool = defineTool({
3333
type: 'string',
3434
description: 'Human-readable display name (e.g. Project Task)',
3535
},
36+
packageId: {
37+
type: 'string',
38+
description: 'Package ID that will own this object (e.g., com.acme.crm). If not provided, uses the active package from conversation context.',
39+
},
3640
fields: {
3741
type: 'array',
3842
description: 'Initial fields to create with the object',

0 commit comments

Comments
 (0)