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/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/kernel/contracts/metadata-service.mdx b/content/docs/kernel/contracts/metadata-service.mdx index 504e77aaaf..73b241d33a 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.default', - 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.default', - 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.default', { - 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/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..7fa1b1e164 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 (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..cacfa7fb25 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 (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 | @@ -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 (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 a5fa30a425..60540a04dd 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 (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/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/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 9f1134158e..62a352661f 100644 --- a/packages/metadata/src/plugin.ts +++ b/packages/metadata/src/plugin.ts @@ -383,7 +383,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/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/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/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/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 | 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/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/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.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/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..d4a5e70205 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 (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.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/kernel/metadata-plugin.zod.ts b/packages/spec/src/kernel/metadata-plugin.zod.ts index bc998cb102..27973841ba 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 ' + + '(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 ' + + '(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..02b7716e01 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', @@ -6539,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 — ' @@ -8095,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 @@ -8118,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 @@ -9014,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 @@ -9111,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). // @@ -9147,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 @@ -9163,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/packages/spec/src/type-alias-convention.pin.test.ts b/packages/spec/src/type-alias-convention.pin.test.ts index 3631c0c08f..66ea8ab6d1 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'; @@ -267,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 @@ -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 > >>; @@ -1676,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 @@ -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