From 425f36734f5b1f10036f61917a9515895fc795ce Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 29 Aug 2026 06:13:23 +0000 Subject: [PATCH 1/6] wip: retire paper metadata-customization protocol (spec module, keys, contracts, metadata limb) Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4 --- ...metadata-customization-protocol-retired.md | 64 ++++ .../kernel/contracts/metadata-service.mdx | 82 +---- .../docs/protocol/kernel/metadata-service.mdx | 12 +- packages/metadata/ROADMAP.md | 17 +- packages/metadata/src/metadata-manager.ts | 98 +----- .../metadata/src/metadata-service.test.ts | 125 +------ packages/metadata/src/metadata.test.ts | 47 +-- packages/metadata/src/plugin.ts | 2 +- packages/spec/src/api/metadata.test.ts | 85 +---- packages/spec/src/api/metadata.zod.ts | 46 +-- .../spec/src/contracts/metadata-service.ts | 52 +-- packages/spec/src/kernel/index.ts | 10 +- .../metadata-customization-retirement.test.ts | 116 +++++++ .../src/kernel/metadata-customization.test.ts | 241 ------------- .../src/kernel/metadata-customization.zod.ts | 321 ------------------ .../spec/src/kernel/metadata-loader.zod.ts | 29 +- .../spec/src/kernel/metadata-plugin.zod.ts | 50 ++- .../18.api__MetadataEffectiveResponse.ts | 26 ++ .../18.api__MetadataOverlayResponse.ts | 25 ++ .../18.api__MetadataOverlaySaveRequest.ts | 24 ++ .../18.kernel__CustomizationOrigin.ts | 24 ++ .../18.kernel__CustomizationPolicy.ts | 26 ++ .../retired-defs/18.kernel__FieldChange.ts | 27 ++ .../retired-defs/18.kernel__MergeConflict.ts | 24 ++ .../retired-defs/18.kernel__MergeResult.ts | 24 ++ .../18.kernel__MergeStrategyConfig.ts | 28 ++ .../18.kernel__MetadataOverlay.ts | 28 ++ ...agerConfig__persistence.overlayWritable.ts | 25 ++ ...dataPluginConfig__customizationPolicies.ts | 30 ++ ...el__MetadataPluginConfig__mergeStrategy.ts | 28 ++ ...metadata-customization-protocol-retired.ts | 53 +++ packages/spec/src/migrations/registry.ts | 17 +- .../src/type-alias-convention.pin.test.ts | 23 +- 33 files changed, 760 insertions(+), 1069 deletions(-) create mode 100644 .changeset/metadata-customization-protocol-retired.md create mode 100644 packages/spec/src/kernel/metadata-customization-retirement.test.ts delete mode 100644 packages/spec/src/kernel/metadata-customization.test.ts delete mode 100644 packages/spec/src/kernel/metadata-customization.zod.ts create mode 100644 packages/spec/src/migrations/entries/retired-defs/18.api__MetadataEffectiveResponse.ts create mode 100644 packages/spec/src/migrations/entries/retired-defs/18.api__MetadataOverlayResponse.ts create mode 100644 packages/spec/src/migrations/entries/retired-defs/18.api__MetadataOverlaySaveRequest.ts create mode 100644 packages/spec/src/migrations/entries/retired-defs/18.kernel__CustomizationOrigin.ts create mode 100644 packages/spec/src/migrations/entries/retired-defs/18.kernel__CustomizationPolicy.ts create mode 100644 packages/spec/src/migrations/entries/retired-defs/18.kernel__FieldChange.ts create mode 100644 packages/spec/src/migrations/entries/retired-defs/18.kernel__MergeConflict.ts create mode 100644 packages/spec/src/migrations/entries/retired-defs/18.kernel__MergeResult.ts create mode 100644 packages/spec/src/migrations/entries/retired-defs/18.kernel__MergeStrategyConfig.ts create mode 100644 packages/spec/src/migrations/entries/retired-defs/18.kernel__MetadataOverlay.ts create mode 100644 packages/spec/src/migrations/entries/retired-keys/18.kernel__MetadataManagerConfig__persistence.overlayWritable.ts create mode 100644 packages/spec/src/migrations/entries/retired-keys/18.kernel__MetadataPluginConfig__customizationPolicies.ts create mode 100644 packages/spec/src/migrations/entries/retired-keys/18.kernel__MetadataPluginConfig__mergeStrategy.ts create mode 100644 packages/spec/src/migrations/entries/semantic/18.metadata-customization-protocol-retired.ts diff --git a/.changeset/metadata-customization-protocol-retired.md b/.changeset/metadata-customization-protocol-retired.md new file mode 100644 index 0000000000..3e0b0578a1 --- /dev/null +++ b/.changeset/metadata-customization-protocol-retired.md @@ -0,0 +1,64 @@ +--- +"@objectstack/spec": minor +"@objectstack/metadata": minor +--- + +feat(spec): retire the paper metadata-customization protocol with its full coupling set (#13135, re-charter of #12057; ADR-0049, ADR-0126) + + + +**BREAKING** export removal + authorable-key retirement, landing after the +v17.0.0 cut (the lockstep launch-window convention ships it as `minor`; the +prescriptions are registered under protocol major 18 — +`RETIRED_DEFS_BY_MAJOR[18]`, `RETIRED_KEYS_BY_MAJOR[18]` and the D3 semantic +entry `metadata-customization-protocol-retired` — where `os migrate meta` +users will look). + +`kernel/metadata-customization.zod.ts` declared a three-layer platform/user +patch-overlay protocol (field-level change tracking, customization policies, a +3-way-merge story) that nothing reachable implemented: no route ever served +the paper `…/overlay` / `…/effective` endpoints, the only implementation +(`packages/metadata`'s manager limb) was called solely by its own unit tests, +no merge engine ever existed, and no code read a `CustomizationPolicy`. +ADR-0126 §6 wall 4 supersedes the protocol as a matter of record ("nothing may +build against it"); the maintainer adopted retirement on #12057 (2026-08-29, +「同意」), and #13135 charters the full coupling set the fork report measured. + +FROM → TO: + +- `MetadataOverlaySchema` / `FieldChangeSchema` / `CustomizationOriginSchema` / + `MergeConflictSchema` / `MergeStrategyConfigSchema` / `MergeResultSchema` / + `CustomizationPolicySchema` and their `…`/`…Parsed` types + (`@objectstack/spec/kernel`) → *(removed — no replacement protocol)*. The + customization that actually ships: ADR-0005's org-scoped overlay + (`allowOrgOverride` on `DEFAULT_METADATA_TYPE_REGISTRY`, `sys_metadata` org + rows, layered read `code`/`overlay`/`effective`) and ADR-0126's + packaged-metadata model (clone + ledger disable). +- `MetadataOverlayResponseSchema` / `MetadataOverlaySaveRequestSchema` / + `MetadataEffectiveResponseSchema` (`@objectstack/spec/api` §5) → + *(removed)* — contracts for endpoints no adapter ever served; the layered + read's contracts (`getMetaItemLayered`) are the live API. +- `IMetadataService.getOverlay` / `.saveOverlay` / `.removeOverlay` / + `.getEffective` optional members (`@objectstack/spec/contracts`) → + *(removed)*, together with `packages/metadata`'s in-memory limb and its + `'overlay'` feature log entry. +- `MetadataPluginConfig.customizationPolicies` / `.mergeStrategy` and + `MetadataManagerConfig.persistence.overlayWritable` → *(removed — retiredKey + tombstones)*: authoring one is now a `tsc` error and a parse error carrying + the prescription. Delete the keys; nothing replaces them (`persistence.writable` + remains the base write gate). + +One-line fix: delete the keys and any code building against the removed +exports — they configured and described nothing that ever ran; org-level +customization keeps riding the ADR-0005 overlay unchanged. + +The retirement kit: whole-module deletion + kernel barrel line; 10 +`RETIRED_DEFS_BY_MAJOR[18]` entries (7 kernel defs + 3 api §5 contracts); 3 +`RETIRED_KEYS_BY_MAJOR[18]` tombstone entries (no D2 conversion — +plugin/manager configs are not stack collection members, the +`kernel/MetadataPluginConfig:additionalTypes` precedent); D3 semantic entry +`metadata-customization-protocol-retired`; retirement pin test +(`kernel/metadata-customization-retirement.test.ts`); type-alias pin rows +Iso408-411 vacated; api-surface / export-origins / json-schema manifest / +authorable-surface / reference docs regenerated (the +`kernel/metadata-customization` reference page disappears with the module). diff --git a/content/docs/kernel/contracts/metadata-service.mdx b/content/docs/kernel/contracts/metadata-service.mdx index 4a730bc1b8..a6fbfe04bc 100644 --- a/content/docs/kernel/contracts/metadata-service.mdx +++ b/content/docs/kernel/contracts/metadata-service.mdx @@ -1,6 +1,6 @@ --- title: IMetadataService Contract -description: Reference for the Metadata Service contract — CRUD operations for object and field definitions, schema registry, overlay management, and import/export +description: Reference for the Metadata Service contract — CRUD operations for object and field definitions, schema registry, and import/export --- The Metadata Service manages all object and field definitions at runtime. It serves as the **schema registry** — plugins, the Kernel, and the API layer all query this service to discover what objects exist and what fields they contain. @@ -61,12 +61,6 @@ export interface IMetadataService { bulkRegister?(items: Array<{ type: string; name: string; data: unknown }>, options?: { continueOnError?: boolean; validate?: boolean }): Promise; bulkUnregister?(items: Array<{ type: string; name: string }>): Promise; - // Overlay / customization (optional) - getOverlay?(type: string, name: string, scope?: 'platform' | 'user'): Promise; - saveOverlay?(overlay: MetadataOverlay): Promise; - removeOverlay?(type: string, name: string, scope?: 'platform' | 'user'): Promise; - getEffective?(type: string, name: string, context?: { userId?: string; tenantId?: string; positions?: string[]; permissions?: string[] }): Promise; - // Watch / subscribe (optional) watch?(type: string, callback: MetadataWatchCallback): MetadataWatchHandle; @@ -206,35 +200,16 @@ const validation = await metadataService.validate('object', definition); --- -## Overlay Management - -Overlays customize a metadata item without modifying the base (system) definition. -A `MetadataOverlay` references the target by `baseType` + `baseName`, carries a JSON -Merge Patch in `patch`, and resolves in the order **system ← platform ← user**. - -```typescript -// Save a platform-scope overlay -await metadataService.saveOverlay({ - id: 'overlay-platform-1', - baseType: 'object', - baseName: 'task', - scope: 'platform', - patch: { label: 'Work Item' }, -}); +## Overlay Management — removed -// Read the merged (effective) definition with overlays applied -const effective = await metadataService.getEffective('object', 'task', { - userId: 'user-123', -}); -``` - -| Property | Type | Description | -|:---|:---|:---| -| `baseType` | `string` | Metadata type being customized | -| `baseName` | `string` | Metadata name being customized | -| `scope` | `'platform' \| 'user'` | Customization scope (default `platform`) | -| `owner` | `string` | Owner user ID, for `user`-scope overlays | -| `patch` | `object` | JSON Merge Patch (changed fields only) | +The optional `getOverlay` / `saveOverlay` / `removeOverlay` / `getEffective` +members and their `MetadataOverlay` record were removed in #13135 (ADR-0049 +enforce-or-remove): they belonged to a paper customization protocol no route +ever served, and ADR-0126 supersedes it on the record. Org-scoped +customization is [ADR-0005's metadata overlay](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0005-metadata-customization-overlay.md) +— opt-in per type via `allowOrgOverride`, written through the REST meta write +doors, and read back through the layered read (`code` / `overlay` / +`effective`). --- @@ -293,7 +268,6 @@ console.log(result.failed); // failed | Type | Description | |:---|:---| | `MetadataQuery` / `MetadataQueryResult` | Query parameters and paginated result for `query()` | -| `MetadataOverlay` | Runtime customization layer (`baseType`, `baseName`, `scope`, `patch`) | | `MetadataExportOptions` | `{ types?, namespaces?, format? }` for `exportMetadata` | | `MetadataImportOptions` | `{ conflictResolution?, validate?, dryRun? }` for `importMetadata` | | `MetadataImportResult` | `{ total, imported, skipped, failed, errors? }` | @@ -321,36 +295,14 @@ const views = await metadataService.listViews('account'); const dashboard = await metadataService.get('dashboard', 'sales_overview'); ``` -### User-Level Customization +### Org-Level Customization -Users can customize views via the overlay system: - -```typescript -// Admin customizes a view for all users -await metadataService.saveOverlay({ - id: 'overlay-platform-1', - baseType: 'view', - baseName: 'account_list', - scope: 'platform', - patch: { columns: ['name', 'email', 'status', 'created_at'] }, -}); - -// A specific user saves personal column preferences -await metadataService.saveOverlay({ - id: 'overlay-user-123', - baseType: 'view', - baseName: 'account_list', - scope: 'user', - owner: 'user-123', - patch: { columns: ['name', 'status'] }, // user only wants 2 columns -}); - -// Resolve effective view for a specific user -const effectiveView = await metadataService.getEffective('view', 'account_list', { - userId: 'user-123', -}); -// Result: base view ← platform overlay ← user-123 overlay -``` +Per-org view customization rides ADR-0005's metadata overlay (opt-in per type +via `allowOrgOverride`, `view` among the overlay types): an org-scoped write +through the REST meta doors stores a `sys_metadata` row, and the layered read +returns `code` / `overlay` / `effective` for it. The per-user, per-field patch +overlay a previous revision of this page taught here was removed in #13135 — +it was never served by any route. ### Permission-Based UI Filtering diff --git a/content/docs/protocol/kernel/metadata-service.mdx b/content/docs/protocol/kernel/metadata-service.mdx index 363ed27432..42a1c143c1 100644 --- a/content/docs/protocol/kernel/metadata-service.mdx +++ b/content/docs/protocol/kernel/metadata-service.mdx @@ -227,21 +227,25 @@ instance goes through `@objectstack/cloud-connection` (`os package install`). A still-configured `artifact-api` source fails loudly at `start()` rather than silently falling back to the filesystem scan. -### 2. Persistence Write Gates +### 2. Persistence Write Gate -`MetadataManagerConfigSchema.persistence` is a two-axis runtime freeze. Both flags default to `true`. +`MetadataManagerConfigSchema.persistence` is a runtime freeze. The flag defaults to `true`. | Flag | Effect when `false` | | :--- | :--- | | `persistence.writable` | `register()` becomes a no-op (or throws when `validation.throwOnError`). | -| `persistence.overlayWritable` | `saveOverlay()` is rejected. Disables Studio overlays in sealed deployments. | ```typescript new MetadataManager({ - persistence: { writable: false, overlayWritable: false }, + persistence: { writable: false }, }); ``` +(`persistence.overlayWritable` was removed in #13135 with the paper +metadata-customization protocol — the `saveOverlay()` it gated was never +reachable from any served surface. Authoring it is now a compile-time and +parse-time error carrying the prescription.) + ### 3. DatabaseLoader Read-Through Cache `DatabaseLoader` wraps `load` / `loadMany` / `list` / `stat` results in a generic LRU cache (lazy TTL, promote-on-get, write invalidation). Reads always observe writes performed through the same loader instance; out-of-band SQL writes are honored within `ttl` milliseconds. diff --git a/packages/metadata/ROADMAP.md b/packages/metadata/ROADMAP.md index b187f61deb..461ff408f1 100644 --- a/packages/metadata/ROADMAP.md +++ b/packages/metadata/ROADMAP.md @@ -166,14 +166,15 @@ - [ ] Implement `rollback(type, name, version)` to restore a previous version - [ ] Add `checksum` field for change detection -### 4b. Package Upgrade & Three-Way Merge - -- [ ] Implement three-way merge when upgrading package-delivered metadata - - Base: previous package version - - Ours: current platform customizations (overlays) - - Theirs: new package version -- [ ] Merge conflict detection and resolution UI support -- [ ] Leverage `MergeStrategyConfigSchema` from spec (keep-custom, accept-incoming, three-way-merge) +### 4b. Package Upgrade & Three-Way Merge — RETIRED, not planned + +The three-way-merge plan (merge package updates into customization overlays, +driven by `MergeStrategyConfigSchema`) left with the paper +metadata-customization protocol (#13135, ADR-0049 remove). ADR-0126 rules the +opposite model: package upgrades rewrite the packaged BASE and never touch the +customer's recorded choices — upgrade and customization share no columns and +never merge. Customization rides ADR-0005's org overlay and ADR-0126's +clone + ledger-disable primitives. ### 4c. Metadata Sync & Distribution diff --git a/packages/metadata/src/metadata-manager.ts b/packages/metadata/src/metadata-manager.ts index 53a167d4cb..60c509d0a7 100644 --- a/packages/metadata/src/metadata-manager.ts +++ b/packages/metadata/src/metadata-manager.ts @@ -42,7 +42,6 @@ import type { MetadataDependency, MetadataTypeRegistryEntryParsed, } from '@objectstack/spec/kernel'; -import type { MetadataOverlay } from '@objectstack/spec/kernel'; import { getMetadataTypeActions } from '@objectstack/spec/kernel'; import { MetadataEventType, @@ -312,9 +311,6 @@ export class MetadataManager implements IMetadataService { // In-memory metadata registry: type -> name -> data private registry = new Map>(); - // Overlay storage: "type:name:scope" -> MetadataOverlay - private overlays = new Map(); - // Type registry for metadata type info private typeRegistry: MetadataTypeRegistryEntryParsed[] = []; @@ -2163,90 +2159,18 @@ export class MetadataManager implements IMetadataService { } // ========================================== - // Overlay / Customization Management + // Overlay / Customization Management — REMOVED (#13135, ADR-0049) // ========================================== - - private overlayKey(type: string, name: string, scope: string = 'platform'): string { - return `${encodeURIComponent(type)}:${encodeURIComponent(name)}:${scope}`; - } - - /** - * Get the active overlay for a metadata item - */ - async getOverlay(type: string, name: string, scope?: 'platform' | 'user'): Promise { - return this.overlays.get(this.overlayKey(type, name, scope ?? 'platform')); - } - - /** - * Save/update an overlay for a metadata item - */ - async saveOverlay(overlay: MetadataOverlay): Promise { - // Overlay write gate — independent from base writability so deployments - // can freeze Studio overlays while still permitting base register(). - if (this.config.persistence?.overlayWritable === false) { - const msg = `MetadataManager overlays are read-only (persistence.overlayWritable=false); refusing to save overlay for ${overlay.baseType}/${overlay.baseName}`; - if (this.config.validation?.throwOnError) { - throw new Error(msg); - } - this.logger.warn(msg); - return; - } - const key = this.overlayKey(overlay.baseType, overlay.baseName, overlay.scope); - this.overlays.set(key, overlay); - } - - /** - * Remove an overlay, reverting to the base definition - */ - async removeOverlay(type: string, name: string, scope?: 'platform' | 'user'): Promise { - this.overlays.delete(this.overlayKey(type, name, scope ?? 'platform')); - } - - /** - * Get the effective (merged) metadata after applying all overlays. - * Resolution order: system ← merge(platform) ← merge(user) - */ - async getEffective(type: string, name: string, context?: { - userId?: string; - tenantId?: string; - roles?: string[]; - permissions?: string[]; - }): Promise { - const base = await this.get(type, name); - if (!base) return undefined; - - let effective = { ...(base as Record) }; - - // Apply platform overlay - const platformOverlay = await this.getOverlay(type, name, 'platform'); - if (platformOverlay?.active && platformOverlay.patch) { - effective = { ...effective, ...platformOverlay.patch }; - } - - // Apply user overlay (scoped to specific user if context provided) - if (context?.userId) { - // Try user-specific key first, then fall back to generic user overlay. - // The owner check below ensures we never apply another user's overlay. - const userOverlayKey = this.overlayKey(type, name, 'user') + `:${context.userId}`; - const userOverlay = this.overlays.get(userOverlayKey) - ?? await this.getOverlay(type, name, 'user'); - if (userOverlay?.active && userOverlay.patch) { - // Apply if: overlay has no owner (generic user-level), or owner matches current user - if (!userOverlay.owner || userOverlay.owner === context.userId) { - effective = { ...effective, ...userOverlay.patch }; - } - } - } else { - // No user context — only apply user overlays without an owner restriction - // (owner-scoped overlays require a userId to resolve) - const userOverlay = await this.getOverlay(type, name, 'user'); - if (userOverlay?.active && userOverlay.patch && !userOverlay.owner) { - effective = { ...effective, ...userOverlay.patch }; - } - } - - return effective; - } + // + // The in-memory overlay limb (`getOverlay` / `saveOverlay` / `removeOverlay` + // / `getEffective`, keyed `type:name:scope`) implemented the paper + // metadata-customization protocol removed from `@objectstack/spec` in the + // same change: no route ever served the paper `.../overlay` or + // `.../effective` endpoints, and the only callers of these methods were this + // package's own unit tests. ADR-0126 supersedes the protocol on the record. + // The org-scoped customization that actually ships is ADR-0005's + // `sys_metadata` overlay (`getMetaItemLayered` in metadata-protocol), which + // never lived here. // ========================================== // Watch / Subscribe (IMetadataService) diff --git a/packages/metadata/src/metadata-service.test.ts b/packages/metadata/src/metadata-service.test.ts index d95191837d..d0f441cf56 100644 --- a/packages/metadata/src/metadata-service.test.ts +++ b/packages/metadata/src/metadata-service.test.ts @@ -4,7 +4,6 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { MetadataManager } from './metadata-manager'; import { MemoryLoader } from './loaders/memory-loader'; import { DEFAULT_METADATA_TYPE_REGISTRY } from '@objectstack/spec/kernel'; -import type { MetadataOverlay } from '@objectstack/spec/kernel'; // Suppress logger output during tests vi.mock('@objectstack/core', async (orig) => ({ @@ -298,126 +297,12 @@ describe('MetadataManager — IMetadataService Contract', () => { }); // ========================================== - // Overlay / Customization + // Overlay / Customization — REMOVED (#13135, ADR-0049) // ========================================== - - describe('overlay management', () => { - const testOverlay: MetadataOverlay = { - id: 'overlay-1', - baseType: 'object', - baseName: 'account', - scope: 'platform', - patch: { label: 'Custom Account' }, - active: true, - }; - - it('should save and retrieve an overlay', async () => { - await manager.saveOverlay(testOverlay); - const result = await manager.getOverlay('object', 'account', 'platform'); - expect(result).toEqual(testOverlay); - }); - - it('should return undefined for missing overlay', async () => { - const result = await manager.getOverlay('object', 'nonexistent'); - expect(result).toBeUndefined(); - }); - - it('should remove an overlay', async () => { - await manager.saveOverlay(testOverlay); - await manager.removeOverlay('object', 'account', 'platform'); - const result = await manager.getOverlay('object', 'account', 'platform'); - expect(result).toBeUndefined(); - }); - - it('should get effective metadata with overlays applied', async () => { - await manager.register('object', 'account', { name: 'account', label: 'Account', type: 'object' }); - await manager.saveOverlay(testOverlay); - - const effective = await manager.getEffective('object', 'account') as any; - expect(effective.label).toBe('Custom Account'); - expect(effective.name).toBe('account'); - expect(effective.type).toBe('object'); - }); - - it('should apply user overlay on top of platform overlay', async () => { - await manager.register('object', 'account', { name: 'account', label: 'Account' }); - - await manager.saveOverlay({ - id: 'platform-1', - baseType: 'object', - baseName: 'account', - scope: 'platform', - patch: { label: 'Platform Label', description: 'Platform Desc' }, - active: true, - }); - - await manager.saveOverlay({ - id: 'user-1', - baseType: 'object', - baseName: 'account', - scope: 'user', - patch: { label: 'User Label' }, - active: true, - }); - - const effective = await manager.getEffective('object', 'account') as any; - expect(effective.label).toBe('User Label'); - expect(effective.description).toBe('Platform Desc'); - }); - - it('should not apply inactive overlays', async () => { - await manager.register('object', 'account', { name: 'account', label: 'Original' }); - await manager.saveOverlay({ - id: 'inactive-1', - baseType: 'object', - baseName: 'account', - scope: 'platform', - patch: { label: 'Should Not Apply' }, - active: false, - }); - - const effective = await manager.getEffective('object', 'account') as any; - expect(effective.label).toBe('Original'); - }); - - it('should apply user overlay scoped to specific userId via getEffective context', async () => { - await manager.register('view', 'account_list', { - name: 'account_list', - columns: ['name', 'email', 'status'] - }); - - // Platform overlay - await manager.saveOverlay({ - id: 'platform-view-1', - baseType: 'view', - baseName: 'account_list', - scope: 'platform', - patch: { columns: ['name', 'email', 'status', 'created_at'] }, - active: true, - }); - - // User-specific overlay - await manager.saveOverlay({ - id: 'user-view-1', - baseType: 'view', - baseName: 'account_list', - scope: 'user', - owner: 'user-456', - patch: { columns: ['name', 'status'] }, - active: true, - }); - - // Without context — should apply platform but not user overlay (no owner match) - const general = await manager.getEffective('view', 'account_list') as any; - expect(general.columns).toEqual(['name', 'email', 'status', 'created_at']); - - // With userId context — should apply user overlay - const forUser = await manager.getEffective('view', 'account_list', { - userId: 'user-456' - }) as any; - expect(forUser.columns).toEqual(['name', 'status']); - }); - }); + // + // The `overlay management` cases left with the manager's paper-protocol + // limb: these tests were the limb's ONLY callers (no route or UI ever + // reached it), so they pinned an API nothing served. // ========================================== // Watch / Subscribe (IMetadataService) diff --git a/packages/metadata/src/metadata.test.ts b/packages/metadata/src/metadata.test.ts index cafe55bf11..30cc3e85ed 100644 --- a/packages/metadata/src/metadata.test.ts +++ b/packages/metadata/src/metadata.test.ts @@ -442,55 +442,18 @@ describe('MetadataManager', () => { ); }); - it('saveOverlay() is a no-op when persistence.overlayWritable is false', async () => { - const m = new MetadataManager({ - formats: ['json'], - loaders: [new MemoryLoader()], - persistence: { overlayWritable: false }, - }); - await m.saveOverlay({ - id: 'overlay-1', - baseType: 'object', - baseName: 'account', - scope: 'platform', - patch: { label: 'X' }, - } as any); - expect(await m.getOverlay('object', 'account', 'platform')).toBeUndefined(); - }); - - it('saveOverlay() throws when persistence.overlayWritable=false and validation.throwOnError', async () => { - const m = new MetadataManager({ - formats: ['json'], - loaders: [new MemoryLoader()], - persistence: { overlayWritable: false }, - validation: { throwOnError: true }, - }); - await expect( - m.saveOverlay({ - id: 'overlay-1', - baseType: 'object', - baseName: 'account', - scope: 'platform', - patch: { label: 'X' }, - } as any), - ).rejects.toThrow(/persistence\.overlayWritable=false/); - }); + // (The `saveOverlay()` / `overlayWritable` cases left with the manager's + // paper-protocol overlay limb — #13135, ADR-0049. These tests were the + // limb's only callers; `persistence.overlayWritable` is a retiredKey + // tombstone on the spec side now.) - it('defaults preserve write behavior (writable=true, overlayWritable=true)', async () => { + it('defaults preserve write behavior (writable=true)', async () => { const m = new MetadataManager({ formats: ['json'], loaders: [new MemoryLoader()], }); await m.register('object', 'account', { name: 'account' }); expect(await m.listNames('object')).toContain('account'); - await m.saveOverlay({ - id: 'overlay-1', - baseType: 'object', - baseName: 'account', - scope: 'platform', - patch: { label: 'X' }, - } as any); - expect(await m.getOverlay('object', 'account', 'platform')).toBeDefined(); }); }); }); diff --git a/packages/metadata/src/plugin.ts b/packages/metadata/src/plugin.ts index c2747661f6..c770c625fd 100644 --- a/packages/metadata/src/plugin.ts +++ b/packages/metadata/src/plugin.ts @@ -363,7 +363,7 @@ export class MetadataPlugin implements Plugin { ctx.logger.info('MetadataPlugin providing metadata service (primary mode)', { mode: this.options.artifactSource?.mode ?? 'file-system', - features: ['watch', 'multi-format', 'query', 'overlay', 'type-registry'] + features: ['watch', 'multi-format', 'query', 'type-registry'] }); } diff --git a/packages/spec/src/api/metadata.test.ts b/packages/spec/src/api/metadata.test.ts index f207db427d..ef91a2522b 100644 --- a/packages/spec/src/api/metadata.test.ts +++ b/packages/spec/src/api/metadata.test.ts @@ -17,10 +17,6 @@ import { MetadataBulkRegisterRequestSchema, MetadataBulkUnregisterRequestSchema, MetadataBulkResponseSchema, - // Overlay - MetadataOverlayResponseSchema, - MetadataOverlaySaveRequestSchema, - MetadataEffectiveResponseSchema, // Import/Export MetadataExportRequestSchema, MetadataExportResponseSchema, @@ -536,53 +532,9 @@ describe('MetadataBulkResponseSchema', () => { }); // ========================================== -// 5. Overlay / Customization +// 5. Overlay / Customization — REMOVED (#13135, ADR-0049; see metadata.zod.ts §5) // ========================================== -describe('MetadataOverlayResponseSchema', () => { - it('should accept response with overlay', () => { - const result = MetadataOverlayResponseSchema.parse({ - success: true, - data: { - id: 'overlay-001', - baseType: 'object', - baseName: 'account', - scope: 'platform', - patch: { fields: { status: { label: 'Account Status' } } }, - }, - }); - expect(result.data?.baseType).toBe('object'); - }); - - it('should accept response without overlay (undefined)', () => { - const result = MetadataOverlayResponseSchema.parse({ - success: true, - }); - expect(result.data).toBeUndefined(); - }); -}); - -describe('MetadataEffectiveResponseSchema', () => { - it('should accept effective metadata response', () => { - const result = MetadataEffectiveResponseSchema.parse({ - success: true, - data: { - name: 'account', - label: 'Account', - fields: { status: { label: 'Account Status', type: 'select' } }, - }, - }); - expect(result.data).toBeDefined(); - }); - - it('should accept null/undefined effective response', () => { - const result = MetadataEffectiveResponseSchema.parse({ - success: true, - }); - expect(result.data).toBeUndefined(); - }); -}); - // ========================================== // 6. Import / Export // ========================================== @@ -1041,38 +993,9 @@ describe('Cross-Framework Metadata API Contracts', () => { }); }); - describe('GET /api/meta/:type/:name/overlay — Get overlay', () => { - it('returns overlay when customization exists', () => { - const response = MetadataOverlayResponseSchema.parse({ - success: true, - data: { - id: 'overlay-123', - baseType: 'object', - baseName: 'account', - scope: 'platform', - patch: { fields: { status: { label: 'Custom Status' } } }, - }, - }); - expect(response.data?.scope).toBe('platform'); - }); - }); - - describe('GET /api/meta/:type/:name/effective — Get effective metadata', () => { - it('returns merged metadata with overlays applied', () => { - const response = MetadataEffectiveResponseSchema.parse({ - success: true, - data: { - name: 'account', - label: 'Account', - fields: { - status: { label: 'Custom Status', type: 'select' }, - name: { label: 'Account Name', type: 'text' }, - }, - }, - }); - expect(response.data).toBeDefined(); - }); - }); + // (The `…/overlay` and `…/effective` route cases were removed with the + // section-5 contracts — #13135, ADR-0049: no adapter ever served those + // paths, so the cases pinned an API that did not exist.) describe('GET /api/meta/:type/:name/dependencies — Get dependencies', () => { it('returns what this item depends on', () => { diff --git a/packages/spec/src/api/metadata.zod.ts b/packages/spec/src/api/metadata.zod.ts index f6a48fbc94..b888735ea1 100644 --- a/packages/spec/src/api/metadata.zod.ts +++ b/packages/spec/src/api/metadata.zod.ts @@ -6,7 +6,6 @@ import { ObjectSchema } from '../data/object.zod'; import { AppSchema } from '../ui/app.zod'; import { MetadataTypeSchema, MetadataQuerySchema, MetadataQueryResultSchema, MetadataValidationResultSchema, MetadataBulkResultSchema, MetadataDependencySchema } from '../kernel/metadata-plugin.zod'; import { ActionSchema } from '../ui/action.zod'; -import { MetadataOverlaySchema } from '../kernel/metadata-customization.zod'; /** * Metadata Service Protocol @@ -190,33 +189,20 @@ export const MetadataBulkResponseSchema = lazySchema(() => BaseResponseSchema.ex })); // ========================================== -// 5. Overlay / Customization +// 5. Overlay / Customization — REMOVED // ========================================== - -/** - * Get Overlay Response - * GET /api/meta/:type/:name/overlay - */ -export const MetadataOverlayResponseSchema = lazySchema(() => BaseResponseSchema.extend({ - data: MetadataOverlaySchema.optional().describe('Overlay definition, undefined if none'), -})); - -/** - * Save Overlay Request - * PUT /api/meta/:type/:name/overlay - */ -export const MetadataOverlaySaveRequestSchema = lazySchema(() => MetadataOverlaySchema.describe( - 'Overlay to save', -)); - -/** - * Get Effective (merged) Response - * GET /api/meta/:type/:name/effective - */ -export const MetadataEffectiveResponseSchema = lazySchema(() => BaseResponseSchema.extend({ - data: z.record(z.string(), z.unknown()).optional() - .describe('Effective metadata with all overlays applied'), -})); +// +// The section-5 contracts (`MetadataOverlayResponseSchema`, +// `MetadataOverlaySaveRequestSchema`, `MetadataEffectiveResponseSchema`) were +// REMOVED per ADR-0049 enforce-or-remove (#13135, re-charter of #12057): +// they declared REST contracts for the paper metadata-customization protocol +// — `GET/PUT …/overlay`, `GET …/effective` — endpoints NO adapter ever +// served (measured: no route spelling exists in packages/rest or +// packages/metadata). ADR-0126 §6 wall 4 supersedes the protocol on the +// record. The layered read that actually ships is `getMetaItemLayered` +// (ADR-0005 org overlay; `code` / `overlay` / `effective` layers) with its +// own contracts. Section numbering is preserved — ids are claims, not +// positions. // ========================================== // 6. Import / Export @@ -386,12 +372,6 @@ export type MetadataBulkRegisterRequestParsed = z.infer; /** Post-parse shape of {@link MetadataBulkResponse} — defaults applied, transforms run (ADR-0122). */ export type MetadataBulkResponseParsed = z.infer; -export type MetadataOverlayResponse = z.input; -/** Post-parse shape of {@link MetadataOverlayResponse} — defaults applied, transforms run (ADR-0122). */ -export type MetadataOverlayResponseParsed = z.infer; -export type MetadataEffectiveResponse = z.input; -/** Post-parse shape of {@link MetadataEffectiveResponse} — defaults applied, transforms run (ADR-0122). */ -export type MetadataEffectiveResponseParsed = z.infer; export type MetadataExportResponse = z.input; /** Post-parse shape of {@link MetadataExportResponse} — defaults applied, transforms run (ADR-0122). */ export type MetadataExportResponseParsed = z.infer; diff --git a/packages/spec/src/contracts/metadata-service.ts b/packages/spec/src/contracts/metadata-service.ts index 0ed1dd45e4..eddcf91029 100644 --- a/packages/spec/src/contracts/metadata-service.ts +++ b/packages/spec/src/contracts/metadata-service.ts @@ -46,7 +46,6 @@ import type { MetadataQuery, MetadataQueryResult, MetadataValidationResult, Meta import type { MetadataWatchEvent } from '../system/metadata-persistence.zod'; import type { ApiEndpoint } from '../api/endpoint.zod'; import type { Action } from '../ui/action.zod'; -import type { MetadataOverlay } from '../kernel/metadata-customization.zod'; import type { PackagePublishResult, MetadataHistoryQueryOptions, MetadataHistoryQueryResult, MetadataDiffResult } from '../system/metadata-persistence.zod'; /** @@ -617,47 +616,18 @@ export interface IMetadataService { bulkUnregister?(items: Array<{ type: string; name: string }>, options?: MetadataWriteOptions): Promise; // ========================================== - // Overlay / Customization Management + // Overlay / Customization Management — REMOVED // ========================================== - - /** - * Get the active overlay for a metadata item. - * Returns the customization delta applied on top of the base definition. - * @param type - Metadata type - * @param name - Item name - * @param scope - Overlay scope ('platform' or 'user') - * @returns The overlay, or undefined if no customization exists - */ - getOverlay?(type: string, name: string, scope?: 'platform' | 'user'): Promise; - - /** - * Save/update an overlay for a metadata item. - * @param overlay - The overlay to save - */ - saveOverlay?(overlay: MetadataOverlay): Promise; - - /** - * Remove an overlay, reverting to the base definition. - * @param type - Metadata type - * @param name - Item name - * @param scope - Overlay scope - */ - removeOverlay?(type: string, name: string, scope?: 'platform' | 'user'): Promise; - - /** - * Get the effective (merged) metadata after applying all overlays. - * Resolution order: system ← merge(platform) ← merge(user) - * @param type - Metadata type - * @param name - Item name - * @param context - Optional auth context for user-scoped overlay resolution - * @returns The effective metadata with all overlays applied - */ - getEffective?(type: string, name: string, context?: { - userId?: string; - tenantId?: string; - positions?: string[]; - permissions?: string[]; - }): Promise; + // + // The optional `getOverlay` / `saveOverlay` / `removeOverlay` / + // `getEffective` members (typed against the paper protocol's + // `MetadataOverlay`) were REMOVED per ADR-0049 enforce-or-remove + // (#13135, re-charter of #12057): their only implementation was + // `packages/metadata`'s in-memory limb, served by no route and called + // only by its own unit tests; ADR-0126 §6 wall 4 supersedes the protocol + // on the record. The layered read that actually ships is + // `getMetaItemLayered` on the protocol dispatcher (ADR-0005 org + // overlay), not an optional member here. // ========================================== // Watch / Subscribe diff --git a/packages/spec/src/kernel/index.ts b/packages/spec/src/kernel/index.ts index 851e248211..7273e96ad3 100644 --- a/packages/spec/src/kernel/index.ts +++ b/packages/spec/src/kernel/index.ts @@ -21,7 +21,15 @@ export * from './events.zod'; // manifest (ADR-0007, env-overridable via OS_FEATURE_FLAGS_*) and the // PUBLIC_AUTH_FEATURES registry (public-auth-features.ts) for auth gates. export * from './manifest.zod'; -export * from './metadata-customization.zod'; +// metadata-customization.zod (MetadataOverlaySchema, FieldChangeSchema, the +// three-layer overlay / 3-way-merge protocol) was REMOVED per ADR-0049 +// enforce-or-remove (#13135, re-charter of #12057; ADR-0126 §6 wall 4 +// supersedes it as a matter of record — "nothing may build against it"). +// Zero reachable consumers: the only implementation was `packages/metadata`'s +// manager limb, served by no route and called only by its own unit tests. The +// REAL customization mechanisms are ADR-0005's org-scoped overlay +// (`allowOrgOverride` on DEFAULT_METADATA_TYPE_REGISTRY, sys_metadata rows) +// and ADR-0126's packaged-metadata model (clone + ledger disable). export * from './namespace-prefix'; export * from './platform-capabilities'; export * from './metadata-loader.zod'; diff --git a/packages/spec/src/kernel/metadata-customization-retirement.test.ts b/packages/spec/src/kernel/metadata-customization-retirement.test.ts new file mode 100644 index 0000000000..fd9683c0c6 --- /dev/null +++ b/packages/spec/src/kernel/metadata-customization-retirement.test.ts @@ -0,0 +1,116 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +import { describe, it, expect } from 'vitest'; +import { MetadataPluginConfigSchema } from './metadata-plugin.zod'; +import { MetadataManagerConfigSchema } from './metadata-loader.zod'; + +// ─── [#13135] The paper metadata-customization protocol is REMOVED ──────────── +// +// ADR-0049 enforce-or-remove, executing the maintainer ruling of 2026-08-29 on +// #12057 (「同意」 — retirement adopted; re-scope rejected). ADR-0126 §6 wall 4 +// supersedes the protocol on the record: "nothing may build against it". The +// module `kernel/metadata-customization.zod.ts` is deleted whole; these pins +// cover the three AUTHORABLE keys that embedded it and survive as tombstones: +// +// - `MetadataPluginConfig.customizationPolicies` (embedded +// `CustomizationPolicySchema`) — read by nothing; no code ever consulted +// a policy before accepting or refusing a customization. +// - `MetadataPluginConfig.mergeStrategy` (embedded +// `MergeStrategyConfigSchema`) — read by nothing; no 3-way merge engine +// ever existed, and package upgrades never merge customizations +// (ADR-0126 §6 wall 3). +// - `MetadataManagerConfig.persistence.overlayWritable` — gated only +// `MetadataManager.saveOverlay()`, a paper-protocol method reachable +// only from its own unit tests (no route or UI ever called it), removed +// with the protocol. +// +// Route: `retiredKey()` tombstones, NOT plain deletions — neither carrier +// schema (nor the nested `persistence` object) is `.strict()`, so deleting a +// key would make zod strip it in silence (the #3726 / #3733 shape, ADR-0104). +// The assertion set follows the #8586 `additionalTypes` precedent in this +// directory: refusal, the issue `code`, the `path` naming WHICH key was +// refused, and the prescription text (#5240: where the wording is the +// contract, pin the wording). +describe('[#13135] paper metadata-customization protocol retirement', () => { + /** A config that is valid except for whatever the individual test adds. */ + const baseConfig = { storage: {} } as const; + + it('REJECTS an authored `customizationPolicies`, naming the key and carrying the fix', () => { + const result = MetadataPluginConfigSchema.safeParse({ + ...baseConfig, + customizationPolicies: [{ + metadataType: 'object', + lockedFields: ['name', 'fields.*.type'], + }], + }); + + expect(result.success).toBe(false); + if (result.success) return; // narrowing; the assertion above already failed + + const issue = result.error.issues.find((i) => i.path[0] === 'customizationPolicies'); + expect(issue, 'the refusal must name `customizationPolicies`').toBeDefined(); + expect(issue!.code).toBe('invalid_type'); + expect(issue!.path).toEqual(['customizationPolicies']); + // The prescription IS the migration doc for whoever hits it. + expect(issue!.message).toMatch(/`config\.customizationPolicies`.*removed.*17/s); + expect(issue!.message).toMatch(/Delete the key/s); + // The live mechanisms must be named. + expect(issue!.message).toMatch(/allowOrgOverride/s); + expect(issue!.message).toMatch(/ADR-0126/s); + }); + + it('REJECTS an authored `mergeStrategy`, naming the key and carrying the fix', () => { + const result = MetadataPluginConfigSchema.safeParse({ + ...baseConfig, + mergeStrategy: { defaultStrategy: 'three-way-merge' }, + }); + + expect(result.success).toBe(false); + if (result.success) return; + + const issue = result.error.issues.find((i) => i.path[0] === 'mergeStrategy'); + expect(issue, 'the refusal must name `mergeStrategy`').toBeDefined(); + expect(issue!.code).toBe('invalid_type'); + expect(issue!.path).toEqual(['mergeStrategy']); + expect(issue!.message).toMatch(/`config\.mergeStrategy`.*removed.*17/s); + expect(issue!.message).toMatch(/Delete the key/s); + // The model that replaces a configurable strategy must be named. + expect(issue!.message).toMatch(/upgrades\s+rewrite the packaged base/s); + }); + + it('REJECTS an authored `persistence.overlayWritable`, naming the nested path', () => { + const result = MetadataManagerConfigSchema.safeParse({ + persistence: { writable: true, overlayWritable: false }, + }); + + expect(result.success).toBe(false); + if (result.success) return; + + const issue = result.error.issues.find( + (i) => i.path[0] === 'persistence' && i.path[1] === 'overlayWritable', + ); + expect(issue, 'the refusal must surface at persistence.overlayWritable').toBeDefined(); + expect(issue!.code).toBe('invalid_type'); + expect(issue!.message).toMatch(/`persistence\.overlayWritable`.*removed.*17/s); + expect(issue!.message).toMatch(/Delete the key/s); + // The gate that remains must be named. + expect(issue!.message).toMatch(/`persistence\.writable`/s); + }); + + it('parses cleanly once the keys are deleted, and grows none of them back', () => { + const parsed = MetadataPluginConfigSchema.parse({ ...baseConfig }); + expect(parsed.enableEvents).toBe(true); // control: defaults still apply + // The non-strict strip path: absence must stay absence. If a tombstone + // were ever replaced by a plain deletion, an authored key would be + // stripped here in silence — these pins plus the rejections above are + // what make that regression loud. + expect(parsed).not.toHaveProperty('customizationPolicies'); + expect(parsed).not.toHaveProperty('mergeStrategy'); + + const managerParsed = MetadataManagerConfigSchema.parse({ + persistence: { writable: false }, + }); + expect(managerParsed.persistence?.writable).toBe(false); // control + expect(managerParsed.persistence).not.toHaveProperty('overlayWritable'); + }); +}); diff --git a/packages/spec/src/kernel/metadata-customization.test.ts b/packages/spec/src/kernel/metadata-customization.test.ts deleted file mode 100644 index b6d18022b4..0000000000 --- a/packages/spec/src/kernel/metadata-customization.test.ts +++ /dev/null @@ -1,241 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { - CustomizationOriginSchema, - FieldChangeSchema, - MetadataOverlaySchema, - MergeConflictSchema, - MergeStrategyConfigSchema, - MergeResultSchema, - CustomizationPolicySchema, -} from './metadata-customization.zod'; - -describe('CustomizationOriginSchema', () => { - it('should accept valid origins', () => { - const origins = ['package', 'admin', 'user', 'migration', 'api']; - origins.forEach(origin => { - expect(() => CustomizationOriginSchema.parse(origin)).not.toThrow(); - }); - }); - - it('should reject invalid origins', () => { - expect(() => CustomizationOriginSchema.parse('system')).toThrow(); - expect(() => CustomizationOriginSchema.parse('')).toThrow(); - }); -}); - -describe('FieldChangeSchema', () => { - it('should accept minimal field change', () => { - const change = { - path: 'fields.status.label', - currentValue: 'Account Status', - }; - expect(() => FieldChangeSchema.parse(change)).not.toThrow(); - }); - - it('should accept full field change with tracking', () => { - const change = { - path: 'fields.status.label', - originalValue: 'Status', - currentValue: 'Account Status', - changedBy: 'admin@acme.com', - changedAt: '2025-06-15T10:00:00Z', - }; - const parsed = FieldChangeSchema.parse(change); - expect(parsed.path).toBe('fields.status.label'); - expect(parsed.originalValue).toBe('Status'); - expect(parsed.currentValue).toBe('Account Status'); - }); - - it('should reject missing required fields', () => { - expect(() => FieldChangeSchema.parse({ currentValue: 'test' })).toThrow(); - expect(() => FieldChangeSchema.parse({})).toThrow(); - }); -}); - -describe('MetadataOverlaySchema', () => { - it('should accept minimal overlay', () => { - const overlay = { - id: 'overlay-001', - baseType: 'object', - baseName: 'account', - patch: { label: 'My Custom Account' }, - }; - const parsed = MetadataOverlaySchema.parse(overlay); - expect(parsed.scope).toBe('platform'); - expect(parsed.active).toBe(true); - }); - - it('should accept full overlay with package reference', () => { - const overlay = { - id: 'overlay-002', - baseType: 'object', - baseName: 'account', - packageId: 'com.acme.crm', - packageVersion: '1.0.0', - scope: 'platform' as const, - tenantId: 'tenant-001', - patch: { - label: 'Custom Account', - 'fields.status.label': 'Account Status', - }, - changes: [ - { - path: 'label', - originalValue: 'Account', - currentValue: 'Custom Account', - changedBy: 'admin@acme.com', - changedAt: '2025-06-15T10:00:00Z', - }, - ], - active: true, - createdAt: '2025-06-15T10:00:00Z', - createdBy: 'admin@acme.com', - }; - - const parsed = MetadataOverlaySchema.parse(overlay); - expect(parsed.packageId).toBe('com.acme.crm'); - expect(parsed.changes).toHaveLength(1); - expect(parsed.patch.label).toBe('Custom Account'); - }); - - it('should accept user-scope overlay', () => { - const overlay = { - id: 'overlay-003', - baseType: 'view', - baseName: 'account_list', - scope: 'user' as const, - owner: 'user-123', - patch: { columns: ['name', 'status', 'created_at'] }, - }; - const parsed = MetadataOverlaySchema.parse(overlay); - expect(parsed.scope).toBe('user'); - expect(parsed.owner).toBe('user-123'); - }); - - it('should reject overlay without required fields', () => { - expect(() => MetadataOverlaySchema.parse({})).toThrow(); - expect(() => MetadataOverlaySchema.parse({ id: 'test' })).toThrow(); - }); -}); - -describe('MergeConflictSchema', () => { - it('should accept valid merge conflict', () => { - const conflict = { - path: 'fields.status.options', - baseValue: ['new', 'open', 'closed'], - incomingValue: ['new', 'open', 'in_progress', 'closed'], - customValue: ['new', 'open', 'resolved', 'closed'], - suggestedResolution: 'manual' as const, - reason: 'Both package and customer modified the options list', - }; - const parsed = MergeConflictSchema.parse(conflict); - expect(parsed.suggestedResolution).toBe('manual'); - }); - - it('should accept all resolution strategies', () => { - const strategies = ['keep-custom', 'accept-incoming', 'manual'] as const; - strategies.forEach(strategy => { - const conflict = { - path: 'label', - baseValue: 'old', - incomingValue: 'new', - customValue: 'custom', - suggestedResolution: strategy, - }; - expect(() => MergeConflictSchema.parse(conflict)).not.toThrow(); - }); - }); -}); - -describe('MergeStrategyConfigSchema', () => { - it('should apply defaults', () => { - const parsed = MergeStrategyConfigSchema.parse({}); - expect(parsed.defaultStrategy).toBe('three-way-merge'); - expect(parsed.autoResolveNonConflicting).toBe(true); - }); - - it('should accept full configuration', () => { - const config = { - defaultStrategy: 'keep-custom' as const, - alwaysAcceptIncoming: ['fields.*.type', 'triggers.*'], - alwaysKeepCustom: ['fields.*.label', 'fields.*.helpText', 'description'], - autoResolveNonConflicting: true, - }; - const parsed = MergeStrategyConfigSchema.parse(config); - expect(parsed.alwaysAcceptIncoming).toHaveLength(2); - expect(parsed.alwaysKeepCustom).toHaveLength(3); - }); -}); - -describe('MergeResultSchema', () => { - it('should accept successful merge result', () => { - const result = { - success: true, - mergedMetadata: { name: 'account', label: 'Custom Account' }, - stats: { - totalFields: 15, - unchanged: 10, - autoResolved: 4, - conflicts: 1, - }, - }; - const parsed = MergeResultSchema.parse(result); - expect(parsed.success).toBe(true); - expect(parsed.stats?.totalFields).toBe(15); - }); - - it('should accept merge result with conflicts', () => { - const result = { - success: false, - conflicts: [{ - path: 'fields.status.options', - baseValue: ['a'], - incomingValue: ['a', 'b'], - customValue: ['a', 'c'], - suggestedResolution: 'manual' as const, - }], - autoResolved: [{ - path: 'label', - resolution: 'keep-custom', - description: 'Customer label preserved', - }], - }; - const parsed = MergeResultSchema.parse(result); - expect(parsed.success).toBe(false); - expect(parsed.conflicts).toHaveLength(1); - }); -}); - -describe('CustomizationPolicySchema', () => { - it('should apply defaults', () => { - const parsed = CustomizationPolicySchema.parse({ metadataType: 'object' }); - expect(parsed.allowCustomization).toBe(true); - expect(parsed.allowAddFields).toBe(true); - expect(parsed.allowDeleteFields).toBe(false); - }); - - it('should accept full policy configuration', () => { - const policy = { - metadataType: 'object', - allowCustomization: true, - lockedFields: ['name', 'type', 'fields.*.type'], - customizableFields: ['label', 'description', 'fields.*.label', 'fields.*.helpText'], - allowAddFields: true, - allowDeleteFields: false, - }; - const parsed = CustomizationPolicySchema.parse(policy); - expect(parsed.lockedFields).toHaveLength(3); - expect(parsed.customizableFields).toHaveLength(4); - }); - - it('should accept restrictive policy', () => { - const policy = { - metadataType: 'flow', - allowCustomization: false, - allowAddFields: false, - allowDeleteFields: false, - }; - const parsed = CustomizationPolicySchema.parse(policy); - expect(parsed.allowCustomization).toBe(false); - }); -}); diff --git a/packages/spec/src/kernel/metadata-customization.zod.ts b/packages/spec/src/kernel/metadata-customization.zod.ts deleted file mode 100644 index 078ab6c5ed..0000000000 --- a/packages/spec/src/kernel/metadata-customization.zod.ts +++ /dev/null @@ -1,321 +0,0 @@ -// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. - -import { z } from 'zod'; - -/** - * # Metadata Customization Layer Protocol - * - * Defines the overlay system for managing user customizations on top of - * package-delivered metadata. This protocol solves the critical challenge - * of separating "vendor-managed" metadata from "customer-customized" metadata, - * enabling safe package upgrades without losing user changes. - * - * ## Architecture Alignment - * - **Salesforce**: Managed vs Unmanaged metadata components - * - **ServiceNow**: Update Sets with collision detection - * - **WordPress**: Parent/child theme overlay model - * - **Kubernetes**: Strategic merge patch for resource customization - * - * ## Three-Layer Model - * ``` - * ┌─────────────────────────────────┐ - * │ User Layer (scope: user) │ ← Personal overrides (per-user) - * ├─────────────────────────────────┤ - * │ Platform Layer (scope: platform)│ ← Admin customizations (per-tenant) - * ├─────────────────────────────────┤ - * │ System Layer (scope: system) │ ← Package-delivered metadata (read-only) - * └─────────────────────────────────┘ - * ``` - * - * ## Merge Resolution Order - * Effective metadata = System ← merge(Platform) ← merge(User) - * Each layer only stores the delta (changed fields), not the full definition. - */ - -// ========================================== -// Customization Tracking -// ========================================== - -/** - * Customization Origin - * Identifies who created the customization. - */ -import { lazySchema } from '../shared/lazy-schema'; -export const CustomizationOriginSchema = lazySchema(() => z.enum([ - 'package', // Delivered by a plugin package (system layer, read-only) - 'admin', // Created/modified by platform admin via UI - 'user', // Created/modified by end user via UI - 'migration', // Created during data migration - 'api', // Created via API -])); - -/** - * Field-Level Change Tracking - * Records exactly which fields were modified by the customer. - */ -export const FieldChangeSchema = lazySchema(() => z.object({ - /** JSON path to the changed field (e.g. "fields.status.label") */ - path: z.string().describe('JSON path to the changed field'), - - /** Original value from the package (for diff/rollback) */ - originalValue: z.unknown().optional().describe('Original value from the package'), - - /** Current customized value */ - currentValue: z.unknown().describe('Current customized value'), - - /** Who made this change */ - changedBy: z.string().optional().describe('User or admin who made this change'), - - /** When this change was made */ - changedAt: z.string().datetime().optional().describe('Timestamp of the change'), -})); - -/** - * Metadata Overlay Schema - * - * Represents a customization layer on top of package-delivered metadata. - * Each overlay stores only the delta (changed fields) relative to the base definition. - * - * During package upgrades, the system performs a 3-way merge: - * 1. Old package version (base) - * 2. New package version (theirs) - * 3. Customer customizations (ours) - * - * **Note on `baseName`:** customization records reference metadata by its - * Fully Qualified Name (FQN, `{namespace}__{shortName}`). FQN is used here - * because customizations cross package boundaries and need to disambiguate - * objects with the same short name from different packages. This is one of - * the few internal contexts where FQN is appropriate — user-facing code and - * AI-generated examples should always use short names instead. - * - * @example - * ```yaml - * # Package delivers: object "account" with field "status" label "Status" - * # Admin changes label to "Account Status" - * # Overlay record: - * baseType: object - * baseName: account - * packageId: com.acme.crm - * packageVersion: "1.0.0" - * changes: - * - path: "fields.status.label" - * originalValue: "Status" - * currentValue: "Account Status" - * ``` - */ -export const MetadataOverlaySchema = lazySchema(() => z.object({ - /** Primary key */ - id: z.string().describe('Overlay record ID (UUID)'), - - /** The metadata type being customized (e.g. "object", "view", "flow") */ - baseType: z.string().describe('Metadata type being customized'), - - /** The metadata name being customized (e.g. "account") */ - baseName: z.string().describe('Metadata name being customized'), - - /** Package that owns the base metadata (null for platform-created metadata) */ - packageId: z.string().optional().describe('Package ID that delivered the base metadata'), - - /** Package version when the customization was made (for upgrade diffing) */ - packageVersion: z.string().optional().describe('Package version when overlay was created'), - - /** Customization scope */ - scope: z.enum(['platform', 'user']).default('platform') - .describe('Customization scope (platform=admin, user=personal)'), - - /** Tenant ID for multi-tenant isolation */ - tenantId: z.string().optional().describe('Tenant identifier'), - - /** Owner user ID (for user-scope overlays) */ - owner: z.string().optional().describe('Owner user ID for user-scope overlays'), - - /** - * The overlay payload. - * Contains only the changed fields, using JSON Merge Patch semantics (RFC 7396). - * - To modify a field: include the field with its new value - * - To delete a field: set its value to null - * - Omitted fields remain unchanged from base - */ - patch: z.record(z.string(), z.unknown()).describe('JSON Merge Patch payload (changed fields only)'), - - /** - * Detailed change tracking for each modified field. - * Enables field-level conflict detection during upgrades. - */ - changes: z.array(FieldChangeSchema).optional() - .describe('Field-level change tracking for conflict detection'), - - /** Whether this overlay is currently active */ - active: z.boolean().default(true).describe('Whether this overlay is active'), - - /** Audit timestamps */ - createdAt: z.string().datetime().optional(), - createdBy: z.string().optional(), - updatedAt: z.string().datetime().optional(), - updatedBy: z.string().optional(), -})); - -// ========================================== -// Merge & Conflict Resolution -// ========================================== - -/** - * Merge Conflict - * Represents a conflict between package update and customer customization. - */ -export const MergeConflictSchema = lazySchema(() => z.object({ - /** JSON path to the conflicting field */ - path: z.string().describe('JSON path to the conflicting field'), - - /** Value in the old package version */ - baseValue: z.unknown().describe('Value in the old package version'), - - /** Value in the new package version */ - incomingValue: z.unknown().describe('Value in the new package version'), - - /** Customer's customized value */ - customValue: z.unknown().describe('Customer customized value'), - - /** Suggested resolution strategy */ - suggestedResolution: z.enum([ - 'keep-custom', // Keep customer's customization - 'accept-incoming', // Accept package update - 'manual', // Requires manual resolution - ]).describe('Suggested resolution strategy'), - - /** Reason for the suggested resolution */ - reason: z.string().optional().describe('Explanation for the suggested resolution'), -})); - -/** - * Merge Strategy Configuration - * Controls how metadata merging behaves during package upgrades. - */ -export const MergeStrategyConfigSchema = lazySchema(() => z.object({ - /** Default strategy when no field-level rule matches */ - defaultStrategy: z.enum([ - 'keep-custom', // Preserve all customer customizations (safe) - 'accept-incoming', // Accept all package updates (overwrite) - 'three-way-merge', // Intelligent 3-way merge with conflict detection - ]).default('three-way-merge').describe('Default merge strategy'), - - /** - * Field paths that should always accept incoming package updates. - * Use for fields that the package vendor considers "owned" and should not be customized. - * @example ["fields.*.type", "triggers.*"] - */ - alwaysAcceptIncoming: z.array(z.string()).optional() - .describe('Field paths that always accept package updates'), - - /** - * Field paths where customer customizations always win. - * Use for UI-facing fields like labels, descriptions, help text. - * @example ["fields.*.label", "fields.*.helpText", "description"] - */ - alwaysKeepCustom: z.array(z.string()).optional() - .describe('Field paths where customer customizations always win'), - - /** Whether to automatically resolve non-conflicting changes */ - autoResolveNonConflicting: z.boolean().default(true) - .describe('Auto-resolve changes that do not conflict'), -})); - -/** - * Merge Result - * Result of a 3-way merge operation during package upgrade. - */ -export const MergeResultSchema = lazySchema(() => z.object({ - /** Whether the merge completed successfully (no unresolved conflicts) */ - success: z.boolean().describe('Whether merge completed without unresolved conflicts'), - - /** The merged metadata payload */ - mergedMetadata: z.record(z.string(), z.unknown()).optional() - .describe('Merged metadata result'), - - /** Updated overlay with remaining customizations */ - updatedOverlay: z.record(z.string(), z.unknown()).optional() - .describe('Updated overlay after merge'), - - /** List of conflicts that require manual resolution */ - conflicts: z.array(MergeConflictSchema).optional() - .describe('Unresolved merge conflicts'), - - /** Summary of automatically resolved changes */ - autoResolved: z.array(z.object({ - path: z.string(), - resolution: z.string(), - description: z.string().optional(), - })).optional().describe('Summary of auto-resolved changes'), - - /** Statistics */ - stats: z.object({ - totalFields: z.number().int().min(0).describe('Total fields evaluated'), - unchanged: z.number().int().min(0).describe('Fields with no changes'), - autoResolved: z.number().int().min(0).describe('Fields auto-resolved'), - conflicts: z.number().int().min(0).describe('Fields with conflicts'), - }).optional(), -})); - -// ========================================== -// Customization Management -// ========================================== - -/** - * Customizable Metadata Policy - * Defines what parts of a metadata item can be customized by admins/users. - * Package vendors use this to control customization boundaries. - */ -export const CustomizationPolicySchema = lazySchema(() => z.object({ - /** Metadata type this policy applies to */ - metadataType: z.string().describe('Metadata type (e.g. "object", "view")'), - - /** Whether customization is allowed at all for this type */ - allowCustomization: z.boolean().default(true), - - /** - * Field paths that are locked (cannot be customized). - * @example ["name", "type", "fields.*.type"] - */ - lockedFields: z.array(z.string()).optional() - .describe('Field paths that cannot be customized'), - - /** - * Field paths that are customizable. - * If specified, only these fields can be customized (whitelist mode). - * @example ["label", "description", "fields.*.label", "fields.*.helpText"] - */ - customizableFields: z.array(z.string()).optional() - .describe('Field paths that can be customized (whitelist)'), - - /** - * Whether users can add new fields to package objects. - * When true, admins can extend package objects with custom fields. - */ - allowAddFields: z.boolean().default(true) - .describe('Whether admins can add new fields to package objects'), - - /** - * Whether users can delete package-delivered fields. - * Typically false — fields can only be hidden, not deleted. - */ - allowDeleteFields: z.boolean().default(false) - .describe('Whether admins can delete package-delivered fields'), -})); - -// ========================================== -// Export Types -// ========================================== - -export type CustomizationOrigin = z.input; -export type FieldChange = z.input; -export type MetadataOverlay = z.input; -/** Post-parse shape of {@link MetadataOverlay} — defaults applied, transforms run (ADR-0122). */ -export type MetadataOverlayParsed = z.infer; -export type MergeConflict = z.input; -export type MergeStrategyConfig = z.input; -/** Post-parse shape of {@link MergeStrategyConfig} — defaults applied, transforms run (ADR-0122). */ -export type MergeStrategyConfigParsed = z.infer; -export type MergeResult = z.input; -export type CustomizationPolicy = z.input; -/** Post-parse shape of {@link CustomizationPolicy} — defaults applied, transforms run (ADR-0122). */ -export type CustomizationPolicyParsed = z.infer; diff --git a/packages/spec/src/kernel/metadata-loader.zod.ts b/packages/spec/src/kernel/metadata-loader.zod.ts index 53e20f5e55..5622e39400 100644 --- a/packages/spec/src/kernel/metadata-loader.zod.ts +++ b/packages/spec/src/kernel/metadata-loader.zod.ts @@ -1,6 +1,7 @@ // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. import { z } from 'zod'; +import { retiredKey } from '../shared/retired-key'; /** * # Metadata Manager Configuration @@ -124,24 +125,36 @@ export const MetadataManagerConfigSchema = lazySchema(() => z.object({ loaderOptions: z.record(z.string(), z.unknown()).optional().describe('Loader-specific configuration'), /** - * Persistence Write Gates + * Persistence Write Gate * - * Two-axis gate that controls whether the metadata layer accepts mutations - * at runtime. Read paths are always permitted. + * Controls whether the metadata layer accepts mutations at runtime. Read + * paths are always permitted. * * - `writable: false` — `MetadataManager.register()` becomes a no-op * (or throws, depending on `validation.throwOnError`). Useful for * read-only project kernels booted from a compiled artifact, where the * running process must never write back to `sys_metadata`. - * - `overlayWritable: false` — `MetadataManager.saveOverlay()` is rejected. - * Use this for fully-frozen production deployments where Studio overlays - * are disabled. * - * Both default to `true` so existing dev / Studio flows are unaffected. + * Defaults to `true` so existing dev / Studio flows are unaffected. + * + * `overlayWritable` was REMOVED in v17 (#13135, ADR-0049 enforce-or-remove): + * the only thing it ever gated was `MetadataManager.saveOverlay()` — a + * method of the paper metadata-customization protocol, reachable only from + * its own unit tests (no route or UI ever called it) and removed with that + * protocol. Tombstoned rather than deleted because this nested object is + * not `.strict()` — a plain deletion would strip the key in silence. */ persistence: z.object({ writable: z.boolean().default(true).describe('Allow base metadata writes via register()'), - overlayWritable: z.boolean().default(true).describe('Allow overlay writes via saveOverlay()'), + overlayWritable: retiredKey( + '`persistence.overlayWritable` was removed from `MetadataManagerConfig` in ' + + '@objectstack/spec 17 (#13135, ADR-0049 enforce-or-remove) — the only thing it gated was ' + + '`MetadataManager.saveOverlay()`, a paper-protocol method no route or UI ever called, ' + + 'removed with the metadata-customization protocol (ADR-0126 supersedes it on the record). ' + + 'Delete the key. The base write gate that remains is `persistence.writable`; the real ' + + "org-overlay writes (ADR-0005) ride the REST meta write doors' `manage_metadata` " + + 'permission gate, not this flag.', + ), }).optional().describe('Persistence write gates'), })); diff --git a/packages/spec/src/kernel/metadata-plugin.zod.ts b/packages/spec/src/kernel/metadata-plugin.zod.ts index bc998cb102..3ecd46d454 100644 --- a/packages/spec/src/kernel/metadata-plugin.zod.ts +++ b/packages/spec/src/kernel/metadata-plugin.zod.ts @@ -3,7 +3,6 @@ import { z } from 'zod'; import { retiredKey } from '../shared/retired-key'; import { MetadataManagerConfigSchema } from './metadata-loader.zod'; -import { MergeStrategyConfigSchema, CustomizationPolicySchema } from './metadata-customization.zod'; import { ActionSchema } from '../ui/action.zod'; /** @@ -39,7 +38,6 @@ import { ActionSchema } from '../ui/action.zod'; * * ## References * - kernel/metadata-loader.zod.ts — MetadataManager wiring (datasource, cache, write gates) - * - kernel/metadata-customization.zod.ts — Overlay/merge protocol * - system/metadata-persistence.zod.ts — Database record format + loader/watch envelope types * - contracts/metadata-service.ts — Service interface */ @@ -493,17 +491,51 @@ export const MetadataPluginConfigSchema = lazySchema(() => z.object({ storage: MetadataManagerConfigSchema.describe('Storage backend configuration'), /** - * Default customization policies per metadata type. - * Controls what parts of metadata can be customized by admins/users. + * REMOVED in v17 (#13135, ADR-0049 enforce-or-remove; re-charter of #12057). + * + * `customizationPolicies` embedded the paper metadata-customization + * protocol's `CustomizationPolicySchema` (lockedFields / customizableFields + * whitelists) and was read by NOTHING: no code ever consulted a policy + * before accepting or refusing a customization, and the protocol it + * configured — the three-layer overlay of `metadata-customization.zod.ts` — + * was itself unreachable from any served surface (ADR-0126 §6 wall 4 + * supersedes it: "nothing may build against it"). Authoring a policy got a + * clean parse and zero behaviour, zero diagnostics. + * + * Tombstoned rather than deleted for the same reason as `additionalTypes` + * below: `MetadataPluginConfigSchema` is not `.strict()`, so a plain + * deletion would silently strip the key (the #3726 / #3733 shape, + * ADR-0104). The mechanisms that actually govern customization are in the + * prescription. */ - customizationPolicies: z.array(CustomizationPolicySchema).optional() - .describe('Default customization policies per type'), + customizationPolicies: retiredKey( + '`config.customizationPolicies` was removed from `MetadataPluginConfig` in @objectstack/spec 17 ' + + '(#13135, ADR-0049 enforce-or-remove) — it never had an effect: no code ever read a ' + + 'customization policy, and the overlay protocol it configured was itself unreachable from any ' + + 'served surface (ADR-0126 supersedes it on the record). Delete the key. What a customization ' + + "may touch is governed by the real mechanisms: ADR-0005's org-scoped overlay (opt-in via " + + '`allowOrgOverride` on `DEFAULT_METADATA_TYPE_REGISTRY`, enforced at the REST meta write ' + + "doors) and ADR-0126's packaged-metadata model (clone + ledger disable).", + ), /** - * Merge strategy for package upgrades. + * REMOVED in v17 (#13135, ADR-0049 enforce-or-remove; re-charter of #12057). + * + * `mergeStrategy` embedded the paper protocol's `MergeStrategyConfigSchema` + * (keep-custom / accept-incoming / three-way-merge) and was read by + * NOTHING: no 3-way merge engine ever existed, and package upgrades do not + * merge customizations — ADR-0126 §6 wall 3 separates the packaged BASE + * (upgrades rewrite it) from the customer's recorded choices (never + * touched by an upgrade). Same tombstone reasoning as above. */ - mergeStrategy: MergeStrategyConfigSchema.optional() - .describe('Merge strategy for package upgrades'), + mergeStrategy: retiredKey( + '`config.mergeStrategy` was removed from `MetadataPluginConfig` in @objectstack/spec 17 ' + + '(#13135, ADR-0049 enforce-or-remove) — it never had an effect: no 3-way merge engine ever ' + + 'existed to read it, and package upgrades do not merge customizations (ADR-0126: upgrades ' + + 'rewrite the packaged base; customer choices live in the ledger and are never merged). ' + + 'Delete the key. There is no replacement — upgrade-vs-customization separation is the ' + + 'model, not a configurable strategy.', + ), /** * REMOVED in v17 (#8586, ADR-0049 enforce-or-remove). diff --git a/packages/spec/src/migrations/entries/retired-defs/18.api__MetadataEffectiveResponse.ts b/packages/spec/src/migrations/entries/retired-defs/18.api__MetadataEffectiveResponse.ts new file mode 100644 index 0000000000..95837636ed --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.api__MetadataEffectiveResponse.ts @@ -0,0 +1,26 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #13135 — ADR-0049 enforce-or-remove (maintainer ruling 2026-08-29 on +// #12057: retirement adopted, re-scope rejected; re-charter #13135 executes +// the widened surface). Part of the whole-module removal of +// `kernel/metadata-customization.zod.ts` — the paper three-layer +// customization protocol ADR-0126 §6 wall 4 supersedes on the record +// ("nothing may build against it"). Zero reachable consumers, re-verified +// at the retirement's base commit with positive controls (#12057 fork +// report): the only implementation was `packages/metadata`'s manager limb, +// served by no route and called only by its own unit tests; the real +// mechanisms are ADR-0005's org overlay and ADR-0126's packaged-metadata +// model. This def: the `GET /api/meta/:type/:name/effective` response contract. No adapter +// ever served that path; the layered read that actually ships is +// `getMetaItemLayered` (ADR-0005 org overlay, `code`/`overlay`/`effective` +// layers) with its own contracts. +// +// Registered under 18, not 17: v17.0.0 was cut before this landed, so the +// removal ships on the 17.x line (launch-window convention: accept-set +// narrowings ride minor releases) and the prescription lives at the major +// boundary where `migrate meta` users look. No carrier key survives for +// these defs and no authored document embedded them, so no tombstone and no +// D2 conversion — this table plus the D3 semantic entry +// `metadata-customization-protocol-retired` ARE the declaration (the #8715 +// route-3 shape). +export const entry = 'api/MetadataEffectiveResponse'; diff --git a/packages/spec/src/migrations/entries/retired-defs/18.api__MetadataOverlayResponse.ts b/packages/spec/src/migrations/entries/retired-defs/18.api__MetadataOverlayResponse.ts new file mode 100644 index 0000000000..7cb2dd8f62 --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.api__MetadataOverlayResponse.ts @@ -0,0 +1,25 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #13135 — ADR-0049 enforce-or-remove (maintainer ruling 2026-08-29 on +// #12057: retirement adopted, re-scope rejected; re-charter #13135 executes +// the widened surface). Part of the whole-module removal of +// `kernel/metadata-customization.zod.ts` — the paper three-layer +// customization protocol ADR-0126 §6 wall 4 supersedes on the record +// ("nothing may build against it"). Zero reachable consumers, re-verified +// at the retirement's base commit with positive controls (#12057 fork +// report): the only implementation was `packages/metadata`'s manager limb, +// served by no route and called only by its own unit tests; the real +// mechanisms are ADR-0005's org overlay and ADR-0126's packaged-metadata +// model. This def: the `GET /api/meta/:type/:name/overlay` response contract. No adapter ever +// served that path (measured: no route spelling in packages/rest or +// packages/metadata). +// +// Registered under 18, not 17: v17.0.0 was cut before this landed, so the +// removal ships on the 17.x line (launch-window convention: accept-set +// narrowings ride minor releases) and the prescription lives at the major +// boundary where `migrate meta` users look. No carrier key survives for +// these defs and no authored document embedded them, so no tombstone and no +// D2 conversion — this table plus the D3 semantic entry +// `metadata-customization-protocol-retired` ARE the declaration (the #8715 +// route-3 shape). +export const entry = 'api/MetadataOverlayResponse'; diff --git a/packages/spec/src/migrations/entries/retired-defs/18.api__MetadataOverlaySaveRequest.ts b/packages/spec/src/migrations/entries/retired-defs/18.api__MetadataOverlaySaveRequest.ts new file mode 100644 index 0000000000..ff89d3c7f5 --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.api__MetadataOverlaySaveRequest.ts @@ -0,0 +1,24 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #13135 — ADR-0049 enforce-or-remove (maintainer ruling 2026-08-29 on +// #12057: retirement adopted, re-scope rejected; re-charter #13135 executes +// the widened surface). Part of the whole-module removal of +// `kernel/metadata-customization.zod.ts` — the paper three-layer +// customization protocol ADR-0126 §6 wall 4 supersedes on the record +// ("nothing may build against it"). Zero reachable consumers, re-verified +// at the retirement's base commit with positive controls (#12057 fork +// report): the only implementation was `packages/metadata`'s manager limb, +// served by no route and called only by its own unit tests; the real +// mechanisms are ADR-0005's org overlay and ADR-0126's packaged-metadata +// model. This def: the `PUT /api/meta/:type/:name/overlay` request contract (a bare +// `MetadataOverlaySchema`). No adapter ever served that path. +// +// Registered under 18, not 17: v17.0.0 was cut before this landed, so the +// removal ships on the 17.x line (launch-window convention: accept-set +// narrowings ride minor releases) and the prescription lives at the major +// boundary where `migrate meta` users look. No carrier key survives for +// these defs and no authored document embedded them, so no tombstone and no +// D2 conversion — this table plus the D3 semantic entry +// `metadata-customization-protocol-retired` ARE the declaration (the #8715 +// route-3 shape). +export const entry = 'api/MetadataOverlaySaveRequest'; diff --git a/packages/spec/src/migrations/entries/retired-defs/18.kernel__CustomizationOrigin.ts b/packages/spec/src/migrations/entries/retired-defs/18.kernel__CustomizationOrigin.ts new file mode 100644 index 0000000000..7c20b0cf28 --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.kernel__CustomizationOrigin.ts @@ -0,0 +1,24 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #13135 — ADR-0049 enforce-or-remove (maintainer ruling 2026-08-29 on +// #12057: retirement adopted, re-scope rejected; re-charter #13135 executes +// the widened surface). Part of the whole-module removal of +// `kernel/metadata-customization.zod.ts` — the paper three-layer +// customization protocol ADR-0126 §6 wall 4 supersedes on the record +// ("nothing may build against it"). Zero reachable consumers, re-verified +// at the retirement's base commit with positive controls (#12057 fork +// report): the only implementation was `packages/metadata`'s manager limb, +// served by no route and called only by its own unit tests; the real +// mechanisms are ADR-0005's org overlay and ADR-0126's packaged-metadata +// model. This def: the who-customized enum (`package`/`admin`/`user`/`migration`/`api`). +// Nothing ever wrote or read an origin. +// +// Registered under 18, not 17: v17.0.0 was cut before this landed, so the +// removal ships on the 17.x line (launch-window convention: accept-set +// narrowings ride minor releases) and the prescription lives at the major +// boundary where `migrate meta` users look. No carrier key survives for +// these defs and no authored document embedded them, so no tombstone and no +// D2 conversion — this table plus the D3 semantic entry +// `metadata-customization-protocol-retired` ARE the declaration (the #8715 +// route-3 shape). +export const entry = 'kernel/CustomizationOrigin'; diff --git a/packages/spec/src/migrations/entries/retired-defs/18.kernel__CustomizationPolicy.ts b/packages/spec/src/migrations/entries/retired-defs/18.kernel__CustomizationPolicy.ts new file mode 100644 index 0000000000..3a8bd2a325 --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.kernel__CustomizationPolicy.ts @@ -0,0 +1,26 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #13135 — ADR-0049 enforce-or-remove (maintainer ruling 2026-08-29 on +// #12057: retirement adopted, re-scope rejected; re-charter #13135 executes +// the widened surface). Part of the whole-module removal of +// `kernel/metadata-customization.zod.ts` — the paper three-layer +// customization protocol ADR-0126 §6 wall 4 supersedes on the record +// ("nothing may build against it"). Zero reachable consumers, re-verified +// at the retirement's base commit with positive controls (#12057 fork +// report): the only implementation was `packages/metadata`'s manager limb, +// served by no route and called only by its own unit tests; the real +// mechanisms are ADR-0005's org overlay and ADR-0126's packaged-metadata +// model. This def: the vendor customization-boundary policy (lockedFields / +// customizableFields whitelists), embedded by the retired authorable key +// `MetadataPluginConfig.customizationPolicies` (see +// `RETIRED_KEYS_BY_MAJOR[18]`). No code ever consulted a policy. +// +// Registered under 18, not 17: v17.0.0 was cut before this landed, so the +// removal ships on the 17.x line (launch-window convention: accept-set +// narrowings ride minor releases) and the prescription lives at the major +// boundary where `migrate meta` users look. No carrier key survives for +// these defs and no authored document embedded them, so no tombstone and no +// D2 conversion — this table plus the D3 semantic entry +// `metadata-customization-protocol-retired` ARE the declaration (the #8715 +// route-3 shape). +export const entry = 'kernel/CustomizationPolicy'; diff --git a/packages/spec/src/migrations/entries/retired-defs/18.kernel__FieldChange.ts b/packages/spec/src/migrations/entries/retired-defs/18.kernel__FieldChange.ts new file mode 100644 index 0000000000..d7f6ce1a4f --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.kernel__FieldChange.ts @@ -0,0 +1,27 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #13135 — ADR-0049 enforce-or-remove (maintainer ruling 2026-08-29 on +// #12057: retirement adopted, re-scope rejected; re-charter #13135 executes +// the widened surface). Part of the whole-module removal of +// `kernel/metadata-customization.zod.ts` — the paper three-layer +// customization protocol ADR-0126 §6 wall 4 supersedes on the record +// ("nothing may build against it"). Zero reachable consumers, re-verified +// at the retirement's base commit with positive controls (#12057 fork +// report): the only implementation was `packages/metadata`'s manager limb, +// served by no route and called only by its own unit tests; the real +// mechanisms are ADR-0005's org overlay and ADR-0126's packaged-metadata +// model. This def: the per-field change-tracking record +// (`path`/`originalValue`/`currentValue`) `MetadataOverlay.changes` embedded +// for upgrade conflict detection that never ran. objectui's +// `packages/types/src/views.ts` names it in a comment that itself says 'Do +// not re-point this at it'. +// +// Registered under 18, not 17: v17.0.0 was cut before this landed, so the +// removal ships on the 17.x line (launch-window convention: accept-set +// narrowings ride minor releases) and the prescription lives at the major +// boundary where `migrate meta` users look. No carrier key survives for +// these defs and no authored document embedded them, so no tombstone and no +// D2 conversion — this table plus the D3 semantic entry +// `metadata-customization-protocol-retired` ARE the declaration (the #8715 +// route-3 shape). +export const entry = 'kernel/FieldChange'; diff --git a/packages/spec/src/migrations/entries/retired-defs/18.kernel__MergeConflict.ts b/packages/spec/src/migrations/entries/retired-defs/18.kernel__MergeConflict.ts new file mode 100644 index 0000000000..50939698b9 --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.kernel__MergeConflict.ts @@ -0,0 +1,24 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #13135 — ADR-0049 enforce-or-remove (maintainer ruling 2026-08-29 on +// #12057: retirement adopted, re-scope rejected; re-charter #13135 executes +// the widened surface). Part of the whole-module removal of +// `kernel/metadata-customization.zod.ts` — the paper three-layer +// customization protocol ADR-0126 §6 wall 4 supersedes on the record +// ("nothing may build against it"). Zero reachable consumers, re-verified +// at the retirement's base commit with positive controls (#12057 fork +// report): the only implementation was `packages/metadata`'s manager limb, +// served by no route and called only by its own unit tests; the real +// mechanisms are ADR-0005's org overlay and ADR-0126's packaged-metadata +// model. This def: the 3-way-merge conflict record (`baseValue`/`incomingValue`/`customValue` +// + suggested resolution). No merge engine ever existed to produce one. +// +// Registered under 18, not 17: v17.0.0 was cut before this landed, so the +// removal ships on the 17.x line (launch-window convention: accept-set +// narrowings ride minor releases) and the prescription lives at the major +// boundary where `migrate meta` users look. No carrier key survives for +// these defs and no authored document embedded them, so no tombstone and no +// D2 conversion — this table plus the D3 semantic entry +// `metadata-customization-protocol-retired` ARE the declaration (the #8715 +// route-3 shape). +export const entry = 'kernel/MergeConflict'; diff --git a/packages/spec/src/migrations/entries/retired-defs/18.kernel__MergeResult.ts b/packages/spec/src/migrations/entries/retired-defs/18.kernel__MergeResult.ts new file mode 100644 index 0000000000..ee39163055 --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.kernel__MergeResult.ts @@ -0,0 +1,24 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #13135 — ADR-0049 enforce-or-remove (maintainer ruling 2026-08-29 on +// #12057: retirement adopted, re-scope rejected; re-charter #13135 executes +// the widened surface). Part of the whole-module removal of +// `kernel/metadata-customization.zod.ts` — the paper three-layer +// customization protocol ADR-0126 §6 wall 4 supersedes on the record +// ("nothing may build against it"). Zero reachable consumers, re-verified +// at the retirement's base commit with positive controls (#12057 fork +// report): the only implementation was `packages/metadata`'s manager limb, +// served by no route and called only by its own unit tests; the real +// mechanisms are ADR-0005's org overlay and ADR-0126's packaged-metadata +// model. This def: the 3-way-merge outcome record (merged payload, conflicts, auto-resolved +// stats). No merge engine ever existed to produce one. +// +// Registered under 18, not 17: v17.0.0 was cut before this landed, so the +// removal ships on the 17.x line (launch-window convention: accept-set +// narrowings ride minor releases) and the prescription lives at the major +// boundary where `migrate meta` users look. No carrier key survives for +// these defs and no authored document embedded them, so no tombstone and no +// D2 conversion — this table plus the D3 semantic entry +// `metadata-customization-protocol-retired` ARE the declaration (the #8715 +// route-3 shape). +export const entry = 'kernel/MergeResult'; diff --git a/packages/spec/src/migrations/entries/retired-defs/18.kernel__MergeStrategyConfig.ts b/packages/spec/src/migrations/entries/retired-defs/18.kernel__MergeStrategyConfig.ts new file mode 100644 index 0000000000..90e3c853e5 --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.kernel__MergeStrategyConfig.ts @@ -0,0 +1,28 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #13135 — ADR-0049 enforce-or-remove (maintainer ruling 2026-08-29 on +// #12057: retirement adopted, re-scope rejected; re-charter #13135 executes +// the widened surface). Part of the whole-module removal of +// `kernel/metadata-customization.zod.ts` — the paper three-layer +// customization protocol ADR-0126 §6 wall 4 supersedes on the record +// ("nothing may build against it"). Zero reachable consumers, re-verified +// at the retirement's base commit with positive controls (#12057 fork +// report): the only implementation was `packages/metadata`'s manager limb, +// served by no route and called only by its own unit tests; the real +// mechanisms are ADR-0005's org overlay and ADR-0126's packaged-metadata +// model. This def: the merge-strategy config (`keep-custom`/`accept-incoming`/`three-way- +// merge` + path rules), embedded by the retired authorable key +// `MetadataPluginConfig.mergeStrategy` (see `RETIRED_KEYS_BY_MAJOR[18]`). +// NOT the inline three-value `mergeStrategy` vocabulary on +// `api/PackageUpgradeRequest` / `kernel/UpgradePackageRequest` — separately +// declared twins, untouched. +// +// Registered under 18, not 17: v17.0.0 was cut before this landed, so the +// removal ships on the 17.x line (launch-window convention: accept-set +// narrowings ride minor releases) and the prescription lives at the major +// boundary where `migrate meta` users look. No carrier key survives for +// these defs and no authored document embedded them, so no tombstone and no +// D2 conversion — this table plus the D3 semantic entry +// `metadata-customization-protocol-retired` ARE the declaration (the #8715 +// route-3 shape). +export const entry = 'kernel/MergeStrategyConfig'; diff --git a/packages/spec/src/migrations/entries/retired-defs/18.kernel__MetadataOverlay.ts b/packages/spec/src/migrations/entries/retired-defs/18.kernel__MetadataOverlay.ts new file mode 100644 index 0000000000..69529aad5d --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.kernel__MetadataOverlay.ts @@ -0,0 +1,28 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #13135 — ADR-0049 enforce-or-remove (maintainer ruling 2026-08-29 on +// #12057: retirement adopted, re-scope rejected; re-charter #13135 executes +// the widened surface). Part of the whole-module removal of +// `kernel/metadata-customization.zod.ts` — the paper three-layer +// customization protocol ADR-0126 §6 wall 4 supersedes on the record +// ("nothing may build against it"). Zero reachable consumers, re-verified +// at the retirement's base commit with positive controls (#12057 fork +// report): the only implementation was `packages/metadata`'s manager limb, +// served by no route and called only by its own unit tests; the real +// mechanisms are ADR-0005's org overlay and ADR-0126's packaged-metadata +// model. This def: the protocol's core record: a platform/user-scoped JSON-merge-patch delta +// (`baseType`/`baseName`/`scope`/`patch`) with field-level `changes` +// tracking. Only importers were `api/metadata.zod.ts` section 5, +// `contracts/metadata-service.ts`'s optional members, and +// `packages/metadata`'s unreachable manager limb — all removed in the same +// change. +// +// Registered under 18, not 17: v17.0.0 was cut before this landed, so the +// removal ships on the 17.x line (launch-window convention: accept-set +// narrowings ride minor releases) and the prescription lives at the major +// boundary where `migrate meta` users look. No carrier key survives for +// these defs and no authored document embedded them, so no tombstone and no +// D2 conversion — this table plus the D3 semantic entry +// `metadata-customization-protocol-retired` ARE the declaration (the #8715 +// route-3 shape). +export const entry = 'kernel/MetadataOverlay'; diff --git a/packages/spec/src/migrations/entries/retired-keys/18.kernel__MetadataManagerConfig__persistence.overlayWritable.ts b/packages/spec/src/migrations/entries/retired-keys/18.kernel__MetadataManagerConfig__persistence.overlayWritable.ts new file mode 100644 index 0000000000..b3db94c5bf --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-keys/18.kernel__MetadataManagerConfig__persistence.overlayWritable.ts @@ -0,0 +1,25 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #13135 — ADR-0049 enforce-or-remove (maintainer ruling 2026-08-29 on +// #12057, adopting retirement; re-charter #13135 executes the widened +// surface). `persistence.overlayWritable` gated exactly one method — +// `MetadataManager.saveOverlay()` — which belonged to the paper +// metadata-customization protocol removed whole in the same change: no route +// ever served the paper `…/overlay` endpoints, no UI called the method, and +// its only callers were `packages/metadata`'s own unit tests. With the limb +// gone the flag gates nothing. The base write gate `persistence.writable` +// stays; the real org-overlay writes (ADR-0005) ride the REST meta write +// doors' `manage_metadata` permission gate, not this flag. +// +// Registered under 18, not 17: v17.0.0 was cut before this landed, so the +// tombstone ships on the 17.x line (launch-window convention) and the +// prescription lives at the major boundary where `migrate meta` users look +// (the #8586 precedent). +// +// Registered here but NOT in `src/conversions/registry.ts` — the +// `kernel/MetadataPluginConfig:additionalTypes` reasoning: a +// metadata-manager config is not a stack collection member, so a +// MetadataConversion would be a transform with no seam that ever runs. The +// prescription reaches authors through the tombstone (`tsc` + the parse) and +// the D3 semantic entry `metadata-customization-protocol-retired`. +export const entry = 'kernel/MetadataManagerConfig:persistence.overlayWritable'; diff --git a/packages/spec/src/migrations/entries/retired-keys/18.kernel__MetadataPluginConfig__customizationPolicies.ts b/packages/spec/src/migrations/entries/retired-keys/18.kernel__MetadataPluginConfig__customizationPolicies.ts new file mode 100644 index 0000000000..562347e99e --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-keys/18.kernel__MetadataPluginConfig__customizationPolicies.ts @@ -0,0 +1,30 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #13135 — ADR-0049 enforce-or-remove (maintainer ruling 2026-08-29 on +// #12057, adopting retirement; re-charter #13135 executes the widened +// surface). `customizationPolicies` embedded the paper metadata-customization +// protocol's `CustomizationPolicySchema` (lockedFields / customizableFields +// whitelists) and was read by NOTHING: no code ever consulted a policy before +// accepting or refusing a customization, and the protocol it configured — +// the three-layer overlay of `kernel/metadata-customization.zod.ts`, removed +// whole in the same change (see `RETIRED_DEFS_BY_MAJOR[18]`) — was itself +// unreachable from any served surface. ADR-0126 §6 wall 4 supersedes the +// protocol on the record ("nothing may build against it"). What a +// customization may touch is governed by ADR-0005's org overlay +// (`allowOrgOverride`) and ADR-0126's packaged-metadata model. +// +// Registered under 18, not 17: v17.0.0 was cut before this landed, so the +// tombstone ships on the 17.x line (launch-window convention: accept-set +// narrowings ride minor releases) and the prescription lives at the major +// boundary where `migrate meta` users look (the #8586 precedent). +// +// Registered here but NOT in `src/conversions/registry.ts`, for the reason +// the sibling `kernel/MetadataPluginConfig:additionalTypes` entry gives: the +// conversion chain walks a normalized STACK and +// `applyConversionsToStoredItem` maps a metadata type onto one of its +// collections. A metadata-plugin config is neither — there is no `plugins` +// entry in `PLURAL_TO_SINGULAR`, so a MetadataConversion here would be a +// transform with no seam that ever runs. The prescription reaches authors +// through the tombstone (`tsc` + the parse) and the D3 semantic entry +// `metadata-customization-protocol-retired`. +export const entry = 'kernel/MetadataPluginConfig:customizationPolicies'; diff --git a/packages/spec/src/migrations/entries/retired-keys/18.kernel__MetadataPluginConfig__mergeStrategy.ts b/packages/spec/src/migrations/entries/retired-keys/18.kernel__MetadataPluginConfig__mergeStrategy.ts new file mode 100644 index 0000000000..7ac65d0bc6 --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-keys/18.kernel__MetadataPluginConfig__mergeStrategy.ts @@ -0,0 +1,28 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #13135 — ADR-0049 enforce-or-remove (maintainer ruling 2026-08-29 on +// #12057, adopting retirement; re-charter #13135 executes the widened +// surface). `mergeStrategy` embedded the paper protocol's +// `MergeStrategyConfigSchema` (keep-custom / accept-incoming / +// three-way-merge) and was read by NOTHING: no 3-way merge engine ever +// existed, and package upgrades do not merge customizations — ADR-0126 §6 +// wall 3 separates the packaged BASE (upgrades rewrite it) from the +// customer's recorded choices (never touched by an upgrade). The value +// schema leaves with its module (`kernel/metadata-customization.zod.ts`, +// `RETIRED_DEFS_BY_MAJOR[18]`). NOT the same surface as the inline +// three-value `mergeStrategy` vocabulary on `api/PackageUpgradeRequest` / +// `kernel/UpgradePackageRequest` — those are separately declared twins that +// never imported the module and are deliberately untouched here. +// +// Registered under 18, not 17: v17.0.0 was cut before this landed, so the +// tombstone ships on the 17.x line (launch-window convention) and the +// prescription lives at the major boundary where `migrate meta` users look +// (the #8586 precedent). +// +// Registered here but NOT in `src/conversions/registry.ts` — the +// `kernel/MetadataPluginConfig:additionalTypes` reasoning: a metadata-plugin +// config is not a stack collection member, so a MetadataConversion would be +// a transform with no seam that ever runs. The prescription reaches authors +// through the tombstone (`tsc` + the parse) and the D3 semantic entry +// `metadata-customization-protocol-retired`. +export const entry = 'kernel/MetadataPluginConfig:mergeStrategy'; diff --git a/packages/spec/src/migrations/entries/semantic/18.metadata-customization-protocol-retired.ts b/packages/spec/src/migrations/entries/semantic/18.metadata-customization-protocol-retired.ts new file mode 100644 index 0000000000..e3e02d03ad --- /dev/null +++ b/packages/spec/src/migrations/entries/semantic/18.metadata-customization-protocol-retired.ts @@ -0,0 +1,53 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +import type { SemanticMigration } from '../../types.js'; + +export const entry: SemanticMigration = { + id: 'metadata-customization-protocol-retired', + surface: + 'the paper metadata-customization protocol: `kernel/metadata-customization.zod.ts` ' + + 'whole (`MetadataOverlay`, `FieldChange`, `CustomizationOrigin`, `MergeConflict`, ' + + '`MergeStrategyConfig`, `MergeResult`, `CustomizationPolicy`) / the section-5 ' + + 'Overlay/Customization API contracts (`api/MetadataOverlayResponse`, ' + + '`api/MetadataOverlaySaveRequest`, `api/MetadataEffectiveResponse`) / the optional ' + + '`getOverlay`/`saveOverlay`/`removeOverlay`/`getEffective` members of ' + + '`contracts/metadata-service.ts` / the authorable keys ' + + '`MetadataPluginConfig.customizationPolicies`, `MetadataPluginConfig.mergeStrategy` and ' + + '`MetadataManagerConfig.persistence.overlayWritable` (tombstoned; see ' + + '`RETIRED_KEYS_BY_MAJOR[18]`)', + replacement: + 'nothing to re-declare — delete any authored keys. The customization mechanisms that ' + + 'actually ship: ADR-0005\'s org-scoped overlay (opt-in via `allowOrgOverride` on ' + + '`DEFAULT_METADATA_TYPE_REGISTRY`, stored as `sys_metadata` org rows, written through the ' + + 'REST meta write doors and read back through `getMetaItemLayered`\'s ' + + '`code`/`overlay`/`effective` layers), and ADR-0126\'s packaged-metadata customization ' + + 'model (clone with a new machine name + ledger disable — never a field-level patch ' + + 'overlay)', + reason: + 'ADR-0049 enforce-or-remove; maintainer ruling 2026-08-29 on #12057 (「同意」 — retirement ' + + 'adopted, re-scope rejected), executed widened by #13135 per the fork report on #12057: ' + + 'the module declared a three-layer platform/user patch-overlay protocol with field-level ' + + 'change tracking and a 3-way-merge story, published reference docs described it as the ' + + 'customization architecture — and nothing reachable implemented it. The one ' + + 'implementation (`packages/metadata`\'s manager limb) was served by no route and called ' + + 'only by its own unit tests; no merge engine ever existed; no code read a ' + + '`CustomizationPolicy`. ADR-0126 §6 wall 4 supersedes the protocol as a matter of record ' + + '("nothing may build against it") — the per-field overlay layer it described is ' + + 'precisely what the #11513 ruling recorded as deliberately not chartered. Why D3 ' + + 'semantic and not a D2 conversion: the defs leave with no carrier key in any stack ' + + 'collection, and the three tombstoned keys live on plugin/manager configs, which are not ' + + 'stack collection members (`PLURAL_TO_SINGULAR` has no `plugins` entry) — a ' + + 'MetadataConversion would be a transform with no seam that ever runs (the ' + + '`kernel/Manifest:loading` precedent).', + acceptanceCriteria: + 'No import of `metadata-customization.zod` (or of the retired names from ' + + '`@objectstack/spec/kernel` / `@objectstack/spec/api`) compiles anywhere; no ' + + '`MetadataPluginConfig` carries `customizationPolicies` or `mergeStrategy`; no ' + + '`MetadataManagerConfig` carries `persistence.overlayWritable` (TypeScript authors get ' + + 'the refusal at compile time — the keys are typed `never` — and a value reaching the ' + + 'parse is refused with the prescription at the key\'s path). ⚠️ Runtime behaviour is ' + + 'deliberately UNCHANGED and must be verified as such: no route ever served the paper ' + + '`…/overlay` / `…/effective` endpoints, so removing the limb removes no served ' + + 'behaviour — the ADR-0005 org-overlay read/write path (`getMetaItemLayered`, the REST ' + + 'meta write doors) stays exactly as it was, before and after.', +}; diff --git a/packages/spec/src/migrations/registry.ts b/packages/spec/src/migrations/registry.ts index 1ad7cfea7f..455bb165f6 100644 --- a/packages/spec/src/migrations/registry.ts +++ b/packages/spec/src/migrations/registry.ts @@ -5280,7 +5280,22 @@ const step18: MigrationStep = { 'across the tree, the example apps and the published *.form.ts corpus). The FormView ' + 'vocabulary\'s own option shape (`FormSelectOptionSchema`, ui/view.zod.ts) now refuses ' + 'the key with the prescription; the mechanical conversion strips it from stored ' + - 'sources (pure lossless delete — it never had an effect on this surface to lose).', + 'sources (pure lossless delete — it never had an effect on this surface to lose). ' + + 'It also retires the paper metadata-customization protocol whole (#13135, re-charter ' + + 'of #12057; ADR-0049 enforce-or-remove, maintainer ruling 2026-08-29): ' + + '`kernel/metadata-customization.zod.ts` — the three-layer platform/user patch-overlay ' + + 'model with field-level change tracking and a 3-way-merge story — was exported, ' + + 'documented as the customization architecture, and implemented ONLY by an unreachable ' + + '`packages/metadata` limb (no route served the paper `…/overlay`/`…/effective` ' + + 'endpoints; the four optional service members were called only by their own unit ' + + 'tests). ADR-0126 §6 wall 4 supersedes it on the record ("nothing may build against ' + + 'it"). The module\'s seven defs and the three section-5 API contracts leave via ' + + 'RETIRED_DEFS_BY_MAJOR; the authorable carriers `MetadataPluginConfig.' + + 'customizationPolicies` / `.mergeStrategy` and `MetadataManagerConfig.persistence.' + + 'overlayWritable` are retiredKey tombstones (no D2 conversion — plugin/manager ' + + 'configs are not stack collection members, the additionalTypes reasoning). The ' + + 'customization that actually ships: ADR-0005\'s org overlay and ADR-0126\'s ' + + 'packaged-metadata model.', conversionIds: [ 'field-malformed-scale-precision-removed', 'record-chatter-position-vocabulary', diff --git a/packages/spec/src/type-alias-convention.pin.test.ts b/packages/spec/src/type-alias-convention.pin.test.ts index 3631c0c08f..daa877d1bb 100644 --- a/packages/spec/src/type-alias-convention.pin.test.ts +++ b/packages/spec/src/type-alias-convention.pin.test.ts @@ -161,7 +161,9 @@ import type * as M82 from './kernel/dependency-resolution.zod.js'; import type * as M83 from './kernel/events/core.zod.js'; import type * as M84 from './kernel/events/handlers.zod.js'; import type * as M85 from './kernel/manifest.zod.js'; -import type * as M86 from './kernel/metadata-customization.zod.js'; +// (M86 was kernel/metadata-customization.zod.js, removed whole in #13135 — +// ADR-0049 retirement of the paper customization protocol. The M number is +// positional and stays vacant.) import type * as M87 from './kernel/metadata-loader.zod.js'; import type * as M88 from './kernel/metadata-plugin.zod.js'; import type * as M89 from './kernel/metadata-protection.zod.js'; @@ -886,10 +888,11 @@ export type Iso406 = Assert, z.i export type Iso407 = Assert, z.infer< typeof M85.PluginIntegritySchema > >>; // kernel/metadata-customization.zod.ts -export type Iso408 = Assert, z.infer< typeof M86.CustomizationOriginSchema > >>; -export type Iso409 = Assert, z.infer< typeof M86.FieldChangeSchema > >>; -export type Iso410 = Assert, z.infer< typeof M86.MergeConflictSchema > >>; -export type Iso411 = Assert, z.infer< typeof M86.MergeResultSchema > >>; +// (Iso408 `CustomizationOriginSchema` / Iso409 `FieldChangeSchema` / +// Iso410 `MergeConflictSchema` / Iso411 `MergeResultSchema` removed with +// their module — #13135's ADR-0049 retirement of the paper +// metadata-customization protocol. The Iso numbers are positional and stay +// vacant.) // kernel/metadata-loader.zod.ts export type Iso412 = Assert, z.infer< typeof M87.MetadataFallbackStrategySchema > >>; @@ -2012,9 +2015,17 @@ describe('ADR-0122 type-alias convention', () => { // the alias no longer exists, so there is nothing to be isomorphic. -1 // removed; the Iso number stays vacant (ids are claims about pins, not // positions). + // + // 837 -> 833 is #13135's ADR-0049 retirement of the paper + // metadata-customization protocol: `kernel/metadata-customization.zod.ts` + // removed whole, so its four pins `Iso408`-`Iso411` + // (`CustomizationOriginSchema` / `FieldChangeSchema` / + // `MergeConflictSchema` / `MergeResultSchema`) left with the module. -4 + // removed; the Iso numbers and the `M86` module number stay vacant (ids + // are claims about pins, not positions). const self = readFileSync(fileURLToPath(import.meta.url), 'utf8'); const pins = self.match(/^export type Iso\d+ = Assert Date: Sat, 29 Aug 2026 06:28:56 +0000 Subject: [PATCH 2/6] wip: regenerate artifacts for the customization-protocol retirement Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4 --- content/docs/references/api/metadata.mdx | 115 +----- content/docs/references/index.mdx | 15 +- content/docs/references/kernel/index.mdx | 1 - content/docs/references/kernel/meta.json | 1 - .../kernel/metadata-customization.mdx | 196 ---------- .../references/kernel/metadata-loader.mdx | 4 +- .../references/kernel/metadata-plugin.mdx | 33 +- .../system/metadata-persistence.mdx | 4 +- packages/spec/api-surface/api.json | 7 - packages/spec/api-surface/kernel.json | 17 - packages/spec/authorable-defaults/api.json | 2 - packages/spec/authorable-defaults/kernel.json | 7 - packages/spec/authorable-surface/api.json | 23 -- packages/spec/authorable-surface/kernel.json | 46 +-- .../spec/docs-import-surface.baseline.json | 1 - packages/spec/json-schema.manifest/api.json | 3 - .../spec/json-schema.manifest/kernel.json | 7 - packages/spec/scripts/build-docs.ts | 2 +- .../spec/src/kernel/metadata-plugin.test.ts | 17 +- packages/spec/src/migrations/registry.ts | 362 ++++++++++++++++++ .../objectstack-platform/references/_index.md | 1 - 21 files changed, 388 insertions(+), 476 deletions(-) delete mode 100644 content/docs/references/kernel/metadata-customization.mdx diff --git a/content/docs/references/api/metadata.mdx b/content/docs/references/api/metadata.mdx index 7a4817e720..b1ed75c2a3 100644 --- a/content/docs/references/api/metadata.mdx +++ b/content/docs/references/api/metadata.mdx @@ -35,8 +35,8 @@ the entire platform, including Hono, Next.js, and NestJS adapters. ## TypeScript Usage ```typescript -import { AppDefinitionResponseSchema, ConceptListResponseSchema, MetadataBulkRegisterRequestSchema, MetadataBulkResponseSchema, MetadataBulkUnregisterRequestSchema, MetadataDeleteResponseSchema, MetadataDependenciesResponseSchema, MetadataDependentsResponseSchema, MetadataEffectiveResponseSchema, MetadataExistsResponseSchema, MetadataExportRequestSchema, MetadataExportResponseSchema, MetadataImportRequestSchema, MetadataImportResponseSchema, MetadataItemResponseSchema, MetadataListResponseSchema, MetadataNamesResponseSchema, MetadataOverlayResponseSchema, MetadataOverlaySaveRequestSchema, MetadataQueryRequestSchema, MetadataQueryResponseSchema, MetadataRegisterRequestSchema, MetadataTypeInfoResponseSchema, MetadataTypesResponseSchema, MetadataValidateRequestSchema, MetadataValidateResponseSchema, ObjectDefinitionResponseSchema } from '@objectstack/spec/api'; -import type { AppDefinitionResponse, ConceptListResponse, MetadataBulkRegisterRequest, MetadataBulkResponse, MetadataBulkUnregisterRequest, MetadataDeleteResponse, MetadataDependenciesResponse, MetadataDependentsResponse, MetadataEffectiveResponse, MetadataExistsResponse, MetadataExportResponse, MetadataImportResponse, MetadataItemResponse, MetadataListResponse, MetadataNamesResponse, MetadataOverlayResponse, MetadataQueryResponse, MetadataRegisterRequest, MetadataTypeInfoResponse, MetadataTypesResponse, MetadataValidateRequest, MetadataValidateResponse, ObjectDefinitionResponse } from '@objectstack/spec/api'; +import { AppDefinitionResponseSchema, ConceptListResponseSchema, MetadataBulkRegisterRequestSchema, MetadataBulkResponseSchema, MetadataBulkUnregisterRequestSchema, MetadataDeleteResponseSchema, MetadataDependenciesResponseSchema, MetadataDependentsResponseSchema, MetadataExistsResponseSchema, MetadataExportRequestSchema, MetadataExportResponseSchema, MetadataImportRequestSchema, MetadataImportResponseSchema, MetadataItemResponseSchema, MetadataListResponseSchema, MetadataNamesResponseSchema, MetadataQueryRequestSchema, MetadataQueryResponseSchema, MetadataRegisterRequestSchema, MetadataTypeInfoResponseSchema, MetadataTypesResponseSchema, MetadataValidateRequestSchema, MetadataValidateResponseSchema, ObjectDefinitionResponseSchema } from '@objectstack/spec/api'; +import type { AppDefinitionResponse, ConceptListResponse, MetadataBulkRegisterRequest, MetadataBulkResponse, MetadataBulkUnregisterRequest, MetadataDeleteResponse, MetadataDependenciesResponse, MetadataDependentsResponse, MetadataExistsResponse, MetadataExportResponse, MetadataImportResponse, MetadataItemResponse, MetadataListResponse, MetadataNamesResponse, MetadataQueryResponse, MetadataRegisterRequest, MetadataTypeInfoResponse, MetadataTypesResponse, MetadataValidateRequest, MetadataValidateResponse, ObjectDefinitionResponse } from '@objectstack/spec/api'; // Validate data const result = AppDefinitionResponseSchema.parse(data); @@ -314,33 +314,6 @@ const result = AppDefinitionResponseSchema.parse(data); | **kind** | `Enum<'reference' \| 'extends' \| 'includes' \| 'triggers'>` | ✅ | How the dependency is formed | ---- - -## MetadataEffectiveResponse - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **success** | `boolean` | ✅ | Operation success status | -| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| …>; declaredCode?: string; message: string; userMessage?: string; … }` | optional | Error details if success is false | -| **meta** | `{ timestamp: string; duration?: number; requestId?: string; traceId?: string }` | optional | Response metadata | -| **data** | `Record` | optional | Effective metadata with all overlays applied | - -### Nested Shape: `MetadataEffectiveResponse.error` - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **code** | `Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| …>` | ✅ | Error code (e.g. VALIDATION_ERROR; StandardErrorCode ∪ the ledger the serving side registers — ERROR_CODE_LEDGER for framework packages) | -| **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | -| **message** | `string` | ✅ | Readable error message | -| **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | -| **category** | `string` | optional | Error category (e.g. validation, authorization) | -| **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | -| **details** | `any` | optional | Additional error context (e.g. field validation errors) | -| **requestId** | `string` | optional | Request ID for tracking | - - --- ## MetadataExistsResponse @@ -544,90 +517,6 @@ const result = AppDefinitionResponseSchema.parse(data); | **requestId** | `string` | optional | Request ID for tracking | ---- - -## MetadataOverlayResponse - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **success** | `boolean` | ✅ | Operation success status | -| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| …>; declaredCode?: string; message: string; userMessage?: string; … }` | optional | Error details if success is false | -| **meta** | `{ timestamp: string; duration?: number; requestId?: string; traceId?: string }` | optional | Response metadata | -| **data** | `{ id: string; baseType: string; baseName: string; packageId?: string; … }` | optional | Overlay definition, undefined if none | - -### Nested Shape: `MetadataOverlayResponse.error` - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **code** | `Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| …>` | ✅ | Error code (e.g. VALIDATION_ERROR; StandardErrorCode ∪ the ledger the serving side registers — ERROR_CODE_LEDGER for framework packages) | -| **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | -| **message** | `string` | ✅ | Readable error message | -| **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | -| **category** | `string` | optional | Error category (e.g. validation, authorization) | -| **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | -| **details** | `any` | optional | Additional error context (e.g. field validation errors) | -| **requestId** | `string` | optional | Request ID for tracking | - -### Nested Shape: `MetadataOverlayResponse.data` - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **id** | `string` | ✅ | Overlay record ID (UUID) | -| **baseType** | `string` | ✅ | Metadata type being customized | -| **baseName** | `string` | ✅ | Metadata name being customized | -| **packageId** | `string` | optional | Package ID that delivered the base metadata | -| **packageVersion** | `string` | optional | Package version when overlay was created | -| **scope** | `Enum<'platform' \| 'user'>` | optional (default: `"platform"`) | Customization scope (platform=admin, user=personal) | -| **tenantId** | `string` | optional | Tenant identifier | -| **owner** | `string` | optional | Owner user ID for user-scope overlays | -| **patch** | `Record` | ✅ | JSON Merge Patch payload (changed fields only) | -| **changes** | `{ path: string; originalValue?: any; currentValue: any; changedBy?: string; … }[]` | optional | Field-level change tracking for conflict detection | -| **active** | `boolean` | optional (default: `true`) | Whether this overlay is active | -| **createdAt** | `string` | optional | | -| **createdBy** | `string` | optional | | -| **updatedAt** | `string` | optional | | -| **updatedBy** | `string` | optional | | - - ---- - -## MetadataOverlaySaveRequest - -Overlay to save - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **id** | `string` | ✅ | Overlay record ID (UUID) | -| **baseType** | `string` | ✅ | Metadata type being customized | -| **baseName** | `string` | ✅ | Metadata name being customized | -| **packageId** | `string` | optional | Package ID that delivered the base metadata | -| **packageVersion** | `string` | optional | Package version when overlay was created | -| **scope** | `Enum<'platform' \| 'user'>` | optional (default: `"platform"`) | Customization scope (platform=admin, user=personal) | -| **tenantId** | `string` | optional | Tenant identifier | -| **owner** | `string` | optional | Owner user ID for user-scope overlays | -| **patch** | `Record` | ✅ | JSON Merge Patch payload (changed fields only) | -| **changes** | `{ path: string; originalValue?: any; currentValue: any; changedBy?: string; … }[]` | optional | Field-level change tracking for conflict detection | -| **active** | `boolean` | optional (default: `true`) | Whether this overlay is active | -| **createdAt** | `string` | optional | | -| **createdBy** | `string` | optional | | -| **updatedAt** | `string` | optional | | -| **updatedBy** | `string` | optional | | - -### Nested Shape: `MetadataOverlaySaveRequest.changes[number]` - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **path** | `string` | ✅ | JSON path to the changed field | -| **originalValue** | `any` | optional | Original value from the package | -| **currentValue** | `any` | ✅ | Current customized value | -| **changedBy** | `string` | optional | User or admin who made this change | -| **changedAt** | `string` | optional | Timestamp of the change | - - --- ## MetadataQueryRequest diff --git a/content/docs/references/index.mdx b/content/docs/references/index.mdx index ddb847e0bb..a0a4c2d5bd 100644 --- a/content/docs/references/index.mdx +++ b/content/docs/references/index.mdx @@ -1,6 +1,6 @@ --- title: Protocol Reference -description: Every schema published by @objectstack/spec — 1605 schemas across 14 protocol modules +description: Every schema published by @objectstack/spec — 1595 schemas across 14 protocol modules --- {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} @@ -20,20 +20,20 @@ counts are sums of the rows they head. Regenerate with | Module | Pages | Schemas | Description | | :--- | ---: | ---: | :--- | | [AI Protocol](/docs/references/ai) | 11 | 66 | Agents, tools, skills, RAG and knowledge sources, model registry, conversations. | -| [API Protocol](/docs/references/api) | 31 | 439 | REST contracts, endpoints, routing, realtime, batch, discovery. | +| [API Protocol](/docs/references/api) | 31 | 436 | REST contracts, endpoints, routing, realtime, batch, discovery. | | [Automation Protocol](/docs/references/automation) | 13 | 68 | Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execution records. | | [Cloud Protocol](/docs/references/cloud) | 11 | 94 | Environments, packages and versions, marketplace, developer portal, tenancy. | | [Data Protocol](/docs/references/data) | 29 | 166 | Objects, fields, queries, filters, datasources and drivers — the ObjectQL layer. | | [Identity Protocol](/docs/references/identity) | 5 | 27 | Users and accounts, organizations, positions, SCIM provisioning. | | [Integration Protocol](/docs/references/integration) | 1 | 27 | The single connector protocol (ADR-0097) — catalog descriptors and provider-bound instances. | -| [Kernel Protocol](/docs/references/kernel) | 31 | 170 | Plugin lifecycle and manifests, capabilities and security, metadata loading, service registry. | +| [Kernel Protocol](/docs/references/kernel) | 30 | 163 | Plugin lifecycle and manifests, capabilities and security, metadata loading, service registry. | | [QA Protocol](/docs/references/qa) | 1 | 8 | Declarative test suites — scenarios, steps, actions and assertions. | | [Security Protocol](/docs/references/security) | 5 | 29 | Permission sets, row-level security, sharing rules, tenancy posture. | | [Shared Protocol](/docs/references/shared) | 8 | 32 | Primitives used across every protocol — identifiers, HTTP, expressions, error maps, enums. | | [Studio Protocol](/docs/references/studio) | 3 | 35 | Studio designer metadata — the authoring surfaces for the protocols above. | | [System Protocol](/docs/references/system) | 36 | 291 | The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance. | | [UI Protocol](/docs/references/ui) | 16 | 153 | Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer. | -| **Total** | **201** | **1605** | 14 protocol modules | +| **Total** | **200** | **1595** | 14 protocol modules | --- @@ -61,7 +61,7 @@ Agents, tools, skills, RAG and knowledge sources, model registry, conversations. ## API Protocol -**Source:** `packages/spec/src/api/` · **Import:** `@objectstack/spec/api` · **31 pages, 439 schemas** +**Source:** `packages/spec/src/api/` · **Import:** `@objectstack/spec/api` · **31 pages, 436 schemas** REST contracts, endpoints, routing, realtime, batch, discovery. @@ -82,7 +82,7 @@ REST contracts, endpoints, routing, realtime, batch, discovery. | [`events.zod.ts`](/docs/references/api/events) | `BulkDataEvent`, `BulkDataEventType`, `DataEvent`, `DataEventType`, `MetadataEvent`, `MetadataEventType` | | [`export.zod.ts`](/docs/references/api/export) | `CreateExportJobRequest`, `CreateExportJobResponse`, `CreateImportJobRequest`, `CreateImportJobResponse`, `DeduplicationStrategy`, `ExportFormat`, `ExportImportTemplate`, `ExportJobProgress`, `ExportJobStatus`, `ExportJobSummary`, `FieldMappingEntry`, `GetExportJobDownloadRequest`, `GetExportJobDownloadResponse`, `ImportJobProgress`, `ImportJobResults`, `ImportJobStatus`, `ImportJobSummary`, `ImportMapping`, `ImportRequest`, `ImportResponse`, `ImportRowResult`, `ImportValidationConfig`, `ImportValidationMode`, `ImportValidationResult`, `ImportWriteMode`, `ListExportJobsRequest`, `ListExportJobsResponse`, `ListImportJobsRequest`, `ListImportJobsResponse`, `ScheduleExportRequest`, `ScheduleExportResponse`, `ScheduledExport`, `UndoImportJobResponse` | | [`http-cache.zod.ts`](/docs/references/api/http-cache) | `CacheControl`, `CacheDirective`, `CacheInvalidationRequest`, `CacheInvalidationResponse`, `CacheInvalidationTarget`, `ETag`, `MetadataCacheRequest`, `MetadataCacheResponse` | -| [`metadata.zod.ts`](/docs/references/api/metadata) | `AppDefinitionResponse`, `ConceptListResponse`, `MetadataBulkRegisterRequest`, `MetadataBulkResponse`, `MetadataBulkUnregisterRequest`, `MetadataDeleteResponse`, `MetadataDependenciesResponse`, `MetadataDependentsResponse`, `MetadataEffectiveResponse`, `MetadataExistsResponse`, `MetadataExportRequest`, `MetadataExportResponse`, `MetadataImportRequest`, `MetadataImportResponse`, `MetadataItemResponse`, `MetadataListResponse`, `MetadataNamesResponse`, `MetadataOverlayResponse`, `MetadataOverlaySaveRequest`, `MetadataQueryRequest`, `MetadataQueryResponse`, `MetadataRegisterRequest`, `MetadataTypeInfoResponse`, `MetadataTypesResponse`, `MetadataValidateRequest`, `MetadataValidateResponse`, `ObjectDefinitionResponse` | +| [`metadata.zod.ts`](/docs/references/api/metadata) | `AppDefinitionResponse`, `ConceptListResponse`, `MetadataBulkRegisterRequest`, `MetadataBulkResponse`, `MetadataBulkUnregisterRequest`, `MetadataDeleteResponse`, `MetadataDependenciesResponse`, `MetadataDependentsResponse`, `MetadataExistsResponse`, `MetadataExportRequest`, `MetadataExportResponse`, `MetadataImportRequest`, `MetadataImportResponse`, `MetadataItemResponse`, `MetadataListResponse`, `MetadataNamesResponse`, `MetadataQueryRequest`, `MetadataQueryResponse`, `MetadataRegisterRequest`, `MetadataTypeInfoResponse`, `MetadataTypesResponse`, `MetadataValidateRequest`, `MetadataValidateResponse`, `ObjectDefinitionResponse` | | [`misc`](/docs/references/api/misc) *(no single source file)* | `ResolvedBook`, `ResolvedEntry`, `ResolvedGroup` | | [`odata.zod.ts`](/docs/references/api/odata) | `ODataConfig`, `ODataError`, `ODataFilterFunction`, `ODataMetadata`, `ODataQuery`, `ODataResponse` | | [`package-api.zod.ts`](/docs/references/api/package-api) | `GetInstalledPackageRequest`, `GetInstalledPackageResponse`, `ListInstalledPackagesRequest`, `ListInstalledPackagesResponse`, `PackageApiErrorCode`, `PackageInstallRequest`, `PackageInstallResponse`, `PackagePathParams`, `PackageRollbackRequest`, `PackageUpgradeRequest`, `PackageUpgradeResponse`, `ResolveDependenciesRequest`, `ResolveDependenciesResponse`, `UninstallPackageApiRequest`, `UninstallPackageApiResponse`, `UploadArtifactRequest`, `UploadArtifactResponse` | @@ -217,7 +217,7 @@ The single connector protocol (ADR-0097) — catalog descriptors and provider-bo ## Kernel Protocol -**Source:** `packages/spec/src/kernel/` · **Import:** `@objectstack/spec/kernel` · **31 pages, 170 schemas** +**Source:** `packages/spec/src/kernel/` · **Import:** `@objectstack/spec/kernel` · **30 pages, 163 schemas** Plugin lifecycle and manifests, capabilities and security, metadata loading, service registry. @@ -235,7 +235,6 @@ Plugin lifecycle and manifests, capabilities and security, metadata loading, ser | [`events/queue.zod.ts`](/docs/references/kernel/events-queue) | `EventQueueConfig`, `EventReplayConfig`, `EventSourcingConfig` | | [`execution-context.zod.ts`](/docs/references/kernel/execution-context) | `ExecutionContext` | | [`manifest.zod.ts`](/docs/references/kernel/manifest) | `Manifest`, `ManifestPermissions`, `PluginEngines`, `PluginIntegrity`, `PluginPackaging`, `PluginPermissions`, `PluginRuntime` | -| [`metadata-customization.zod.ts`](/docs/references/kernel/metadata-customization) | `CustomizationOrigin`, `CustomizationPolicy`, `FieldChange`, `MergeConflict`, `MergeResult`, `MergeStrategyConfig`, `MetadataOverlay` | | [`metadata-loader.zod.ts`](/docs/references/kernel/metadata-loader) | `MetadataFallbackStrategy`, `MetadataManagerConfig` | | [`metadata-plugin.zod.ts`](/docs/references/kernel/metadata-plugin) | `MetadataBulkResult`, `MetadataDependency`, `MetadataPluginConfig`, `MetadataPluginManifest`, `MetadataQuery`, `MetadataQueryResult`, `MetadataType`, `MetadataTypeRegistryEntry`, `MetadataValidationResult` | | [`metadata-protection.zod.ts`](/docs/references/kernel/metadata-protection) | `MetadataLock`, `MetadataLockSource`, `MetadataProvenance` | diff --git a/content/docs/references/kernel/index.mdx b/content/docs/references/kernel/index.mdx index 314286cdd8..cd83941daa 100644 --- a/content/docs/references/kernel/index.mdx +++ b/content/docs/references/kernel/index.mdx @@ -18,7 +18,6 @@ This section contains all protocol schemas for the kernel layer of ObjectStack. - diff --git a/content/docs/references/kernel/meta.json b/content/docs/references/kernel/meta.json index 5431806b67..048eb7e7e6 100644 --- a/content/docs/references/kernel/meta.json +++ b/content/docs/references/kernel/meta.json @@ -24,7 +24,6 @@ "cluster", "context", "execution-context", - "metadata-customization", "metadata-loader", "metadata-plugin", "metadata-protection", diff --git a/content/docs/references/kernel/metadata-customization.mdx b/content/docs/references/kernel/metadata-customization.mdx deleted file mode 100644 index 6dffc471ca..0000000000 --- a/content/docs/references/kernel/metadata-customization.mdx +++ /dev/null @@ -1,196 +0,0 @@ ---- -title: Metadata Customization -description: Metadata Customization protocol schemas ---- - -{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} - -## Metadata Customization Layer Protocol - -Defines the overlay system for managing user customizations on top of -package-delivered metadata. This protocol solves the critical challenge -of separating "vendor-managed" metadata from "customer-customized" metadata, -enabling safe package upgrades without losing user changes. - -### Architecture Alignment -- **Salesforce**: Managed vs Unmanaged metadata components -- **ServiceNow**: Update Sets with collision detection -- **WordPress**: Parent/child theme overlay model -- **Kubernetes**: Strategic merge patch for resource customization - -### Three-Layer Model -``` -┌─────────────────────────────────┐ -│ User Layer (scope: user) │ ← Personal overrides (per-user) -├─────────────────────────────────┤ -│ Platform Layer (scope: platform)│ ← Admin customizations (per-tenant) -├─────────────────────────────────┤ -│ System Layer (scope: system) │ ← Package-delivered metadata (read-only) -└─────────────────────────────────┘ -``` - -### Merge Resolution Order -Effective metadata = System ← merge(Platform) ← merge(User) -Each layer only stores the delta (changed fields), not the full definition. - - -**Source:** `packages/spec/src/kernel/metadata-customization.zod.ts` - - -## TypeScript Usage - -```typescript -import { CustomizationOriginSchema, CustomizationPolicySchema, FieldChangeSchema, MergeConflictSchema, MergeResultSchema, MergeStrategyConfigSchema, MetadataOverlaySchema } from '@objectstack/spec/kernel'; -import type { CustomizationOrigin, CustomizationPolicy, FieldChange, MergeConflict, MergeResult, MergeStrategyConfig, MetadataOverlay } from '@objectstack/spec/kernel'; - -// Validate data -const result = CustomizationOriginSchema.parse(data); -``` - ---- - -## CustomizationOrigin - -### Allowed Values - -* `package` -* `admin` -* `user` -* `migration` -* `api` - - ---- - -## CustomizationPolicy - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **metadataType** | `string` | ✅ | Metadata type (e.g. "object", "view") | -| **allowCustomization** | `boolean` | optional (default: `true`) | | -| **lockedFields** | `string[]` | optional | Field paths that cannot be customized | -| **customizableFields** | `string[]` | optional | Field paths that can be customized (whitelist) | -| **allowAddFields** | `boolean` | optional (default: `true`) | Whether admins can add new fields to package objects | -| **allowDeleteFields** | `boolean` | optional (default: `false`) | Whether admins can delete package-delivered fields | - - ---- - -## FieldChange - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **path** | `string` | ✅ | JSON path to the changed field | -| **originalValue** | `any` | optional | Original value from the package | -| **currentValue** | `any` | ✅ | Current customized value | -| **changedBy** | `string` | optional | User or admin who made this change | -| **changedAt** | `string` | optional | Timestamp of the change | - - ---- - -## MergeConflict - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **path** | `string` | ✅ | JSON path to the conflicting field | -| **baseValue** | `any` | ✅ | Value in the old package version | -| **incomingValue** | `any` | ✅ | Value in the new package version | -| **customValue** | `any` | ✅ | Customer customized value | -| **suggestedResolution** | `Enum<'keep-custom' \| 'accept-incoming' \| 'manual'>` | ✅ | Suggested resolution strategy | -| **reason** | `string` | optional | Explanation for the suggested resolution | - - ---- - -## MergeResult - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **success** | `boolean` | ✅ | Whether merge completed without unresolved conflicts | -| **mergedMetadata** | `Record` | optional | Merged metadata result | -| **updatedOverlay** | `Record` | optional | Updated overlay after merge | -| **conflicts** | `{ path: string; baseValue: any; incomingValue: any; customValue: any; … }[]` | optional | Unresolved merge conflicts | -| **autoResolved** | `{ path: string; resolution: string; description?: string }[]` | optional | Summary of auto-resolved changes | -| **stats** | `{ totalFields: integer; unchanged: integer; autoResolved: integer; conflicts: integer }` | optional | | - -### Nested Shape: `MergeResult.conflicts[number]` - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **path** | `string` | ✅ | JSON path to the conflicting field | -| **baseValue** | `any` | ✅ | Value in the old package version | -| **incomingValue** | `any` | ✅ | Value in the new package version | -| **customValue** | `any` | ✅ | Customer customized value | -| **suggestedResolution** | `Enum<'keep-custom' \| 'accept-incoming' \| 'manual'>` | ✅ | Suggested resolution strategy | -| **reason** | `string` | optional | Explanation for the suggested resolution | - -### Nested Shape: `MergeResult.stats` - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **totalFields** | `integer` | ✅ | Total fields evaluated | -| **unchanged** | `integer` | ✅ | Fields with no changes | -| **autoResolved** | `integer` | ✅ | Fields auto-resolved | -| **conflicts** | `integer` | ✅ | Fields with conflicts | - - ---- - -## MergeStrategyConfig - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **defaultStrategy** | `Enum<'keep-custom' \| 'accept-incoming' \| 'three-way-merge'>` | optional (default: `"three-way-merge"`) | Default merge strategy | -| **alwaysAcceptIncoming** | `string[]` | optional | Field paths that always accept package updates | -| **alwaysKeepCustom** | `string[]` | optional | Field paths where customer customizations always win | -| **autoResolveNonConflicting** | `boolean` | optional (default: `true`) | Auto-resolve changes that do not conflict | - - ---- - -## MetadataOverlay - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **id** | `string` | ✅ | Overlay record ID (UUID) | -| **baseType** | `string` | ✅ | Metadata type being customized | -| **baseName** | `string` | ✅ | Metadata name being customized | -| **packageId** | `string` | optional | Package ID that delivered the base metadata | -| **packageVersion** | `string` | optional | Package version when overlay was created | -| **scope** | `Enum<'platform' \| 'user'>` | optional (default: `"platform"`) | Customization scope (platform=admin, user=personal) | -| **tenantId** | `string` | optional | Tenant identifier | -| **owner** | `string` | optional | Owner user ID for user-scope overlays | -| **patch** | `Record` | ✅ | JSON Merge Patch payload (changed fields only) | -| **changes** | `{ path: string; originalValue?: any; currentValue: any; changedBy?: string; … }[]` | optional | Field-level change tracking for conflict detection | -| **active** | `boolean` | optional (default: `true`) | Whether this overlay is active | -| **createdAt** | `string` | optional | | -| **createdBy** | `string` | optional | | -| **updatedAt** | `string` | optional | | -| **updatedBy** | `string` | optional | | - -### Nested Shape: `MetadataOverlay.changes[number]` - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **path** | `string` | ✅ | JSON path to the changed field | -| **originalValue** | `any` | optional | Original value from the package | -| **currentValue** | `any` | ✅ | Current customized value | -| **changedBy** | `string` | optional | User or admin who made this change | -| **changedAt** | `string` | optional | Timestamp of the change | - - ---- - diff --git a/content/docs/references/kernel/metadata-loader.mdx b/content/docs/references/kernel/metadata-loader.mdx index 26de3d765f..89f2bded9c 100644 --- a/content/docs/references/kernel/metadata-loader.mdx +++ b/content/docs/references/kernel/metadata-loader.mdx @@ -54,7 +54,7 @@ const result = MetadataFallbackStrategySchema.parse(data); | **watchOptions** | `{ ignored?: string[]; persistent: boolean; ignoreInitial: boolean }` | optional | File watcher options | | **validation** | `{ strict: boolean; throwOnError: boolean }` | optional | Validation settings | | **loaderOptions** | `Record` | optional | Loader-specific configuration | -| **persistence** | `{ writable: boolean; overlayWritable: boolean }` | optional | Persistence write gates | +| **persistence** | `{ writable: boolean }` | optional | Persistence write gates | ### Nested Shape: `MetadataManagerConfig.cache` @@ -85,7 +85,7 @@ const result = MetadataFallbackStrategySchema.parse(data); | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **writable** | `boolean` | optional (default: `true`) | Allow base metadata writes via register() | -| **overlayWritable** | `boolean` | optional (default: `true`) | Allow overlay writes via saveOverlay() | +| **overlayWritable** | `never` | optional | [REMOVED] `persistence.overlayWritable` was removed from `MetadataManagerConfig` in @objectstack/spec 17 (#13135, ADR-0049 enforce-or-remove) — the only thing it gated was `MetadataManager.saveOverlay()`, a paper-protocol method no route or UI ever called, removed with the metadata-customization protocol (ADR-0126 supersedes it on the record). Delete the key. The base write gate that remains is `persistence.writable`; the real org-overlay writes (ADR-0005) ride the REST meta write doors' `manage_metadata` permission gate, not this flag. | --- diff --git a/content/docs/references/kernel/metadata-plugin.mdx b/content/docs/references/kernel/metadata-plugin.mdx index b907ca8edf..bbf7cff909 100644 --- a/content/docs/references/kernel/metadata-plugin.mdx +++ b/content/docs/references/kernel/metadata-plugin.mdx @@ -37,7 +37,6 @@ cohesive plugin that "takes over" the entire platform's metadata management: ### References - [kernel/metadata-loader.zod.ts](/docs/references/kernel/metadata-loader) — MetadataManager wiring (datasource, cache, write gates) -- [kernel/metadata-customization.zod.ts](/docs/references/kernel/metadata-customization) — Overlay/merge protocol - [system/metadata-persistence.zod.ts](/docs/references/system/metadata-persistence) — Database record format + loader/watch envelope types - contracts/metadata-service.ts — Service interface @@ -101,8 +100,8 @@ const result = MetadataBulkResultSchema.parse(data); | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **storage** | `{ datasource?: string; tableName: string; fallback: Enum<'filesystem' \| 'memory' \| 'none'>; rootDir?: string; … }` | ✅ | Storage backend configuration | -| **customizationPolicies** | `{ metadataType: string; allowCustomization: boolean; lockedFields?: string[]; customizableFields?: string[]; … }[]` | optional | Default customization policies per type | -| **mergeStrategy** | `{ defaultStrategy: Enum<'keep-custom' \| 'accept-incoming' \| 'three-way-merge'>; alwaysAcceptIncoming?: string[]; alwaysKeepCustom?: string[]; autoResolveNonConflicting: boolean }` | optional | Merge strategy for package upgrades | +| **customizationPolicies** | `never` | optional | [REMOVED] `config.customizationPolicies` was removed from `MetadataPluginConfig` in @objectstack/spec 17 (#13135, ADR-0049 enforce-or-remove) — it never had an effect: no code ever read a customization policy, and the overlay protocol it configured was itself unreachable from any served surface (ADR-0126 supersedes it on the record). Delete the key. What a customization may touch is governed by the real mechanisms: ADR-0005's org-scoped overlay (opt-in via `allowOrgOverride` on `DEFAULT_METADATA_TYPE_REGISTRY`, enforced at the REST meta write doors) and ADR-0126's packaged-metadata model (clone + ledger disable). | +| **mergeStrategy** | `never` | optional | [REMOVED] `config.mergeStrategy` was removed from `MetadataPluginConfig` in @objectstack/spec 17 (#13135, ADR-0049 enforce-or-remove) — it never had an effect: no 3-way merge engine ever existed to read it, and package upgrades do not merge customizations (ADR-0126: upgrades rewrite the packaged base; customer choices live in the ledger and are never merged). Delete the key. There is no replacement — upgrade-vs-customization separation is the model, not a configurable strategy. | | **additionalTypes** | `never` | optional | [REMOVED] `config.additionalTypes` was removed from `MetadataPluginConfig` in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — it never had an effect: the only production writer of the metadata type registry is `setTypeRegistry(DEFAULT_METADATA_TYPE_REGISTRY)`, which replaces the array outright, so nothing ever merged these entries and the live type set was exactly the built-in registry whatever you declared here. Delete the key. There is no declared-kind channel: a kind enters the live metadata-type set as a side effect of registering an ITEM of that kind (`SchemaRegistry.registerItem` during app/manifest registration, or `MetadataManager.register` at runtime); bind its schema with `registerMetadataTypeSchema(type, schema)` from your plugin's `init(ctx)` so `GET /api/v1/meta` serves a real JSON Schema for it. | | **enableEvents** | `boolean` | optional (default: `true`) | Emit metadata change events | | **validateOnWrite** | `boolean` | optional (default: `true`) | Validate metadata on write | @@ -124,27 +123,7 @@ const result = MetadataBulkResultSchema.parse(data); | **watchOptions** | `{ ignored?: string[]; persistent: boolean; ignoreInitial: boolean }` | optional | File watcher options | | **validation** | `{ strict: boolean; throwOnError: boolean }` | optional | Validation settings | | **loaderOptions** | `Record` | optional | Loader-specific configuration | -| **persistence** | `{ writable: boolean; overlayWritable: boolean }` | optional | Persistence write gates | - -### Nested Shape: `MetadataPluginConfig.customizationPolicies[number]` - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **metadataType** | `string` | ✅ | Metadata type (e.g. "object", "view") | -| **allowCustomization** | `boolean` | optional (default: `true`) | | -| **lockedFields** | `string[]` | optional | Field paths that cannot be customized | -| **customizableFields** | `string[]` | optional | Field paths that can be customized (whitelist) | -| **allowAddFields** | `boolean` | optional (default: `true`) | Whether admins can add new fields to package objects | -| **allowDeleteFields** | `boolean` | optional (default: `false`) | Whether admins can delete package-delivered fields | - -### Nested Shape: `MetadataPluginConfig.mergeStrategy` - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **defaultStrategy** | `Enum<'keep-custom' \| 'accept-incoming' \| 'three-way-merge'>` | optional (default: `"three-way-merge"`) | Default merge strategy | -| **alwaysAcceptIncoming** | `string[]` | optional | Field paths that always accept package updates | -| **alwaysKeepCustom** | `string[]` | optional | Field paths where customer customizations always win | -| **autoResolveNonConflicting** | `boolean` | optional (default: `true`) | Auto-resolve changes that do not conflict | +| **persistence** | `{ writable: boolean }` | optional | Persistence write gates | --- @@ -161,7 +140,7 @@ const result = MetadataBulkResultSchema.parse(data); | **type** | `'standard'` | ✅ | Plugin type | | **description** | `string` | optional (default: `"Core metadata management service for ObjectStack platform"`) | Plugin description | | **capabilities** | `{ crud: boolean; query: boolean; overlay: boolean; watch: boolean; … }` | ✅ | Plugin capabilities | -| **config** | `{ storage: object; customizationPolicies?: object[]; mergeStrategy?: object; enableEvents: boolean; … }` | optional | Plugin configuration | +| **config** | `{ storage: object; enableEvents: boolean; validateOnWrite: boolean; enableVersioning: boolean; … }` | optional | Plugin configuration | ### Nested Shape: `MetadataPluginManifest.capabilities` @@ -181,8 +160,8 @@ const result = MetadataBulkResultSchema.parse(data); | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **storage** | `{ datasource?: string; tableName: string; fallback: Enum<'filesystem' \| 'memory' \| 'none'>; rootDir?: string; … }` | ✅ | Storage backend configuration | -| **customizationPolicies** | `{ metadataType: string; allowCustomization: boolean; lockedFields?: string[]; customizableFields?: string[]; … }[]` | optional | Default customization policies per type | -| **mergeStrategy** | `{ defaultStrategy: Enum<'keep-custom' \| 'accept-incoming' \| 'three-way-merge'>; alwaysAcceptIncoming?: string[]; alwaysKeepCustom?: string[]; autoResolveNonConflicting: boolean }` | optional | Merge strategy for package upgrades | +| **customizationPolicies** | `never` | optional | [REMOVED] `config.customizationPolicies` was removed from `MetadataPluginConfig` in @objectstack/spec 17 (#13135, ADR-0049 enforce-or-remove) — it never had an effect: no code ever read a customization policy, and the overlay protocol it configured was itself unreachable from any served surface (ADR-0126 supersedes it on the record). Delete the key. What a customization may touch is governed by the real mechanisms: ADR-0005's org-scoped overlay (opt-in via `allowOrgOverride` on `DEFAULT_METADATA_TYPE_REGISTRY`, enforced at the REST meta write doors) and ADR-0126's packaged-metadata model (clone + ledger disable). | +| **mergeStrategy** | `never` | optional | [REMOVED] `config.mergeStrategy` was removed from `MetadataPluginConfig` in @objectstack/spec 17 (#13135, ADR-0049 enforce-or-remove) — it never had an effect: no 3-way merge engine ever existed to read it, and package upgrades do not merge customizations (ADR-0126: upgrades rewrite the packaged base; customer choices live in the ledger and are never merged). Delete the key. There is no replacement — upgrade-vs-customization separation is the model, not a configurable strategy. | | **additionalTypes** | `never` | optional | [REMOVED] `config.additionalTypes` was removed from `MetadataPluginConfig` in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — it never had an effect: the only production writer of the metadata type registry is `setTypeRegistry(DEFAULT_METADATA_TYPE_REGISTRY)`, which replaces the array outright, so nothing ever merged these entries and the live type set was exactly the built-in registry whatever you declared here. Delete the key. There is no declared-kind channel: a kind enters the live metadata-type set as a side effect of registering an ITEM of that kind (`SchemaRegistry.registerItem` during app/manifest registration, or `MetadataManager.register` at runtime); bind its schema with `registerMetadataTypeSchema(type, schema)` from your plugin's `init(ctx)` so `GET /api/v1/meta` serves a real JSON Schema for it. | | **enableEvents** | `boolean` | optional (default: `true`) | Emit metadata change events | | **validateOnWrite** | `boolean` | optional (default: `true`) | Validate metadata on write | diff --git a/content/docs/references/system/metadata-persistence.mdx b/content/docs/references/system/metadata-persistence.mdx index a5fa30a425..11007f02b4 100644 --- a/content/docs/references/system/metadata-persistence.mdx +++ b/content/docs/references/system/metadata-persistence.mdx @@ -250,7 +250,7 @@ Metadata file format | **watchOptions** | `{ ignored?: string[]; persistent: boolean; ignoreInitial: boolean }` | optional | File watcher options | | **validation** | `{ strict: boolean; throwOnError: boolean }` | optional | Validation settings | | **loaderOptions** | `Record` | optional | Loader-specific configuration | -| **persistence** | `{ writable: boolean; overlayWritable: boolean }` | optional | Persistence write gates | +| **persistence** | `{ writable: boolean }` | optional | Persistence write gates | ### Nested Shape: `MetadataManagerConfig.cache` @@ -281,7 +281,7 @@ Metadata file format | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **writable** | `boolean` | optional (default: `true`) | Allow base metadata writes via register() | -| **overlayWritable** | `boolean` | optional (default: `true`) | Allow overlay writes via saveOverlay() | +| **overlayWritable** | `never` | optional | [REMOVED] `persistence.overlayWritable` was removed from `MetadataManagerConfig` in @objectstack/spec 17 (#13135, ADR-0049 enforce-or-remove) — the only thing it gated was `MetadataManager.saveOverlay()`, a paper-protocol method no route or UI ever called, removed with the metadata-customization protocol (ADR-0126 supersedes it on the record). Delete the key. The base write gate that remains is `persistence.writable`; the real org-overlay writes (ADR-0005) ride the REST meta write doors' `manage_metadata` permission gate, not this flag. | --- diff --git a/packages/spec/api-surface/api.json b/packages/spec/api-surface/api.json index 8f4bae3e70..0eb8538386 100644 --- a/packages/spec/api-surface/api.json +++ b/packages/spec/api-surface/api.json @@ -630,9 +630,6 @@ "MetadataDependentsResponse (type)", "MetadataDependentsResponseParsed (type)", "MetadataDependentsResponseSchema (const)", - "MetadataEffectiveResponse (type)", - "MetadataEffectiveResponseParsed (type)", - "MetadataEffectiveResponseSchema (const)", "MetadataEndpointsConfig (type)", "MetadataEndpointsConfigParsed (type)", "MetadataEndpointsConfigSchema (const)", @@ -660,10 +657,6 @@ "MetadataNamesResponse (type)", "MetadataNamesResponseParsed (type)", "MetadataNamesResponseSchema (const)", - "MetadataOverlayResponse (type)", - "MetadataOverlayResponseParsed (type)", - "MetadataOverlayResponseSchema (const)", - "MetadataOverlaySaveRequestSchema (const)", "MetadataProtocol (interface)", "MetadataQueryRequestSchema (const)", "MetadataQueryResponse (type)", diff --git a/packages/spec/api-surface/kernel.json b/packages/spec/api-surface/kernel.json index ca8f39685b..581f6edd9f 100644 --- a/packages/spec/api-surface/kernel.json +++ b/packages/spec/api-surface/kernel.json @@ -33,11 +33,6 @@ "CompatibilityMatrixEntryParsed (type)", "CompatibilityMatrixEntrySchema (const)", "CompletenessFinding (interface)", - "CustomizationOrigin (type)", - "CustomizationOriginSchema (const)", - "CustomizationPolicy (type)", - "CustomizationPolicyParsed (type)", - "CustomizationPolicySchema (const)", "DEFAULT_METADATA_TYPE_REGISTRY (const)", "DeadLetterQueueEntry (type)", "DeadLetterQueueEntryParsed (type)", @@ -123,8 +118,6 @@ "FIELD_RELATIONSHIP_WITHOUT_REFERENCE (const)", "FIELD_SUMMARY_WITHOUT_OPERATIONS (const)", "FUNCTIONAL_COMPLETENESS_RULES (const)", - "FieldChange (type)", - "FieldChangeSchema (const)", "GetPackageRequest (type)", "GetPackageRequestSchema (const)", "GetPackageResponse (type)", @@ -166,13 +159,6 @@ "ManifestPermissions (type)", "ManifestPermissionsSchema (const)", "ManifestSchema (const)", - "MergeConflict (type)", - "MergeConflictSchema (const)", - "MergeResult (type)", - "MergeResultSchema (const)", - "MergeStrategyConfig (type)", - "MergeStrategyConfigParsed (type)", - "MergeStrategyConfigSchema (const)", "MetadataBulkResult (type)", "MetadataBulkResultSchema (const)", "MetadataCategory (type)", @@ -197,9 +183,6 @@ "MetadataManagerConfig (type)", "MetadataManagerConfigParsed (type)", "MetadataManagerConfigSchema (const)", - "MetadataOverlay (type)", - "MetadataOverlayParsed (type)", - "MetadataOverlaySchema (const)", "MetadataPluginConfig (type)", "MetadataPluginConfigParsed (type)", "MetadataPluginConfigSchema (const)", diff --git a/packages/spec/authorable-defaults/api.json b/packages/spec/authorable-defaults/api.json index 3ca93e6313..185d5003c8 100644 --- a/packages/spec/authorable-defaults/api.json +++ b/packages/spec/authorable-defaults/api.json @@ -106,8 +106,6 @@ "api/MetadataImportRequest:conflictResolution = \"skip\"", "api/MetadataImportRequest:dryRun = false", "api/MetadataImportRequest:validate = true", - "api/MetadataOverlaySaveRequest:active = true", - "api/MetadataOverlaySaveRequest:scope = \"platform\"", "api/MetadataQueryRequest:page = 1", "api/MetadataQueryRequest:pageSize = 50", "api/MetadataQueryRequest:sortBy = \"name\"", diff --git a/packages/spec/authorable-defaults/kernel.json b/packages/spec/authorable-defaults/kernel.json index dfda8051b1..c162a52d84 100644 --- a/packages/spec/authorable-defaults/kernel.json +++ b/packages/spec/authorable-defaults/kernel.json @@ -11,9 +11,6 @@ "kernel/ClusterCapabilityConfig:tenantIsolation = \"channel-prefix\"", "kernel/ClusterCapabilityConfig:useExistingPool = true", "kernel/CompatibilityMatrixEntry:migrationRequired = false", - "kernel/CustomizationPolicy:allowAddFields = true", - "kernel/CustomizationPolicy:allowCustomization = true", - "kernel/CustomizationPolicy:allowDeleteFields = false", "kernel/DependencyGraphNode:dependencies = []", "kernel/EventClusterOptions:scope = \"local\"", "kernel/EventHandler:async = true", @@ -58,15 +55,11 @@ "kernel/KernelSecurityVulnerability:patchAvailable = false", "kernel/Manifest:defaultDatasource = \"default\"", "kernel/Manifest:scope = \"project\"", - "kernel/MergeStrategyConfig:autoResolveNonConflicting = true", - "kernel/MergeStrategyConfig:defaultStrategy = \"three-way-merge\"", "kernel/MetadataDiffItem:hasConflict = false", "kernel/MetadataManagerConfig:fallback = \"none\"", "kernel/MetadataManagerConfig:formats = [\"typescript\",\"json\",\"yaml\"]", "kernel/MetadataManagerConfig:tableName = \"sys_metadata\"", "kernel/MetadataManagerConfig:watch = false", - "kernel/MetadataOverlay:active = true", - "kernel/MetadataOverlay:scope = \"platform\"", "kernel/MetadataPluginConfig:bootstrap = \"eager\"", "kernel/MetadataPluginConfig:cacheMaxItems = 10000", "kernel/MetadataPluginConfig:enableEvents = true", diff --git a/packages/spec/authorable-surface/api.json b/packages/spec/authorable-surface/api.json index 892b89620e..a97dfe1d18 100644 --- a/packages/spec/authorable-surface/api.json +++ b/packages/spec/authorable-surface/api.json @@ -1078,10 +1078,6 @@ "api/MetadataDependentsResponse:error", "api/MetadataDependentsResponse:meta", "api/MetadataDependentsResponse:success", - "api/MetadataEffectiveResponse:data", - "api/MetadataEffectiveResponse:error", - "api/MetadataEffectiveResponse:meta", - "api/MetadataEffectiveResponse:success", "api/MetadataEndpointsConfig:cacheTtl", "api/MetadataEndpointsConfig:enableCache", "api/MetadataEndpointsConfig:endpoints", @@ -1126,25 +1122,6 @@ "api/MetadataNamesResponse:error", "api/MetadataNamesResponse:meta", "api/MetadataNamesResponse:success", - "api/MetadataOverlayResponse:data", - "api/MetadataOverlayResponse:error", - "api/MetadataOverlayResponse:meta", - "api/MetadataOverlayResponse:success", - "api/MetadataOverlaySaveRequest:active", - "api/MetadataOverlaySaveRequest:baseName", - "api/MetadataOverlaySaveRequest:baseType", - "api/MetadataOverlaySaveRequest:changes", - "api/MetadataOverlaySaveRequest:createdAt", - "api/MetadataOverlaySaveRequest:createdBy", - "api/MetadataOverlaySaveRequest:id", - "api/MetadataOverlaySaveRequest:owner", - "api/MetadataOverlaySaveRequest:packageId", - "api/MetadataOverlaySaveRequest:packageVersion", - "api/MetadataOverlaySaveRequest:patch", - "api/MetadataOverlaySaveRequest:scope", - "api/MetadataOverlaySaveRequest:tenantId", - "api/MetadataOverlaySaveRequest:updatedAt", - "api/MetadataOverlaySaveRequest:updatedBy", "api/MetadataQueryRequest:namespaces", "api/MetadataQueryRequest:packageId", "api/MetadataQueryRequest:page", diff --git a/packages/spec/authorable-surface/kernel.json b/packages/spec/authorable-surface/kernel.json index e565acf6c7..b685d2ce3b 100644 --- a/packages/spec/authorable-surface/kernel.json +++ b/packages/spec/authorable-surface/kernel.json @@ -37,12 +37,6 @@ "kernel/CompatibilityMatrixEntry:migrationScript", "kernel/CompatibilityMatrixEntry:testCoverage", "kernel/CompatibilityMatrixEntry:to", - "kernel/CustomizationPolicy:allowAddFields", - "kernel/CustomizationPolicy:allowCustomization", - "kernel/CustomizationPolicy:allowDeleteFields", - "kernel/CustomizationPolicy:customizableFields", - "kernel/CustomizationPolicy:lockedFields", - "kernel/CustomizationPolicy:metadataType", "kernel/DeadLetterQueueEntry:error", "kernel/DeadLetterQueueEntry:event", "kernel/DeadLetterQueueEntry:failedHandler", @@ -207,11 +201,6 @@ "kernel/ExtensionPoint:id", "kernel/ExtensionPoint:name", "kernel/ExtensionPoint:type", - "kernel/FieldChange:changedAt", - "kernel/FieldChange:changedBy", - "kernel/FieldChange:currentValue", - "kernel/FieldChange:originalValue", - "kernel/FieldChange:path", "kernel/GetPackageRequest:id", "kernel/GetPackageResponse:package", "kernel/HealthStatus:details", @@ -314,22 +303,6 @@ "kernel/Manifest:scope", "kernel/Manifest:type", "kernel/Manifest:version", - "kernel/MergeConflict:baseValue", - "kernel/MergeConflict:customValue", - "kernel/MergeConflict:incomingValue", - "kernel/MergeConflict:path", - "kernel/MergeConflict:reason", - "kernel/MergeConflict:suggestedResolution", - "kernel/MergeResult:autoResolved", - "kernel/MergeResult:conflicts", - "kernel/MergeResult:mergedMetadata", - "kernel/MergeResult:stats", - "kernel/MergeResult:success", - "kernel/MergeResult:updatedOverlay", - "kernel/MergeStrategyConfig:alwaysAcceptIncoming", - "kernel/MergeStrategyConfig:alwaysKeepCustom", - "kernel/MergeStrategyConfig:autoResolveNonConflicting", - "kernel/MergeStrategyConfig:defaultStrategy", "kernel/MetadataBulkResult:errors", "kernel/MetadataBulkResult:failed", "kernel/MetadataBulkResult:succeeded", @@ -356,28 +329,13 @@ "kernel/MetadataManagerConfig:validation", "kernel/MetadataManagerConfig:watch", "kernel/MetadataManagerConfig:watchOptions", - "kernel/MetadataOverlay:active", - "kernel/MetadataOverlay:baseName", - "kernel/MetadataOverlay:baseType", - "kernel/MetadataOverlay:changes", - "kernel/MetadataOverlay:createdAt", - "kernel/MetadataOverlay:createdBy", - "kernel/MetadataOverlay:id", - "kernel/MetadataOverlay:owner", - "kernel/MetadataOverlay:packageId", - "kernel/MetadataOverlay:packageVersion", - "kernel/MetadataOverlay:patch", - "kernel/MetadataOverlay:scope", - "kernel/MetadataOverlay:tenantId", - "kernel/MetadataOverlay:updatedAt", - "kernel/MetadataOverlay:updatedBy", "kernel/MetadataPluginConfig:additionalTypes [RETIRED]", "kernel/MetadataPluginConfig:bootstrap", "kernel/MetadataPluginConfig:cacheMaxItems", - "kernel/MetadataPluginConfig:customizationPolicies", + "kernel/MetadataPluginConfig:customizationPolicies [RETIRED]", "kernel/MetadataPluginConfig:enableEvents", "kernel/MetadataPluginConfig:enableVersioning", - "kernel/MetadataPluginConfig:mergeStrategy", + "kernel/MetadataPluginConfig:mergeStrategy [RETIRED]", "kernel/MetadataPluginConfig:storage", "kernel/MetadataPluginConfig:validateOnWrite", "kernel/MetadataPluginManifest:capabilities", diff --git a/packages/spec/docs-import-surface.baseline.json b/packages/spec/docs-import-surface.baseline.json index 9f9e1e4963..d7a45e840d 100644 --- a/packages/spec/docs-import-surface.baseline.json +++ b/packages/spec/docs-import-surface.baseline.json @@ -9,7 +9,6 @@ "api/HttpFindQueryParams — no type export", "api/MetadataExportRequest — no type export", "api/MetadataImportRequest — no type export", - "api/MetadataOverlaySaveRequest — no type export", "api/MetadataQueryRequest — no type export", "automation/FlowVariable — no type export", "automation/StateMachine — no type export", diff --git a/packages/spec/json-schema.manifest/api.json b/packages/spec/json-schema.manifest/api.json index eb06f140d5..7cf65f9c8f 100644 --- a/packages/spec/json-schema.manifest/api.json +++ b/packages/spec/json-schema.manifest/api.json @@ -262,7 +262,6 @@ "api/MetadataDeleteResponse", "api/MetadataDependenciesResponse", "api/MetadataDependentsResponse", - "api/MetadataEffectiveResponse", "api/MetadataEndpointsConfig", "api/MetadataEvent", "api/MetadataEventType", @@ -274,8 +273,6 @@ "api/MetadataItemResponse", "api/MetadataListResponse", "api/MetadataNamesResponse", - "api/MetadataOverlayResponse", - "api/MetadataOverlaySaveRequest", "api/MetadataQueryRequest", "api/MetadataQueryResponse", "api/MetadataRegisterRequest", diff --git a/packages/spec/json-schema.manifest/kernel.json b/packages/spec/json-schema.manifest/kernel.json index 822c964283..fa20090d66 100644 --- a/packages/spec/json-schema.manifest/kernel.json +++ b/packages/spec/json-schema.manifest/kernel.json @@ -12,8 +12,6 @@ "kernel/ClusterTenantIsolation", "kernel/CompatibilityLevel", "kernel/CompatibilityMatrixEntry", - "kernel/CustomizationOrigin", - "kernel/CustomizationPolicy", "kernel/DeadLetterQueueEntry", "kernel/DependencyConflict", "kernel/DependencyGraph", @@ -44,7 +42,6 @@ "kernel/EventWebhookConfig", "kernel/ExecutionContext", "kernel/ExtensionPoint", - "kernel/FieldChange", "kernel/GetPackageRequest", "kernel/GetPackageResponse", "kernel/HealthStatus", @@ -60,9 +57,6 @@ "kernel/ListPackagesResponse", "kernel/Manifest", "kernel/ManifestPermissions", - "kernel/MergeConflict", - "kernel/MergeResult", - "kernel/MergeStrategyConfig", "kernel/MetadataBulkResult", "kernel/MetadataCategoryEnum", "kernel/MetadataChangeOperation", @@ -73,7 +67,6 @@ "kernel/MetadataLock", "kernel/MetadataLockSource", "kernel/MetadataManagerConfig", - "kernel/MetadataOverlay", "kernel/MetadataPluginConfig", "kernel/MetadataPluginManifest", "kernel/MetadataProvenance", diff --git a/packages/spec/scripts/build-docs.ts b/packages/spec/scripts/build-docs.ts index 8c6c199219..122512b7c1 100644 --- a/packages/spec/scripts/build-docs.ts +++ b/packages/spec/scripts/build-docs.ts @@ -565,7 +565,7 @@ const SECTION_GROUPS: Record { section: 'Plugin Lifecycle', pages: ['plugin', 'plugin-lifecycle-events', 'plugin-lifecycle-advanced', 'plugin-runtime', 'plugin-loading', 'plugin-registry', 'plugin-structure', 'plugin-validator'] }, { section: 'Plugin Security & Dependencies', pages: ['plugin-security', 'plugin-security-advanced', 'plugin-capability', 'plugin-versioning', 'dependency-resolution', 'manifest'] }, { section: 'Packages', pages: ['package-artifact', 'package-registry', 'package-upgrade'] }, - { section: 'Metadata & Runtime', pages: ['metadata-plugin', 'metadata-loader', 'metadata-customization', 'metadata-protection', 'metadata-persistence', 'misc', 'context', 'execution-context', 'service-registry', 'startup-orchestrator', 'cluster', 'feature', 'cli-extension', 'dev-plugin', 'state-machine'] }, + { section: 'Metadata & Runtime', pages: ['metadata-plugin', 'metadata-loader', 'metadata-protection', 'metadata-persistence', 'misc', 'context', 'execution-context', 'service-registry', 'startup-orchestrator', 'cluster', 'feature', 'cli-extension', 'dev-plugin', 'state-machine'] }, ], system: [ { section: 'Config & Settings', pages: ['settings-manifest', 'settings-client', 'registry-config', 'auth-config', 'email-config', 'email-template', 'license', 'migration', 'deploy-bundle', 'environment-artifact', 'app-install', 'provisioning', 'tenant'] }, diff --git a/packages/spec/src/kernel/metadata-plugin.test.ts b/packages/spec/src/kernel/metadata-plugin.test.ts index b5bf9e8750..49db322b6d 100644 --- a/packages/spec/src/kernel/metadata-plugin.test.ts +++ b/packages/spec/src/kernel/metadata-plugin.test.ts @@ -312,18 +312,10 @@ describe('MetadataPluginProtocol', () => { fallback: 'filesystem' as const, rootDir: '/metadata', }, - customizationPolicies: [{ - metadataType: 'object', - allowCustomization: true, - lockedFields: ['name', 'type'], - customizableFields: ['label', 'description'], - }], - mergeStrategy: { - defaultStrategy: 'three-way-merge' as const, - alwaysKeepCustom: ['fields.*.label'], - }, - // `additionalTypes` was retired by #8586 (ADR-0049) — authoring it is - // now a parse error; see additional-types-retirement.test.ts for the pins. + // `additionalTypes` was retired by #8586, and `customizationPolicies` + // / `mergeStrategy` by #13135 (both ADR-0049) — authoring any of them + // is now a parse error; see additional-types-retirement.test.ts and + // metadata-customization-retirement.test.ts for the pins. enableEvents: true, validateOnWrite: true, enableVersioning: true, @@ -332,7 +324,6 @@ describe('MetadataPluginProtocol', () => { const result = MetadataPluginConfigSchema.parse(config); expect(result.storage.datasource).toBe('default'); - expect(result.customizationPolicies).toHaveLength(1); expect(result.cacheMaxItems).toBe(5000); }); diff --git a/packages/spec/src/migrations/registry.ts b/packages/spec/src/migrations/registry.ts index 455bb165f6..02b7716e01 100644 --- a/packages/spec/src/migrations/registry.ts +++ b/packages/spec/src/migrations/registry.ts @@ -6554,6 +6554,55 @@ const step18: MigrationStep = { '`persistence.key`; `initialData` record values containing literal `${…}` keep parsing ' + 'byte-identically.', }, + { + id: 'metadata-customization-protocol-retired', + surface: + 'the paper metadata-customization protocol: `kernel/metadata-customization.zod.ts` ' + + 'whole (`MetadataOverlay`, `FieldChange`, `CustomizationOrigin`, `MergeConflict`, ' + + '`MergeStrategyConfig`, `MergeResult`, `CustomizationPolicy`) / the section-5 ' + + 'Overlay/Customization API contracts (`api/MetadataOverlayResponse`, ' + + '`api/MetadataOverlaySaveRequest`, `api/MetadataEffectiveResponse`) / the optional ' + + '`getOverlay`/`saveOverlay`/`removeOverlay`/`getEffective` members of ' + + '`contracts/metadata-service.ts` / the authorable keys ' + + '`MetadataPluginConfig.customizationPolicies`, `MetadataPluginConfig.mergeStrategy` and ' + + '`MetadataManagerConfig.persistence.overlayWritable` (tombstoned; see ' + + '`RETIRED_KEYS_BY_MAJOR[18]`)', + replacement: + 'nothing to re-declare — delete any authored keys. The customization mechanisms that ' + + 'actually ship: ADR-0005\'s org-scoped overlay (opt-in via `allowOrgOverride` on ' + + '`DEFAULT_METADATA_TYPE_REGISTRY`, stored as `sys_metadata` org rows, written through the ' + + 'REST meta write doors and read back through `getMetaItemLayered`\'s ' + + '`code`/`overlay`/`effective` layers), and ADR-0126\'s packaged-metadata customization ' + + 'model (clone with a new machine name + ledger disable — never a field-level patch ' + + 'overlay)', + reason: + 'ADR-0049 enforce-or-remove; maintainer ruling 2026-08-29 on #12057 (「同意」 — retirement ' + + 'adopted, re-scope rejected), executed widened by #13135 per the fork report on #12057: ' + + 'the module declared a three-layer platform/user patch-overlay protocol with field-level ' + + 'change tracking and a 3-way-merge story, published reference docs described it as the ' + + 'customization architecture — and nothing reachable implemented it. The one ' + + 'implementation (`packages/metadata`\'s manager limb) was served by no route and called ' + + 'only by its own unit tests; no merge engine ever existed; no code read a ' + + '`CustomizationPolicy`. ADR-0126 §6 wall 4 supersedes the protocol as a matter of record ' + + '("nothing may build against it") — the per-field overlay layer it described is ' + + 'precisely what the #11513 ruling recorded as deliberately not chartered. Why D3 ' + + 'semantic and not a D2 conversion: the defs leave with no carrier key in any stack ' + + 'collection, and the three tombstoned keys live on plugin/manager configs, which are not ' + + 'stack collection members (`PLURAL_TO_SINGULAR` has no `plugins` entry) — a ' + + 'MetadataConversion would be a transform with no seam that ever runs (the ' + + '`kernel/Manifest:loading` precedent).', + acceptanceCriteria: + 'No import of `metadata-customization.zod` (or of the retired names from ' + + '`@objectstack/spec/kernel` / `@objectstack/spec/api`) compiles anywhere; no ' + + '`MetadataPluginConfig` carries `customizationPolicies` or `mergeStrategy`; no ' + + '`MetadataManagerConfig` carries `persistence.overlayWritable` (TypeScript authors get ' + + 'the refusal at compile time — the keys are typed `never` — and a value reaching the ' + + 'parse is refused with the prescription at the key\'s path). ⚠️ Runtime behaviour is ' + + 'deliberately UNCHANGED and must be verified as such: no route ever served the paper ' + + '`…/overlay` / `…/effective` endpoints, so removing the limb removes no served ' + + 'behaviour — the ADR-0005 org-overlay read/write path (`getMetaItemLayered`, the REST ' + + 'meta write doors) stays exactly as it was, before and after.', + }, { id: 'metadata-item-name-grammar-enforced', surface: 'metadata item names (the `name` half of the `type`/`name` addressing pair — ' @@ -8110,6 +8159,29 @@ export const RETIRED_KEYS_BY_MAJOR: Readonly> // `translations` collection (`defineTranslationBundle`), governed by // `packages/spec/liveness/translation.json`. 'kernel/Manifest:contributes.translations', + // #13135 — ADR-0049 enforce-or-remove (maintainer ruling 2026-08-29 on + // #12057, adopting retirement; re-charter #13135 executes the widened + // surface). `persistence.overlayWritable` gated exactly one method — + // `MetadataManager.saveOverlay()` — which belonged to the paper + // metadata-customization protocol removed whole in the same change: no route + // ever served the paper `…/overlay` endpoints, no UI called the method, and + // its only callers were `packages/metadata`'s own unit tests. With the limb + // gone the flag gates nothing. The base write gate `persistence.writable` + // stays; the real org-overlay writes (ADR-0005) ride the REST meta write + // doors' `manage_metadata` permission gate, not this flag. + // + // Registered under 18, not 17: v17.0.0 was cut before this landed, so the + // tombstone ships on the 17.x line (launch-window convention) and the + // prescription lives at the major boundary where `migrate meta` users look + // (the #8586 precedent). + // + // Registered here but NOT in `src/conversions/registry.ts` — the + // `kernel/MetadataPluginConfig:additionalTypes` reasoning: a + // metadata-manager config is not a stack collection member, so a + // MetadataConversion would be a transform with no seam that ever runs. The + // prescription reaches authors through the tombstone (`tsc` + the parse) and + // the D3 semantic entry `metadata-customization-protocol-retired`. + 'kernel/MetadataManagerConfig:persistence.overlayWritable', // #8586 — ADR-0049 enforce-or-remove (maintainer ruling 2026-08-14, ruled // REMOVE). `additionalTypes` was declared, authorable, and documented on four // docs pages as THE way a plugin registers a custom metadata type — and read @@ -8133,6 +8205,60 @@ export const RETIRED_KEYS_BY_MAJOR: Readonly> // The prescription reaches authors through the tombstone (`tsc` + the parse) // and the D3 semantic entry `metadata-plugin-additional-types-retired`. 'kernel/MetadataPluginConfig:additionalTypes', + // #13135 — ADR-0049 enforce-or-remove (maintainer ruling 2026-08-29 on + // #12057, adopting retirement; re-charter #13135 executes the widened + // surface). `customizationPolicies` embedded the paper metadata-customization + // protocol's `CustomizationPolicySchema` (lockedFields / customizableFields + // whitelists) and was read by NOTHING: no code ever consulted a policy before + // accepting or refusing a customization, and the protocol it configured — + // the three-layer overlay of `kernel/metadata-customization.zod.ts`, removed + // whole in the same change (see `RETIRED_DEFS_BY_MAJOR[18]`) — was itself + // unreachable from any served surface. ADR-0126 §6 wall 4 supersedes the + // protocol on the record ("nothing may build against it"). What a + // customization may touch is governed by ADR-0005's org overlay + // (`allowOrgOverride`) and ADR-0126's packaged-metadata model. + // + // Registered under 18, not 17: v17.0.0 was cut before this landed, so the + // tombstone ships on the 17.x line (launch-window convention: accept-set + // narrowings ride minor releases) and the prescription lives at the major + // boundary where `migrate meta` users look (the #8586 precedent). + // + // Registered here but NOT in `src/conversions/registry.ts`, for the reason + // the sibling `kernel/MetadataPluginConfig:additionalTypes` entry gives: the + // conversion chain walks a normalized STACK and + // `applyConversionsToStoredItem` maps a metadata type onto one of its + // collections. A metadata-plugin config is neither — there is no `plugins` + // entry in `PLURAL_TO_SINGULAR`, so a MetadataConversion here would be a + // transform with no seam that ever runs. The prescription reaches authors + // through the tombstone (`tsc` + the parse) and the D3 semantic entry + // `metadata-customization-protocol-retired`. + 'kernel/MetadataPluginConfig:customizationPolicies', + // #13135 — ADR-0049 enforce-or-remove (maintainer ruling 2026-08-29 on + // #12057, adopting retirement; re-charter #13135 executes the widened + // surface). `mergeStrategy` embedded the paper protocol's + // `MergeStrategyConfigSchema` (keep-custom / accept-incoming / + // three-way-merge) and was read by NOTHING: no 3-way merge engine ever + // existed, and package upgrades do not merge customizations — ADR-0126 §6 + // wall 3 separates the packaged BASE (upgrades rewrite it) from the + // customer's recorded choices (never touched by an upgrade). The value + // schema leaves with its module (`kernel/metadata-customization.zod.ts`, + // `RETIRED_DEFS_BY_MAJOR[18]`). NOT the same surface as the inline + // three-value `mergeStrategy` vocabulary on `api/PackageUpgradeRequest` / + // `kernel/UpgradePackageRequest` — those are separately declared twins that + // never imported the module and are deliberately untouched here. + // + // Registered under 18, not 17: v17.0.0 was cut before this landed, so the + // tombstone ships on the 17.x line (launch-window convention) and the + // prescription lives at the major boundary where `migrate meta` users look + // (the #8586 precedent). + // + // Registered here but NOT in `src/conversions/registry.ts` — the + // `kernel/MetadataPluginConfig:additionalTypes` reasoning: a metadata-plugin + // config is not a stack collection member, so a MetadataConversion would be + // a transform with no seam that ever runs. The prescription reaches authors + // through the tombstone (`tsc` + the parse) and the D3 semantic entry + // `metadata-customization-protocol-retired`. + 'kernel/MetadataPluginConfig:mergeStrategy', // #12032 — ADR-0049 enforce-or-remove, one class over from #12428 (PR #12571) // and #12340 (PR #12425) in the same host-driven lifecycle library, and for a // sharper reason than either: this key HAD a reader that acted, and what it did @@ -9029,6 +9155,75 @@ export const RETIRED_DEFS_BY_MAJOR: Readonly> // entry id by `gen:migration-registry` (#7297). Add an entry by adding a // FILE — never by editing between the markers, which is generated. // + // #13135 — ADR-0049 enforce-or-remove (maintainer ruling 2026-08-29 on + // #12057: retirement adopted, re-scope rejected; re-charter #13135 executes + // the widened surface). Part of the whole-module removal of + // `kernel/metadata-customization.zod.ts` — the paper three-layer + // customization protocol ADR-0126 §6 wall 4 supersedes on the record + // ("nothing may build against it"). Zero reachable consumers, re-verified + // at the retirement's base commit with positive controls (#12057 fork + // report): the only implementation was `packages/metadata`'s manager limb, + // served by no route and called only by its own unit tests; the real + // mechanisms are ADR-0005's org overlay and ADR-0126's packaged-metadata + // model. This def: the `GET /api/meta/:type/:name/effective` response contract. No adapter + // ever served that path; the layered read that actually ships is + // `getMetaItemLayered` (ADR-0005 org overlay, `code`/`overlay`/`effective` + // layers) with its own contracts. + // + // Registered under 18, not 17: v17.0.0 was cut before this landed, so the + // removal ships on the 17.x line (launch-window convention: accept-set + // narrowings ride minor releases) and the prescription lives at the major + // boundary where `migrate meta` users look. No carrier key survives for + // these defs and no authored document embedded them, so no tombstone and no + // D2 conversion — this table plus the D3 semantic entry + // `metadata-customization-protocol-retired` ARE the declaration (the #8715 + // route-3 shape). + 'api/MetadataEffectiveResponse', + // #13135 — ADR-0049 enforce-or-remove (maintainer ruling 2026-08-29 on + // #12057: retirement adopted, re-scope rejected; re-charter #13135 executes + // the widened surface). Part of the whole-module removal of + // `kernel/metadata-customization.zod.ts` — the paper three-layer + // customization protocol ADR-0126 §6 wall 4 supersedes on the record + // ("nothing may build against it"). Zero reachable consumers, re-verified + // at the retirement's base commit with positive controls (#12057 fork + // report): the only implementation was `packages/metadata`'s manager limb, + // served by no route and called only by its own unit tests; the real + // mechanisms are ADR-0005's org overlay and ADR-0126's packaged-metadata + // model. This def: the `GET /api/meta/:type/:name/overlay` response contract. No adapter ever + // served that path (measured: no route spelling in packages/rest or + // packages/metadata). + // + // Registered under 18, not 17: v17.0.0 was cut before this landed, so the + // removal ships on the 17.x line (launch-window convention: accept-set + // narrowings ride minor releases) and the prescription lives at the major + // boundary where `migrate meta` users look. No carrier key survives for + // these defs and no authored document embedded them, so no tombstone and no + // D2 conversion — this table plus the D3 semantic entry + // `metadata-customization-protocol-retired` ARE the declaration (the #8715 + // route-3 shape). + 'api/MetadataOverlayResponse', + // #13135 — ADR-0049 enforce-or-remove (maintainer ruling 2026-08-29 on + // #12057: retirement adopted, re-scope rejected; re-charter #13135 executes + // the widened surface). Part of the whole-module removal of + // `kernel/metadata-customization.zod.ts` — the paper three-layer + // customization protocol ADR-0126 §6 wall 4 supersedes on the record + // ("nothing may build against it"). Zero reachable consumers, re-verified + // at the retirement's base commit with positive controls (#12057 fork + // report): the only implementation was `packages/metadata`'s manager limb, + // served by no route and called only by its own unit tests; the real + // mechanisms are ADR-0005's org overlay and ADR-0126's packaged-metadata + // model. This def: the `PUT /api/meta/:type/:name/overlay` request contract (a bare + // `MetadataOverlaySchema`). No adapter ever served that path. + // + // Registered under 18, not 17: v17.0.0 was cut before this landed, so the + // removal ships on the 17.x line (launch-window convention: accept-set + // narrowings ride minor releases) and the prescription lives at the major + // boundary where `migrate meta` users look. No carrier key survives for + // these defs and no authored document embedded them, so no tombstone and no + // D2 conversion — this table plus the D3 semantic entry + // `metadata-customization-protocol-retired` ARE the declaration (the #8715 + // route-3 shape). + 'api/MetadataOverlaySaveRequest', // #12038 — `api/package-api.zod.ts` `PackageRollbackResponseSchema`, retired // whole together with the `PackageApiContracts.rollbackPackage` entry that // bound it (maintainer ruling 2026-08-27, sub-question 3A). The schema @@ -9126,6 +9321,52 @@ export const RETIRED_DEFS_BY_MAJOR: Readonly> // narrowings ride minor releases) and the prescription lives at the major // boundary where `migrate meta` users look (the #8586 / PR #8702 precedent). 'kernel/CLICommandContribution', + // #13135 — ADR-0049 enforce-or-remove (maintainer ruling 2026-08-29 on + // #12057: retirement adopted, re-scope rejected; re-charter #13135 executes + // the widened surface). Part of the whole-module removal of + // `kernel/metadata-customization.zod.ts` — the paper three-layer + // customization protocol ADR-0126 §6 wall 4 supersedes on the record + // ("nothing may build against it"). Zero reachable consumers, re-verified + // at the retirement's base commit with positive controls (#12057 fork + // report): the only implementation was `packages/metadata`'s manager limb, + // served by no route and called only by its own unit tests; the real + // mechanisms are ADR-0005's org overlay and ADR-0126's packaged-metadata + // model. This def: the who-customized enum (`package`/`admin`/`user`/`migration`/`api`). + // Nothing ever wrote or read an origin. + // + // Registered under 18, not 17: v17.0.0 was cut before this landed, so the + // removal ships on the 17.x line (launch-window convention: accept-set + // narrowings ride minor releases) and the prescription lives at the major + // boundary where `migrate meta` users look. No carrier key survives for + // these defs and no authored document embedded them, so no tombstone and no + // D2 conversion — this table plus the D3 semantic entry + // `metadata-customization-protocol-retired` ARE the declaration (the #8715 + // route-3 shape). + 'kernel/CustomizationOrigin', + // #13135 — ADR-0049 enforce-or-remove (maintainer ruling 2026-08-29 on + // #12057: retirement adopted, re-scope rejected; re-charter #13135 executes + // the widened surface). Part of the whole-module removal of + // `kernel/metadata-customization.zod.ts` — the paper three-layer + // customization protocol ADR-0126 §6 wall 4 supersedes on the record + // ("nothing may build against it"). Zero reachable consumers, re-verified + // at the retirement's base commit with positive controls (#12057 fork + // report): the only implementation was `packages/metadata`'s manager limb, + // served by no route and called only by its own unit tests; the real + // mechanisms are ADR-0005's org overlay and ADR-0126's packaged-metadata + // model. This def: the vendor customization-boundary policy (lockedFields / + // customizableFields whitelists), embedded by the retired authorable key + // `MetadataPluginConfig.customizationPolicies` (see + // `RETIRED_KEYS_BY_MAJOR[18]`). No code ever consulted a policy. + // + // Registered under 18, not 17: v17.0.0 was cut before this landed, so the + // removal ships on the 17.x line (launch-window convention: accept-set + // narrowings ride minor releases) and the prescription lives at the major + // boundary where `migrate meta` users look. No carrier key survives for + // these defs and no authored document embedded them, so no tombstone and no + // D2 conversion — this table plus the D3 semantic entry + // `metadata-customization-protocol-retired` ARE the declaration (the #8715 + // route-3 shape). + 'kernel/CustomizationPolicy', // #12340 — kernel/plugin-lifecycle-advanced.zod.ts // `DistributedStateConfigSchema`, retired whole (ADR-0049 enforce-or-remove). // @@ -9162,6 +9403,31 @@ export const RETIRED_DEFS_BY_MAJOR: Readonly> // receive a parse-time tombstone. This table plus the D3 semantic entry // `hot-reload-inert-state-strategies-retired` ARE the declaration. 'kernel/DistributedStateConfig', + // #13135 — ADR-0049 enforce-or-remove (maintainer ruling 2026-08-29 on + // #12057: retirement adopted, re-scope rejected; re-charter #13135 executes + // the widened surface). Part of the whole-module removal of + // `kernel/metadata-customization.zod.ts` — the paper three-layer + // customization protocol ADR-0126 §6 wall 4 supersedes on the record + // ("nothing may build against it"). Zero reachable consumers, re-verified + // at the retirement's base commit with positive controls (#12057 fork + // report): the only implementation was `packages/metadata`'s manager limb, + // served by no route and called only by its own unit tests; the real + // mechanisms are ADR-0005's org overlay and ADR-0126's packaged-metadata + // model. This def: the per-field change-tracking record + // (`path`/`originalValue`/`currentValue`) `MetadataOverlay.changes` embedded + // for upgrade conflict detection that never ran. objectui's + // `packages/types/src/views.ts` names it in a comment that itself says 'Do + // not re-point this at it'. + // + // Registered under 18, not 17: v17.0.0 was cut before this landed, so the + // removal ships on the 17.x line (launch-window convention: accept-set + // narrowings ride minor releases) and the prescription lives at the major + // boundary where `migrate meta` users look. No carrier key survives for + // these defs and no authored document embedded them, so no tombstone and no + // D2 conversion — this table plus the D3 semantic entry + // `metadata-customization-protocol-retired` ARE the declaration (the #8715 + // route-3 shape). + 'kernel/FieldChange', // #11825 — `kernel/GracefulDegradation` left with // `kernel/AdvancedPluginLifecycleConfig`: its ONLY consumer was the retired // container's `degradation` key (the #3950 rule — an exported value schema @@ -9178,6 +9444,102 @@ export const RETIRED_DEFS_BY_MAJOR: Readonly> // ENFORCE route of ADR-0049 through a new ADR — the implementation first. // See `18.kernel__AdvancedPluginLifecycleConfig.ts` for the family record. 'kernel/GracefulDegradation', + // #13135 — ADR-0049 enforce-or-remove (maintainer ruling 2026-08-29 on + // #12057: retirement adopted, re-scope rejected; re-charter #13135 executes + // the widened surface). Part of the whole-module removal of + // `kernel/metadata-customization.zod.ts` — the paper three-layer + // customization protocol ADR-0126 §6 wall 4 supersedes on the record + // ("nothing may build against it"). Zero reachable consumers, re-verified + // at the retirement's base commit with positive controls (#12057 fork + // report): the only implementation was `packages/metadata`'s manager limb, + // served by no route and called only by its own unit tests; the real + // mechanisms are ADR-0005's org overlay and ADR-0126's packaged-metadata + // model. This def: the 3-way-merge conflict record (`baseValue`/`incomingValue`/`customValue` + // + suggested resolution). No merge engine ever existed to produce one. + // + // Registered under 18, not 17: v17.0.0 was cut before this landed, so the + // removal ships on the 17.x line (launch-window convention: accept-set + // narrowings ride minor releases) and the prescription lives at the major + // boundary where `migrate meta` users look. No carrier key survives for + // these defs and no authored document embedded them, so no tombstone and no + // D2 conversion — this table plus the D3 semantic entry + // `metadata-customization-protocol-retired` ARE the declaration (the #8715 + // route-3 shape). + 'kernel/MergeConflict', + // #13135 — ADR-0049 enforce-or-remove (maintainer ruling 2026-08-29 on + // #12057: retirement adopted, re-scope rejected; re-charter #13135 executes + // the widened surface). Part of the whole-module removal of + // `kernel/metadata-customization.zod.ts` — the paper three-layer + // customization protocol ADR-0126 §6 wall 4 supersedes on the record + // ("nothing may build against it"). Zero reachable consumers, re-verified + // at the retirement's base commit with positive controls (#12057 fork + // report): the only implementation was `packages/metadata`'s manager limb, + // served by no route and called only by its own unit tests; the real + // mechanisms are ADR-0005's org overlay and ADR-0126's packaged-metadata + // model. This def: the 3-way-merge outcome record (merged payload, conflicts, auto-resolved + // stats). No merge engine ever existed to produce one. + // + // Registered under 18, not 17: v17.0.0 was cut before this landed, so the + // removal ships on the 17.x line (launch-window convention: accept-set + // narrowings ride minor releases) and the prescription lives at the major + // boundary where `migrate meta` users look. No carrier key survives for + // these defs and no authored document embedded them, so no tombstone and no + // D2 conversion — this table plus the D3 semantic entry + // `metadata-customization-protocol-retired` ARE the declaration (the #8715 + // route-3 shape). + 'kernel/MergeResult', + // #13135 — ADR-0049 enforce-or-remove (maintainer ruling 2026-08-29 on + // #12057: retirement adopted, re-scope rejected; re-charter #13135 executes + // the widened surface). Part of the whole-module removal of + // `kernel/metadata-customization.zod.ts` — the paper three-layer + // customization protocol ADR-0126 §6 wall 4 supersedes on the record + // ("nothing may build against it"). Zero reachable consumers, re-verified + // at the retirement's base commit with positive controls (#12057 fork + // report): the only implementation was `packages/metadata`'s manager limb, + // served by no route and called only by its own unit tests; the real + // mechanisms are ADR-0005's org overlay and ADR-0126's packaged-metadata + // model. This def: the merge-strategy config (`keep-custom`/`accept-incoming`/`three-way- + // merge` + path rules), embedded by the retired authorable key + // `MetadataPluginConfig.mergeStrategy` (see `RETIRED_KEYS_BY_MAJOR[18]`). + // NOT the inline three-value `mergeStrategy` vocabulary on + // `api/PackageUpgradeRequest` / `kernel/UpgradePackageRequest` — separately + // declared twins, untouched. + // + // Registered under 18, not 17: v17.0.0 was cut before this landed, so the + // removal ships on the 17.x line (launch-window convention: accept-set + // narrowings ride minor releases) and the prescription lives at the major + // boundary where `migrate meta` users look. No carrier key survives for + // these defs and no authored document embedded them, so no tombstone and no + // D2 conversion — this table plus the D3 semantic entry + // `metadata-customization-protocol-retired` ARE the declaration (the #8715 + // route-3 shape). + 'kernel/MergeStrategyConfig', + // #13135 — ADR-0049 enforce-or-remove (maintainer ruling 2026-08-29 on + // #12057: retirement adopted, re-scope rejected; re-charter #13135 executes + // the widened surface). Part of the whole-module removal of + // `kernel/metadata-customization.zod.ts` — the paper three-layer + // customization protocol ADR-0126 §6 wall 4 supersedes on the record + // ("nothing may build against it"). Zero reachable consumers, re-verified + // at the retirement's base commit with positive controls (#12057 fork + // report): the only implementation was `packages/metadata`'s manager limb, + // served by no route and called only by its own unit tests; the real + // mechanisms are ADR-0005's org overlay and ADR-0126's packaged-metadata + // model. This def: the protocol's core record: a platform/user-scoped JSON-merge-patch delta + // (`baseType`/`baseName`/`scope`/`patch`) with field-level `changes` + // tracking. Only importers were `api/metadata.zod.ts` section 5, + // `contracts/metadata-service.ts`'s optional members, and + // `packages/metadata`'s unreachable manager limb — all removed in the same + // change. + // + // Registered under 18, not 17: v17.0.0 was cut before this landed, so the + // removal ships on the 17.x line (launch-window convention: accept-set + // narrowings ride minor releases) and the prescription lives at the major + // boundary where `migrate meta` users look. No carrier key survives for + // these defs and no authored document embedded them, so no tombstone and no + // D2 conversion — this table plus the D3 semantic entry + // `metadata-customization-protocol-retired` ARE the declaration (the #8715 + // route-3 shape). + 'kernel/MetadataOverlay', // #11825 — `kernel/PluginUpdateStrategy` left with // `kernel/AdvancedPluginLifecycleConfig`: its ONLY consumer was the retired // container's `updates` key (the #3950 rule — an exported value schema with diff --git a/skills/objectstack-platform/references/_index.md b/skills/objectstack-platform/references/_index.md index 55dd5f05e6..f16d3e9d14 100644 --- a/skills/objectstack-platform/references/_index.md +++ b/skills/objectstack-platform/references/_index.md @@ -35,7 +35,6 @@ from `node_modules` — there is no local copy in the skill bundle. - `node_modules/@objectstack/spec/src/data/filter.zod.ts` — Unified Query DSL Specification - `node_modules/@objectstack/spec/src/data/hook-body.zod.ts` — Exports: HookBodyCapability, ExpressionBodySchema, ScriptBodySchema, HookBodySchema - `node_modules/@objectstack/spec/src/kernel/cluster.zod.ts` — Cluster Protocol -- `node_modules/@objectstack/spec/src/kernel/metadata-customization.zod.ts` — Metadata Customization Layer Protocol - `node_modules/@objectstack/spec/src/kernel/metadata-loader.zod.ts` — Metadata Manager Configuration - `node_modules/@objectstack/spec/src/kernel/metadata-protection.zod.ts` — Metadata Protection Model — Phase 1 (ADR-0010) - `node_modules/@objectstack/spec/src/shared/expression.zod.ts` — Expression Protocol From c2ef279f06c37be3b8821422913ea3010c845afa Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 29 Aug 2026 06:39:15 +0000 Subject: [PATCH 3/6] wip: fix contracts/api test typecheck debt; regen export-origins + strictness ledger Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4 --- ...07-unknown-key-strictness-ledger.counts.md | 2 +- packages/spec/export-origins/api.json | 7 ---- packages/spec/export-origins/kernel.json | 17 -------- .../src/contracts/metadata-service.test.ts | 41 ++----------------- packages/spec/test-typecheck-debt.json | 1 - 5 files changed, 5 insertions(+), 63 deletions(-) diff --git a/docs/audits/2026-07-unknown-key-strictness-ledger.counts.md b/docs/audits/2026-07-unknown-key-strictness-ledger.counts.md index dfd18ba561..7454615c8c 100644 --- a/docs/audits/2026-07-unknown-key-strictness-ledger.counts.md +++ b/docs/audits/2026-07-unknown-key-strictness-ledger.counts.md @@ -261,7 +261,7 @@ directory rather than per file. | `cloud/` | 83 | | `identity/` | 32 | | `integration/` | 10 | -| `kernel/` | 271 | +| `kernel/` | 263 | | `qa/` | 6 | | `shared/` | 20 | | `system/` | 364 | diff --git a/packages/spec/export-origins/api.json b/packages/spec/export-origins/api.json index 5586449faa..a9b2c23a46 100644 --- a/packages/spec/export-origins/api.json +++ b/packages/spec/export-origins/api.json @@ -630,9 +630,6 @@ "MetadataDependentsResponse": "src/api/metadata.zod.ts#MetadataDependentsResponse (type)", "MetadataDependentsResponseParsed": "src/api/metadata.zod.ts#MetadataDependentsResponseParsed (type)", "MetadataDependentsResponseSchema": "src/api/metadata.zod.ts#MetadataDependentsResponseSchema (const)", - "MetadataEffectiveResponse": "src/api/metadata.zod.ts#MetadataEffectiveResponse (type)", - "MetadataEffectiveResponseParsed": "src/api/metadata.zod.ts#MetadataEffectiveResponseParsed (type)", - "MetadataEffectiveResponseSchema": "src/api/metadata.zod.ts#MetadataEffectiveResponseSchema (const)", "MetadataEndpointsConfig": "src/api/rest-server.zod.ts#MetadataEndpointsConfig (type)", "MetadataEndpointsConfigParsed": "src/api/rest-server.zod.ts#MetadataEndpointsConfigParsed (type)", "MetadataEndpointsConfigSchema": "src/api/rest-server.zod.ts#MetadataEndpointsConfigSchema (const)", @@ -660,10 +657,6 @@ "MetadataNamesResponse": "src/api/metadata.zod.ts#MetadataNamesResponse (type)", "MetadataNamesResponseParsed": "src/api/metadata.zod.ts#MetadataNamesResponseParsed (type)", "MetadataNamesResponseSchema": "src/api/metadata.zod.ts#MetadataNamesResponseSchema (const)", - "MetadataOverlayResponse": "src/api/metadata.zod.ts#MetadataOverlayResponse (type)", - "MetadataOverlayResponseParsed": "src/api/metadata.zod.ts#MetadataOverlayResponseParsed (type)", - "MetadataOverlayResponseSchema": "src/api/metadata.zod.ts#MetadataOverlayResponseSchema (const)", - "MetadataOverlaySaveRequestSchema": "src/api/metadata.zod.ts#MetadataOverlaySaveRequestSchema (const)", "MetadataProtocol": "src/api/protocol.zod.ts#MetadataProtocol (interface)", "MetadataQueryRequestSchema": "src/api/metadata.zod.ts#MetadataQueryRequestSchema (const)", "MetadataQueryResponse": "src/api/metadata.zod.ts#MetadataQueryResponse (type)", diff --git a/packages/spec/export-origins/kernel.json b/packages/spec/export-origins/kernel.json index 4d5fb99470..7be30f52ab 100644 --- a/packages/spec/export-origins/kernel.json +++ b/packages/spec/export-origins/kernel.json @@ -33,11 +33,6 @@ "CompatibilityMatrixEntryParsed": "src/kernel/plugin-versioning.zod.ts#CompatibilityMatrixEntryParsed (type)", "CompatibilityMatrixEntrySchema": "src/kernel/plugin-versioning.zod.ts#CompatibilityMatrixEntrySchema (const)", "CompletenessFinding": "src/kernel/functional-completeness.ts#CompletenessFinding (interface)", - "CustomizationOrigin": "src/kernel/metadata-customization.zod.ts#CustomizationOrigin (type)", - "CustomizationOriginSchema": "src/kernel/metadata-customization.zod.ts#CustomizationOriginSchema (const)", - "CustomizationPolicy": "src/kernel/metadata-customization.zod.ts#CustomizationPolicy (type)", - "CustomizationPolicyParsed": "src/kernel/metadata-customization.zod.ts#CustomizationPolicyParsed (type)", - "CustomizationPolicySchema": "src/kernel/metadata-customization.zod.ts#CustomizationPolicySchema (const)", "DEFAULT_METADATA_TYPE_REGISTRY": "src/kernel/metadata-plugin.zod.ts#DEFAULT_METADATA_TYPE_REGISTRY (const)", "DeadLetterQueueEntry": "src/kernel/events/dlq.zod.ts#DeadLetterQueueEntry (type)", "DeadLetterQueueEntryParsed": "src/kernel/events/dlq.zod.ts#DeadLetterQueueEntryParsed (type)", @@ -123,8 +118,6 @@ "FIELD_RELATIONSHIP_WITHOUT_REFERENCE": "src/kernel/functional-completeness.ts#FIELD_RELATIONSHIP_WITHOUT_REFERENCE (const)", "FIELD_SUMMARY_WITHOUT_OPERATIONS": "src/kernel/functional-completeness.ts#FIELD_SUMMARY_WITHOUT_OPERATIONS (const)", "FUNCTIONAL_COMPLETENESS_RULES": "src/kernel/functional-completeness.ts#FUNCTIONAL_COMPLETENESS_RULES (const)", - "FieldChange": "src/kernel/metadata-customization.zod.ts#FieldChange (type)", - "FieldChangeSchema": "src/kernel/metadata-customization.zod.ts#FieldChangeSchema (const)", "GetPackageRequest": "src/kernel/package-registry.zod.ts#GetPackageRequest (type)", "GetPackageRequestSchema": "src/kernel/package-registry.zod.ts#GetPackageRequestSchema (const)", "GetPackageResponse": "src/kernel/package-registry.zod.ts#GetPackageResponse (type)", @@ -166,13 +159,6 @@ "ManifestPermissions": "src/kernel/manifest.zod.ts#ManifestPermissions (type)", "ManifestPermissionsSchema": "src/kernel/manifest.zod.ts#ManifestPermissionsSchema (const)", "ManifestSchema": "src/kernel/manifest.zod.ts#ManifestSchema (const)", - "MergeConflict": "src/kernel/metadata-customization.zod.ts#MergeConflict (type)", - "MergeConflictSchema": "src/kernel/metadata-customization.zod.ts#MergeConflictSchema (const)", - "MergeResult": "src/kernel/metadata-customization.zod.ts#MergeResult (type)", - "MergeResultSchema": "src/kernel/metadata-customization.zod.ts#MergeResultSchema (const)", - "MergeStrategyConfig": "src/kernel/metadata-customization.zod.ts#MergeStrategyConfig (type)", - "MergeStrategyConfigParsed": "src/kernel/metadata-customization.zod.ts#MergeStrategyConfigParsed (type)", - "MergeStrategyConfigSchema": "src/kernel/metadata-customization.zod.ts#MergeStrategyConfigSchema (const)", "MetadataBulkResult": "src/kernel/metadata-plugin.zod.ts#MetadataBulkResult (type)", "MetadataBulkResultSchema": "src/kernel/metadata-plugin.zod.ts#MetadataBulkResultSchema (const)", "MetadataCategory": "src/kernel/package-artifact.zod.ts#MetadataCategory (type)", @@ -197,9 +183,6 @@ "MetadataManagerConfig": "src/kernel/metadata-loader.zod.ts#MetadataManagerConfig (type)", "MetadataManagerConfigParsed": "src/kernel/metadata-loader.zod.ts#MetadataManagerConfigParsed (type)", "MetadataManagerConfigSchema": "src/kernel/metadata-loader.zod.ts#MetadataManagerConfigSchema (const)", - "MetadataOverlay": "src/kernel/metadata-customization.zod.ts#MetadataOverlay (type)", - "MetadataOverlayParsed": "src/kernel/metadata-customization.zod.ts#MetadataOverlayParsed (type)", - "MetadataOverlaySchema": "src/kernel/metadata-customization.zod.ts#MetadataOverlaySchema (const)", "MetadataPluginConfig": "src/kernel/metadata-plugin.zod.ts#MetadataPluginConfig (type)", "MetadataPluginConfigParsed": "src/kernel/metadata-plugin.zod.ts#MetadataPluginConfigParsed (type)", "MetadataPluginConfigSchema": "src/kernel/metadata-plugin.zod.ts#MetadataPluginConfigSchema (const)", diff --git a/packages/spec/src/contracts/metadata-service.test.ts b/packages/spec/src/contracts/metadata-service.test.ts index a17f6695e1..42f4403faa 100644 --- a/packages/spec/src/contracts/metadata-service.test.ts +++ b/packages/spec/src/contracts/metadata-service.test.ts @@ -156,37 +156,10 @@ describe('Metadata Service Contract', () => { expect(result.succeeded).toBe(2); }); - it('should allow implementation with overlay management', async () => { - const overlayStore = new Map(); - - const service: IMetadataService = { - register: async () => {}, - get: async () => undefined, - list: async () => [], - unregister: async () => {}, - exists: async () => false, - listNames: async () => [], - getObject: async () => undefined, - listObjects: async () => [], - getOverlay: async (type, name) => { - const key = `${type}:${name}`; - return overlayStore.get(key) as any; - }, - saveOverlay: async (overlay) => { - const key = `${overlay.baseType}:${overlay.baseName}`; - overlayStore.set(key, overlay); - }, - removeOverlay: async (type, name) => { - overlayStore.delete(`${type}:${name}`); - }, - getEffective: async () => undefined, - }; - - expect(service.getOverlay).toBeDefined(); - expect(service.saveOverlay).toBeDefined(); - expect(service.removeOverlay).toBeDefined(); - expect(service.getEffective).toBeDefined(); - }); + // (The `overlay management` implementation case left with the optional + // `getOverlay` / `saveOverlay` / `removeOverlay` / `getEffective` members — + // #13135, ADR-0049: they belonged to the paper customization protocol no + // route ever served.) it('should allow implementation with watch support', () => { const callbacks: MetadataWatchCallback[] = []; @@ -358,11 +331,6 @@ describe('Metadata Service Contract', () => { // Bulk bulkRegister: async () => ({ total: 0, succeeded: 0, failed: 0 }), bulkUnregister: async () => ({ total: 0, succeeded: 0, failed: 0 }), - // Overlay - getOverlay: async () => undefined, - saveOverlay: async () => {}, - removeOverlay: async () => {}, - getEffective: async () => undefined, // Watch watch: () => ({ unsubscribe: () => {} }), // Import/Export @@ -382,7 +350,6 @@ describe('Metadata Service Contract', () => { expect(typeof service.register).toBe('function'); expect(typeof service.query).toBe('function'); expect(typeof service.bulkRegister).toBe('function'); - expect(typeof service.getOverlay).toBe('function'); expect(typeof service.watch).toBe('function'); expect(typeof service.exportMetadata).toBe('function'); expect(typeof service.validate).toBe('function'); diff --git a/packages/spec/test-typecheck-debt.json b/packages/spec/test-typecheck-debt.json index 6d8b7c71b5..99d45e3072 100644 --- a/packages/spec/test-typecheck-debt.json +++ b/packages/spec/test-typecheck-debt.json @@ -4,7 +4,6 @@ "src/ai/conversation.test.ts": 5, "src/api/documentation.test.ts": 1, "src/api/errors.test.ts": 1, - "src/api/metadata.test.ts": 1, "src/api/odata.test.ts": 1, "src/api/package-api.test.ts": 4, "src/api/rest-server.test.ts": 2, From c035383dad1b3a10647e9b8a49cc72ceeeb607ff Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 29 Aug 2026 07:31:53 +0000 Subject: [PATCH 4/6] test(spec): re-baseline the two corpus counts the module deletion moves `scripts/file-description.test.ts` pins how many rendered descriptions the level-1 heading demotion touches (#12249). `kernel/metadata-customization.zod.ts` was one of those level-1 openers, so removing the module moves the count 38 -> 37 in both assertions. `src/type-alias-convention.pin.test.ts` pins the isomorphic-alias count in prose AND recomputes it from the source (ADR-0122), so the four vacated Iso408-411 rows move it 837 -> 833 in the section header and the test title the runtime companion checks the prose against. Both are re-baselines of a machine-checked count, not relaxations: the recomputation still runs and still fails if either number goes stale. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4 --- packages/spec/scripts/file-description.test.ts | 8 +++++--- packages/spec/src/type-alias-convention.pin.test.ts | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/spec/scripts/file-description.test.ts b/packages/spec/scripts/file-description.test.ts index 3da18371e4..1ce331672d 100644 --- a/packages/spec/scripts/file-description.test.ts +++ b/packages/spec/scripts/file-description.test.ts @@ -1388,7 +1388,7 @@ describe('corpus — every rendered description is well-formed markdown', () => expect(offenders).toEqual([]); }); - it('demotes only the 38 descriptions that needed it (#12249)', () => { + it('demotes only the 37 descriptions that needed it (#12249)', () => { // The other half, and the reason this is a renumbering rather than a // blanket `#`→`##`: 26 described modules already start their headings at // level 2, and shifting those too would have moved 105 correct headings a @@ -1412,9 +1412,11 @@ describe('corpus — every rendered description is well-formed markdown', () => }) .filter(d => d.untouched !== d.emitted); - expect(shifted.length).toBe(38); + // (38 -> 37: `kernel/metadata-customization.zod.ts` — one of the level-1 + // openers — was removed whole by #13135's ADR-0049 retirement.) + expect(shifted.length).toBe(37); // …and every one of them was shifted because it opened at level 1. - expect(shifted.filter(d => /^ {0,3}#(?:[ \t]|$)/m.test(withoutFences(d.untouched)))).toHaveLength(38); + expect(shifted.filter(d => /^ {0,3}#(?:[ \t]|$)/m.test(withoutFences(d.untouched)))).toHaveLength(37); }); it('keeps a description for every source that had one — #6134 selection is untouched', () => { diff --git a/packages/spec/src/type-alias-convention.pin.test.ts b/packages/spec/src/type-alias-convention.pin.test.ts index daa877d1bb..66ea8ab6d1 100644 --- a/packages/spec/src/type-alias-convention.pin.test.ts +++ b/packages/spec/src/type-alias-convention.pin.test.ts @@ -269,7 +269,7 @@ import type * as M170 from './ui/component.zod.js'; import type * as M183 from './api/sortability.zod.js'; // --------------------------------------------------------------------------- -// 837 isomorphic aliases: `z.input` === `z.infer`, so no `XParsed` is declared. +// 833 isomorphic aliases: `z.input` === `z.infer`, so no `XParsed` is declared. // // That number is machine-checked, not hand-kept. The runtime companion at the // bottom of this file recomputes the pin count from the source and asserts that @@ -1679,7 +1679,7 @@ describe('ADR-0122 type-alias convention', () => { // this title and the section header above the pin list — are now asserted // against the recomputed count below, so neither can go stale without a red // test naming it. - it('still declares all 837 isomorphic pins', () => { + it('still declares all 833 isomorphic pins', () => { // The truth of each pin is proved by tsc, not here — an `Assert>` // that stops holding is a compile error with the alias named. What tsc // cannot notice is a pin that was DELETED: removing the assertion removes From 7af90336bfdb870d4cbacb4d98e65c8887653a5c Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 29 Aug 2026 08:13:01 +0000 Subject: [PATCH 5/6] fix(spec,docs): strip internal issue ids from the three prescriptions, re-declare the kernel page count `check:doc-authoring` flagged the three `retiredKey()` prescriptions: a customer reading a parse refusal has no tracker, so an issue id there is a citation-shaped token resolving to nothing. Per that gate's rule the issue id NEXT TO an ADR id is the strippable half -- each prescription keeps `ADR-0049 enforce-or-remove` plus its ADR-0126/ADR-0005 FROM -> TO mapping, so none is left bare. Reference pages regenerated (the tombstone text projects into `content/docs/references/**`). `check:quick-reference-counts` flagged the [total] side: deleting the `kernel/metadata-customization` reference page moves what `content/docs/references/kernel/` publishes from 31 to 30. The curated table's own row count (17) is untouched -- no row ever pointed at that page. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4 --- content/docs/getting-started/quick-reference.mdx | 2 +- content/docs/references/kernel/metadata-loader.mdx | 2 +- content/docs/references/kernel/metadata-plugin.mdx | 8 ++++---- content/docs/references/system/metadata-persistence.mdx | 2 +- packages/spec/src/kernel/metadata-loader.zod.ts | 2 +- packages/spec/src/kernel/metadata-plugin.zod.ts | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/content/docs/getting-started/quick-reference.mdx b/content/docs/getting-started/quick-reference.mdx index 243c3f3f32..2c6c34f213 100644 --- a/content/docs/getting-started/quick-reference.mdx +++ b/content/docs/getting-started/quick-reference.mdx @@ -61,7 +61,7 @@ Presentation layer - views, forms, dashboards, and app branding. | **[Chart](/docs/references/ui/chart)** | `chart.zod.ts` | Chart, ChartType | Chart definitions | | **[Widget Contract](/docs/protocol/objectui/widget-contract)** ↗ | `widget.zod.ts` | FieldWidgetProps | Props a custom field widget receives — the contract is documented with ObjectUI, outside `references/ui/` | -## Kernel Protocol (17 of 31 schemas) +## Kernel Protocol (17 of 30 schemas) Plugin architecture, manifests, and kernel runtime. diff --git a/content/docs/references/kernel/metadata-loader.mdx b/content/docs/references/kernel/metadata-loader.mdx index 89f2bded9c..7fa1b1e164 100644 --- a/content/docs/references/kernel/metadata-loader.mdx +++ b/content/docs/references/kernel/metadata-loader.mdx @@ -85,7 +85,7 @@ const result = MetadataFallbackStrategySchema.parse(data); | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **writable** | `boolean` | optional (default: `true`) | Allow base metadata writes via register() | -| **overlayWritable** | `never` | optional | [REMOVED] `persistence.overlayWritable` was removed from `MetadataManagerConfig` in @objectstack/spec 17 (#13135, ADR-0049 enforce-or-remove) — the only thing it gated was `MetadataManager.saveOverlay()`, a paper-protocol method no route or UI ever called, removed with the metadata-customization protocol (ADR-0126 supersedes it on the record). Delete the key. The base write gate that remains is `persistence.writable`; the real org-overlay writes (ADR-0005) ride the REST meta write doors' `manage_metadata` permission gate, not this flag. | +| **overlayWritable** | `never` | optional | [REMOVED] `persistence.overlayWritable` was removed from `MetadataManagerConfig` in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — the only thing it gated was `MetadataManager.saveOverlay()`, a paper-protocol method no route or UI ever called, removed with the metadata-customization protocol (ADR-0126 supersedes it on the record). Delete the key. The base write gate that remains is `persistence.writable`; the real org-overlay writes (ADR-0005) ride the REST meta write doors' `manage_metadata` permission gate, not this flag. | --- diff --git a/content/docs/references/kernel/metadata-plugin.mdx b/content/docs/references/kernel/metadata-plugin.mdx index bbf7cff909..cacfa7fb25 100644 --- a/content/docs/references/kernel/metadata-plugin.mdx +++ b/content/docs/references/kernel/metadata-plugin.mdx @@ -100,8 +100,8 @@ const result = MetadataBulkResultSchema.parse(data); | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **storage** | `{ datasource?: string; tableName: string; fallback: Enum<'filesystem' \| 'memory' \| 'none'>; rootDir?: string; … }` | ✅ | Storage backend configuration | -| **customizationPolicies** | `never` | optional | [REMOVED] `config.customizationPolicies` was removed from `MetadataPluginConfig` in @objectstack/spec 17 (#13135, ADR-0049 enforce-or-remove) — it never had an effect: no code ever read a customization policy, and the overlay protocol it configured was itself unreachable from any served surface (ADR-0126 supersedes it on the record). Delete the key. What a customization may touch is governed by the real mechanisms: ADR-0005's org-scoped overlay (opt-in via `allowOrgOverride` on `DEFAULT_METADATA_TYPE_REGISTRY`, enforced at the REST meta write doors) and ADR-0126's packaged-metadata model (clone + ledger disable). | -| **mergeStrategy** | `never` | optional | [REMOVED] `config.mergeStrategy` was removed from `MetadataPluginConfig` in @objectstack/spec 17 (#13135, ADR-0049 enforce-or-remove) — it never had an effect: no 3-way merge engine ever existed to read it, and package upgrades do not merge customizations (ADR-0126: upgrades rewrite the packaged base; customer choices live in the ledger and are never merged). Delete the key. There is no replacement — upgrade-vs-customization separation is the model, not a configurable strategy. | +| **customizationPolicies** | `never` | optional | [REMOVED] `config.customizationPolicies` was removed from `MetadataPluginConfig` in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — it never had an effect: no code ever read a customization policy, and the overlay protocol it configured was itself unreachable from any served surface (ADR-0126 supersedes it on the record). Delete the key. What a customization may touch is governed by the real mechanisms: ADR-0005's org-scoped overlay (opt-in via `allowOrgOverride` on `DEFAULT_METADATA_TYPE_REGISTRY`, enforced at the REST meta write doors) and ADR-0126's packaged-metadata model (clone + ledger disable). | +| **mergeStrategy** | `never` | optional | [REMOVED] `config.mergeStrategy` was removed from `MetadataPluginConfig` in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — it never had an effect: no 3-way merge engine ever existed to read it, and package upgrades do not merge customizations (ADR-0126: upgrades rewrite the packaged base; customer choices live in the ledger and are never merged). Delete the key. There is no replacement — upgrade-vs-customization separation is the model, not a configurable strategy. | | **additionalTypes** | `never` | optional | [REMOVED] `config.additionalTypes` was removed from `MetadataPluginConfig` in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — it never had an effect: the only production writer of the metadata type registry is `setTypeRegistry(DEFAULT_METADATA_TYPE_REGISTRY)`, which replaces the array outright, so nothing ever merged these entries and the live type set was exactly the built-in registry whatever you declared here. Delete the key. There is no declared-kind channel: a kind enters the live metadata-type set as a side effect of registering an ITEM of that kind (`SchemaRegistry.registerItem` during app/manifest registration, or `MetadataManager.register` at runtime); bind its schema with `registerMetadataTypeSchema(type, schema)` from your plugin's `init(ctx)` so `GET /api/v1/meta` serves a real JSON Schema for it. | | **enableEvents** | `boolean` | optional (default: `true`) | Emit metadata change events | | **validateOnWrite** | `boolean` | optional (default: `true`) | Validate metadata on write | @@ -160,8 +160,8 @@ const result = MetadataBulkResultSchema.parse(data); | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **storage** | `{ datasource?: string; tableName: string; fallback: Enum<'filesystem' \| 'memory' \| 'none'>; rootDir?: string; … }` | ✅ | Storage backend configuration | -| **customizationPolicies** | `never` | optional | [REMOVED] `config.customizationPolicies` was removed from `MetadataPluginConfig` in @objectstack/spec 17 (#13135, ADR-0049 enforce-or-remove) — it never had an effect: no code ever read a customization policy, and the overlay protocol it configured was itself unreachable from any served surface (ADR-0126 supersedes it on the record). Delete the key. What a customization may touch is governed by the real mechanisms: ADR-0005's org-scoped overlay (opt-in via `allowOrgOverride` on `DEFAULT_METADATA_TYPE_REGISTRY`, enforced at the REST meta write doors) and ADR-0126's packaged-metadata model (clone + ledger disable). | -| **mergeStrategy** | `never` | optional | [REMOVED] `config.mergeStrategy` was removed from `MetadataPluginConfig` in @objectstack/spec 17 (#13135, ADR-0049 enforce-or-remove) — it never had an effect: no 3-way merge engine ever existed to read it, and package upgrades do not merge customizations (ADR-0126: upgrades rewrite the packaged base; customer choices live in the ledger and are never merged). Delete the key. There is no replacement — upgrade-vs-customization separation is the model, not a configurable strategy. | +| **customizationPolicies** | `never` | optional | [REMOVED] `config.customizationPolicies` was removed from `MetadataPluginConfig` in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — it never had an effect: no code ever read a customization policy, and the overlay protocol it configured was itself unreachable from any served surface (ADR-0126 supersedes it on the record). Delete the key. What a customization may touch is governed by the real mechanisms: ADR-0005's org-scoped overlay (opt-in via `allowOrgOverride` on `DEFAULT_METADATA_TYPE_REGISTRY`, enforced at the REST meta write doors) and ADR-0126's packaged-metadata model (clone + ledger disable). | +| **mergeStrategy** | `never` | optional | [REMOVED] `config.mergeStrategy` was removed from `MetadataPluginConfig` in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — it never had an effect: no 3-way merge engine ever existed to read it, and package upgrades do not merge customizations (ADR-0126: upgrades rewrite the packaged base; customer choices live in the ledger and are never merged). Delete the key. There is no replacement — upgrade-vs-customization separation is the model, not a configurable strategy. | | **additionalTypes** | `never` | optional | [REMOVED] `config.additionalTypes` was removed from `MetadataPluginConfig` in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — it never had an effect: the only production writer of the metadata type registry is `setTypeRegistry(DEFAULT_METADATA_TYPE_REGISTRY)`, which replaces the array outright, so nothing ever merged these entries and the live type set was exactly the built-in registry whatever you declared here. Delete the key. There is no declared-kind channel: a kind enters the live metadata-type set as a side effect of registering an ITEM of that kind (`SchemaRegistry.registerItem` during app/manifest registration, or `MetadataManager.register` at runtime); bind its schema with `registerMetadataTypeSchema(type, schema)` from your plugin's `init(ctx)` so `GET /api/v1/meta` serves a real JSON Schema for it. | | **enableEvents** | `boolean` | optional (default: `true`) | Emit metadata change events | | **validateOnWrite** | `boolean` | optional (default: `true`) | Validate metadata on write | diff --git a/content/docs/references/system/metadata-persistence.mdx b/content/docs/references/system/metadata-persistence.mdx index 11007f02b4..60540a04dd 100644 --- a/content/docs/references/system/metadata-persistence.mdx +++ b/content/docs/references/system/metadata-persistence.mdx @@ -281,7 +281,7 @@ Metadata file format | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **writable** | `boolean` | optional (default: `true`) | Allow base metadata writes via register() | -| **overlayWritable** | `never` | optional | [REMOVED] `persistence.overlayWritable` was removed from `MetadataManagerConfig` in @objectstack/spec 17 (#13135, ADR-0049 enforce-or-remove) — the only thing it gated was `MetadataManager.saveOverlay()`, a paper-protocol method no route or UI ever called, removed with the metadata-customization protocol (ADR-0126 supersedes it on the record). Delete the key. The base write gate that remains is `persistence.writable`; the real org-overlay writes (ADR-0005) ride the REST meta write doors' `manage_metadata` permission gate, not this flag. | +| **overlayWritable** | `never` | optional | [REMOVED] `persistence.overlayWritable` was removed from `MetadataManagerConfig` in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — the only thing it gated was `MetadataManager.saveOverlay()`, a paper-protocol method no route or UI ever called, removed with the metadata-customization protocol (ADR-0126 supersedes it on the record). Delete the key. The base write gate that remains is `persistence.writable`; the real org-overlay writes (ADR-0005) ride the REST meta write doors' `manage_metadata` permission gate, not this flag. | --- diff --git a/packages/spec/src/kernel/metadata-loader.zod.ts b/packages/spec/src/kernel/metadata-loader.zod.ts index 5622e39400..d4a5e70205 100644 --- a/packages/spec/src/kernel/metadata-loader.zod.ts +++ b/packages/spec/src/kernel/metadata-loader.zod.ts @@ -148,7 +148,7 @@ export const MetadataManagerConfigSchema = lazySchema(() => z.object({ writable: z.boolean().default(true).describe('Allow base metadata writes via register()'), overlayWritable: retiredKey( '`persistence.overlayWritable` was removed from `MetadataManagerConfig` in ' + - '@objectstack/spec 17 (#13135, ADR-0049 enforce-or-remove) — the only thing it gated was ' + + '@objectstack/spec 17 (ADR-0049 enforce-or-remove) — the only thing it gated was ' + '`MetadataManager.saveOverlay()`, a paper-protocol method no route or UI ever called, ' + 'removed with the metadata-customization protocol (ADR-0126 supersedes it on the record). ' + 'Delete the key. The base write gate that remains is `persistence.writable`; the real ' + diff --git a/packages/spec/src/kernel/metadata-plugin.zod.ts b/packages/spec/src/kernel/metadata-plugin.zod.ts index 3ecd46d454..27973841ba 100644 --- a/packages/spec/src/kernel/metadata-plugin.zod.ts +++ b/packages/spec/src/kernel/metadata-plugin.zod.ts @@ -510,7 +510,7 @@ export const MetadataPluginConfigSchema = lazySchema(() => z.object({ */ customizationPolicies: retiredKey( '`config.customizationPolicies` was removed from `MetadataPluginConfig` in @objectstack/spec 17 ' + - '(#13135, ADR-0049 enforce-or-remove) — it never had an effect: no code ever read a ' + + '(ADR-0049 enforce-or-remove) — it never had an effect: no code ever read a ' + 'customization policy, and the overlay protocol it configured was itself unreachable from any ' + 'served surface (ADR-0126 supersedes it on the record). Delete the key. What a customization ' + "may touch is governed by the real mechanisms: ADR-0005's org-scoped overlay (opt-in via " + @@ -530,7 +530,7 @@ export const MetadataPluginConfigSchema = lazySchema(() => z.object({ */ mergeStrategy: retiredKey( '`config.mergeStrategy` was removed from `MetadataPluginConfig` in @objectstack/spec 17 ' + - '(#13135, ADR-0049 enforce-or-remove) — it never had an effect: no 3-way merge engine ever ' + + '(ADR-0049 enforce-or-remove) — it never had an effect: no 3-way merge engine ever ' + 'existed to read it, and package upgrades do not merge customizations (ADR-0126: upgrades ' + 'rewrite the packaged base; customer choices live in the ledger and are never merged). ' + 'Delete the key. There is no replacement — upgrade-vs-customization separation is the ' + From 9d35c730cab0e4c6c547717e1d06da89f24775b4 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 29 Aug 2026 10:20:37 +0000 Subject: [PATCH 6/6] fix(spec): re-derive the llms.txt claims the module deletion falsified MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `check:llms-txt` reds on two counts, and the file is hand-kept with no generator on purpose: deleting `kernel/metadata-customization.zod.ts` takes `src/kernel/` from 32 to 31 and the domain-summed total from 208 to 207. Both rows re-read rather than digit-patched, per the gate's own instruction: - The heading's method prose ("counted as `*.zod.ts` under `src//`") stays exactly true, and is in fact what explains 207 against the 208 files on disk -- `src/stack.zod.ts` sits outside any domain directory. Every other domain row was re-counted independently and already matched; only kernel was stale. - The kernel row's Key Schemas column (Plugin, Manifest, Events, Feature, Context, Package Registry) never named the removed module, so nothing beside that number became untrue. Third fix, which the gate does NOT catch: the section-6 contract table lists `IMetadataService` METHODS, and `overlay` was one of them. That entry was true at the base commit (4 members) and is false now (0) -- this retirement removed them. Left alone it would ship inside the npm tarball telling AI consumers to generate `metadataService.saveOverlay(...)`, which is exactly the failure the file's header warns about. Dropped from the row. ⛔ Deliberately NOT touched: the same row claims `delete`, which resolves to 0 members at the base commit too -- the interface spells it `unregister`. That is pre-existing and unrelated to this retirement, so it is reported rather than folded in. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4 --- packages/spec/llms.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/spec/llms.txt b/packages/spec/llms.txt index ed42d56099..d50fc335ae 100644 --- a/packages/spec/llms.txt +++ b/packages/spec/llms.txt @@ -77,7 +77,7 @@ const query = { --- -## 3. Schema Inventory by Domain (208 schemas) +## 3. Schema Inventory by Domain (207 schemas) Counted as `*.zod.ts` modules under `packages/spec/src//` — the sources that ship in this tarball (`files` includes `src/**/*.zod.ts`), so every number @@ -86,7 +86,7 @@ here is verifiable from the installed package. | Domain | Count | Key Schemas | |--------|-------|-------------| | system | 36 | Auth, Cache, Compliance, Encryption, HTTP Server, License, Logging, Metrics | -| kernel | 32 | Plugin, Manifest, Events (6 sub-modules), Feature, Context, Package Registry | +| kernel | 31 | Plugin, Manifest, Events (6 sub-modules), Feature, Context, Package Registry | | data | 30 | Object, Field, Query, Filter, Driver (SQL/NoSQL/Memory/Mongo/Postgres), Cube | | api | 30 | Endpoint, REST Server, Discovery, OData, Batch, WebSocket, Response Envelope, Package Lifecycle | | ui | 18 | View, App, Action, Dashboard, Page, Chart, Component, Animation | @@ -166,7 +166,7 @@ function registerObject(rawConfig: unknown) { | Contract | Methods | |----------|---------| -| `IMetadataService` | register, get, list, delete, query, bulk ops, overlay, watch, import/export | +| `IMetadataService` | register, get, list, delete, query, bulk ops, watch, import/export | | `IAnalyticsService` | query, aggregate, timeSeries | | `IAuthService` | authenticate, authorize, validateToken | | `IAutomationService` | executeFlow, triggerWorkflow |