From d14fbe02881873e0681f0fd31acc530082b6ba77 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 11 Oct 2026 13:33:23 +0000 Subject: [PATCH 01/15] feat(spec,metadata-protocol)!: rename the type registry's allowOrgOverride to allowEnvironmentOverlay The registry flag decides one environment-scope question at every door since ADR-0131 D6 retired the per-organization overlay axis: whether an environment may overlay an item a managed package ships. It is renamed to say so, with the old key a retiredKey() tombstone, a retired-keys entry and a D3 semantic entry (no D2 conversion), and the GET /meta/types wire field moves with it on both writer arms. supportsOverlay's describes and TSDoc are corrected to what it gates: the removal carve-out, no save and no read. WIP: source and spec; tests, docs, tooling and generated artifacts follow. Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude --- examples/app-showcase/src/coverage.ts | 2 +- .../lint/src/validate-ai-agent-authoring.ts | 2 +- packages/metadata-core/src/contract-suite.ts | 2 +- packages/metadata-core/src/types.ts | 2 +- .../src/package-writability.ts | 2 +- .../src/packaged-base-regime.ts | 2 +- packages/metadata-protocol/src/protocol.ts | 153 ++++++++------ .../src/sys-metadata-repository.ts | 44 ++-- packages/objectql/src/engine.ts | 2 +- .../src/audit/sys-job.object.ts | 3 +- .../src/object-posture-gate.ts | 2 +- .../src/permission-set-projection.ts | 4 +- .../src/position-write-through.ts | 2 +- .../plugin-security/src/security-plugin.ts | 4 +- packages/rest/src/rest-server.ts | 6 +- packages/runtime/src/domains/mcp.ts | 8 +- packages/runtime/src/domains/meta.ts | 2 +- packages/runtime/src/domains/packages.ts | 6 +- .../src/sys-flow-credential.object.ts | 4 +- packages/spec/src/api/metadata.zod.ts | 2 +- packages/spec/src/api/protocol.zod.ts | 8 +- packages/spec/src/kernel/index.ts | 7 +- .../spec/src/kernel/metadata-create-seeds.ts | 2 +- .../spec/src/kernel/metadata-plugin.zod.ts | 190 ++++++++++++------ .../spec/src/kernel/metadata-type-schemas.ts | 2 +- ...dataPluginConfig__customizationPolicies.ts | 4 +- ...dataTypeRegistryEntry__allowOrgOverride.ts | 22 ++ ...metadata-customization-protocol-retired.ts | 10 +- ...ype-registry-allow-org-override-renamed.ts | 55 +++++ .../spec/src/migrations/registry.ts.template | 16 +- .../spec/src/system/email-template.zod.ts | 4 +- 31 files changed, 377 insertions(+), 197 deletions(-) create mode 100644 packages/spec/src/migrations/entries/retired-keys/18.kernel__MetadataTypeRegistryEntry__allowOrgOverride.ts create mode 100644 packages/spec/src/migrations/entries/semantic/18.metadata-type-registry-allow-org-override-renamed.ts diff --git a/examples/app-showcase/src/coverage.ts b/examples/app-showcase/src/coverage.ts index 91f737b815a..3800fd7665e 100644 --- a/examples/app-showcase/src/coverage.ts +++ b/examples/app-showcase/src/coverage.ts @@ -158,7 +158,7 @@ export const KIND_COVERAGE: Record = { notes: 'Flat positions only (no hierarchy — ADR-0090 D3); everyone/guest are built-in anchors, never declared.', }, // [#5961] `capability` graduated into the registry (schema + entry, code-only: - // allowRuntimeCreate/allowOrgOverride both false — ADR-0066 D1). The showcase + // allowRuntimeCreate/allowEnvironmentOverlay both false — ADR-0066 D1). The showcase // has declared capabilities since ADR-0066 landed; the registry entry only // made the kind's coverage ownable here. capability: { diff --git a/packages/lint/src/validate-ai-agent-authoring.ts b/packages/lint/src/validate-ai-agent-authoring.ts index 99799d54bd0..70a0f8b4992 100644 --- a/packages/lint/src/validate-ai-agent-authoring.ts +++ b/packages/lint/src/validate-ai-agent-authoring.ts @@ -7,7 +7,7 @@ * exactly two agents (`ask`, `build`), the surface the user is in binds one, * and third parties extend the platform by authoring **skills**, never * `*.agent.ts`. The `agent` metadata type carries the decision - * (`allowRuntimeCreate: false, allowOrgOverride: false`), and the runtime + * (`allowRuntimeCreate: false, allowEnvironmentOverlay: false`), and the runtime * enforces it on both paths — `listAgents()` filters non-platform records out * of the catalog, and `loadAgent()` refuses them outright (cloud#904), so a * stack-authored agent 404s on chat and cannot be pinned via diff --git a/packages/metadata-core/src/contract-suite.ts b/packages/metadata-core/src/contract-suite.ts index 21d3563fb8d..4b6d1172e20 100644 --- a/packages/metadata-core/src/contract-suite.ts +++ b/packages/metadata-core/src/contract-suite.ts @@ -43,7 +43,7 @@ export interface ContractSuiteOptions { * invariants is a statement about a particular type. It exists because an * implementation may sit behind a **write-authorization door** keyed on the * type — `SysMetadataRepository.assertAllowed()` refuses any type whose - * registry entry lacks `allowOrgOverride` — so a hard-coded fixture type + * registry entry lacks `allowEnvironmentOverlay` — so a hard-coded fixture type * decides which implementations can be held to the table at all. Naming the * two types here is what keeps that ONE table, instead of carving a second * one for the engine-backed implementation to be measured against. diff --git a/packages/metadata-core/src/types.ts b/packages/metadata-core/src/types.ts index 9b6dac1aa5e..68342c47bae 100644 --- a/packages/metadata-core/src/types.ts +++ b/packages/metadata-core/src/types.ts @@ -157,7 +157,7 @@ export type MetadataEvent = z.infer; * * - `override-artifact`: the write targets an item that ships from a code * package (an artifact). Only permitted when the type opts into - * per-org overlay writes via `allowOrgOverride: true`. + * environment overlays of packaged items via `allowEnvironmentOverlay: true`. * - `runtime-only`: the write targets a brand-new item OR an item that * exists only in `sys_metadata` (no artifact backing). Permitted for * types that opt into runtime creation via `allowRuntimeCreate: true`, diff --git a/packages/metadata-protocol/src/package-writability.ts b/packages/metadata-protocol/src/package-writability.ts index 022c5982442..59eaef1d25b 100644 --- a/packages/metadata-protocol/src/package-writability.ts +++ b/packages/metadata-protocol/src/package-writability.ts @@ -44,7 +44,7 @@ export const READ_ONLY_PACKAGE_SCOPES: readonly string[] = ['system', 'cloud']; * * • **Booted code packages** — they register a manifest into the engine at * startup (`registerApp` → `engine.manifests`); their items are code-shipped - * artifacts. Only `allowOrgOverride` overlays are allowed (ADR-0005), never + * artifacts. Only `allowEnvironmentOverlay` overlays are allowed (ADR-0005), never * fresh authored items. * • **Installed / platform packages** — manifest `scope` is `system` or * `cloud` (marketplace / platform-delivered). diff --git a/packages/metadata-protocol/src/packaged-base-regime.ts b/packages/metadata-protocol/src/packaged-base-regime.ts index a0fda00025d..737656013a0 100644 --- a/packages/metadata-protocol/src/packaged-base-regime.ts +++ b/packages/metadata-protocol/src/packaged-base-regime.ts @@ -404,7 +404,7 @@ export function managedItemSealedSentence(type: string, name: string, operation: if (regime !== undefined) return regime; return `Metadata item '${singular}/${name}' is provided by a managed package and is sealed ` + (operation === 'delete' ? 'against removal' : 'against in-place edits') - + `: its type takes no environment overlay (allowOrgOverride=false), and OS_METADATA_WRITABLE does not ` + + `: its type takes no environment overlay (allowEnvironmentOverlay=false), and OS_METADATA_WRITABLE does not ` + `open a managed item. ` + (operation === 'delete' ? '' : 'Edit the source artifact and redeploy. ') + `See ${MANAGED_SEAL_ADR}.`; diff --git a/packages/metadata-protocol/src/protocol.ts b/packages/metadata-protocol/src/protocol.ts index 7c2cd7685a1..d87eeb00837 100644 --- a/packages/metadata-protocol/src/protocol.ts +++ b/packages/metadata-protocol/src/protocol.ts @@ -7715,9 +7715,18 @@ export class ObjectStackProtocolImplementation implements // `types: string[]` field is preserved alongside the new `entries`. // // Phase 3a-env-writable: `OS_METADATA_WRITABLE` env var (comma - // separated singular type names) flips `allowOrgOverride` on listed - // types so admins can self-serve. The same env var is consulted by - // `isOverlayAllowed()` at write time — they must stay in sync. + // separated singular type names) elevates `allowEnvironmentOverlay` on + // listed types (`overrideSource: 'env'`). The same env var is consulted + // by `isOverlayAllowed()` at write time — they must stay in sync — and + // since ADR-0131 D6 it opens only items no managed package ships: a + // managed item stays sealed whatever the hatch names + // (`isSealedManagedItem`). + // + // ONE wire spelling, on BOTH arms below: the registry arm's `...base` + // spread already carries `allowEnvironmentOverlay` at the registry + // value, and the elevated value OVERWRITES that same key — never a + // second field — so no entry carries `allowEnvironmentOverlay`, the spelling + // retired in 18 (its tombstone is `MetadataTypeRegistryEntry`'s). const writableOverrides = ObjectStackProtocolImplementation.envWritableTypes(); const registryByType = new Map( DEFAULT_METADATA_TYPE_REGISTRY.map((e) => [e.type, e] as const) @@ -7759,8 +7768,8 @@ export class ObjectStackProtocolImplementation implements ...base, type: singular, schemaId: singular, // API client expects schemaId field - allowOrgOverride: base.allowOrgOverride || isEnvOverridden, - overrideSource: isEnvOverridden && !base.allowOrgOverride + allowEnvironmentOverlay: base.allowEnvironmentOverlay || isEnvOverridden, + overrideSource: isEnvOverridden && !base.allowEnvironmentOverlay ? 'env' as const : 'registry' as const, schema, @@ -7829,7 +7838,7 @@ export class ObjectStackProtocolImplementation implements description: undefined, filePatterns: [], supportsOverlay: false, - allowOrgOverride: writableOverrides.has(singular), + allowEnvironmentOverlay: writableOverrides.has(singular), allowRuntimeCreate: mintableByContract, supportsVersioning: false, executionPinned: false, @@ -11047,14 +11056,15 @@ export class ObjectStackProtocolImplementation implements * * - **Registry present, object absent → fail CLOSED** (404 * `OBJECT_NOT_FOUND`). The registry is authoritative for objects: - * `object` is `allowOrgOverride: false` (ADR-0005), so no per-org - * overlay can legitimately exist outside the process-wide registry, and + * `object` is `allowEnvironmentOverlay: false` (ADR-0005) and every + * metadata row is environment-wide (ADR-0131 D6), so no object + * definition can legitimately exist outside the process-wide registry, and * both boot hydration (`loadMetaFromDb`) and runtime authoring * (`applyObjectRegistryMutation`) register the schema before its table * is reachable. * * [#6190] That justification rested on a premise the WRITE path did not - * enforce until this note was written. `allowOrgOverride: false` closed + * enforce until this note was written. `allowEnvironmentOverlay: false` closed * the overlay tier only; `object` is also `allowRuntimeCreate: true`, and * that tier stamped `organization_id` on the row like any other — so a * Studio-authored `object` COULD legitimately exist as a per-org row, @@ -15681,7 +15691,7 @@ export class ObjectStackProtocolImplementation implements * Metadata types that are customer-overridable via {@link saveMetaItem}/ * {@link deleteMetaItem} in project-kernel mode. Derived from the canonical * registry in {@link DEFAULT_METADATA_TYPE_REGISTRY}: a type opts in by - * setting `allowOrgOverride: true` on its registry entry. The set is + * setting `allowEnvironmentOverlay: true` on its registry entry. The set is * augmented with the plural form of every singular so callers using REST * conventions (`/api/v1/meta/views/...`) get the same gate. See ADR-0005 * §"Whitelist enforcement" for the rationale and the per-type rollout @@ -15690,7 +15700,7 @@ export class ObjectStackProtocolImplementation implements private static readonly OVERLAY_ALLOWED_TYPES: ReadonlySet = (() => { const out = new Set(); for (const entry of DEFAULT_METADATA_TYPE_REGISTRY) { - if (!entry.allowOrgOverride) continue; + if (!entry.allowEnvironmentOverlay) continue; out.add(entry.type); const plural = SINGULAR_TO_PLURAL[entry.type]; if (plural) out.add(plural); @@ -15701,7 +15711,7 @@ export class ObjectStackProtocolImplementation implements /** * Phase 3a-env-writable: parse `OS_METADATA_WRITABLE` once. * Comma-separated singular type names. When the env var is set, the - * listed types get treated as `allowOrgOverride: true` regardless of + * listed types get treated as `allowEnvironmentOverlay: true` regardless of * their static registry entry. This is the runtime escape hatch admins * use to enable Studio-side editing of types whose protocol-level flag * is still false (object, field, permission, …). @@ -15729,7 +15739,7 @@ export class ObjectStackProtocolImplementation implements /** * Types that opt into runtime creation of brand-new items (ADR-0005 * extension — two-tier model). A type may have - * `allowOrgOverride: false` (cannot overlay artifact-shipped items) + * `allowEnvironmentOverlay: false` (cannot overlay artifact-shipped items) * yet still set `allowRuntimeCreate: true` (users can author new * items in `sys_metadata`). The two flags are orthogonal; see * {@link isArtifactBacked} for how the protocol decides which gate @@ -15771,19 +15781,24 @@ export class ObjectStackProtocolImplementation implements })(); /** - * [#6960] Metadata types whose LOADER merges a per-org/env overlay row on - * top of the artifact at read time — `supportsOverlay: true` on the - * registry entry, read straight off the declaration. + * [#6960] Metadata types whose stored overlay row of an item a managed + * package ships may be REMOVED through the ordinary delete door without + * the type's environment-overlay permission — `supportsOverlay: true` on + * the registry entry, read straight off the declaration. * * This is a strictly different question from {@link OVERLAY_ALLOWED_TYPES} - * (`allowOrgOverride`), and the registry keeps the two flags apart on - * purpose: `supportsOverlay` is a CAPABILITY of the read path ("an overlay - * row under this name changes what is served"), `allowOrgOverride` is a - * PERMISSION on the write path ("a tenant may author one"). Commit + * (`allowEnvironmentOverlay`), and the registry keeps the two flags apart on + * purpose: `allowEnvironmentOverlay` decides whether an environment may + * overlay an item a managed package ships, on the save AND on the served + * read ({@link isSealedManagedItem}, {@link declinesStoredRow}); + * `supportsOverlay` decides the removal carve-out alone — such a row may be + * removed without that permission, because removing it restores the + * managed definition. It opens no save and it decides no read. Commit * ee58392e1 rolled the permission back for six types — `permission`, * `position`, `page`, `app`, `dataset`, `book` — and deliberately left - * the capability alone, which is exactly the state #6960 was filed about: - * a row authored BEFORE the rollback still merges overlay-wins today. + * this flag alone, which is exactly the state #6960 was filed about: rows + * authored BEFORE the rollback stay stored, and this carve-out is how an + * operator removes them. * * Registry-derived, never a hand-written list (Prime Directive #8), and * augmented with the plural spelling so `/api/v1/meta/pages/...` is judged @@ -15802,8 +15817,12 @@ export class ObjectStackProtocolImplementation implements })(); /** - * [#6960] Does this type's loader merge an overlay row at read time? - * Normalizes plural→singular, like every other predicate here. + * [#6960] Does this type's registry entry carry the removal carve-out + * (`supportsOverlay`)? The name predates ADR-0131 D6's seal and no read + * consults it: a stored overlay row of a managed item is served or + * withheld by {@link declinesStoredRow}, which keys on + * `allowEnvironmentOverlay`. Normalizes plural→singular, like every other + * predicate here. * * Read ONLY by `deleteMetaItem`'s two-tier authorization (and mirrored by * `SysMetadataRepository`'s delete gate); it never widens a create or an @@ -15816,7 +15835,7 @@ export class ObjectStackProtocolImplementation implements } /** - * Does the type's REGISTRY entry open an overlay channel (`allowOrgOverride`)? + * Does the type's REGISTRY entry open an overlay channel (`allowEnvironmentOverlay`)? * The registry arm of {@link isOverlayAllowed}, without the hatch. * Normalizes plural→singular before consulting the allow-list. * @@ -15949,7 +15968,7 @@ export class ObjectStackProtocolImplementation implements /** * #5086 — a brand-new item of a type the registry declares code-only - * (`allowRuntimeCreate: false` AND `allowOrgOverride: false`). + * (`allowRuntimeCreate: false` AND `allowEnvironmentOverlay: false`). * * Names the type, the flags that produced the verdict, the prescription * and the escape hatch — the same shape the retired-key refusals in this @@ -15960,7 +15979,7 @@ export class ObjectStackProtocolImplementation implements private static codeOnlyCreateError(type: string): Error { const err = new Error( `Metadata type '${type}' is code-only: the metadata-type registry declares ` - + `allowRuntimeCreate=false and allowOrgOverride=false, so it cannot be created through the ` + + `allowRuntimeCreate=false and allowEnvironmentOverlay=false, so it cannot be created through the ` + `runtime metadata API (PUT /api/v1/meta/${type}/:name) on any kernel.` + ObjectStackProtocolImplementation.codeOnlySourceHint(type) + ` An operator may set OS_METADATA_WRITABLE=${PLURAL_TO_SINGULAR[type] ?? type} to grant a runtime escape hatch. ` @@ -15985,7 +16004,7 @@ export class ObjectStackProtocolImplementation implements private static codeOnlyOverrideError(type: string, name: string): Error { const err = new Error( `Metadata item '${type}/${name}' is provided by a managed package and its type is ` - + `code-only (allowRuntimeCreate=false, allowOrgOverride=false), so it is sealed against ` + + `code-only (allowRuntimeCreate=false, allowEnvironmentOverlay=false), so it is sealed against ` + `runtime edits on any kernel, and OS_METADATA_WRITABLE does not open a managed item.` + ObjectStackProtocolImplementation.codeOnlySourceHint(type) + ` See docs/adr/0131-total-organization-ownership-no-null-organization-id.md.` @@ -16079,7 +16098,7 @@ export class ObjectStackProtocolImplementation implements * excluded here. * * Used by the two-tier authorization model to distinguish - * "overlaying a packaged item" (requires `allowOrgOverride`) from + * "overlaying a packaged item" (requires `allowEnvironmentOverlay`) from * "authoring a DB-only item" (requires only `allowRuntimeCreate`). * * [#7743] …and it must answer that question about the artifact as SHIPPED, @@ -16151,7 +16170,7 @@ export class ObjectStackProtocolImplementation implements * `runtime-only`) and what arms `saveMetaItem`'s own `NOT_OVERRIDABLE` * gate. With the lookup empty, an override of a packaged field was * classified as a runtime-only CREATE, and `field` carries - * `allowRuntimeCreate: true` — so `allowOrgOverride: false` was never + * `allowRuntimeCreate: true` — so `allowEnvironmentOverlay: false` was never * consulted, and `PUT /api/v1/meta/field/showcase_task.title` answered * **200** on a write the registry forbids. Measured on the showcase before * this fix: 200 `state:'active'`, the row reading back with @@ -16328,7 +16347,7 @@ export class ObjectStackProtocolImplementation implements * artifact-only lookup, which answers from the entries the artifact loader * registered under a package id — never from the body a caller sends) and * "does the type have an overlay channel?" — [ADR-0131 D6] asked together - * as {@link isSealedManagedItem}, the registry's `allowOrgOverride` alone, + * as {@link isSealedManagedItem}, the registry's `allowEnvironmentOverlay` alone, * never the `OS_METADATA_WRITABLE` hatch — keep exactly one spelling. The refusal — code, status and sentence — is registered to * this package in the ADR-0112 ledger, so the caller relays it verbatim * and stamps no code of its own. @@ -16354,7 +16373,7 @@ export class ObjectStackProtocolImplementation implements * lock's; * - the ADR-0010 per-item `_lock` (L3), which `saveMetaItem` enforces * separately and records to `sys_metadata_audit`: with the hatch shut, - * an artifact-backed item of a type without `allowOrgOverride` is + * an artifact-backed item of a type without `allowEnvironmentOverlay` is * refused here before its `_lock` could matter; * - a named base (`?package=`) on a door that carries none: the * package-less refusal (`NOT_OVERRIDABLE`) is the whole answer there. @@ -16945,7 +16964,7 @@ export class ObjectStackProtocolImplementation implements // hatch is not a limb at all. // • Below the registry limb — this whole branch is guarded // by {@link isSealedManagedItem}, whose type half is the - // registry's `allowOrgOverride` alone, so a regime-O type + // registry's `allowEnvironmentOverlay` alone, so a regime-O type // never reaches the door. That is ADR-0005: an overlay of // a code-shipped item ALWAYS names the read-only package // it customizes, and a door one limb higher would close @@ -16993,7 +17012,7 @@ export class ObjectStackProtocolImplementation implements * merges an overlay at read time, where removing the row is repair (the * #6960 ruling; the call site in {@link deleteMetaItem} carries the record * and the tier boundary, which is `supportsOverlay`, never - * `allowOrgOverride`). Throws the refusal; returns when the removal is not + * `allowEnvironmentOverlay`). Throws the refusal; returns when the removal is not * refused on that ground. * * [#20679] Lifted out of `deleteMetaItem` UNCHANGED, for the reason {@link @@ -18029,7 +18048,7 @@ export class ObjectStackProtocolImplementation implements * with package provenance IS a code artifact as far as * `getArtifactItem` is concerned, so `isArtifactBacked` turns true and * `saveMetaItem`'s overlay gate refuses the NEXT write to it with - * `not_overridable` — `object` declares `allowOrgOverride: false`. + * `not_overridable` — `object` declares `allowEnvironmentOverlay: false`. * Moving the key without the stamp therefore re-creates cloud#970 (an * app the user just built becomes silently un-editable) on the write * path, one save later instead of one restart later. Measured, not @@ -18938,8 +18957,9 @@ export class ObjectStackProtocolImplementation implements // assert exists to make a producer that stops folding FAIL LOUDLY // instead of silently — not to repair its key. if (request.type === 'object') { - // NOT org-gated, deliberately: an `object` is `allowOrgOverride: - // false` (ADR-0005) and its physical TABLE is env-wide, so the + // NOT org-gated, deliberately: no overlay of a packaged `object` + // exists at any scope (`allowEnvironmentOverlay: false`, ADR-0005), + // and its physical TABLE is env-wide, so the // registry entry backing it is env-wide too — `assertObjectRegistered` // fails CLOSED on a missing entry, and refusing to register here // would make a runtime-created object unreachable for data CRUD @@ -19894,11 +19914,11 @@ export class ObjectStackProtocolImplementation implements // ADR-0005 (extended — two-tier model): project-kernel customization is // gated by per-item provenance, not just the type-level flag. // - // • Item exists as a packaged artifact → require `allowOrgOverride` + // • Item exists as a packaged artifact → require `allowEnvironmentOverlay` // (writing here would overlay code-shipped behaviour; gated for // security on executable types like hook/trigger/validation). // • Item does NOT exist as an artifact → require `allowRuntimeCreate` - // OR `allowOrgOverride`. This lets users author brand-new hooks / + // OR `allowEnvironmentOverlay`. This lets users author brand-new hooks / // validations / triggers without unlocking the artifact-shadowing // capability. Returns `not_creatable` (vs `not_overridable`) so // the UI can present a tailored message. @@ -19907,7 +19927,7 @@ export class ObjectStackProtocolImplementation implements // #5086 — CODE-ONLY TYPES ARE REFUSED ON EVERY KERNEL, not only on // project-scoped ones. A type whose registry entry sets BOTH - // `allowRuntimeCreate: false` AND `allowOrgOverride: false` declares + // `allowRuntimeCreate: false` AND `allowEnvironmentOverlay: false` declares // that it has **no runtime write channel at all** — today `job` // (#4509: `handler` resolves only through the compiled bundle's // function table, so a runtime-created job could never be scheduled) @@ -19947,10 +19967,10 @@ export class ObjectStackProtocolImplementation implements // // [#6960] THAT CARVE-OUT NOW NAMES BOTH TIERS, because as written it // covered only the CODE-ONLY one (`allowRuntimeCreate: false` AND - // `allowOrgOverride: false`, i.e. the `if` immediately below) while + // `allowEnvironmentOverlay: false`, i.e. the `if` immediately below) while // the identical argument had grown a second population: an // ARTIFACT-BACKED item of a type that kept `allowRuntimeCreate: true` - // and had its `allowOrgOverride` ROLLED BACK to `false` (commit + // and had its `allowEnvironmentOverlay` ROLLED BACK to `false` (commit // ee58392e1 — `permission` / `position` / `page` / `app` / `dataset` / // `book`). Its loader still merges the overlay at read time // (`supportsOverlay: true`, untouched by the rollback), so a row @@ -19967,7 +19987,7 @@ export class ObjectStackProtocolImplementation implements // symmetry" in either direction — symmetrizing towards delete re-opens // the write door commit ee58392e1 closed, symmetrizing towards save re-traps the // repair. And the relaxation is keyed on `supportsOverlay`, not on - // `allowOrgOverride`, so it stops at the tier boundary: `object` + // `allowEnvironmentOverlay`, so it stops at the tier boundary: `object` // (`supportsOverlay: false`, its overlay a contributor LAYER per // ADR-0029 D9) keeps refusing both verbs, which is D9.6's declared // cost and is pinned. See {@link deleteMetaItem}. @@ -20015,7 +20035,7 @@ export class ObjectStackProtocolImplementation implements // and none of them has to learn to defer to it. // // ⛔ NO ACCEPTANCE SET MOVES. This predicate is the repository's - // (the registry's `allowOrgOverride` — [ADR-0131 D6] never the + // (the registry's `allowEnvironmentOverlay` — [ADR-0131 D6] never the // `OS_METADATA_WRITABLE` hatch for an item a managed package ships — // and a named read-only base through the one `isWritablePackage`), // and `repo.put` refuses every write it refuses, on every topology — @@ -20107,7 +20127,7 @@ export class ObjectStackProtocolImplementation implements // while the same-shaped change on an `object` refused in the same // harness. // 2. `field` is code-only (`allowRuntimeCreate: false` AND - // `allowOrgOverride: false` in the `packages/spec` kernel + // `allowEnvironmentOverlay: false` in the `packages/spec` kernel // registry), so the #5086 refusal ABOVE this gate answers first — // `NOT_CREATABLE` for a runtime-only parent, `NOT_OVERRIDABLE` for // an artifact-backed one (#7743's `isNestedArtifactField` path) — @@ -21069,7 +21089,7 @@ export class ObjectStackProtocolImplementation implements * inert engine to hold the registry, and `POST /meta/_migrate-stored` * resolves a live one from the services registry — so * `content/docs/deployment/cli.mdx` carries it too. - * - **Types with no repository write path** (neither `allowOrgOverride` nor + * - **Types with no repository write path** (neither `allowEnvironmentOverlay` nor * `allowRuntimeCreate`). This pass declines them, and since #5086 it * would have no choice: `saveMetaItem` refuses a code-only type with * `403 NOT_CREATABLE` / `NOT_OVERRIDABLE` before persistence. The skip @@ -21368,7 +21388,7 @@ export class ObjectStackProtocolImplementation implements record({ ...base, outcome: 'skipped', - reason: `type '${singular}' has no repository write path (allowOrgOverride and ` + reason: `type '${singular}' has no repository write path (allowEnvironmentOverlay and ` + 'allowRuntimeCreate are both false), so a rewrite would record no history', }); continue; @@ -21499,7 +21519,7 @@ export class ObjectStackProtocolImplementation implements * in event_seq order. Powers the Studio "History" tab and any * client-side audit timeline. * - * Returns `[]` for code-only types (neither `allowOrgOverride` nor + * Returns `[]` for code-only types (neither `allowEnvironmentOverlay` nor * `allowRuntimeCreate`) instead of throwing — callers can treat "no * history" uniformly. Those types have no history to yield by * construction: `saveMetaItem` refuses them outright (#5086), and the one @@ -21522,7 +21542,7 @@ export class ObjectStackProtocolImplementation implements // here — it was a door AROUND the gate immediately below, measured on // `origin/main`: // - // `translation` declares `allowOrgOverride: true`, so the singular + // `translation` declares `allowEnvironmentOverlay: true`, so the singular // passes `isOverlayAllowed` and reads real `sys_metadata_history` // rows. `translations` is MANIFEST-ABSENT, so the old line left it // plural; `isOverlayAllowed('translations')` is false, and @@ -22832,7 +22852,7 @@ export class ObjectStackProtocolImplementation implements // state with no consumer. // // Why it MUST go rather than sit unreached: `api` is now code-only - // (`allowRuntimeCreate: false` + `allowOrgOverride: false`), and the + // (`allowRuntimeCreate: false` + `allowEnvironmentOverlay: false`), and the // #5086 inlet refuses the write BEFORE persistence and BEFORE the // draft/publish branch — it does not look at `mode`. So no `api` draft // row can be created any more, and this gate could never again see one. @@ -24926,7 +24946,7 @@ export class ObjectStackProtocolImplementation implements // limb used to carry, `SysMetadataRepository.delete` opened // with `assertAllowed(ref.type, opts.intent)` — the same // gate `put` uses — which refuses every type that is not - // `allowOrgOverride`, `object` among them. So a commit that + // `allowEnvironmentOverlay`, `object` among them. So a commit that // CREATED an object could not be reverted at all: the // first-build undo (publish a brand-new app, then undo it) // left every created object behind, answered `success: @@ -25056,7 +25076,7 @@ export class ObjectStackProtocolImplementation implements // sibling caller {@link rollbackMetaItem} derives it. Left // unstated, `SysMetadataRepository.restoreVersion` defaults to // `'override-artifact'` and `put`'s `assertAllowed` refuses every - // type that is not `allowOrgOverride` — `object` among them — so + // type that is not `allowEnvironmentOverlay` — `object` among them — so // each `object` item of a reverted commit came back in `failed[]` // as `NOT_OVERRIDABLE` while the same edit reverted fine one // artifact at a time through the version-history revert. The @@ -25922,12 +25942,13 @@ export class ObjectStackProtocolImplementation implements const originGatedRefusal = this.originGatedRemovalRefusal(request.type, request.name); // Two-tier authorization for delete (mirrors saveMetaItem). // • Artifact-backed item → delete becomes a tombstone overlay, - // requires `allowOrgOverride`… - // • …EXCEPT when the type's loader merges overlays at read time - // (`supportsOverlay: true`) — then removing the row is repair and - // is allowed without it. [#6960, delete-only; see below.] + // requires `allowEnvironmentOverlay`… + // • …EXCEPT when the type's entry carries `supportsOverlay: true` — + // the removal carve-out: removing the row restores the managed + // definition, so it is repair and is allowed without it. The flag + // opens no save and decides no read. [#6960, delete-only; see below.] // • DB-only item → hard delete of a user-created row, - // requires `allowRuntimeCreate` (or `allowOrgOverride`). + // requires `allowRuntimeCreate` (or `allowEnvironmentOverlay`). if (this.environmentId !== undefined) { const overlayAllowed = ObjectStackProtocolImplementation.isOverlayAllowed(request.type); const runtimeCreateAllowed = ObjectStackProtocolImplementation.isRuntimeCreateAllowed(request.type); @@ -25949,17 +25970,19 @@ export class ObjectStackProtocolImplementation implements // // THE TIER BOUNDARY, which is what makes this narrow enough to be // safe: {@link mergesOverlayAtRead} is `supportsOverlay`, NOT - // `allowOrgOverride`. + // `allowEnvironmentOverlay`. // - // • `supportsOverlay: true` + `allowOrgOverride: false` — the + // • `supportsOverlay: true` + `allowEnvironmentOverlay: false` — the // ROLLED-BACK OVERLAYABLE tier (`permission` / `position` / // `page` / `app` / `dataset` / `book`, and any type that lands // in this shape later). Commit ee58392e1 closed the write door - // and left the read path merging overlay-wins, so a row - // authored before the rollback still shapes the effective body - // and the ordinary "Reset to package default" flow answered 403 - // with the item still customized. That is the stuck operator - // this branch releases. + // and, until ADR-0131 D6's seal, left the read path merging + // overlay-wins, so a row authored before the rollback shaped the + // effective body and the ordinary "Reset to package default" flow + // answered 403 with the item still customized. The seal now + // withholds such a row from the served read (`permission` + // excepted, {@link isUnservedSealedOverlay}); the row stays stored, + // and this branch is how the operator removes it. // • `supportsOverlay: false` — `object` above all, whose overlay // is a CONTRIBUTOR LAYER (ADR-0029 D9) rather than a merge, and // whose reset refusal is D9.6's declared and maintainer-approved @@ -26272,7 +26295,7 @@ export class ObjectStackProtocolImplementation implements // ── Legacy raw-engine path: reachable in control-plane bootstrap // (`environmentId === undefined`) for a code-only type — one whose - // registry entry sets BOTH `allowOrgOverride: false` and + // registry entry sets BOTH `allowEnvironmentOverlay: false` and // `allowRuntimeCreate: false` (today `job` and `agent`). No history // row, no watch event — these types don't participate in the // change-log model, and `SysMetadataRepository.assertAllowed()` would diff --git a/packages/metadata-protocol/src/sys-metadata-repository.ts b/packages/metadata-protocol/src/sys-metadata-repository.ts index 0d13161d099..df5af81fac5 100644 --- a/packages/metadata-protocol/src/sys-metadata-repository.ts +++ b/packages/metadata-protocol/src/sys-metadata-repository.ts @@ -19,7 +19,7 @@ * (invariant 6). With no `since` the subscriber is owed live events * only — see `watch()` for why that is not an oversight. * - whitelist enforcement: refuses to persist types whose registry - * entry has `allowOrgOverride: false` (Prime Directive #8) + * entry has `allowEnvironmentOverlay: false` (Prime Directive #8) * - **M1**: every successful put/delete appends a durable row to * `sys_metadata_history` inside the same engine.transaction() as the * parent `sys_metadata` write. No-op puts (identical content — see @@ -349,14 +349,14 @@ export interface SysMetadataRepositoryOptions { /** Derived from registry — single source of truth (Prime Directive #8). */ const OVERLAY_ALLOWED_TYPES: ReadonlySet = new Set( DEFAULT_METADATA_TYPE_REGISTRY - .filter((e) => e.allowOrgOverride) + .filter((e) => e.allowEnvironmentOverlay) .map((e) => e.type), ); /** * Types that opt into runtime creation of brand-new items (two-tier * model — ADR-0005 extension). These items live only in `sys_metadata`; - * there is no artifact backing them and `allowOrgOverride` need not be + * there is no artifact backing them and `allowEnvironmentOverlay` need not be * granted on the type. Used by `assertAllowed()` when called with * `intent: 'runtime-only'` — a signal from the protocol layer that it * has already verified the absence of an artifact-shadowing collision. @@ -379,11 +379,14 @@ const RUNTIME_CREATE_ALLOWED_TYPES: ReadonlySet = new Set( ); /** - * [#6960] Types whose LOADER merges an overlay row on top of the artifact at - * read time — `supportsOverlay: true`. Derived from the same registry as the - * two sets above (Prime Directive #8), and deliberately a different question - * from {@link OVERLAY_ALLOWED_TYPES}: `supportsOverlay` is a capability of the - * READ path, `allowOrgOverride` a permission on the WRITE path. Read only by + * [#6960] Types whose stored overlay row of an item a managed package ships + * may be REMOVED without the type's environment-overlay permission — + * `supportsOverlay: true`. Derived from the same registry as the two sets + * above (Prime Directive #8), and deliberately a different question from + * {@link OVERLAY_ALLOWED_TYPES}: `allowEnvironmentOverlay` decides whether an + * environment may overlay a packaged item at all, `supportsOverlay` only that + * removing such a row restores the managed definition, so it is repair. It + * opens no save and decides no read. Read only by * {@link SysMetadataRepository.assertDeleteAllowed}. */ const OVERLAY_CAPABLE_TYPES: ReadonlySet = new Set( @@ -1726,7 +1729,7 @@ export class SysMetadataRepository implements MetadataRepository { * behavior). The protocol layer passes `'runtime-only'` after it has * verified — via the schema registry — that no artifact item exists * at `(type, name)`. In that case we accept types with - * `allowRuntimeCreate: true`, even when `allowOrgOverride` is false. + * `allowRuntimeCreate: true`, even when `allowEnvironmentOverlay` is false. * * The env-var escape hatch (`OS_METADATA_WRITABLE`) applies to the * `runtime-only` intent ONLY. [ADR-0131 D6] Managed content is sealed: an @@ -1893,7 +1896,7 @@ export class SysMetadataRepository implements MetadataRepository { // declares below, so a bracketed restatement duplicates onto the prose // axis a fact the envelope already carries. const err: any = new Error( - `'${type}' has neither allowOrgOverride nor allowRuntimeCreate in the registry. ` + + `'${type}' has neither allowEnvironmentOverlay nor allowRuntimeCreate in the registry. ` + `Overlay-allowed: ${Array.from(new Set(allowed)).join(', ') || '(none)'}. ` + `Set OS_METADATA_WRITABLE to enable additional types at runtime.`, ); @@ -2005,7 +2008,7 @@ export class SysMetadataRepository implements MetadataRepository { ? `Cannot overlay '${type}' in package '${packageId}': that package is read-only, and its packaged base ` + `is locked against in-place edits. ${regimePrescription}` : `Cannot overlay '${type}' in package '${packageId}': that package is read-only (a managed package) ` - + `and the type has no environment overlay channel (allowOrgOverride=false), so this item is sealed ` + + `and the type has no environment overlay channel (allowEnvironmentOverlay=false), so this item is sealed ` + `against runtime edits, and OS_METADATA_WRITABLE does not open a managed item. ` + `Edit the source artifact and redeploy. See docs/adr/0010-metadata-protection-model.md.`); err.code = 'ITEM_LOCKED'; @@ -2023,11 +2026,11 @@ export class SysMetadataRepository implements MetadataRepository { * ## Why the divergence exists * * `assertAllowed` refuses an `override-artifact` write on any type without - * `allowOrgOverride`, and it is TOPOLOGY-INDEPENDENT — a control-plane + * `allowEnvironmentOverlay`, and it is TOPOLOGY-INDEPENDENT — a control-plane * kernel (`environmentId === undefined`) skips the protocol's own two-tier * block entirely and lands here instead. That made it the second of the two * refusal points #6960 measured: on an environment carrying an overlay row - * authored BEFORE commit ee58392e1 rolled `allowOrgOverride` back to + * authored BEFORE commit ee58392e1 rolled `allowEnvironmentOverlay` back to * `false`, the row kept merging overlay-wins at read time while the ordinary * "Reset to package default" answered 403 — the removal reachable only * through `OS_METADATA_WRITABLE`. Maintainer ruling, 2026-08-10: the delete @@ -2037,13 +2040,14 @@ export class SysMetadataRepository implements MetadataRepository { * ## The boundary, which is the whole safety argument * * Keyed on `supportsOverlay` ({@link OVERLAY_CAPABLE_TYPES}), NOT on - * `allowOrgOverride`: - * - * - `supportsOverlay: true` — the loader merges the row, so a row under - * this name really is a customization sitting on top of a code-declared - * default, and subtracting it restores that default. This is the tier - * commit ee58392e1 rolled back (`permission` / `position` / `page` / `app` / - * `dataset` / `book`). + * `allowEnvironmentOverlay`: + * + * - `supportsOverlay: true` — the removal carve-out: a row under this + * name is a customization sitting on top of a code-declared default + * (served for `permission`, withheld from the read by ADR-0131 D6's seal + * for every other such type), and subtracting it restores that default. + * This is the tier commit ee58392e1 rolled back (`permission` / + * `position` / `page` / `app` / `dataset` / `book`). * - `supportsOverlay: false` — `object` above all, whose overlay registers * as its own contributor LAYER (ADR-0029 D9) rather than merging, and * whose reset refusal is D9.6's declared, maintainer-approved cost. It diff --git a/packages/objectql/src/engine.ts b/packages/objectql/src/engine.ts index 9905baf9fb8..a45747c1abd 100644 --- a/packages/objectql/src/engine.ts +++ b/packages/objectql/src/engine.ts @@ -3443,7 +3443,7 @@ const METADATA_ARRAY_KEYS = [ // decides "a code package ships this" from exactly that entry // (`isArtifactBacked` → `getArtifactItem`), so a save over a package's // position took the runtime-create tier and was accepted, although the type - // registry declares `position` `allowOrgOverride: false`. With the entry + // registry declares `position` `allowEnvironmentOverlay: false`. With the entry // present the door's type-level refusal covers it like every other // `security`-domain type (`engine-security-catalog-package-door.test.ts`). // `roles` stays in the list as the inert retired spelling it already was. diff --git a/packages/platform-objects/src/audit/sys-job.object.ts b/packages/platform-objects/src/audit/sys-job.object.ts index 457b25c9ef9..a9d9fa6788d 100644 --- a/packages/platform-objects/src/audit/sys-job.object.ts +++ b/packages/platform-objects/src/audit/sys-job.object.ts @@ -162,7 +162,8 @@ export const SysJob = ObjectSchema.create({ // its rows up by `where: { name }` with no organization dimension — a // per-organization key would make that lookup ambiguous rather than fix // anything. The `job` metadata type is closed to tenants on all three - // flags (`allowOrgOverride: false` — "no per-org job fork" — plus + // flags (`allowEnvironmentOverlay: false` — "no environment fork of a + // packaged job" — plus // `allowRuntimeCreate: false` and `supportsOverlay: false`), and `enable` // below advertises no generic write verb at all. ADR-0120's S5 inventory // names `sys_job.name` outright as one of the engine idempotency keys that diff --git a/packages/plugins/plugin-security/src/object-posture-gate.ts b/packages/plugins/plugin-security/src/object-posture-gate.ts index aa47f2560ec..80115de02a4 100644 --- a/packages/plugins/plugin-security/src/object-posture-gate.ts +++ b/packages/plugins/plugin-security/src/object-posture-gate.ts @@ -12,7 +12,7 @@ * - **R1 — env-tighten-only (ADR-0086 D1, ADR-0049).** An environment write * over a PACKAGED object (artifact-backed — reachable only via the * `OS_METADATA_WRITABLE=object` escape hatch, since `object` ships - * `allowOrgOverride:false`) may not set `sharingModel` / + * `allowEnvironmentOverlay:false`) may not set `sharingModel` / * `externalSharingModel` WIDER than the packaged declaration. Widening * legitimately = author it in the package source and publish (ADR-0090 * D7), never an env overlay. No lint rule covers this comparison — it diff --git a/packages/plugins/plugin-security/src/permission-set-projection.ts b/packages/plugins/plugin-security/src/permission-set-projection.ts index 9e91bba87ed..abf0d9e4932 100644 --- a/packages/plugins/plugin-security/src/permission-set-projection.ts +++ b/packages/plugins/plugin-security/src/permission-set-projection.ts @@ -30,7 +30,7 @@ * 2026-07-14 direction confirmation that used to sit here — "the environment * customizes them through the platform's standard ADR-0005 metadata overlay" * — is **RETIRED** (ADR-0094 D5-R, 2026-08-09, recording ruling A, executed by - * #6858). Commit ee58392e1 rolled `permission` back to `allowOrgOverride: false` + * #6858). Commit ee58392e1 rolled `permission` back to `allowEnvironmentOverlay: false` * (the nine-type rollback), so ADR-0005's security row is enforced again: an overlay of the * authorization surface IS the "silent privilege drift" it excludes. * @@ -535,7 +535,7 @@ function hasSchemaRegistry(ql: any): boolean { * declaration" — is **RETIRED**, in BOTH halves (ADR-0094 D5-R, 2026-08-09; * the ruling A it records, executed by #6858; the file header above records the same * retirement). Since commit ee58392e1 rolled `permission` back to - * `allowOrgOverride: false`: + * `allowEnvironmentOverlay: false`: * * - **no new overlay of a packaged set can be minted.** A metadata write * against a CODE-DECLARED (artifact-backed) set — `*.permission.ts`, a diff --git a/packages/plugins/plugin-security/src/position-write-through.ts b/packages/plugins/plugin-security/src/position-write-through.ts index f97c47663d6..f1e4ca552fb 100644 --- a/packages/plugins/plugin-security/src/position-write-through.ts +++ b/packages/plugins/plugin-security/src/position-write-through.ts @@ -44,7 +44,7 @@ * Positions hold one name per deployment (ADR-0048 addendum, N.2), and a write * with no package provenance over a package-held name stays under ADR-0005 * overlay precedence (N.3): `position` takes no environment overlay - * (`allowOrgOverride: false`), so the metadata door refuses that save with its + * (`allowEnvironmentOverlay: false`), so the metadata door refuses that save with its * locked-base refusal (`403 NOT_OVERRIDABLE`). A Setup **create**, or a * **rename into** such a name, is now answered with that same refusal, and the * row write is undone — before this stage the write-through stood down and the diff --git a/packages/plugins/plugin-security/src/security-plugin.ts b/packages/plugins/plugin-security/src/security-plugin.ts index f6d7ebef6f0..c8aee736f04 100644 --- a/packages/plugins/plugin-security/src/security-plugin.ts +++ b/packages/plugins/plugin-security/src/security-plugin.ts @@ -2474,7 +2474,7 @@ export class SecurityPlugin implements Plugin { // ADR-0094 write-through, which TRANSLATES them into a metadata write. // Whether that write is ACCEPTED is ADR-0005's call, not this gate's, // and since ADR-0094 D5-R (commit ee58392e1 rolled `permission` back to - // `allowOrgOverride: false`) a CODE-DECLARED set is refused there with + // `allowEnvironmentOverlay: false`) a CODE-DECLARED set is refused there with // 403 `NOT_OVERRIDABLE` — the 2026-07-14 "customize / reset via an env // overlay" direction this comment used to state is RETIRED. // Placed BEFORE the principal-less refusal and the CRUD check so the @@ -7136,7 +7136,7 @@ export class SecurityPlugin implements Plugin { // refused HERE: the write-through middleware (which runs after this gate // + the delegated-admin gate + the CRUD checks) translates them into a // metadata write, and the refusal is LEFT TO THAT PRODUCER. Since commit - // ee58392e1 rolled `permission` back to `allowOrgOverride: false`, a + // ee58392e1 rolled `permission` back to `allowEnvironmentOverlay: false`, a // CODE-DECLARED (artifact-backed) set is refused there with 403 // `NOT_OVERRIDABLE`; a `sys_metadata`-backed set rides // `allowRuntimeCreate` and still lands. The 2026-07-14 "customize / reset diff --git a/packages/rest/src/rest-server.ts b/packages/rest/src/rest-server.ts index a9372ee5aa3..7e84e818708 100644 --- a/packages/rest/src/rest-server.ts +++ b/packages/rest/src/rest-server.ts @@ -7065,8 +7065,10 @@ export class RestServer { // door admits lands environment-wide (`organization_id` // NULL), its audit row included, whatever the caller's // active organization. Until this stage the door threaded - // the active organization for the five types the registry - // declared `allowOrgOverride`; a single-posture Studio save + // the active organization for the five types whose registry + // flag opened an overlay (then spelled `allowOrgOverride`, + // `allowEnvironmentOverlay` since 18, which decides no + // organization); a single-posture Studio save // of those types therefore sits under the Default // Organization, and is not served again until ADR-0131 C7 // promotes it to the environment layer. diff --git a/packages/runtime/src/domains/mcp.ts b/packages/runtime/src/domains/mcp.ts index efb29cdf8ca..23a671e51d5 100644 --- a/packages/runtime/src/domains/mcp.ts +++ b/packages/runtime/src/domains/mcp.ts @@ -381,10 +381,10 @@ export type McpMergedMetadataRead = Pick; * "Degradation log levels", a failure handed to the CALLER is not a * degradation at all — the requester was told — so this branch logs nothing. * - * `organizationId` is deliberately NOT threaded: `skill` is declared - * `allowOrgOverride: false`, so the runtime write path lands its rows env-wide - * and `getMetaItems` reads env-wide rows unconditionally. Passing the active - * org would ask for a per-org overlay this type has no write channel for. + * `organizationId` is deliberately NOT threaded: every metadata write lands + * env-wide (ADR-0131 D6), and `getMetaItems` reads env-wide rows + * unconditionally. Passing the active org would ask for a per-organization + * overlay no metadata type has a write channel for. */ async function readMergedSkillRows( deps: DomainHandlerDeps, diff --git a/packages/runtime/src/domains/meta.ts b/packages/runtime/src/domains/meta.ts index 513c7aa0667..1d7796054da 100644 --- a/packages/runtime/src/domains/meta.ts +++ b/packages/runtime/src/domains/meta.ts @@ -943,7 +943,7 @@ export async function handleMetadataRequest(deps: DomainHandlerDeps, path: strin if (parts[0] === 'types') { // PRIORITY 1: Try protocol service — it returns BOTH legacy // `types: string[]` AND the richer `entries` array (with - // JSON Schemas, allowOrgOverride flags, domain, etc) needed by + // JSON Schemas, allowEnvironmentOverlay flags, domain, etc) needed by // the metadata admin UI. It internally also merges // MetadataService runtime types, so this path is strictly richer. const protocol = await resolveProtocol(deps, _context); diff --git a/packages/runtime/src/domains/packages.ts b/packages/runtime/src/domains/packages.ts index c854d1ee036..918f0cb305f 100644 --- a/packages/runtime/src/domains/packages.ts +++ b/packages/runtime/src/domains/packages.ts @@ -1538,9 +1538,9 @@ export async function handlePackagesRequest(deps: DomainHandlerDeps, path: strin // producer-side rename card (option B), not a rider on this // one. // - // [#7018 / the #6190 ruling, Option A] `app` declares - // `allowOrgOverride: false`, so this flip carries no - // organization — it lands env-wide, on the + // [#7018 / the #6190 ruling, Option A] this flip carries + // no organization — every metadata write lands env-wide + // (ADR-0131 D6), on the // very row boot hydrates and the App Switcher reads. An // org-scoped flip was a phantom: the app looked published for // the life of the process and went back to `_unpublished: diff --git a/packages/services/service-automation/src/sys-flow-credential.object.ts b/packages/services/service-automation/src/sys-flow-credential.object.ts index 31501f8d80f..daa0672b0b4 100644 --- a/packages/services/service-automation/src/sys-flow-credential.object.ts +++ b/packages/services/service-automation/src/sys-flow-credential.object.ts @@ -30,8 +30,8 @@ import { ObjectSchema, Field } from '@objectstack/spec/data'; * provider), masks it on every read path, and only the privileged * `resolveSecretField()` dereferences it, at verification and execution time. * - * Env-wide, like the engine's flow map, which keys flows by bare name: a flow - * declares `allowOrgOverride: false`, so its stored rows are env-wide too. + * Env-wide, like the engine's flow map, which keys flows by bare name: every + * metadata row is env-wide (ADR-0131 D6), a flow's stored rows included. * * Writers: the automation plugin's credential channel (the metadata save door, * the publish promotion, a delete, the one-time migration), under a system diff --git a/packages/spec/src/api/metadata.zod.ts b/packages/spec/src/api/metadata.zod.ts index 4935a217628..8e2a65c15fe 100644 --- a/packages/spec/src/api/metadata.zod.ts +++ b/packages/spec/src/api/metadata.zod.ts @@ -299,7 +299,7 @@ export const MetadataTypeInfoResponseSchema = lazySchema(() => BaseResponseSchem label: z.string().describe('Display label'), description: z.string().optional().describe('Description'), filePatterns: z.array(z.string()).describe('File glob patterns'), - supportsOverlay: z.boolean().describe('Overlay support'), + supportsOverlay: z.boolean().describe('Whether a stored overlay row of an item a managed package ships may be removed through the ordinary delete door without the type\'s environment-overlay permission, because removing it restores the managed definition; it opens no save and it decides no read'), domain: z.string().describe('Protocol domain'), actions: z.array(ActionSchema).optional().describe('Declarative type-level actions (buttons) the metadata-admin UI renders for this type'), }).optional().describe('Type info'), diff --git a/packages/spec/src/api/protocol.zod.ts b/packages/spec/src/api/protocol.zod.ts index 82f15e35348..765f086389c 100644 --- a/packages/spec/src/api/protocol.zod.ts +++ b/packages/spec/src/api/protocol.zod.ts @@ -207,14 +207,14 @@ export const GetMetaTypesResponseSchema = lazySchema(() => z.object({ label: z.string().describe('Human-readable label'), description: z.string().optional().describe('Brief description'), filePatterns: z.array(z.string()).describe('Glob patterns used to discover artifacts of this type'), - supportsOverlay: z.boolean().describe('Loader can merge per-org overlays on top of artifact'), - allowOrgOverride: z.boolean().describe('Per-org overlay writes accepted at runtime (may be env-elevated)'), + supportsOverlay: z.boolean().describe('Whether a stored overlay row of an item a managed package ships may be removed through the ordinary delete door without the type\'s environment-overlay permission, because removing it restores the managed definition; it opens no save and it decides no read'), + allowEnvironmentOverlay: z.boolean().describe('True when the registry grants environment overlays of this type\'s packaged items, or when OS_METADATA_WRITABLE names the type (overrideSource env), which opens the type\'s writes of items no managed package ships and never a managed item (ADR-0131 D6)'), allowRuntimeCreate: z.boolean().describe('New artifacts of this type can be created via runtime API'), supportsVersioning: z.boolean().describe('History is tracked for this type'), executionPinned: z.boolean().describe('Runtime transactions pin a specific historical version_hash (ADR-0009)'), loadOrder: z.number().int().describe('Loading priority (lower = earlier)'), domain: z.enum(['data', 'ui', 'automation', 'system', 'security', 'ai']).describe('Protocol domain'), - overrideSource: z.enum(['registry', 'env']).describe('Whether allowOrgOverride is set in the static registry or via OS_METADATA_WRITABLE env var'), + overrideSource: z.enum(['registry', 'env']).describe('Whether allowEnvironmentOverlay comes from the static registry or is elevated by the OS_METADATA_WRITABLE environment variable'), createSeed: z.unknown().optional().describe('Authoritative minimal valid create seed for this type — Studio/CLI/API derive create defaults from it (single source of truth in @objectstack/spec). Absent for canvas-create types whose shape is built interactively.'), })).optional().describe('Enriched per-type registry entries (Phase 3a)'), })); @@ -1603,7 +1603,7 @@ export const HistoryMetaItemRequestSchema = lazySchema(() => z.object({ * carries no spelling regex. * * `{ events: [] }` is the honest answer for a genuinely empty change log AND - * for a non-overlay metadata type (neither `allowOrgOverride` nor + * for a non-overlay metadata type (neither `allowEnvironmentOverlay` nor * `allowRuntimeCreate`): such types have no history by construction — * `saveMetaItem` refuses them outright (#5086) — and the implementation * answers `[]` rather than throwing so callers can treat "no history" diff --git a/packages/spec/src/kernel/index.ts b/packages/spec/src/kernel/index.ts index 1e432827aa0..919d7090a10 100644 --- a/packages/spec/src/kernel/index.ts +++ b/packages/spec/src/kernel/index.ts @@ -27,9 +27,10 @@ export * from './manifest.zod'; // 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). +// REAL customization mechanisms are ADR-0005's environment overlay +// (`allowEnvironmentOverlay` on DEFAULT_METADATA_TYPE_REGISTRY, environment-wide +// sys_metadata rows — ADR-0131 D6 retired the per-organization axis) 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-create-seeds.ts b/packages/spec/src/kernel/metadata-create-seeds.ts index 38828bd2447..7f610474050 100644 --- a/packages/spec/src/kernel/metadata-create-seeds.ts +++ b/packages/spec/src/kernel/metadata-create-seeds.ts @@ -138,7 +138,7 @@ const BUILTIN_METADATA_CREATE_SEEDS: Partial> = { tools: [], }, // [#5488] `api` HAD a create seed here (#5271). It was REMOVED when `api` - // became code-only (`allowRuntimeCreate: false` + `allowOrgOverride: false`, + // became code-only (`allowRuntimeCreate: false` + `allowEnvironmentOverlay: false`, // maintainer ruling 2026-08-07T16:59Z): a create seed exists to make a // runtime CREATE round-trip, and there is no longer a runtime create surface // for it to seed — the #5086 inlet refuses `PUT /api/v1/meta/api/:name` with diff --git a/packages/spec/src/kernel/metadata-plugin.zod.ts b/packages/spec/src/kernel/metadata-plugin.zod.ts index 78c1b68d3e9..4d07fc8d880 100644 --- a/packages/spec/src/kernel/metadata-plugin.zod.ts +++ b/packages/spec/src/kernel/metadata-plugin.zod.ts @@ -247,25 +247,77 @@ const MetadataTypeRegistryEntryBaseSchema = z.object({ filePatterns: z.array(z.string()).describe('Glob patterns to discover files of this type'), /** - * Whether this type supports the customization overlay system. - * When true, platform/user overlays can be applied on top of package-delivered metadata. + * Whether a stored overlay row of an item a managed package ships may be + * REMOVED through the ordinary delete door without the type's + * environment-overlay permission ({@link allowEnvironmentOverlay}), because + * removing it restores the managed definition (the repair carve-out for + * overlay rows stored before a type's permission was rolled back). + * + * It opens no save and it decides no read: whether an environment overlay of + * a packaged item is written or served is decided by + * `allowEnvironmentOverlay` alone (ADR-0131 D6). Its readers are the delete + * tier of the metadata protocol and the repository's delete gate. + */ + supportsOverlay: z.boolean().default(true).describe( + 'Whether a stored overlay row of an item a managed package ships may be removed through the ' + + 'ordinary delete door without the type\'s environment-overlay permission, because removing it ' + + 'restores the managed definition; it opens no save and it decides no read' + ), + + /** + * Whether an environment may overlay an item of this type that a managed + * package ships — ADR-0131 D6 regime O, ADR-0005's environment layer: an + * environment row of the same name replaces the packaged item whole. + * + * This is the single decider of that question at every door: the metadata + * protocol's save door, the managed-content seal on write AND on read (a + * stored environment overlay of a packaged item of a type without it is + * withheld from the served read), the repository's put gate, and Studio's + * write gate (`GET /api/v1/meta/types` carries it per entry). The + * `OS_METADATA_WRITABLE` hatch elevates the advertised value but never + * opens a managed item. Defaults to `false`: a type opts in deliberately, + * and a write against a packaged item of a type without it is refused with + * HTTP 403 `NOT_OVERRIDABLE`. Which types opt in is + * `DEFAULT_METADATA_TYPE_REGISTRY` below, and only there (ADR-0005: no + * parallel whitelists). + * + * Renamed from `allowOrgOverride` in 18: ADR-0131 D6 retired the + * per-organization overlay axis, and the key now names the one scope it + * governs. */ - supportsOverlay: z.boolean().default(true).describe('Whether overlay customization is supported'), + allowEnvironmentOverlay: z.boolean().default(false).describe( + 'Whether an environment may overlay an item of this type that a managed package ships ' + + '(ADR-0131 D6 regime O). Decides the save door, the managed-content seal on write and on ' + + 'read, and Studio\'s write gate; the OS_METADATA_WRITABLE hatch does not reach a managed ' + + 'item. Default false.' + ), /** - * Whether end-user organizations may write per-org overlay rows for this - * type via the runtime metadata API (`PUT /api/v1/meta/:type/:name`). + * RENAMED in 18 to {@link allowEnvironmentOverlay} — tombstoned, not deleted. * - * This is the **runtime opt-in gate** for ADR-0005 metadata customization. - * `supportsOverlay` describes the *capability* (the loader can merge overlays); - * `allowOrgOverride` is the explicit *permission* to actually accept writes - * from a tenant. Defaults to `false` for safety — plugin authors must - * deliberately opt in. The runtime returns HTTP 403 `not_overridable` when - * a write targets a type with this flag unset. + * ADR-0131 D6 retired the per-organization overlay axis, so the key that + * said "per-org" was renamed to say the one scope it governs (ruling C on + * the ADR-0131 C5 split: no second key, and not the old name with a new + * meaning). `MetadataTypeRegistryEntryBaseSchema` is a plain `z.object`, + * so a bare deletion would strip an authored old key in silence (the + * ADR-0104 shape); the tombstone refuses it at `tsc` and at the parse with + * the rename. No D2 conversion: no manifest, stack, plugin configuration or + * stored row carries a registry entry — the registry is a TypeScript + * literal — so a conversion would have no seam that ever runs. Registered + * in `RETIRED_KEYS_BY_MAJOR[18]` with the D3 semantic entry + * `metadata-type-registry-allow-org-override-renamed`. * - * As of ADR-0005 Phase 1, only `view` and `dashboard` opt in. + * ⚠️ A truthiness READER of the old key still compiles against this + * tombstone (the output is an optional `undefined`) and silently reads + * `undefined`, which closes every type. Read `allowEnvironmentOverlay`. */ - allowOrgOverride: z.boolean().default(false).describe('Allow per-org overlay writes via runtime metadata API'), + allowOrgOverride: retiredKey( + '`MetadataTypeRegistryEntry.allowOrgOverride` was renamed to `allowEnvironmentOverlay` in ' + + '@objectstack/spec 18 (ADR-0131 D6) — the per-organization overlay axis was retired, and ' + + 'the key now names the one scope it governs: whether an environment may overlay an item ' + + 'of this type that a managed package ships. Rename the key to `allowEnvironmentOverlay`; ' + + 'the value is unchanged.', + ), /** * Whether metadata of this type can be created at runtime via API. @@ -516,9 +568,10 @@ export const MetadataPluginConfigSchema = lazySchema(() => z.object({ '(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).", + "may touch is governed by the real mechanisms: ADR-0005's environment overlay (opt-in via " + + '`allowEnvironmentOverlay` on `DEFAULT_METADATA_TYPE_REGISTRY`, enforced at the metadata ' + + "protocol's write doors and its served read) and ADR-0126's packaged-metadata model (clone + " + + 'ledger disable).', ), /** @@ -711,11 +764,15 @@ export type MetadataPluginManifestParsed = z.infer> = // to continue "…and `/meta/types` emits a real JSON Schema so the // metadata-admin engine renders a form instead of a raw-JSON textarea". // That is no longer the point of the binding: `api` is CODE-ONLY as of #5488 - // (`allowRuntimeCreate: false` + `allowOrgOverride: false`, maintainer ruling + // (`allowRuntimeCreate: false` + `allowEnvironmentOverlay: false`, maintainer ruling // 2026-08-07), because a runtime-created endpoint was never served — the // matcher reads `listForIndex('api')`, and a runtime write lands in // `sys_metadata`, which it does not read. So `PUT /meta/api/:name` is refused 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 index 9b1abacb129..1c9456c59f7 100644 --- 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 @@ -10,8 +10,8 @@ // 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. +// customization may touch is governed by ADR-0005's environment overlay +// (`allowEnvironmentOverlay`) 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 diff --git a/packages/spec/src/migrations/entries/retired-keys/18.kernel__MetadataTypeRegistryEntry__allowOrgOverride.ts b/packages/spec/src/migrations/entries/retired-keys/18.kernel__MetadataTypeRegistryEntry__allowOrgOverride.ts new file mode 100644 index 00000000000..cef7146e044 --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-keys/18.kernel__MetadataTypeRegistryEntry__allowOrgOverride.ts @@ -0,0 +1,22 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// ADR-0131 C5's spec half — ruling C on the C5 split (rename the key to say +// what it now means; no second key, and not the old name with a new meaning) +// and the maintainer's ruling on its mechanism (Q1 A: a tombstone, this +// entry and a D3 semantic entry; no D2 conversion). ADR-0131 D6 retired the +// per-organization overlay axis, so the registry flag that said "per-org" was +// renamed to `allowEnvironmentOverlay`: whether an environment may overlay an +// item of the type that a managed package ships, on write, on read and in +// Studio. The value is unchanged on every one of the 28 built-in entries. +// +// Tombstoned with `retiredKey()`: `MetadataTypeRegistryEntryBaseSchema` is a +// plain `z.object`, so a bare deletion would strip an authored old key in +// silence. No D2 conversion: no manifest, stack, plugin configuration or +// stored row can carry a type-registry entry — `contributes.kinds` is closed, +// `config.additionalTypes` is a tombstone, and the registry is the TypeScript +// literal `DEFAULT_METADATA_TYPE_REGISTRY` — so a conversion would be a +// transform with no seam that ever runs (the `additionalTypes` precedent). +// The prescription reaches authors through the tombstone (`tsc` + the parse) +// and the D3 semantic entry `metadata-type-registry-allow-org-override-renamed`, +// which also records the `GET /meta/types` wire field that moved with it. +export const entry = 'kernel/MetadataTypeRegistryEntry:allowOrgOverride'; 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 index eb361eec539..cc7b41c2802 100644 --- 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 @@ -17,9 +17,9 @@ export const entry: SemanticMigration = { + '`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 ' + + 'actually ship: ADR-0005\'s environment overlay (opt-in via `allowEnvironmentOverlay` on ' + + '`DEFAULT_METADATA_TYPE_REGISTRY`, stored as environment-wide `sys_metadata` rows, written ' + + 'through the metadata protocol\'s 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)', @@ -50,6 +50,6 @@ export const entry: SemanticMigration = { + '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.', + + 'behaviour — the ADR-0005 overlay read/write path (`getMetaItemLayered`, the metadata ' + + 'write doors) stays exactly as it was, before and after.', }; diff --git a/packages/spec/src/migrations/entries/semantic/18.metadata-type-registry-allow-org-override-renamed.ts b/packages/spec/src/migrations/entries/semantic/18.metadata-type-registry-allow-org-override-renamed.ts new file mode 100644 index 00000000000..d381db820bf --- /dev/null +++ b/packages/spec/src/migrations/entries/semantic/18.metadata-type-registry-allow-org-override-renamed.ts @@ -0,0 +1,55 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +import type { SemanticMigration } from '../../types.js'; + +// ADR-0131 C5's spec half. The registry flag and the wire field that projects +// it move together, in one landing (the maintainer's ruling: no dual spelling +// on the wire), so this one entry records both. The key's own retired-keys +// entry is `kernel/MetadataTypeRegistryEntry:allowOrgOverride`. +export const entry: SemanticMigration = { + id: 'metadata-type-registry-allow-org-override-renamed', + // No backticks in `surface` — build-upgrade-guide.ts renders it inside a + // code span AND a table cell. + surface: + 'MetadataTypeRegistryEntry.allowOrgOverride (DEFAULT_METADATA_TYPE_REGISTRY, ' + + 'kernel/metadata-plugin.zod.ts) / the allowOrgOverride field of every entries[] item of the ' + + 'GET /api/v1/meta/types response (GetMetaTypesResponse, api/protocol.zod.ts)', + replacement: + '`allowEnvironmentOverlay` on both — rename the key in a registry entry and read the new ' + + 'field from `GET /api/v1/meta/types`; every value is unchanged (`true` on `view`, ' + + '`dashboard`, `report`, `translation` and `email_template`, `false` on every other built-in ' + + 'type), and `overrideSource` keeps its `registry` / `env` values and now names the new field', + reason: + 'ADR-0131 D6 retired the per-organization overlay axis: an organization-scoped metadata ' + + 'write is refused and every metadata row is environment-wide. The flag kept its old name ' + + 'and so said "per-org" while it decided one environment-scope question at every door — ' + + 'whether an environment may overlay an item of the type that a managed package ships: the ' + + 'metadata protocol\'s save door, the managed-content seal on write and on the served read ' + + '(a stored environment overlay of a packaged item of a type without the flag is withheld), ' + + 'the repository put gate, and Studio\'s write gate through the wire field. Renamed to say ' + + 'that, as a hard rename: no second key, not the old name with a new meaning, and no window ' + + 'in which both spellings are accepted or served. Why no D2 conversion: no manifest, stack, ' + + 'plugin configuration or stored row can carry a type-registry entry (`contributes.kinds` is ' + + 'closed, `config.additionalTypes` is a tombstone, and the registry is the TypeScript literal ' + + '`DEFAULT_METADATA_TYPE_REGISTRY`), so a conversion would be a transform with no seam that ' + + 'ever runs; and a response field has no source a conversion could rewrite. What the ' + + 'consumer still owes is the READ side: a truthiness reader of the old key ' + + '(`if (!entry.allowOrgOverride)`, a filter, a partial-match assertion) still compiles ' + + 'against the tombstone and reads `undefined`, which treats every type as closed to ' + + 'environment overlays; and a client that declares its own type for the `/meta/types` ' + + 'response instead of importing `GetMetaTypesResponse` sees no compile error at all, and ' + + 'reads every type as locked. The value the wire carries is also unchanged in meaning: it ' + + 'is `true` when the registry grants the overlay OR when `OS_METADATA_WRITABLE` names the ' + + 'type (`overrideSource: env`), and the hatch opens only items no managed package ships, ' + + 'never a managed one.', + acceptanceCriteria: + 'No registry entry, fixture or reader spells `allowOrgOverride`: writing it fails to compile ' + + '(its input type is `never`) and fails to parse with the rename prescription at path ' + + '`allowOrgOverride` (issue code `invalid_type`); every reader of the flag reads ' + + '`allowEnvironmentOverlay`. `GET /api/v1/meta/types` carries `allowEnvironmentOverlay` on ' + + 'every entry, a registry type and a registry-less runtime type alike, and no entry carries ' + + '`allowOrgOverride`. Behaviour is unchanged and must be verified as such: the five types ' + + 'that opted in still accept an environment overlay of a packaged item and still serve it, ' + + 'every other type still refuses one with 403 `NOT_OVERRIDABLE`, and Studio still opens ' + + 'exactly the same editors.', +}; diff --git a/packages/spec/src/migrations/registry.ts.template b/packages/spec/src/migrations/registry.ts.template index e11bbaf60af..d9d7edeeb58 100644 --- a/packages/spec/src/migrations/registry.ts.template +++ b/packages/spec/src/migrations/registry.ts.template @@ -2234,7 +2234,7 @@ const STEP18_RATIONALE: readonly RationaleFragment[] = [ + '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 ' + + 'customization that actually ships: ADR-0005\'s environment overlay and ADR-0126\'s ' + 'packaged-metadata model.', }, { @@ -2261,6 +2261,20 @@ const STEP18_RATIONALE: readonly RationaleFragment[] = [ + 'authoring it configured nothing. A kind enters the live set as a side effect of ' + 'registering an item of that kind.', }, + { + id: 'metadata-type-registry-allow-org-override-renamed', + order: 94, + text: + 'It also renames the metadata type registry\'s `allowOrgOverride` to ' + + '`allowEnvironmentOverlay`, on `DEFAULT_METADATA_TYPE_REGISTRY` and on the ' + + '`GET /meta/types` wire field that projects it, in one landing and with no window in ' + + 'which both spellings are accepted or served: ADR-0131 D6 retired the per-organization ' + + 'overlay axis, and the flag now decides one environment-scope question at every door — ' + + 'whether an environment may overlay an item a managed package ships. The old key is a ' + + 'retiredKey tombstone registered in RETIRED_KEYS_BY_MAJOR[18] with one D3 semantic entry ' + + 'and no D2 conversion: no manifest, stack or stored row can carry a registry entry. Every ' + + 'value is unchanged.', + }, { id: 'object-grid-default-filters-retired', order: 90, diff --git a/packages/spec/src/system/email-template.zod.ts b/packages/spec/src/system/email-template.zod.ts index 915c2e31267..0b835dfcdb1 100644 --- a/packages/spec/src/system/email-template.zod.ts +++ b/packages/spec/src/system/email-template.zod.ts @@ -12,8 +12,8 @@ import { strictObject } from '../shared/strict-object'; * Declarative template definition consumed by `IEmailService.sendTemplate()` * to render outbound mail. Persisted as rows of `sys_email_template` so * administrators can author/edit/translate templates in Studio without - * shipping code, and tenants can override the built-in defaults - * (`allowOrgOverride: true` in the metadata registry). + * shipping code, and an environment can overlay the packaged defaults + * (`allowEnvironmentOverlay: true` in the metadata registry). * * Aligned with Salesforce `EmailTemplate` and ServiceNow * `sysevent_email_action` conventions: a single named template is From 778aa6062587050f2ae2a552d8bd5537319e8bd0 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 11 Oct 2026 13:42:10 +0000 Subject: [PATCH 02/15] test: rename the registry flag across the suites; pin the tombstone, the rows, the wire and the read-side absence Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude --- ...protocol-publish-drafts-advisories.test.ts | 2 +- .../protocol-publish-drafts-org-scope.test.ts | 5 +- ...ocol.adr0005-org-override-rollback.test.ts | 17 ++- .../protocol.capability-write-door.test.ts | 2 +- .../src/protocol.code-only-types.test.ts | 10 +- .../protocol.delete-receipt-wording.test.ts | 10 +- .../protocol.delete-rewrap-envelope.test.ts | 6 +- ...l.destructive-gate-reachable-types.test.ts | 2 +- .../protocol.diff-dead-history-read.test.ts | 2 +- .../protocol.driver-text-disclosure.test.ts | 6 +- .../protocol.flow-org-override-closed.test.ts | 16 +- .../protocol.legacy-overlay-delete.test.ts | 16 +- .../src/protocol.lock-org-axis-agree.test.ts | 5 +- ...ocol.marked-refusal-classification.test.ts | 2 +- .../protocol.org-scoped-write-refused.test.ts | 15 +- ...protocol.package-door-before-gates.test.ts | 6 +- .../protocol.packaged-base-refusal.test.ts | 12 +- ...tocol.publish-item-draft-org-scope.test.ts | 7 +- ...rotocol.read-lock-flags-write-door.test.ts | 4 +- .../protocol.read-verb-canonical-fold.test.ts | 6 +- ...tocol.recovery-doors-emit-mutation.test.ts | 2 +- ...tocol.runtime-gate-stored-universe.test.ts | 2 +- .../src/protocol.save-receipt-wording.test.ts | 6 +- .../src/protocol.sealed-remedy-kind.test.ts | 2 +- .../src/protocol.stored-migration.test.ts | 2 +- .../protocol.unrecognised-meta-type.test.ts | 2 +- .../sys-metadata-repository.contract.test.ts | 6 +- ...ata-repository.package-writability.test.ts | 18 +-- ...gine-security-catalog-package-door.test.ts | 18 +-- .../environment-overlay-five-types.test.ts | 17 ++- ...-object-search-companion-roundtrip.test.ts | 2 +- .../objectql/src/overlay-precedence.test.ts | 32 ++-- .../src/protocol-commit-history.test.ts | 6 +- ...otocol-delete-object-registry-heal.test.ts | 2 +- .../protocol-meta-effective-schema.test.ts | 2 +- .../src/protocol-meta-types-rich.test.ts | 80 ++++++++-- packages/objectql/src/protocol-meta.test.ts | 18 ++- .../src/protocol-object-overlay-layer.test.ts | 4 +- ...protocol-org-overlay-registry-gate.test.ts | 16 +- .../protocol-publish-canonical-fold.test.ts | 2 +- .../src/protocol-registry-shadow.test.ts | 4 +- ...rotocol-writepath-object-ownership.test.ts | 2 +- .../publish-meta-response-conformance.test.ts | 4 +- ...ackage-drafts-response-conformance.test.ts | 2 +- .../save-meta-response-conformance.test.ts | 8 +- .../src/sys-metadata-repository.test.ts | 8 +- .../src/audit/sys-job.global-unique.test.ts | 13 +- ...ackaged-permission-set-restore-leg.test.ts | 11 +- .../src/permission-set-projection.test.ts | 10 +- .../src/permission-set-projection.ts | 4 +- .../managed-content-sealed.dogfood.test.ts | 2 +- ...case-permission-projection.dogfood.test.ts | 6 +- .../test/two-doors-permission.dogfood.test.ts | 6 +- .../rest/src/execctx-consumer-census.test.ts | 9 +- .../src/meta-publish-package-scope.test.ts | 2 +- .../src/rest-meta-managed-seal-hatch.test.ts | 6 +- .../rest-meta-packaged-flow-refusal.test.ts | 2 +- ...server-meta-cached-etag-door-scope.test.ts | 4 +- ...server-meta-history-diff-org-scope.test.ts | 12 +- ...server-meta-org-scope-url-spelling.test.ts | 6 +- .../rest-server-meta-read-org-scope.test.ts | 16 +- .../rest-server-meta-write-org-scope.test.ts | 4 +- .../meta-read-org-scope-parity.test.ts | 18 ++- .../packages-seed-apply-org-scope.test.ts | 10 +- .../src/meta-field-overlay-lock.test.ts | 8 +- .../src/meta-managed-content-seal.test.ts | 6 +- .../src/meta-overlay-read-your-writes.test.ts | 4 +- .../runtime/src/meta-write-org-scope.test.ts | 22 +-- ...licate-adopt-org-scope.integration.test.ts | 4 +- packages/spec/authorable-defaults/kernel.json | 2 +- packages/spec/authorable-surface/kernel.json | 3 +- packages/spec/src/data/picklist.test.ts | 2 +- .../kernel/capability-metadata-kind.test.ts | 6 +- .../src/kernel/metadata-create-seeds.test.ts | 4 +- .../metadata-customization-retirement.test.ts | 2 +- .../metadata-type-api-registration.test.ts | 10 +- .../metadata-type-field-registration.test.ts | 6 +- ...egistry-allow-org-override-absence.test.ts | 140 ++++++++++++++++++ ...stry-allow-org-override-retirement.test.ts | 97 ++++++++++++ packages/spec/vitest.repo-tests.json | 1 + 80 files changed, 606 insertions(+), 270 deletions(-) create mode 100644 packages/spec/src/kernel/metadata-type-registry-allow-org-override-absence.test.ts create mode 100644 packages/spec/src/kernel/metadata-type-registry-allow-org-override-retirement.test.ts diff --git a/packages/metadata-protocol/src/protocol-publish-drafts-advisories.test.ts b/packages/metadata-protocol/src/protocol-publish-drafts-advisories.test.ts index 64bdc37b62f..2c95a51e4e5 100644 --- a/packages/metadata-protocol/src/protocol-publish-drafts-advisories.test.ts +++ b/packages/metadata-protocol/src/protocol-publish-drafts-advisories.test.ts @@ -29,7 +29,7 @@ * Harness: the same faithful stub engine as * `protocol-publish-drafts-org-scope.test.ts` (kept local — self-contained * harnesses are the established shape here, so two tripwires can fail - * independently). Flows are env-wide (`flow` is `allowOrgOverride: false`), + * independently). Flows are env-wide (every metadata row is, ADR-0131 D6), * saved as package-bound drafts, published through the REAL * `publishPackageDrafts` — nothing on the gate path is stubbed. */ diff --git a/packages/metadata-protocol/src/protocol-publish-drafts-org-scope.test.ts b/packages/metadata-protocol/src/protocol-publish-drafts-org-scope.test.ts index 4f14f4a8908..427356d847c 100644 --- a/packages/metadata-protocol/src/protocol-publish-drafts-org-scope.test.ts +++ b/packages/metadata-protocol/src/protocol-publish-drafts-org-scope.test.ts @@ -195,8 +195,9 @@ const objectBody = (name: string) => ({ }, }); -/** [#6190] The org-scoped specimen — `view` is `allowOrgOverride: true`, so it - * is a type that legitimately carries per-org rows. See the third case. */ +/** [#6190] The org-scoped specimen — `view`, one of the five overlay-enabled + * types, was a type that legitimately carried per-org rows before ADR-0131 + * D6. See the third case. */ const viewBody = (name: string) => ({ name, label: 'Project Tasks', diff --git a/packages/metadata-protocol/src/protocol.adr0005-org-override-rollback.test.ts b/packages/metadata-protocol/src/protocol.adr0005-org-override-rollback.test.ts index c9f3c61b2cf..10b2f347e0a 100644 --- a/packages/metadata-protocol/src/protocol.adr0005-org-override-rollback.test.ts +++ b/packages/metadata-protocol/src/protocol.adr0005-org-override-rollback.test.ts @@ -4,7 +4,7 @@ * Commit ee58392e1 — the nine ADR-0005 whitelist divergences, rolled back. The WRITE * PATH now says what the ADR's amendment table has said since 2026-05-22. * - * `DEFAULT_METADATA_TYPE_REGISTRY` carried `allowOrgOverride: true` on nine + * `DEFAULT_METADATA_TYPE_REGISTRY` carried `allowEnvironmentOverlay: true` on nine * rows the ADR-0005 tenant-customizable whitelist * (`docs/adr/0005-metadata-customization-overlay.md:53-64`) never granted: * @@ -17,7 +17,8 @@ * model providers; treat like flows." * • `dataset` / `book` / `position` — absent from the table entirely, and * the amendment's closing rule assigns absentees their default: "The - * default for any new metadata type is `allowOrgOverride: false`", + * default for any new metadata type is `allowOrgOverride: false`" (the + * ADR's spelling; the key is `allowEnvironmentOverlay` since 18), * admission requiring (a) an overlay schema `resolveOverlaySchema()` * accepts and (b) a WRITTEN render-only rationale. (a) holds for every * registered type since the schema registry unified @@ -82,7 +83,7 @@ * --------------------------------------------------------------------------- * Reverse verification, direction predicted BEFORE running * --------------------------------------------------------------------------- - * Restoring `allowOrgOverride: true` on any single rolled-back row (the run + * Restoring `allowEnvironmentOverlay: true` on any single rolled-back row (the run * recorded below used `skill`) must turn that type's declaration pin, its * derived-set pin, and its two refusal cases red, while every other type's * cases and the `view`/`dashboard`/`report` controls stay green — the @@ -218,7 +219,7 @@ const VIEW = { const entry = (type: string) => DEFAULT_METADATA_TYPE_REGISTRY.find((e) => e.type === type); -describe('#6483 — the nine ADR-0005 divergences: allowOrgOverride rolled back to false', () => { +describe('#6483 — the nine ADR-0005 divergences: allowEnvironmentOverlay rolled back to false', () => { // ── the declaration ─────────────────────────────────────────────────── it.each(ROLLED_BACK)('the registry declares %s non-overridable, runtime-create untouched', (type) => { @@ -226,7 +227,7 @@ describe('#6483 — the nine ADR-0005 divergences: allowOrgOverride rolled back // as the flag going false: the rollback closes per-org shadowing of // PACKAGED items, not runtime authoring of new ones. expect(entry(type)).toMatchObject({ - allowOrgOverride: false, + allowEnvironmentOverlay: false, allowRuntimeCreate: true, }); }); @@ -235,12 +236,12 @@ describe('#6483 — the nine ADR-0005 divergences: allowOrgOverride rolled back // Same self-contradiction #6283 rolled back on `flow`: the loader // cannot merge an overlay of this type, so per-org write permission // authorized writes nothing could ever read back (#6190). - expect(entry('action')).toMatchObject({ supportsOverlay: false, allowOrgOverride: false }); + expect(entry('action')).toMatchObject({ supportsOverlay: false, allowEnvironmentOverlay: false }); }); it('the ADR-0005 whitelist controls (view/dashboard/report) are untouched', () => { for (const type of CONTROLS) { - expect(entry(type)).toMatchObject({ supportsOverlay: true, allowOrgOverride: true }); + expect(entry(type)).toMatchObject({ supportsOverlay: true, allowEnvironmentOverlay: true }); } }); @@ -248,7 +249,7 @@ describe('#6483 — the nine ADR-0005 divergences: allowOrgOverride rolled back // Prime Directive #8: `OVERLAY_ALLOWED_TYPES` is DERIVED from this // registry in both `protocol.ts` and `sys-metadata-repository.ts`. const derived = new Set( - DEFAULT_METADATA_TYPE_REGISTRY.filter((e) => e.allowOrgOverride).map((e) => e.type), + DEFAULT_METADATA_TYPE_REGISTRY.filter((e) => e.allowEnvironmentOverlay).map((e) => e.type), ); for (const type of ROLLED_BACK) expect(derived.has(type), type).toBe(false); for (const type of CONTROLS) expect(derived.has(type), type).toBe(true); diff --git a/packages/metadata-protocol/src/protocol.capability-write-door.test.ts b/packages/metadata-protocol/src/protocol.capability-write-door.test.ts index 2a77d6796b3..5c9f7777484 100644 --- a/packages/metadata-protocol/src/protocol.capability-write-door.test.ts +++ b/packages/metadata-protocol/src/protocol.capability-write-door.test.ts @@ -28,7 +28,7 @@ * The three doors, and which one each case is about * --------------------------------------------------------------------------- * `capability` is CODE-ONLY (`allowRuntimeCreate: false` + - * `allowOrgOverride: false`, ADR-0066 D1 — packages DEFINE capabilities). That + * `allowEnvironmentOverlay: false`, ADR-0066 D1 — packages DEFINE capabilities). That * makes the ORDER inside `saveMetaItem` load-bearing, and this file pins the * order rather than asserting a 422 that the real code never reaches: * diff --git a/packages/metadata-protocol/src/protocol.code-only-types.test.ts b/packages/metadata-protocol/src/protocol.code-only-types.test.ts index d17b86cfb53..adf325d1608 100644 --- a/packages/metadata-protocol/src/protocol.code-only-types.test.ts +++ b/packages/metadata-protocol/src/protocol.code-only-types.test.ts @@ -57,7 +57,7 @@ interface Row { * the coverage guard below turns red until it has a probe. */ const CODE_ONLY_TYPES = DEFAULT_METADATA_TYPE_REGISTRY - .filter((e) => !e.allowRuntimeCreate && !e.allowOrgOverride) + .filter((e) => !e.allowRuntimeCreate && !e.allowEnvironmentOverlay) .map((e) => e.type); /** @@ -371,7 +371,7 @@ describe('code-only metadata types are refused on every kernel (#5086)', () => { // ── no over-blocking: types WITHOUT the flags still save ────────────── describe('types the registry does not declare code-only still save', () => { - it('view (allowOrgOverride + allowRuntimeCreate) saves on a single kernel', async () => { + it('view (allowEnvironmentOverlay + allowRuntimeCreate) saves on a single kernel', async () => { const { protocol, rows } = makeProtocol(undefined); const result = await protocol.saveMetaItem({ type: 'view', @@ -388,14 +388,14 @@ describe('code-only metadata types are refused on every kernel (#5086)', () => { expect(metaRows(rows).length).toBe(1); }); - it('hook (allowOrgOverride:false, allowRuntimeCreate:true) still saves on both kernels', async () => { + it('hook (allowEnvironmentOverlay:false, allowRuntimeCreate:true) still saves on both kernels', async () => { // The two-tier model (ADR-0005 PR-10d.7): no artifact at this name // means only `allowRuntimeCreate` is required. This is the case the // #5086 gate must NOT catch — it is the difference between "code-only" // and "packaged items are locked". // [#6190] The per-kernel `organizationId` was incidental scenery — // what this case measures is that the #5086 code-only gate does - // NOT catch a type that merely lacks `allowOrgOverride`. Since the + // NOT catch a type that merely lacks `allowEnvironmentOverlay`. Since the // #6190 ruling an org-scoped write of such a type is refused by a // DIFFERENT gate, so passing one here would have measured that // refusal instead of this one. The two-kernel matrix, which is the @@ -506,7 +506,7 @@ describe('code-only metadata types are refused on every kernel (#5086)', () => { // One savable type per shape the two-tier model distinguishes. const ACCEPTED: Array<{ type: string; item: Record }> = [ { - type: 'view', // allowOrgOverride + allowRuntimeCreate + type: 'view', // allowEnvironmentOverlay + allowRuntimeCreate item: { name: 'rc3_receipt_view', label: 'Receipt', diff --git a/packages/metadata-protocol/src/protocol.delete-receipt-wording.test.ts b/packages/metadata-protocol/src/protocol.delete-receipt-wording.test.ts index e3e4500e851..bb1cb246361 100644 --- a/packages/metadata-protocol/src/protocol.delete-receipt-wording.test.ts +++ b/packages/metadata-protocol/src/protocol.delete-receipt-wording.test.ts @@ -142,7 +142,7 @@ function tenantProtocol(opts: Parameters[0]) { /** * CONTROL-PLANE scope (`environmentId` undefined) — the only scope from which - * a code-only type (`allowOrgOverride: false` AND `allowRuntimeCreate: false`) + * a code-only type (`allowEnvironmentOverlay: false` AND `allowRuntimeCreate: false`) * reaches the legacy raw-engine delete path that owns sentences 3 and 4. See * the block comment on that path in `protocol.ts` (#5264): it is alive on * purpose, and it is the only code that can serve such a delete. @@ -162,9 +162,9 @@ describe('#5927 — a delete receipt names what actually happened', () => { // `view` is both, so it can be either; `job` is neither, which is what // routes it to the legacy path. If any of this flips, these cases must // be re-read rather than repaired. - expect(entry('object')).toMatchObject({ allowRuntimeCreate: true, allowOrgOverride: false }); - expect(entry('view')).toMatchObject({ allowRuntimeCreate: true, allowOrgOverride: true }); - expect(entry('job')).toMatchObject({ allowRuntimeCreate: false, allowOrgOverride: false }); + expect(entry('object')).toMatchObject({ allowRuntimeCreate: true, allowEnvironmentOverlay: false }); + expect(entry('view')).toMatchObject({ allowRuntimeCreate: true, allowEnvironmentOverlay: true }); + expect(entry('job')).toMatchObject({ allowRuntimeCreate: false, allowEnvironmentOverlay: false }); // #6283 → commit ee58392e1 — the overlay-less-yet-overridable specimen the last // case in this file needs. It was `flow` until #6283 rolled that // flag back to `false` (ADR-0005:57), then `action` until commit ee58392e1 @@ -174,7 +174,7 @@ describe('#5927 — a delete receipt names what actually happened', () => { // documented door left. A future registry edit minting a new member // turns this pin red instead of landing an untested pairing. const overlaylessYetOverridable = DEFAULT_METADATA_TYPE_REGISTRY - .filter((e) => e.supportsOverlay === false && e.allowOrgOverride) + .filter((e) => e.supportsOverlay === false && e.allowEnvironmentOverlay) .map((e) => e.type); expect(overlaylessYetOverridable).toEqual([]); }); diff --git a/packages/metadata-protocol/src/protocol.delete-rewrap-envelope.test.ts b/packages/metadata-protocol/src/protocol.delete-rewrap-envelope.test.ts index a15ab136f1a..f05729d6241 100644 --- a/packages/metadata-protocol/src/protocol.delete-rewrap-envelope.test.ts +++ b/packages/metadata-protocol/src/protocol.delete-rewrap-envelope.test.ts @@ -87,7 +87,7 @@ import { isOriginGatedType, packagedBaseRegimeRow } from './packaged-base-regime * control-plane kernel, derived from the registry and never listed by hand * (Prime Directive #8): no read-time overlay merge (`supportsOverlay: false`, * so #6960's carve-out does not reach it) and no per-org write door - * (`allowOrgOverride: false`, so `assertAllowed` refuses), but a runtime write + * (`allowEnvironmentOverlay: false`, so `assertAllowed` refuses), but a runtime write * channel (`allowRuntimeCreate: true`) — which is what puts `useRepoPath` true * and routes the delete into the repository in the first place. * @@ -100,7 +100,7 @@ import { isOriginGatedType, packagedBaseRegimeRow } from './packaged-base-regime * `protocol.code-defined-datasource-door.test.ts`. */ const REPO_REFUSED_TYPES: readonly string[] = DEFAULT_METADATA_TYPE_REGISTRY - .filter((e) => !e.supportsOverlay && !e.allowOrgOverride && e.allowRuntimeCreate) + .filter((e) => !e.supportsOverlay && !e.allowEnvironmentOverlay && e.allowRuntimeCreate) .filter((e) => !isOriginGatedType(e.type)) .map((e) => e.type) .sort(); @@ -112,7 +112,7 @@ const REPO_REFUSED_TYPES: readonly string[] = DEFAULT_METADATA_TYPE_REGISTRY * path ungated on purpose). This is how the second re-wrap exit is reached. */ const CODE_ONLY_TYPES: readonly string[] = DEFAULT_METADATA_TYPE_REGISTRY - .filter((e) => !e.supportsOverlay && !e.allowOrgOverride && !e.allowRuntimeCreate) + .filter((e) => !e.supportsOverlay && !e.allowEnvironmentOverlay && !e.allowRuntimeCreate) .map((e) => e.type) .sort(); diff --git a/packages/metadata-protocol/src/protocol.destructive-gate-reachable-types.test.ts b/packages/metadata-protocol/src/protocol.destructive-gate-reachable-types.test.ts index 4b8754b944d..2fabedc86d1 100644 --- a/packages/metadata-protocol/src/protocol.destructive-gate-reachable-types.test.ts +++ b/packages/metadata-protocol/src/protocol.destructive-gate-reachable-types.test.ts @@ -32,7 +32,7 @@ * every loop in the detector iterates zero times. * - **Reason 2 (§1)** — `field` cannot reach the gate at all under the * default posture. The type is code-only in the `packages/spec` kernel - * registry (`allowRuntimeCreate: false` AND `allowOrgOverride: false`), so + * registry (`allowRuntimeCreate: false` AND `allowEnvironmentOverlay: false`), so * the #5086 refusal one gate up answers first. * * ## ⚠️ Where each reason STOPS — measured, and the reason the trim is right diff --git a/packages/metadata-protocol/src/protocol.diff-dead-history-read.test.ts b/packages/metadata-protocol/src/protocol.diff-dead-history-read.test.ts index c1efb8ae3a1..1f1cfe7c8e3 100644 --- a/packages/metadata-protocol/src/protocol.diff-dead-history-read.test.ts +++ b/packages/metadata-protocol/src/protocol.diff-dead-history-read.test.ts @@ -21,7 +21,7 @@ * is the ordinary-type control. * * ⛔ `earlyReturnFixtureIsStillEarlyReturn` below is the anti-vacuity arm and is - * not decoration. If `field` ever gains `allowOrgOverride` or + * not decoration. If `field` ever gains `allowEnvironmentOverlay` or * `allowRuntimeCreate`, every other assertion here silently stops covering the * early-return case while staying green. That test going red is the signal to * re-pick the fixture from the registry, not to delete the assertion. diff --git a/packages/metadata-protocol/src/protocol.driver-text-disclosure.test.ts b/packages/metadata-protocol/src/protocol.driver-text-disclosure.test.ts index 36a535054f4..bcc11b8c5ca 100644 --- a/packages/metadata-protocol/src/protocol.driver-text-disclosure.test.ts +++ b/packages/metadata-protocol/src/protocol.driver-text-disclosure.test.ts @@ -153,13 +153,13 @@ function expectNothingLeaked(payload: unknown, dialectText: string): void { * with nothing to keep in sync. */ const REPO_PATH_TYPE = DEFAULT_METADATA_TYPE_REGISTRY - .filter((e) => e.allowOrgOverride || e.allowRuntimeCreate) + .filter((e) => e.allowEnvironmentOverlay || e.allowRuntimeCreate) .map((e) => e.type) .sort()[0]!; -/** Code-only (`allowRuntimeCreate: false` and `allowOrgOverride: false`) — the legacy raw-engine path #5264 kept alive. */ +/** Code-only (`allowRuntimeCreate: false` and `allowEnvironmentOverlay: false`) — the legacy raw-engine path #5264 kept alive. */ const LEGACY_PATH_TYPE = DEFAULT_METADATA_TYPE_REGISTRY - .filter((e) => !e.allowOrgOverride && !e.allowRuntimeCreate) + .filter((e) => !e.allowEnvironmentOverlay && !e.allowRuntimeCreate) .map((e) => e.type) .sort()[0]!; diff --git a/packages/metadata-protocol/src/protocol.flow-org-override-closed.test.ts b/packages/metadata-protocol/src/protocol.flow-org-override-closed.test.ts index aa82fc9cc3a..2391be018f3 100644 --- a/packages/metadata-protocol/src/protocol.flow-org-override-closed.test.ts +++ b/packages/metadata-protocol/src/protocol.flow-org-override-closed.test.ts @@ -3,7 +3,7 @@ /** * #6283 — `flow` is not per-org overridable, and the WRITE PATH says so. * - * The registry row for `flow` carried `allowOrgOverride: true` from commit + * The registry row for `flow` carried `allowEnvironmentOverlay: true` from commit * ba252da0b (recorded, unreviewed, in #6191) while * `docs/adr/0005-metadata-customization-overlay.md:57` said — and had always * said — the opposite: @@ -26,7 +26,7 @@ * --------------------------------------------------------------------------- * What the flag does and does NOT close — measured, not assumed * --------------------------------------------------------------------------- - * `allowOrgOverride` governs ONE of the two tiers ADR-0005's extension + * `allowEnvironmentOverlay` governs ONE of the two tiers ADR-0005's extension * defines, and this file pins both sides so the boundary is not mistaken for * an oversight later: * @@ -57,7 +57,7 @@ * Reverse verification, direction predicted BEFORE running * --------------------------------------------------------------------------- * Ordinary red, with a deliberately green half — predicted, then measured. - * Restoring `allowOrgOverride: true` on the `flow` registry row turns the two + * Restoring `allowEnvironmentOverlay: true` on the `flow` registry row turns the two * declaration pins AND the two refusal cases red, and leaves the * runtime-create case and the `view` control green, because neither reads that * flag. Predicted 4 red / 2 green; measured 4 red / 2 green, and the refusal @@ -174,7 +174,7 @@ const FLOW = { edges: [{ id: 'e1', source: 'start', target: 'end' }], }; -/** The control specimen: still `allowOrgOverride: true`, untouched by #6283. */ +/** The control specimen: still `allowEnvironmentOverlay: true`, untouched by #6283. */ const VIEW = { name: 'overdue_grid', label: 'Overdue', @@ -185,19 +185,19 @@ const VIEW = { const entry = (type: string) => DEFAULT_METADATA_TYPE_REGISTRY.find((e) => e.type === type); -describe('#6283 — flow: allowOrgOverride rolled back to false', () => { +describe('#6283 — flow: allowEnvironmentOverlay rolled back to false', () => { // ── the declaration ─────────────────────────────────────────────────── it('the registry declares flow non-overridable, and the row no longer contradicts itself', () => { // Both halves of the ADR-0005 verdict in one assertion. The pairing is // the point: `supportsOverlay: false` says the LOADER cannot merge a - // per-org flow overlay, so `allowOrgOverride: true` had been granting + // flow overlay, so the flag's `true` (then spelled `allowOrgOverride`) had been granting // permission for a write nothing could ever read back — the phantom // #6190 measured. `allowRuntimeCreate` stays true on purpose; see the // two-tier case below. expect(entry('flow')).toMatchObject({ supportsOverlay: false, - allowOrgOverride: false, + allowEnvironmentOverlay: false, allowRuntimeCreate: true, }); }); @@ -209,7 +209,7 @@ describe('#6283 — flow: allowOrgOverride rolled back to false', () => { // cases below go red rather than this one — which is why they, not // this, are the acceptance criterion. const derived = new Set( - DEFAULT_METADATA_TYPE_REGISTRY.filter((e) => e.allowOrgOverride).map((e) => e.type), + DEFAULT_METADATA_TYPE_REGISTRY.filter((e) => e.allowEnvironmentOverlay).map((e) => e.type), ); expect(derived.has('flow')).toBe(false); }); diff --git a/packages/metadata-protocol/src/protocol.legacy-overlay-delete.test.ts b/packages/metadata-protocol/src/protocol.legacy-overlay-delete.test.ts index c4a8d54bd24..0f4677d1601 100644 --- a/packages/metadata-protocol/src/protocol.legacy-overlay-delete.test.ts +++ b/packages/metadata-protocol/src/protocol.legacy-overlay-delete.test.ts @@ -6,7 +6,7 @@ * * ## The defect * - * Commit ee58392e1 flipped six types to `allowOrgOverride: false`. That closed + * Commit ee58392e1 flipped six types to `allowEnvironmentOverlay: false`. That closed * the WRITE door and deliberately left the READ path alone: `supportsOverlay` * stayed `true`, so an overlay row authored BEFORE the rollback still merges * overlay-wins and still shapes the effective body. Removing it was refused at @@ -35,9 +35,9 @@ * ## The tier boundary, which is the whole safety argument * * Two tiers reach the same two refusal points and only ONE moves. The - * separator is `supportsOverlay`, NOT `allowOrgOverride`: + * separator is `supportsOverlay`, NOT `allowEnvironmentOverlay`: * - * - `supportsOverlay: true` + `allowOrgOverride: false` → the ROLLED-BACK + * - `supportsOverlay: true` + `allowEnvironmentOverlay: false` → the ROLLED-BACK * OVERLAYABLE tier. Delete is now allowed. Cases below. * - `supportsOverlay: false` → `object` above all, whose overlay registers as * its own contributor LAYER (ADR-0029 D9) rather than merging, and whose @@ -92,11 +92,11 @@ import { isOriginGatedType } from './packaged-base-regime.js'; * THE TIER THAT MOVES, derived from the registry and never listed by hand * (Prime Directive #8): the loader merges an overlay at read time * (`supportsOverlay: true`) while the per-org write door is shut - * (`allowOrgOverride: false`). A registry entry flipping either flag enrols or + * (`allowEnvironmentOverlay: false`). A registry entry flipping either flag enrols or * releases a type here with nothing to keep in sync. */ const ROLLED_BACK_OVERLAYABLE_TYPES: readonly string[] = DEFAULT_METADATA_TYPE_REGISTRY - .filter((e) => e.supportsOverlay && !e.allowOrgOverride) + .filter((e) => e.supportsOverlay && !e.allowEnvironmentOverlay) .map((e) => e.type) .sort(); @@ -106,7 +106,7 @@ const ROLLED_BACK_OVERLAYABLE_TYPES: readonly string[] = DEFAULT_METADATA_TYPE_R * `action` and `hook` are the same shape and are probed as boundary cases. */ const OVERLAY_INCAPABLE_TYPES: readonly string[] = DEFAULT_METADATA_TYPE_REGISTRY - .filter((e) => !e.supportsOverlay && !e.allowOrgOverride) + .filter((e) => !e.supportsOverlay && !e.allowEnvironmentOverlay) .map((e) => e.type) .sort(); @@ -129,7 +129,7 @@ const OVERLAY_INCAPABLE_TYPES: readonly string[] = DEFAULT_METADATA_TYPE_REGISTR * `protocol.code-defined-datasource-door.test.ts`. */ const OVERLAY_INCAPABLE_RUNTIME_CREATABLE_TYPES: readonly string[] = DEFAULT_METADATA_TYPE_REGISTRY - .filter((e) => !e.supportsOverlay && !e.allowOrgOverride && e.allowRuntimeCreate) + .filter((e) => !e.supportsOverlay && !e.allowEnvironmentOverlay && e.allowRuntimeCreate) .filter((e) => !isOriginGatedType(e.type)) .map((e) => e.type) .sort(); @@ -267,7 +267,7 @@ describe('#6960 — the registry is the tier boundary, and it is DATA', () => { /** * Auto-enrolment, the reason both sets are derived. If a registry entry - * flips `supportsOverlay` or `allowOrgOverride`, this assertion turns red + * flips `supportsOverlay` or `allowEnvironmentOverlay`, this assertion turns red * on the spec edit ALONE — before a line of this file is touched — which * is what stops the two tiers from silently merging. */ diff --git a/packages/metadata-protocol/src/protocol.lock-org-axis-agree.test.ts b/packages/metadata-protocol/src/protocol.lock-org-axis-agree.test.ts index 40fcafc9896..af24442f52e 100644 --- a/packages/metadata-protocol/src/protocol.lock-org-axis-agree.test.ts +++ b/packages/metadata-protocol/src/protocol.lock-org-axis-agree.test.ts @@ -250,8 +250,9 @@ describe('[#21716, ADR-0131 D6] pin 3 — both rows present: every request is se }); describe('[#21716] pin 4 — a type with no per-org channel: neither the read nor the door serves an org-scoped row', () => { - // `page` declares `allowOrgOverride: false`, so an org-scoped row of it is - // pre-#6190 residue boot hydration walks past. The reads gate the + // `page` was outside the five types the pre-D6 doors let an organization + // write, so an org-scoped row of it is pre-#6190 residue boot hydration + // walks past. The reads gate the // organization away (ADR-0131 D6: no read takes one) and serve the env-wide // row. [ADR-0131 D6] Every org-scoped write — the removal included — is // refused by the org-scope door (`NOT_OVERRIDABLE`) before the `_lock` gate. diff --git a/packages/metadata-protocol/src/protocol.marked-refusal-classification.test.ts b/packages/metadata-protocol/src/protocol.marked-refusal-classification.test.ts index 5f5b1d7ba2a..277242fa341 100644 --- a/packages/metadata-protocol/src/protocol.marked-refusal-classification.test.ts +++ b/packages/metadata-protocol/src/protocol.marked-refusal-classification.test.ts @@ -224,7 +224,7 @@ function unreadableEngine(error: () => unknown) { * under test is DERIVED (Prime Directive #8) rather than named. */ const REPO_PATH_TYPE = DEFAULT_METADATA_TYPE_REGISTRY - .filter((e) => e.allowOrgOverride || e.allowRuntimeCreate) + .filter((e) => e.allowEnvironmentOverlay || e.allowRuntimeCreate) .map((e) => e.type) .sort()[0]!; diff --git a/packages/metadata-protocol/src/protocol.org-scoped-write-refused.test.ts b/packages/metadata-protocol/src/protocol.org-scoped-write-refused.test.ts index 695b88b89d8..67bcf0f9c75 100644 --- a/packages/metadata-protocol/src/protocol.org-scoped-write-refused.test.ts +++ b/packages/metadata-protocol/src/protocol.org-scoped-write-refused.test.ts @@ -242,7 +242,11 @@ const FLOW = { edges: [{ id: 'e1', source: 'start', target: 'end' }], }; -/** The control specimen: `allowOrgOverride: true`, so it HAS a per-org channel. */ +/** + * The control specimen: one of the five overlay-enabled types, which the + * retired exemption gave a per-org channel. Its organization-scoped write is + * refused like every other since ADR-0131 D6. + */ const VIEW = { name: 'org_grid', label: 'Org grid', @@ -260,7 +264,12 @@ const PLUGIN_TYPE = 'acme_widget'; /** Every registry type plus the plugin-registered one — the refused set, derived. */ const ALL_TYPES = [...DEFAULT_METADATA_TYPE_REGISTRY.map((e) => e.type), PLUGIN_TYPE]; -/** The five types the retired exemption accepted (`allowOrgOverride: true`). */ +/** + * The five types the retired exemption accepted — the registry's + * overlay-enabled set (`allowEnvironmentOverlay: true`, then spelled + * `allowOrgOverride`). The flag now decides no organization; it is how the set + * is read. + */ const TIER_A = ['view', 'dashboard', 'report', 'translation', 'email_template']; const REFUSED = { code: 'NOT_OVERRIDABLE', status: 403, organizationId: 'org_a' }; @@ -273,7 +282,7 @@ describe('[ADR-0131 D6] no organization-scoped metadata write is accepted', () = }); it('the enumeration covers the registry, the five formerly exempt types and a plugin-registered one', () => { - expect(DEFAULT_METADATA_TYPE_REGISTRY.filter((e) => e.allowOrgOverride).map((e) => e.type)).toEqual(TIER_A); + expect(DEFAULT_METADATA_TYPE_REGISTRY.filter((e) => e.allowEnvironmentOverlay).map((e) => e.type)).toEqual(TIER_A); for (const t of TIER_A) expect(ALL_TYPES).toContain(t); expect(DEFAULT_METADATA_TYPE_REGISTRY.some((e) => (e.type as string) === PLUGIN_TYPE)).toBe(false); }); diff --git a/packages/metadata-protocol/src/protocol.package-door-before-gates.test.ts b/packages/metadata-protocol/src/protocol.package-door-before-gates.test.ts index 3f066d8aade..92580253ff3 100644 --- a/packages/metadata-protocol/src/protocol.package-door-before-gates.test.ts +++ b/packages/metadata-protocol/src/protocol.package-door-before-gates.test.ts @@ -6,7 +6,7 @@ * * `saveMetaItem`'s package door (`refusePackagedBaseOverride`) refuses an * in-place write onto an item a code package ships, on a type with no per-org - * overlay channel (`allowOrgOverride: false`). It used to be asked only on an + * overlay channel (`allowEnvironmentOverlay: false`). It used to be asked only on an * environment kernel (`environmentId !== undefined`); a host-config kernel — * the CLI's assembler, the showcase's boot shape — met the same predicate only * at the repository write (`SysMetadataRepository.assertAllowed`, the first @@ -27,7 +27,7 @@ * 2. controls, unchanged: an environment-local object with a gate-refused or * a spec-refused body still answers `422 INVALID_METADATA` on both * kernels; a packaged item of a type that allows overlays - * (`allowOrgOverride: true`) is still judged by the gates; [ADR-0131 D6] + * (`allowEnvironmentOverlay: true`) is still judged by the gates; [ADR-0131 D6] * a packaged object with the `OS_METADATA_WRITABLE` hatch open is not — * managed content is sealed, so it answers the package door like row 1; * 3. every type in `DEFAULT_METADATA_TYPE_REGISTRY`: the same packaged @@ -302,7 +302,7 @@ describe('[#22220] controls: the gates still judge what the door does not refuse describe('[#22220] every registry type: one packaged publish, one envelope, on both kernels', () => { const governed = new Set( - DEFAULT_METADATA_TYPE_REGISTRY.filter((e) => !e.allowOrgOverride && e.allowRuntimeCreate).map((e) => e.type), + DEFAULT_METADATA_TYPE_REGISTRY.filter((e) => !e.allowEnvironmentOverlay && e.allowRuntimeCreate).map((e) => e.type), ); for (const { type } of DEFAULT_METADATA_TYPE_REGISTRY) { diff --git a/packages/metadata-protocol/src/protocol.packaged-base-refusal.test.ts b/packages/metadata-protocol/src/protocol.packaged-base-refusal.test.ts index 1bba1f2c3f9..9d424a42258 100644 --- a/packages/metadata-protocol/src/protocol.packaged-base-refusal.test.ts +++ b/packages/metadata-protocol/src/protocol.packaged-base-refusal.test.ts @@ -100,7 +100,7 @@ describe('packagedBaseRefusal — the /meta door\'s locked-base verdict, handed expect(p.packagedBaseRefusal({ type: 'flow', name: 'customer_flow', operation: 'delete' })).toBeNull(); }); - it('a Regime O overlay type (allowOrgOverride) is never refused on this ground', () => { + it('a Regime O overlay type (allowEnvironmentOverlay) is never refused on this ground', () => { const p = protocolOn(undefined); expect(p.packagedBaseRefusal({ type: 'view', name: 'pkg_view', operation: 'save' })).toBeNull(); expect(p.packagedBaseRefusal({ type: 'view', name: 'pkg_view', operation: 'delete' })).toBeNull(); @@ -173,10 +173,10 @@ describe('packagedBaseRefusal — the /meta door\'s locked-base verdict, handed const { entries } = await (p as any).getMetaTypes(); return Object.fromEntries(['flow', 'page'].map((t) => { const e = entries.find((x: any) => x.type === t); - return [t, { allowOrgOverride: e?.allowOrgOverride, overrideSource: e?.overrideSource }]; + return [t, { allowEnvironmentOverlay: e?.allowEnvironmentOverlay, overrideSource: e?.overrideSource }]; })); }; - const SHUT = { allowOrgOverride: false, overrideSource: 'registry' }; + const SHUT = { allowEnvironmentOverlay: false, overrideSource: 'registry' }; expect(await hatchOf(shut)).toEqual({ flow: SHUT, page: SHUT }); process.env.OBJECTSTACK_METADATA_WRITABLE = 'flow,page'; @@ -194,7 +194,7 @@ describe('packagedBaseRefusal — the /meta door\'s locked-base verdict, handed // Control: the preferred spelling is read, so the same names open the listing. process.env.OS_METADATA_WRITABLE = 'flow,page'; ObjectStackProtocolImplementation.resetEnvWritableCache(); - const OPEN = { allowOrgOverride: true, overrideSource: 'env' }; + const OPEN = { allowEnvironmentOverlay: true, overrideSource: 'env' }; expect(await hatchOf(protocolOn(environmentId))).toEqual({ flow: OPEN, page: OPEN }); }); } @@ -227,13 +227,13 @@ describe('packagedBaseRefusal — the sentence is chosen per ADR-0126 regime', ( */ const SEALED_SAVE = (type: string, name: string) => `Metadata item '${type}/${name}' is provided by a managed package and is sealed against in-place edits: ` - + 'its type takes no environment overlay (allowOrgOverride=false), and OS_METADATA_WRITABLE does not open ' + + 'its type takes no environment overlay (allowEnvironmentOverlay=false), and OS_METADATA_WRITABLE does not open ' + 'a managed item. Edit the source artifact and redeploy. ' + 'See docs/adr/0131-total-organization-ownership-no-null-organization-id.md.'; /** …and its removal twin (`refusePackagedBaseRemoval`), which has no remedy to name. */ const SEALED_DELETE = (type: string, name: string) => `Metadata item '${type}/${name}' is provided by a managed package and is sealed against removal: ` - + 'its type takes no environment overlay (allowOrgOverride=false), and OS_METADATA_WRITABLE does not open ' + + 'its type takes no environment overlay (allowEnvironmentOverlay=false), and OS_METADATA_WRITABLE does not open ' + 'a managed item. See docs/adr/0131-total-organization-ownership-no-null-organization-id.md.'; for (const environmentId of [undefined, 'env_1']) { diff --git a/packages/metadata-protocol/src/protocol.publish-item-draft-org-scope.test.ts b/packages/metadata-protocol/src/protocol.publish-item-draft-org-scope.test.ts index 9dea1efebb5..4a7085545eb 100644 --- a/packages/metadata-protocol/src/protocol.publish-item-draft-org-scope.test.ts +++ b/packages/metadata-protocol/src/protocol.publish-item-draft-org-scope.test.ts @@ -9,8 +9,9 @@ * by its one-click "publish 4 changes" button, were each refused per item with * `404 [no_draft] No pending draft exists for view/... — nothing to publish.` * - * `view` is `allowOrgOverride: true`, so the REST seam threads the session's - * active organization into the publish (`organizationIdForMetaWrite`); the + * `view` was then declared `allowOrgOverride: true`, so the REST seam threaded + * the session's active organization into the publish + * (`organizationIdForMetaWrite`, retired with ADR-0131 D6); the * drafts were authored env-wide (`organization_id = NULL`), which is what * package/AI authoring writes, and a strict `organization_id = ` lookup can * never match them. `object` and `flow` are NOT org-overridable, which is why @@ -174,7 +175,7 @@ function makeStubEngine() { const PKG = 'app.ops'; -/** `view` is `allowOrgOverride: true` — the org-overridable specimen of case B. */ +/** `view`, one of the five overlay-enabled types — the org-scoped specimen of case B before ADR-0131 D6. */ const viewBody = (name: string) => ({ name, label: 'Customers', diff --git a/packages/metadata-protocol/src/protocol.read-lock-flags-write-door.test.ts b/packages/metadata-protocol/src/protocol.read-lock-flags-write-door.test.ts index f88f28f5461..c6a6bee381f 100644 --- a/packages/metadata-protocol/src/protocol.read-lock-flags-write-door.test.ts +++ b/packages/metadata-protocol/src/protocol.read-lock-flags-write-door.test.ts @@ -37,7 +37,7 @@ * that does not persist) is not a lock verdict and is not read as one. * * Out of the table, deliberately: the six code-only types (`allowRuntimeCreate` - * and `allowOrgOverride` both false) have no org-owned arm, because no runtime + * and `allowEnvironmentOverlay` both false) have no org-owned arm, because no runtime * door can author an item of those types (`NOT_CREATABLE`). Their packaged arm * is in the table like every other type's. * @@ -252,7 +252,7 @@ interface Row { } const CODE_ONLY = new Set( - DEFAULT_METADATA_TYPE_REGISTRY.filter((e) => !e.allowOrgOverride && !e.allowRuntimeCreate).map((e) => e.type), + DEFAULT_METADATA_TYPE_REGISTRY.filter((e) => !e.allowEnvironmentOverlay && !e.allowRuntimeCreate).map((e) => e.type), ); async function measure(kernel: Kernel, arm: Arm, type: string): Promise { diff --git a/packages/metadata-protocol/src/protocol.read-verb-canonical-fold.test.ts b/packages/metadata-protocol/src/protocol.read-verb-canonical-fold.test.ts index f80e894a49b..d7ae96bfe22 100644 --- a/packages/metadata-protocol/src/protocol.read-verb-canonical-fold.test.ts +++ b/packages/metadata-protocol/src/protocol.read-verb-canonical-fold.test.ts @@ -41,7 +41,7 @@ * fold. The subjects are the MANIFEST-ABSENT types, and the two of them behave * differently enough that both are needed: * - * - `translation` (`allowOrgOverride: true`) — the singular passes + * - `translation` (`allowEnvironmentOverlay: true`) — the singular passes * `historyMetaItem`'s gate and reads real rows. The plural did not fold, so * it asked the repository for `type='translations'`, matched nothing, and * answered `{ events: [] }` about an item with a full change log. @@ -64,7 +64,7 @@ import { META_URL_TO_SINGULAR, PLURAL_TO_SINGULAR, metaUrlSpellingRefusal } from import { ObjectStackProtocolImplementation } from './protocol.js'; import { assertEngineFindOnePredicate } from '@objectstack/metadata-core'; -/** Manifest-ABSENT and `allowOrgOverride: true` — history reads REAL rows for it. */ +/** Manifest-ABSENT and `allowEnvironmentOverlay: true` — history reads REAL rows for it. */ const OVERLAY_ABSENT_TYPE = 'translation'; const OVERLAY_ABSENT_PLURAL = 'translations'; /** Manifest-ABSENT and gated shut — the "plural is a door around the gate" case. */ @@ -387,7 +387,7 @@ describe('#9157 — historyMetaItem', () => { }); it('THE OTHER PIN: the plural stops being a door AROUND the overlay gate', async () => { - // `field` declares neither `allowOrgOverride` nor `allowRuntimeCreate`, + // `field` declares neither `allowEnvironmentOverlay` nor `allowRuntimeCreate`, // so the canonical spelling is refused by the gate and never touches the // store. The unfolded plural took `isRuntimeCreateAllowed`'s // no-static-registry-entry arm — the PLUGIN path, permissive by diff --git a/packages/metadata-protocol/src/protocol.recovery-doors-emit-mutation.test.ts b/packages/metadata-protocol/src/protocol.recovery-doors-emit-mutation.test.ts index dbc356020be..09c6dd01672 100644 --- a/packages/metadata-protocol/src/protocol.recovery-doors-emit-mutation.test.ts +++ b/packages/metadata-protocol/src/protocol.recovery-doors-emit-mutation.test.ts @@ -535,7 +535,7 @@ describe('[#14179] door 3 — deleteMetaItem’s legacy raw-engine exit', () => */ describe('[#14179] reachability: the legacy exit is confined to bootstrap mode', () => { const codeOnly = DEFAULT_METADATA_TYPE_REGISTRY.filter( - (e) => !e.allowOrgOverride && !e.allowRuntimeCreate, + (e) => !e.allowEnvironmentOverlay && !e.allowRuntimeCreate, ); it('the `useRepoPath === false` set is non-empty and none of it merges an overlay at read', () => { diff --git a/packages/metadata-protocol/src/protocol.runtime-gate-stored-universe.test.ts b/packages/metadata-protocol/src/protocol.runtime-gate-stored-universe.test.ts index 5d437b088c9..a4c80907b09 100644 --- a/packages/metadata-protocol/src/protocol.runtime-gate-stored-universe.test.ts +++ b/packages/metadata-protocol/src/protocol.runtime-gate-stored-universe.test.ts @@ -453,7 +453,7 @@ describe('#16224 — an overlay that REDEFINES a code-package item is judged fro /** * The overlay: same name, same object, same dimensions — one measure - * REMOVED and one added. `dataset` is `allowOrgOverride: false` + * REMOVED and one added. `dataset` is `allowEnvironmentOverlay: false` * (`DEFAULT_METADATA_TYPE_REGISTRY`), so the reachable redefinition is the * env-wide one, which is the other limb the card names. */ diff --git a/packages/metadata-protocol/src/protocol.save-receipt-wording.test.ts b/packages/metadata-protocol/src/protocol.save-receipt-wording.test.ts index da814e96d73..d1bbfd68aad 100644 --- a/packages/metadata-protocol/src/protocol.save-receipt-wording.test.ts +++ b/packages/metadata-protocol/src/protocol.save-receipt-wording.test.ts @@ -17,7 +17,7 @@ * the sentence on a `view` (`supportsOverlay: true`, so the receipt was true * there); the same sentence for `object` / `flow` simply was not. * - * The discriminator is NOT `supportsOverlay` and NOT `allowOrgOverride` — the + * The discriminator is NOT `supportsOverlay` and NOT `allowEnvironmentOverlay` — the * spec's TSDoc keeps those two apart on purpose (loader merge *capability* vs * runtime write *permission*), and neither is the fact the sentence claims. * The fact the sentence claims is "something was overlaid", and the write path @@ -175,7 +175,7 @@ describe('#5265 — a save receipt names what was actually written', () => { } // #6283 → commit ee58392e1 — the override-artifact case at the bottom of this // file needs a type that is BOTH overlay-less and per-org overridable. - // `flow` played that part until #6283 rolled its `allowOrgOverride` + // `flow` played that part until #6283 rolled its `allowEnvironmentOverlay` // back to `false` (ADR-0005:57); `action` inherited it until commit ee58392e1 // rolled back the remaining nine unratified `true` flags — `action`'s // overlay-less-yet-overridable pairing was the #6190 phantom shape @@ -184,7 +184,7 @@ describe('#5265 — a save receipt names what was actually written', () => { // new member re-opens the statically-reachable case below instead of // silently landing an untested pairing. const overlaylessYetOverridable = DEFAULT_METADATA_TYPE_REGISTRY - .filter((e) => e.supportsOverlay === false && e.allowOrgOverride) + .filter((e) => e.supportsOverlay === false && e.allowEnvironmentOverlay) .map((e) => e.type); expect(overlaylessYetOverridable).toEqual([]); }); diff --git a/packages/metadata-protocol/src/protocol.sealed-remedy-kind.test.ts b/packages/metadata-protocol/src/protocol.sealed-remedy-kind.test.ts index 991ea88c385..b7b363c7594 100644 --- a/packages/metadata-protocol/src/protocol.sealed-remedy-kind.test.ts +++ b/packages/metadata-protocol/src/protocol.sealed-remedy-kind.test.ts @@ -97,7 +97,7 @@ describe('[#22591] the sealed-item refusal names the remedy the caller\'s ACT ha // Measured against the registry's own flag, never a transcribed list: // a type is sealed exactly when its entry opens no overlay channel. const expected = [ - ...DEFAULT_METADATA_TYPE_REGISTRY.filter((e) => !e.allowOrgOverride).map((e) => e.type), + ...DEFAULT_METADATA_TYPE_REGISTRY.filter((e) => !e.allowEnvironmentOverlay).map((e) => e.type), PLUGIN_TYPE, ]; expect(reached.sort()).toEqual(expected.sort()); diff --git a/packages/metadata-protocol/src/protocol.stored-migration.test.ts b/packages/metadata-protocol/src/protocol.stored-migration.test.ts index 719d5318d98..d49841f8fd9 100644 --- a/packages/metadata-protocol/src/protocol.stored-migration.test.ts +++ b/packages/metadata-protocol/src/protocol.stored-migration.test.ts @@ -505,7 +505,7 @@ describe('migrateStoredMetadata — what it declines to touch, loudly (#4327)', }); it('skips a type with no repository write path rather than rewriting it without history', async () => { - // `agent` is allowOrgOverride:false + allowRuntimeCreate:false. The + // `agent` is allowEnvironmentOverlay:false + allowRuntimeCreate:false. The // skip is what this test pins, and it is unchanged; only its rationale // moved. It used to be "`saveMetaItem` would take the legacy raw-engine // branch: no history row and a forced `state: 'active'`" — #5086 diff --git a/packages/metadata-protocol/src/protocol.unrecognised-meta-type.test.ts b/packages/metadata-protocol/src/protocol.unrecognised-meta-type.test.ts index f69a9f3eb04..34835e3a6d6 100644 --- a/packages/metadata-protocol/src/protocol.unrecognised-meta-type.test.ts +++ b/packages/metadata-protocol/src/protocol.unrecognised-meta-type.test.ts @@ -203,7 +203,7 @@ describe('#8421 — the traffic that must keep working', () => { const ACCEPTED: Array<{ type: string; item: Record; why: string }> = [ { type: 'view', - why: 'declared, allowOrgOverride + allowRuntimeCreate', + why: 'declared, allowEnvironmentOverlay + allowRuntimeCreate', item: { name: 'probe_item', label: 'Probe', diff --git a/packages/metadata-protocol/src/sys-metadata-repository.contract.test.ts b/packages/metadata-protocol/src/sys-metadata-repository.contract.test.ts index fd0c001b22d..fe3fbe5dc2b 100644 --- a/packages/metadata-protocol/src/sys-metadata-repository.contract.test.ts +++ b/packages/metadata-protocol/src/sys-metadata-repository.contract.test.ts @@ -38,7 +38,7 @@ * the other two implementations — has a write-authorization door keyed on the * type: `assertAllowed()` refuses `'object'` under the default * `override-artifact` intent because its registry entry is - * `allowOrgOverride: false`. So `ContractSuiteOptions` grew `primaryType` / + * `allowEnvironmentOverlay: false`. So `ContractSuiteOptions` grew `primaryType` / * `secondaryType`, defaulting to today's values, and this call site names two * types the door admits. That keeps ONE invariant table; the alternative — * wrapping the repository in an adapter that injects `intent: 'runtime-only'` — @@ -179,7 +179,7 @@ function makeRepo(): SysMetadataRepository { runRepositoryContractTests('SysMetadataRepository', makeRepo, { // Both types must clear `assertAllowed()` under the suite's default - // `override-artifact` intent, i.e. both are `allowOrgOverride: true` in + // `override-artifact` intent, i.e. both are `allowEnvironmentOverlay: true` in // `DEFAULT_METADATA_TYPE_REGISTRY`. `'object'` — the suite's default second // type — is not, on purpose (packaged objects are locked); `'dashboard'` is. primaryType: 'view', @@ -600,7 +600,7 @@ describe('SysMetadataRepository — close() terminates every live watcher (#1102 */ describe('SysMetadataRepository — a declared ordered map: object `fields` (#21790)', () => { const ref = { org: 'system', type: 'object' as const, name: 'invoice' }; - // `object` is `allowRuntimeCreate`, not `allowOrgOverride`: the runtime door. + // `object` is `allowRuntimeCreate`, not `allowEnvironmentOverlay`: the runtime door. const intent = 'runtime-only' as const; const FIELDS: Record = { title: { type: 'text', label: 'Title' }, diff --git a/packages/metadata-protocol/src/sys-metadata-repository.package-writability.test.ts b/packages/metadata-protocol/src/sys-metadata-repository.package-writability.test.ts index 4c985c5cc9b..da4d955dd9f 100644 --- a/packages/metadata-protocol/src/sys-metadata-repository.package-writability.test.ts +++ b/packages/metadata-protocol/src/sys-metadata-repository.package-writability.test.ts @@ -292,7 +292,7 @@ describe('#7682 — the refusal discriminates on package writability', () => { }); it('a runtime-only create names the base: WRITABLE_PACKAGE_REQUIRED / 422 vs NOT_CREATABLE / 403', async () => { - // `job` is code-only (no allowRuntimeCreate, no allowOrgOverride), so + // `job` is code-only (no allowRuntimeCreate, no allowEnvironmentOverlay), so // both bases are refused — and the codes still differ, because "you // named a read-only base" and "this type has no create channel" are // different facts. 422 + the wording mirror `saveMetaItem`'s ADR-0070 D1 @@ -327,7 +327,7 @@ describe('#7682 — the refusal discriminates on package writability', () => { describe('no allow decision moves', () => { it('an ADR-0005 overlay of a code-shipped item still lands (the naive gate would break this)', async () => { - // `view` is allowOrgOverride, and an overlay of a packaged view names + // `view` is allowEnvironmentOverlay, and an overlay of a packaged view names // the read-only package it customizes BY CONSTRUCTION. If the package // door refused instead of choosing a code, the whole overlay model would // close — this is the case that decides the shape of the fix. @@ -384,7 +384,7 @@ describe('#7682 — the refusal discriminates on package writability', () => { * * One measurement worth carrying, because it narrows what this card proves: * `{ package_id: , organization_id: null }` is ALSO what a genuine - * `allowOrgOverride` overlay writes whenever the caller names the package it + * `allowEnvironmentOverlay` overlay writes whenever the caller names the package it * customizes (the `view` case in "no allow decision moves" above). So the * defect was never "the hatch writes a row shape nothing else produces" — it * is precisely the ruling's own sentence: a type-level unlock reached the @@ -527,7 +527,7 @@ describe('#7682 — the refusal discriminates on package writability', () => { it('the ADR-0005 overlay is untouched: a registry-allowed type still overlays a read-only package', async () => { // The limb ordering IS the fix, so it needs a pin on BOTH sides of the - // door. `view` is allowOrgOverride and returns at the registry limb — + // door. `view` is allowEnvironmentOverlay and returns at the registry limb — // above the door — with the hatch open for an unrelated type. If the // door had been placed one limb higher, this goes red and the whole // overlay model closes. @@ -765,7 +765,7 @@ describe('#8184 — the scoped kernel answers the same code as the host-config k getArtifactItem: (type: string, name: string) => (type === 'object' && name === 'showcase_task') || (type === 'permission' && name === 'showcase_contributor') - // [#8184] `view` is `allowOrgOverride: true`, so it returns at the + // [#8184] `view` is `allowEnvironmentOverlay: true`, so it returns at the // REGISTRY limb — above the door. Artifact-backed on purpose: that is // the ADR-0005 overlay the door must never refuse. || (type === 'view' && name === 'case_grid') @@ -890,7 +890,7 @@ describe('#8184 — the scoped kernel answers the same code as the host-config k it('an ADR-0005 overlay of a code-shipped item still lands — the door is BELOW every registry limb', async () => { // If the door had been placed one limb higher this goes red and the whole - // overlay model closes. `view` is allowOrgOverride, artifact-backed, and + // overlay model closes. `view` is allowEnvironmentOverlay, artifact-backed, and // names the read-only package it customizes — by construction. const { engine, protocol } = boot('env_alpha'); const err = await save(protocol, { @@ -1000,7 +1000,7 @@ describe('#8361 — the create-side hatch clause reaches saveMetaItem', () => { /** Not artifact-backed under {@link boot}, so `saveMetaItem` intends `runtime-only`. */ const runtimePermissionBody = { name: 'runtime_reviewer', label: 'Reviewer', objects: {} }; /** - * `job` is `allowOrgOverride: false` AND `allowRuntimeCreate: false`, so the + * `job` is `allowEnvironmentOverlay: false` AND `allowRuntimeCreate: false`, so the * hatch is the ONLY thing that can carry this write as far as D1 — which is * what makes it the honest test of "the clause is reachable", as opposed to * `permission`, which reaches D1 either way. Spec-valid on purpose: the Zod @@ -1151,7 +1151,7 @@ describe('#8361 — the create-side hatch clause reaches saveMetaItem', () => { it('a type with NO create channel reaches D1 only through the hatch', async () => { // Both halves in one case, because either alone is ambiguous. `job` has - // neither `allowOrgOverride` nor `allowRuntimeCreate`: shut, the code-only + // neither `allowEnvironmentOverlay` nor `allowRuntimeCreate`: shut, the code-only // refusal answers first and D1 is never consulted (so a hatch clause there // would be unreachable for this type); open, `isOverlayAllowed` folds the // hatch in and the write lands on D1 — carrying the clause. @@ -1318,7 +1318,7 @@ describe('[#20910] the repository doors name a Regime C type\'s sanctioned path, expect(err).toMatchObject({ code: 'NOT_OVERRIDABLE', status: 403 }); expect(String(err.message)).toBe( "Metadata item 'object/showcase_task' is provided by a managed package and is sealed against in-place edits: " - + 'its type takes no environment overlay (allowOrgOverride=false), and OS_METADATA_WRITABLE does not open ' + + 'its type takes no environment overlay (allowEnvironmentOverlay=false), and OS_METADATA_WRITABLE does not open ' + 'a managed item. Edit the source artifact and redeploy. ' + 'See docs/adr/0131-total-organization-ownership-no-null-organization-id.md.', ); diff --git a/packages/objectql/src/engine-security-catalog-package-door.test.ts b/packages/objectql/src/engine-security-catalog-package-door.test.ts index 005bc3a9bef..af17a778023 100644 --- a/packages/objectql/src/engine-security-catalog-package-door.test.ts +++ b/packages/objectql/src/engine-security-catalog-package-door.test.ts @@ -2,7 +2,7 @@ /** * [#22203] Every package-declared item of a `security`-domain type that the - * type registry declares `allowOrgOverride: false` is refused at the metadata + * type registry declares `allowEnvironmentOverlay: false` is refused at the metadata * save door — and the refusal is the type-level one, fed by the provenance * seam, not a lock written for one type. * @@ -12,7 +12,7 @@ * onto an item a code package ships when the type has no overlay channel * (`refusePackagedBaseOverride` in `saveMetaItem` on an environment-scoped * kernel, `SysMetadataRepository.assertAllowed`'s `override-artifact` intent on - * a host-config one), and both read `allowOrgOverride` off + * a host-config one), and both read `allowEnvironmentOverlay` off * `DEFAULT_METADATA_TYPE_REGISTRY`. What decides "a code package ships it" is * `isArtifactBacked`, which asks this engine's SchemaRegistry for an entry a * package registered (`getArtifactItem`, `_packageId` provenance). @@ -31,7 +31,7 @@ * ## What this file pins * * The set under test is ENUMERATED FROM THE REGISTRY, not written out: every - * `domain: 'security'` row with `allowOrgOverride: false`. For each, a real + * `domain: 'security'` row with `allowEnvironmentOverlay: false`. For each, a real * `ObjectQL` registers a manifest that declares one item through the type's * stack collection, and a real `ObjectStackProtocolImplementation` over that * engine is asked to save over it — on both topologies, because the two answer @@ -44,7 +44,7 @@ * * Controls, so the refusals cannot pass for the wrong reason: a position no * package declares still saves (the `allowRuntimeCreate` tier is untouched); an - * `allowOrgOverride: true` type still saves over its packaged item; and the + * `allowEnvironmentOverlay: true` type still saves over its packaged item; and the * by-name read after a refusal still serves the package's declaration. */ @@ -61,7 +61,7 @@ const NAME = 'probe_item'; /** The pin's population, read off the registry: every non-overridable `security` type. */ const SECURITY_NON_OVERRIDABLE = DEFAULT_METADATA_TYPE_REGISTRY - .filter((entry) => entry.domain === 'security' && entry.allowOrgOverride === false) + .filter((entry) => entry.domain === 'security' && entry.allowEnvironmentOverlay === false) .map((entry) => entry.type); /** @@ -74,7 +74,7 @@ const BODIES: Record> = { capability: { name: NAME, label: 'Probe' }, }; -/** The overlayable control: `allowOrgOverride: true`, declared through a stack collection. */ +/** The overlayable control: `allowEnvironmentOverlay: true`, declared through a stack collection. */ const OVERLAYABLE_TYPE = 'email_template'; const OVERLAYABLE_BODY = { name: NAME, label: 'Probe', subject: 'Probe', bodyHtml: '

Probe

' }; @@ -204,7 +204,7 @@ async function boot(topology: Topology) { return { engine, protocol, storedRows }; } -describe('[#22203] security-domain allowOrgOverride:false — the package door is type-level', () => { +describe('[#22203] security-domain allowEnvironmentOverlay:false — the package door is type-level', () => { it('the population is read from the registry, and every member has a fixture', () => { // The three the registry declares today. Asserted as a superset so a // fourth `security` type joins the pin instead of breaking it — and the @@ -259,9 +259,9 @@ describe('[#22203] security-domain allowOrgOverride:false — the package door i expect(storedRows().map((r: any) => `${r.type}/${r.name}`)).toEqual(['position/env_only_position']); }); - it.each(TOPOLOGIES)(`%s: an allowOrgOverride:true type (${OVERLAYABLE_TYPE}) still saves over its packaged item`, async (topology) => { + it.each(TOPOLOGIES)(`%s: an allowEnvironmentOverlay:true type (${OVERLAYABLE_TYPE}) still saves over its packaged item`, async (topology) => { const entry = DEFAULT_METADATA_TYPE_REGISTRY.find((e) => e.type === OVERLAYABLE_TYPE); - expect(entry?.allowOrgOverride).toBe(true); + expect(entry?.allowEnvironmentOverlay).toBe(true); const { engine, protocol, storedRows } = await boot(topology); // The control is only a control if its item is package-declared too. expect(engine.registry.getArtifactItem(OVERLAYABLE_TYPE, NAME)?._packageId).toBe(PKG); diff --git a/packages/objectql/src/environment-overlay-five-types.test.ts b/packages/objectql/src/environment-overlay-five-types.test.ts index e585fbccd13..95cd3bf6969 100644 --- a/packages/objectql/src/environment-overlay-five-types.test.ts +++ b/packages/objectql/src/environment-overlay-five-types.test.ts @@ -8,11 +8,12 @@ * * ## Why this file exists (#22340, stage S1) * - * The registry flag that opens these writes, `allowOrgOverride`, is about to - * change twice: #15206 rewrites the write doors and deletes the per-organization - * overlay path that also reads it, and #22340 then renames the flag to say what - * it will mean, "may an environment overlay this packaged item". Neither change - * may lose an environment overlay, and before this file that was checkable for + * The registry flag that opens these writes changed twice: #15206 rewrote the + * write doors and deleted the per-organization overlay path that also read it, + * and #22340 then renamed the flag from `allowOrgOverride` to + * `allowEnvironmentOverlay`, to say what it means: "may an environment overlay + * this packaged item". Neither change may lose an environment overlay, and + * before this file that was checkable for * part of the population only. So every case here is the ENVIRONMENT path and * nothing else: no `organizationId` reaches any call, the protocol has no * organization of its own, and the item is one a code package ships. The @@ -306,7 +307,7 @@ afterEach(() => { describe('the five overlay-enabled types keep their environment overlay of a packaged item', () => { it('the registry opens an overlay on exactly these five types', () => { const flagged = DEFAULT_METADATA_TYPE_REGISTRY - .filter((entry) => entry.allowOrgOverride === true) + .filter((entry) => entry.allowEnvironmentOverlay === true) .map((entry) => entry.type) .sort(); expect(flagged).toEqual([...OVERLAY_ENABLED].sort()); @@ -354,6 +355,8 @@ describe('the five overlay-enabled types keep their environment overlay of a pac const result: any = await protocol.getMetaTypes(); const entry = result?.entries?.find((e: any) => e.type === type); - expect(entry).toMatchObject({ allowOrgOverride: true, overrideSource: 'registry' }); + expect(entry).toMatchObject({ allowEnvironmentOverlay: true, overrideSource: 'registry' }); + // One wire spelling: the retired name rides along on no entry. + expect(entry).not.toHaveProperty('allowOrgOverride'); }); }); diff --git a/packages/objectql/src/meta-object-search-companion-roundtrip.test.ts b/packages/objectql/src/meta-object-search-companion-roundtrip.test.ts index 1f75e82a417..12d7f684563 100644 --- a/packages/objectql/src/meta-object-search-companion-roundtrip.test.ts +++ b/packages/objectql/src/meta-object-search-companion-roundtrip.test.ts @@ -21,7 +21,7 @@ * * ## Why the object here is RUNTIME-CREATED, and why this file is in objectql * - * Type `object` carries `allowOrgOverride: false`, so a save over an + * Type `object` carries `allowEnvironmentOverlay: false`, so a save over an * artifact-backed object is (correctly) refused with `NOT_OVERRIDABLE` and a * test written that way would only measure the refusal. The write door that IS * open by default is the runtime-created path (`allowRuntimeCreate` — the diff --git a/packages/objectql/src/overlay-precedence.test.ts b/packages/objectql/src/overlay-precedence.test.ts index 7fd31176129..22df74ec489 100644 --- a/packages/objectql/src/overlay-precedence.test.ts +++ b/packages/objectql/src/overlay-precedence.test.ts @@ -7,7 +7,7 @@ * (re-expressing the overlay path as a LayeredRepository): * * 1. **Whitelist enforcement** — only metadata types whose registry - * entry sets `allowOrgOverride: true` may be persisted as + * entry sets `allowEnvironmentOverlay: true` may be persisted as * per-organization overlays. Everything else (trigger, hook, * datasource, function, service, …) MUST throw with * `code='NOT_OVERRIDABLE'`, `status=403`. This is the @@ -15,7 +15,7 @@ * §"Tenant-customizable type whitelist"). * * Note: object, field, flow, workflow, agent, permission, role, - * and profile all flipped to `allowOrgOverride: true` in commit + * and profile all flipped to `allowEnvironmentOverlay: true` in commit * ba252da0b (feat: add project mode, metadata forms, and org * overlays). The invariant now pins the execution/wiring-layer * types that MUST stay false. Several of that commit's flips have @@ -130,7 +130,7 @@ describe('overlay whitelist enforcement (shared-DB invariant)', () => { afterEach(() => vi.clearAllMocks()); // ── allowed types: pure render-time, safe per-org override ── - describe('allowed (allowOrgOverride: true) — must accept', () => { + describe('allowed (allowEnvironmentOverlay: true) — must accept', () => { // [ADR-0131 D6] These writes used to pass `organizationId: 'org_alpha'`; // every org-scoped write is now refused (403 NOT_OVERRIDABLE), so they // run environment-wide, as the runtime-creatable loop below already does. @@ -152,7 +152,7 @@ describe('overlay whitelist enforcement (shared-DB invariant)', () => { expect(result.success).toBe(true); }); - it('accepts report (flipped to allowOrgOverride:true on 2026-05-22)', async () => { + it('accepts report (flipped to allowEnvironmentOverlay:true on 2026-05-22)', async () => { // This test pins the user-requested change from 8494fe8e — // if someone flips report back to false, this fails loud. const result = await protocol.saveMetaItem({ @@ -237,10 +237,10 @@ describe('overlay whitelist enforcement (shared-DB invariant)', () => { }); // ── runtime-creatable types: brand-new items succeed; overriding an - // artifact-backed item still requires allowOrgOverride. The test + // artifact-backed item still requires allowEnvironmentOverlay. The test // registry has no artifacts, so saves all succeed here; provenance- // aware rejection is exercised in `protocol-meta.test.ts`. - describe('runtime-creatable (allowOrgOverride:false, allowRuntimeCreate:true) — brand-new items succeed', () => { + describe('runtime-creatable (allowEnvironmentOverlay:false, allowRuntimeCreate:true) — brand-new items succeed', () => { const runtimeCreatable: Array<{ type: string; item: any }> = [ // [#8421] `trigger` left this list on 2026-08-15, and it was DEBT // rather than a consequence of that card: ADR-0088 retired the kind @@ -252,7 +252,7 @@ describe('overlay whitelist enforcement (shared-DB invariant)', () => { // registry has never heard of, which is the arm #8421 closed. It is // REPLACED rather than deleted, and replaced by a type that carries // the flags in this describe's title for real — `mapping` declares - // `allowOrgOverride: false, allowRuntimeCreate: true` (#2611: the + // `allowEnvironmentOverlay: false, allowRuntimeCreate: true` (#2611: the // import wizard saves one at runtime) — so the case still measures // the tier it is named for instead of the closed hole. { @@ -271,7 +271,7 @@ describe('overlay whitelist enforcement (shared-DB invariant)', () => { // two-tier verdict, not that refusal. { type: 'hook', item: { name: 'before_save', object: 'case', events: ['beforeInsert'], body: { language: 'js', source: 'return;' } } }, { type: 'hooks', item: { name: 'before_save', object: 'case', events: ['beforeInsert'], body: { language: 'js', source: 'return;' } } }, // plural - // object reverted to allowOrgOverride:false on 2026-05-29 — + // object reverted to allowEnvironmentOverlay:false on 2026-05-29 — // packaged items locked, brand-new tenant-authored items succeed. { type: 'object', @@ -340,10 +340,10 @@ describe('overlay whitelist enforcement (shared-DB invariant)', () => { // // The specimen used to be `agent`, which #5086 moved out from under // this bypass: a type declaring BOTH `allowRuntimeCreate: false` and - // `allowOrgOverride: false` is code-only and is refused on EVERY + // `allowEnvironmentOverlay: false` is code-only and is refused on EVERY // kernel (see `protocol.code-only-types.test.ts`). What ADR-0005's // sentence actually granted single kernels — the *overlay* whitelist - // staying off — is unchanged, so `hook` (allowOrgOverride:false, + // staying off — is unchanged, so `hook` (allowEnvironmentOverlay:false, // allowRuntimeCreate:true) is now the honest specimen for it. const { protocol: localProto } = makeProtocol({ environmentId: undefined }); const result = await localProto.saveMetaItem({ @@ -369,14 +369,14 @@ describe('overlay whitelist enforcement (shared-DB invariant)', () => { // ── registry invariant: whitelist derives from spec, no parallel list ── describe('registry-as-source-of-truth (Prime Directive #8)', () => { - it('every type in OVERLAY_ALLOWED_TYPES has allowOrgOverride:true in the registry', () => { + it('every type in OVERLAY_ALLOWED_TYPES has allowEnvironmentOverlay:true in the registry', () => { // The protocol's whitelist is derived from // DEFAULT_METADATA_TYPE_REGISTRY. If anyone introduces a parallel // list, this test catches it: every accepted type must trace back // to a registry entry that opted in. const allowedFromRegistry = new Set(); for (const entry of DEFAULT_METADATA_TYPE_REGISTRY) { - if (entry.allowOrgOverride) allowedFromRegistry.add(entry.type); + if (entry.allowEnvironmentOverlay) allowedFromRegistry.add(entry.type); } // Render-time types must be in the set; if any of these drop out, // the shared-DB contract is broken. @@ -384,11 +384,11 @@ describe('overlay whitelist enforcement (shared-DB invariant)', () => { expect(allowedFromRegistry.has('dashboard')).toBe(true); expect(allowedFromRegistry.has('report')).toBe(true); expect(allowedFromRegistry.has('email_template')).toBe(true); - // object/field reverted to allowOrgOverride:false on 2026-05-29 + // object/field reverted to allowEnvironmentOverlay:false on 2026-05-29 // (packaged-object lock; tenants create new ones via runtime-create). expect(allowedFromRegistry.has('object')).toBe(false); expect(allowedFromRegistry.has('field')).toBe(false); - // #6283 — `flow` rolled BACK to allowOrgOverride:false. The `true` + // #6283 — `flow` rolled BACK to allowEnvironmentOverlay:false. The `true` // this line used to assert came from commit ba252da0b (see the file // header) and never had an ADR behind it: ADR-0005's amendment // table (`docs/adr/0005-metadata-customization-overlay.md:57`) has @@ -401,11 +401,11 @@ describe('overlay whitelist enforcement (shared-DB invariant)', () => { // ADR-0020: `workflow` retired as a metadata type. expect(allowedFromRegistry.has('workflow')).toBe(false); // ADR-0063 §2: tenant custom agents withdrawn — `agent` is now - // allowOrgOverride:false (no per-org agent fork). The kernel ships + // allowEnvironmentOverlay:false (no environment fork of a packaged agent). The kernel ships // exactly two platform agents; tenants extend via skills + tools. expect(allowedFromRegistry.has('agent')).toBe(false); // Commit ee58392e1 — `permission`/`position` rolled BACK to - // allowOrgOverride:false (with page/app/action/dataset/book/ + // allowEnvironmentOverlay:false (with page/app/action/dataset/book/ // tool/skill; the whole nine-type divergence family). ADR-0005's // security row has always said ❌: "Authorization correctness; // overlays would create silent privilege drift." Reintroduction diff --git a/packages/objectql/src/protocol-commit-history.test.ts b/packages/objectql/src/protocol-commit-history.test.ts index 15689f4ce4c..a7c0a60cf89 100644 --- a/packages/objectql/src/protocol-commit-history.test.ts +++ b/packages/objectql/src/protocol-commit-history.test.ts @@ -402,7 +402,7 @@ function makeRealRepoHarness(seedCommits: any[] = [], opts: { controlPlane?: boo /** * The reverted artifact is a `view`, not an `object`, and deliberately: the * repository's `assertAllowed` refuses `override-artifact` on `object` (not - * `allowOrgOverride`), and `revertCommit` — unlike `rollbackMetaItem`, which + * `allowEnvironmentOverlay`), and `revertCommit` — unlike `rollbackMetaItem`, which * derives `runtime-only` for a runtime-created artifact — passed no intent, so * an object item failed that gate BEFORE reaching the scoping this file pins. * That was a separate defect of the same family, fixed as #6563 (whose own pins @@ -535,7 +535,7 @@ describe('#6215 — revertCommit restores a PACKAGE-BOUND overlay row', () => { * no `intent`, so `SysMetadataRepository.restoreVersion` fell back to its * `?? 'override-artifact'` default, `put` opened with * `assertAllowed(ref.type, opts.intent)`, and every type that is not - * `allowOrgOverride` was refused — `object` among them. So the metadata type + * `allowEnvironmentOverlay` was refused — `object` among them. So the metadata type * Studio creates most could not be reverted through the package-commit undo AT * ALL, while the same edit reverted fine one artifact at a time through * `rollbackMetaItem`, which derives the intent instead of defaulting it. The @@ -742,7 +742,7 @@ describe('#6563 — rollbackToPackageCommit inherits the per-item intent', () => * CONSTANT — `intent: 'override-artifact'`, written into the `repo.delete(...)` * call — and `SysMetadataRepository.delete` opens with the same * `assertAllowed(ref.type, opts.intent)` gate `put` uses. So `object`, which is - * not `allowOrgOverride`, was refused on the delete path exactly as it had been + * not `allowEnvironmentOverlay`, was refused on the delete path exactly as it had been * on the restore path, and a commit that CREATED an object could not be * reverted either. * diff --git a/packages/objectql/src/protocol-delete-object-registry-heal.test.ts b/packages/objectql/src/protocol-delete-object-registry-heal.test.ts index 3c247d3db0c..32688bcaed1 100644 --- a/packages/objectql/src/protocol-delete-object-registry-heal.test.ts +++ b/packages/objectql/src/protocol-delete-object-registry-heal.test.ts @@ -226,7 +226,7 @@ describe('#6808 — deleteMetaItem stops BOTH registry exits serving a deleted o * Seed order is deliberate: the packaged artifact is registered AFTER the * overlay save. Registered first, its package provenance would make * `isArtifactBacked` true and `saveMetaItem`'s overlay gate would refuse the - * write outright (`object` is `allowOrgOverride: false`), so there would be no + * write outright (`object` is `allowEnvironmentOverlay: false`), so there would be no * overlay to delete and the pin would measure nothing. */ it('an object whose overlay shadows a packaged artifact STAYS registered (tier 1)', async () => { diff --git a/packages/objectql/src/protocol-meta-effective-schema.test.ts b/packages/objectql/src/protocol-meta-effective-schema.test.ts index a56bb518dcb..c8c934b26e0 100644 --- a/packages/objectql/src/protocol-meta-effective-schema.test.ts +++ b/packages/objectql/src/protocol-meta-effective-schema.test.ts @@ -184,7 +184,7 @@ describe.each([true, false])('[#6562] /meta object read — effective schema (mu // Seeded straight into `sys_metadata` rather than through // `saveMetaItem` — the same reason #4513's `page` row is, and the reason // is the point of this file: the subject here is the READ path, and - // `object` carries `allowOrgOverride: false`, so a save over an + // `object` carries `allowEnvironmentOverlay: false`, so a save over an // ARTIFACT-backed object is (correctly) refused with `NOT_OVERRIDABLE` // and going through it would only measure that refusal. The rows this // shape stands for are real and reachable three ways: a runtime-created diff --git a/packages/objectql/src/protocol-meta-types-rich.test.ts b/packages/objectql/src/protocol-meta-types-rich.test.ts index 2abb843eb79..a05baa2acb3 100644 --- a/packages/objectql/src/protocol-meta-types-rich.test.ts +++ b/packages/objectql/src/protocol-meta-types-rich.test.ts @@ -11,9 +11,9 @@ import { resetEnvWritableMetadataTypes } from '@objectstack/metadata-protocol'; * Validates that: * • `getMetaTypes()` returns enriched `entries` alongside the legacy * `types` array (back-compat preserved). - * • Registry metadata (label, domain, allowOrgOverride, …) flows through + * • Registry metadata (label, domain, allowEnvironmentOverlay, …) flows through * from DEFAULT_METADATA_TYPE_REGISTRY. - * • `OS_METADATA_WRITABLE` env var elevates `allowOrgOverride` + * • `OS_METADATA_WRITABLE` env var elevates `allowEnvironmentOverlay` * at runtime, and tags the entry with `overrideSource: 'env'`. * • The env-elevated set is also honoured by the saveMetaItem 403 gate. */ @@ -34,7 +34,7 @@ describe('ObjectStackProtocolImplementation - getMetaTypes rich response', () => // Register control types so getMetaTypes() includes them in `entries` // (getMetaTypes only returns types present in getRegisteredTypes()). // `hook`/`seed`/`external_catalog` are registry-default - // `allowOrgOverride: false` — used by the OS_METADATA_WRITABLE tests. + // `allowEnvironmentOverlay: false` — used by the OS_METADATA_WRITABLE tests. // (`validation` used to stand here; #4509 retired the kind.) registry.registerItem('hook', { name: 'audit_stamp' }, 'name'); registry.registerItem('seed', { name: 'demo_rows' }, 'name'); @@ -79,22 +79,22 @@ describe('ObjectStackProtocolImplementation - getMetaTypes rich response', () => expect(objectEntry).toBeDefined(); expect(objectEntry.label).toBe('Object'); expect(objectEntry.domain).toBe('data'); - // object reverted to allowOrgOverride:false on 2026-05-29 — packaged + // object reverted to allowEnvironmentOverlay:false on 2026-05-29 — packaged // objects are LOCKED at runtime; tenants must create new objects. - expect(objectEntry.allowOrgOverride).toBe(false); + expect(objectEntry.allowEnvironmentOverlay).toBe(false); expect(objectEntry.allowRuntimeCreate).toBe(true); expect(objectEntry.overrideSource).toBe('registry'); expect(objectEntry.supportsOverlay).toBe(false); const viewEntry = result.entries.find((e: any) => e.type === 'view'); expect(viewEntry).toBeDefined(); - expect(viewEntry.allowOrgOverride).toBe(true); + expect(viewEntry.allowEnvironmentOverlay).toBe(true); expect(viewEntry.domain).toBe('ui'); }); - it('honours OS_METADATA_WRITABLE to elevate allowOrgOverride', async () => { + it('honours OS_METADATA_WRITABLE to elevate allowEnvironmentOverlay', async () => { // Use `hook` and `seed` — both are registry-default - // `allowOrgOverride: false` (ADR-0088 retired the former code-only + // `allowEnvironmentOverlay: false` (ADR-0088 retired the former code-only // placeholder kinds this test used to lean on, and #4509 retired // `validation`, which stood here until then). process.env.OS_METADATA_WRITABLE = 'hook,seed'; @@ -102,17 +102,73 @@ describe('ObjectStackProtocolImplementation - getMetaTypes rich response', () => const result: any = await protocol.getMetaTypes(); const hookEntry = result.entries.find((e: any) => e.type === 'hook'); - expect(hookEntry.allowOrgOverride).toBe(true); + expect(hookEntry.allowEnvironmentOverlay).toBe(true); expect(hookEntry.overrideSource).toBe('env'); // Types not listed AND not writable in the registry default retain - // `allowOrgOverride: false`. `external_catalog` is one such type — - // runtime-created by the Sync wizard, never org-overridable. + // `allowEnvironmentOverlay: false`. `external_catalog` is one such type — + // runtime-created by the Sync wizard, never overlay-enabled. const catalogEntry = result.entries.find((e: any) => e.type === 'external_catalog'); - expect(catalogEntry.allowOrgOverride).toBe(false); + expect(catalogEntry.allowEnvironmentOverlay).toBe(false); expect(catalogEntry.overrideSource).toBe('registry'); }); + // ── [ADR-0131 C5] ONE wire spelling, on BOTH writer arms ──────────────── + // + // `getMetaTypes` has two arms: a registry type's entry SPREADS the + // registry row (which carries `allowEnvironmentOverlay` at the registry + // value) and then writes the hatch-elevated value; a runtime-registered + // type with no registry row gets a SYNTHESISED entry. `allowOrgOverride`, + // the spelling retired in 18, must be on neither: renaming only the spec + // would leave the registry arm carrying BOTH names (the new one from the + // spread, unelevated; the old one from the elevation line), and the + // synthesised arm the old one alone — and no type, gate or consumer + // reads a wire field it does not expect, so nothing else goes red. The + // elevation must land on the new key, on both arms. + it('carries allowEnvironmentOverlay and never allowOrgOverride on both arms, elevated by the hatch', async () => { + // `theme` is a runtime-registered kind with no registry row — the + // synthesised arm. `hook` is a registry row at `false` — the spread arm. + registry.registerItem('theme', { name: 'dark' }, 'name'); + process.env.OS_METADATA_WRITABLE = 'hook,theme'; + ObjectStackProtocolImplementation.resetEnvWritableCache(); + resetEnvWritableMetadataTypes(); + + const result: any = await protocol.getMetaTypes(); + const byType = (t: string) => result.entries.find((e: any) => e.type === t); + const view = byType('view'); + const hook = byType('hook'); + const theme = byType('theme'); + const object = byType('object'); + // Control: each arm is really present, so the absences below are not vacuous. + expect(theme, 'the synthesised arm is in the listing').toBeDefined(); + expect(theme.filePatterns).toEqual([]); + expect(hook, 'the registry arm is in the listing').toBeDefined(); + + // The registry value, on the registry arm. + expect(view).toMatchObject({ allowEnvironmentOverlay: true, overrideSource: 'registry' }); + expect(object).toMatchObject({ allowEnvironmentOverlay: false, overrideSource: 'registry' }); + // The hatch elevation lands on the NEW key, on both arms. + expect(hook).toMatchObject({ allowEnvironmentOverlay: true, overrideSource: 'env' }); + expect(theme).toMatchObject({ allowEnvironmentOverlay: true, overrideSource: 'env' }); + // And the retired spelling is on no entry at all. + for (const entry of [view, hook, theme, object]) { + expect(entry).not.toHaveProperty('allowOrgOverride'); + } + expect(result.entries.filter((e: any) => 'allowOrgOverride' in e)).toEqual([]); + }); + + it('a registry-less runtime type the hatch does not name carries allowEnvironmentOverlay false', async () => { + registry.registerItem('theme', { name: 'dark' }, 'name'); + delete process.env.OS_METADATA_WRITABLE; + ObjectStackProtocolImplementation.resetEnvWritableCache(); + resetEnvWritableMetadataTypes(); + + const result: any = await protocol.getMetaTypes(); + const theme = result.entries.find((e: any) => e.type === 'theme'); + expect(theme).toMatchObject({ allowEnvironmentOverlay: false, overrideSource: 'registry' }); + expect(theme).not.toHaveProperty('allowOrgOverride'); + }); + it('saveMetaItem honours the env-elevated allow list', async () => { // Scoped (project) protocol — overlay gate applies. const scoped = new ObjectStackProtocolImplementation(mockEngine, undefined, 'env_alpha'); diff --git a/packages/objectql/src/protocol-meta.test.ts b/packages/objectql/src/protocol-meta.test.ts index 7d49c13cf87..b838c972f16 100644 --- a/packages/objectql/src/protocol-meta.test.ts +++ b/packages/objectql/src/protocol-meta.test.ts @@ -20,8 +20,10 @@ describe('ObjectStackProtocolImplementation - Metadata Persistence', () => { description: 'A test application', }; - /** [#6190] An org-overridable specimen — `view` declares `allowOrgOverride`, - * so it is the type that may legitimately carry an org-scoped row. */ + /** [#6190] An overlay-enabled specimen — `view` declares + * `allowEnvironmentOverlay`, so an environment may overlay a packaged + * one; it was the type that could carry an org-scoped row before + * ADR-0131 D6. */ const sampleView = { name: 'test_grid', type: 'grid', @@ -313,7 +315,7 @@ describe('ObjectStackProtocolImplementation - Metadata Persistence', () => { // [ADR-0131 D6] The specimen is a packaged VIEW, the regime-O type // whose overlay the registry allows. It was a packaged `app` behind // the `OS_METADATA_WRITABLE=app` hatch (commit ee58392e1 rolled - // `app`'s `allowOrgOverride` back to `false`), but managed content + // `app`'s `allowEnvironmentOverlay` back to `false`), but managed content // is sealed now and the hatch no longer opens that overlay. const sampleView = { name: 'test_grid', label: 'Test Grid', object: 'account', list: { columns: ['name'] } }; registry.registerItem('view', { ...sampleView }, 'name', 'com.acme.showcase'); @@ -1368,7 +1370,7 @@ describe('ObjectStackProtocolImplementation - Metadata Persistence', () => { // ═══════════════════════════════════════════════════════════════ // ADR-0005 PR-10d.7 — two-tier metadata authorization model. // - // For types declaring `allowOrgOverride:false, allowRuntimeCreate:true` + // For types declaring `allowEnvironmentOverlay:false, allowRuntimeCreate:true` // (hook, trigger, validation), the per-item provenance decides: // // • Item exists in the SchemaRegistry tagged with `_packageId` @@ -1470,7 +1472,7 @@ describe('ObjectStackProtocolImplementation - Metadata Persistence', () => { // ⇒ writable" fall-through, i.e. through the hole #8421 closed, while // reading as a pin on the declared `allowRuntimeCreate` tier. Debt // regardless of that card's ruling. REPLACED, not deleted — `mapping` - // really does declare `allowOrgOverride: false, allowRuntimeCreate: + // really does declare `allowEnvironmentOverlay: false, allowRuntimeCreate: // true` — so the tier keeps a live specimen. it('accepts brand-new mapping and seed (allowRuntimeCreate:true)', async () => { mockEngine.findOne.mockResolvedValue(null); @@ -1903,7 +1905,7 @@ describe('ObjectStackProtocolImplementation - Metadata Persistence', () => { // The body #5271 pinned at 422. It never reaches the schema now. await expect( - // [#6190] Env-wide: `api` is `allowOrgOverride: false`, so an + // [#6190] Env-wide: `api` is `allowEnvironmentOverlay: false`, so an // org-scoped write is refused BEFORE the schema is consulted and // this case would have measured that refusal instead of the 422. scoped.saveMetaItem({ @@ -1917,7 +1919,7 @@ describe('ObjectStackProtocolImplementation - Metadata Persistence', () => { }); }); - it('artifact-backed view (allowOrgOverride:true) still overlays cleanly', async () => { + it('artifact-backed view (allowEnvironmentOverlay:true) still overlays cleanly', async () => { // Regression: types that DO allow overlays must keep working // even when the item is artifact-backed. // [#7741] the inline arm requires the object binding pair. @@ -2012,7 +2014,7 @@ describe('ObjectStackProtocolImplementation - Metadata Persistence', () => { // `loadMetaFromDb` registers DB-rehydrated objects with a // synthetic packageId of `'sys_metadata'`. Without the sentinel // filter in `isArtifactBacked`, a DB-only object would be - // misclassified as artifact-backed. Object is allowOrgOverride:true + // misclassified as artifact-backed. Object is allowEnvironmentOverlay:true // so the gate still passes either way, but this test pins down // the classification so future refactors do not regress the // semantic distinction (which matters for any future type that diff --git a/packages/objectql/src/protocol-object-overlay-layer.test.ts b/packages/objectql/src/protocol-object-overlay-layer.test.ts index 1ae32557060..01a2a774ce1 100644 --- a/packages/objectql/src/protocol-object-overlay-layer.test.ts +++ b/packages/objectql/src/protocol-object-overlay-layer.test.ts @@ -42,7 +42,7 @@ import { assertEngineFindOnePredicate } from './engine-findone-predicate.js'; * * ## The cost D9.6 declares, and the second gate it also reaches * - * With the predicate honest, `object`'s `allowOrgOverride: false` is enforced + * With the predicate honest, `object`'s `allowEnvironmentOverlay: false` is enforced * CONSISTENTLY rather than only on the first write. D9.6 states that for * `saveMetaItem`; the same predicate feeds `deleteMetaItem`'s two-tier * authorization and `SysMetadataRepository.assertAllowed`, so a tenant overlay @@ -420,7 +420,7 @@ describe('ADR-0029 D9.7 — the delete is a SUBTRACTION, and #7012\'s guard is r * A CONTROL-PLANE kernel skips `deleteMetaItem`'s two-tier authorization * entirely (`environmentId === undefined`) — but not the repository's * `assertAllowed`, which is topology-independent and refuses an - * `override-artifact` delete of a type without `allowOrgOverride`. Pinned + * `override-artifact` delete of a type without `allowEnvironmentOverlay`. Pinned * so "the tenant gate is skipped" is never mistaken for "ungated". * [ADR-0131 D6] With the hatch open too: the repository no longer lets the * hatch reach an `override-artifact` delete (it used to subtract here). diff --git a/packages/objectql/src/protocol-org-overlay-registry-gate.test.ts b/packages/objectql/src/protocol-org-overlay-registry-gate.test.ts index 505a9a77985..3d29170aa60 100644 --- a/packages/objectql/src/protocol-org-overlay-registry-gate.test.ts +++ b/packages/objectql/src/protocol-org-overlay-registry-gate.test.ts @@ -165,18 +165,20 @@ const itemNamed = (listed: { items?: unknown[] }, name: string): any => (listed?.items ?? []).find((i) => (i as { name?: string })?.name === name); describe('#6602 — the premise, read from the registry rather than restated', () => { - it('view is the specimen: a legitimately per-org-overridable type', () => { - // If a later ruling closes this flag, the leak this file pins stops - // being reachable through the overlay tier and the whole file should - // be re-read, not repaired. + it('view is the specimen: an overlay-enabled type, the tier the per-org leak rode before ADR-0131 D6', () => { + // If a later ruling closes this flag, the overlay tier this file drives + // is no longer reachable for `view` and the whole file should be + // re-read, not repaired. The flag decides no organization since + // ADR-0131 D6 (it was spelled `allowOrgOverride` while it did); the + // seam below still refuses an org-scoped row whoever produces one. expect(DEFAULT_METADATA_TYPE_REGISTRY.find((e) => e.type === 'view')).toMatchObject({ - allowOrgOverride: true, + allowEnvironmentOverlay: true, }); // `flow` reaches the same seam through the OTHER write tier: not - // per-org overridable (#6283 / commit 474f131cf) but still runtime-creatable, + // overlay-enabled (#6283 / commit 474f131cf) but still runtime-creatable, // which is the tier a Studio-authored flow uses. expect(DEFAULT_METADATA_TYPE_REGISTRY.find((e) => e.type === 'flow')).toMatchObject({ - allowOrgOverride: false, + allowEnvironmentOverlay: false, allowRuntimeCreate: true, }); }); diff --git a/packages/objectql/src/protocol-publish-canonical-fold.test.ts b/packages/objectql/src/protocol-publish-canonical-fold.test.ts index 6a7d73d1948..f2ca91896bf 100644 --- a/packages/objectql/src/protocol-publish-canonical-fold.test.ts +++ b/packages/objectql/src/protocol-publish-canonical-fold.test.ts @@ -344,7 +344,7 @@ describe('#8769 · publish addressed with a manifest-absent plural resolves the // B — the permissive PLUGIN branch is no longer taken for a declared type // ═══════════════════════════════════════════════════════════════════════════ // -// `field` declares `allowOrgOverride: false, allowRuntimeCreate: false` — it is +// `field` declares `allowEnvironmentOverlay: false, allowRuntimeCreate: false` — it is // not draftable at all. Unfolded, `'fields'` is in neither // `STATIC_REGISTRY_TYPES` nor `RUNTIME_CREATE_ALLOWED_TYPES` (both index the // manifest plural, which `field` does not have), and diff --git a/packages/objectql/src/protocol-registry-shadow.test.ts b/packages/objectql/src/protocol-registry-shadow.test.ts index b1b48c199e2..d45700ea3e4 100644 --- a/packages/objectql/src/protocol-registry-shadow.test.ts +++ b/packages/objectql/src/protocol-registry-shadow.test.ts @@ -190,7 +190,7 @@ function findByName(items: any[], name: string): any { return (items as any[]).find((it) => it?.name === name); } -// Commit ee58392e1 rolled `app`'s `allowOrgOverride` back to `false` (ADR-0005 — the +// Commit ee58392e1 rolled `app`'s `allowEnvironmentOverlay` back to `false` (ADR-0005 — the // amendment table says ❌ for `page`/`app`/`action`), so overriding the // PACKAGED app these suites are built around now needs the ONE documented // door that remains: the `OS_METADATA_WRITABLE` operator escape hatch, which @@ -323,7 +323,7 @@ describe('registry shadow — control-plane PUT → GET → DELETE keeps the art }); describe('registry shadow — scoped-kernel lock enforcement is shadow-immune', () => { - // Same commit ee58392e1 door as above: with `app` no longer allowOrgOverride, the + // Same commit ee58392e1 door as above: with `app` no longer allowEnvironmentOverlay, the // save would 403 NOT_OVERRIDABLE at the type gate and never reach the // L3 lock this case exists to prove is shadow-immune. Behind the hatch // the type gate used to pass and the LOCK refused. [ADR-0131 D6] The hatch diff --git a/packages/objectql/src/protocol-writepath-object-ownership.test.ts b/packages/objectql/src/protocol-writepath-object-ownership.test.ts index e0cb3105fb3..03d1cdb7f52 100644 --- a/packages/objectql/src/protocol-writepath-object-ownership.test.ts +++ b/packages/objectql/src/protocol-writepath-object-ownership.test.ts @@ -21,7 +21,7 @@ * `_provenance: 'package'` on any body handed to `registerObject` with a * package id and no provenance of its own; `getArtifactItem` reads exactly * that, `isArtifactBacked` turns true, and `object` declares - * `allowOrgOverride: false` — so the NEXT save of the object the user just + * `allowEnvironmentOverlay: false` — so the NEXT save of the object the user just * created answers `403 not_overridable`. The B-minimal counter-example below * reproduces that on the real registry, so the stamp is pinned by the failure * it prevents rather than by a comment. diff --git a/packages/objectql/src/publish-meta-response-conformance.test.ts b/packages/objectql/src/publish-meta-response-conformance.test.ts index 6effd87df12..cb9ce93314f 100644 --- a/packages/objectql/src/publish-meta-response-conformance.test.ts +++ b/packages/objectql/src/publish-meta-response-conformance.test.ts @@ -381,8 +381,8 @@ describe('publishMetaItem carries the runtime authoring gate\'s advisories (#917 * becomes a 422 — a refusal wearing an advisory's clothes, which would * make this case pass for the wrong reason. * - * Env-wide (`org: null`), not org-scoped: the registry declares - * `allowOrgOverride: false` for `flow`, so an org-scoped draft is refused + * Env-wide (`org: null`), not org-scoped: the protocol refuses every + * org-scoped write (ADR-0131 D6), so an org-scoped draft is refused * 403 NOT_OVERRIDABLE long before the authoring gate runs. */ const advisoryFlow = () => ({ diff --git a/packages/objectql/src/publish-package-drafts-response-conformance.test.ts b/packages/objectql/src/publish-package-drafts-response-conformance.test.ts index cb00daaeb0f..0292e9ed854 100644 --- a/packages/objectql/src/publish-package-drafts-response-conformance.test.ts +++ b/packages/objectql/src/publish-package-drafts-response-conformance.test.ts @@ -183,7 +183,7 @@ const seedBody = { object: 'sys_metadata', records: [{ name: 'row_a', type: 'vie * Stage drafts bound to PKG and publish the whole package — the REST pair * `PUT /meta/:type/:name?mode=draft` (with `packageId`) + * `POST /packages/:id/publish-drafts`. Env-wide (`organizationId` absent) on - * purpose: `flow` and `seed` declare `allowOrgOverride: false`, so an + * purpose: the protocol refuses every org-scoped write (ADR-0131 D6), so an * org-scoped draft would be refused before the door under test runs. */ async function stageDrafts( diff --git a/packages/objectql/src/save-meta-response-conformance.test.ts b/packages/objectql/src/save-meta-response-conformance.test.ts index 396be49b064..d4627249ed5 100644 --- a/packages/objectql/src/save-meta-response-conformance.test.ts +++ b/packages/objectql/src/save-meta-response-conformance.test.ts @@ -236,7 +236,7 @@ describe('saveMetaItem response conforms to SaveMetaItemResponseSchema (#5745)', // PR #5782 after being proved unreachable; the gate that made it // unreachable is the one exercised here, and it is still what keeps a // second, receipt-less write path from appearing. A type declaring - // neither `allowOrgOverride` nor `allowRuntimeCreate` (`agent`, `job`) + // neither `allowEnvironmentOverlay` nor `allowRuntimeCreate` (`agent`, `job`) // is refused outright rather than persisted without a receipt. // // This is the tripwire for the `required` decision: if that gate is @@ -305,9 +305,9 @@ describe('saveMetaItem carries the runtime authoring gate\'s advisories (#4717 it('a save whose only defect is advisory succeeds AND reports the finding', async () => { const p = await makeProtocol(); const raw: any = await p.saveMetaItem({ - // Env-wide, not org-scoped: the registry declares - // `allowOrgOverride: false` for `flow`, so an org-scoped write is - // refused 403 NOT_OVERRIDABLE long before the authoring gate runs. + // Env-wide, not org-scoped: the protocol refuses every org-scoped + // write (ADR-0131 D6), so an org-scoped write is refused 403 + // NOT_OVERRIDABLE long before the authoring gate runs. type: 'flow', name: 'nightly_purge', item: advisoryFlow(), }); diff --git a/packages/objectql/src/sys-metadata-repository.test.ts b/packages/objectql/src/sys-metadata-repository.test.ts index 7bd5497f04c..d070739684f 100644 --- a/packages/objectql/src/sys-metadata-repository.test.ts +++ b/packages/objectql/src/sys-metadata-repository.test.ts @@ -274,7 +274,7 @@ describe('SysMetadataRepository', () => { // ── whitelist enforcement (mirrors PR-10a, at the repo layer) ─── - it('put refuses non-allowOrgOverride types (trigger)', async () => { + it('put refuses non-allowEnvironmentOverlay types (trigger)', async () => { await expect( repo.put( { org: 'org_alpha', type: 'trigger', name: 'on_insert' }, @@ -284,7 +284,7 @@ describe('SysMetadataRepository', () => { ).rejects.toMatchObject({ code: 'NOT_OVERRIDABLE', status: 403 }); }); - it('put refuses non-allowOrgOverride types (datasource)', async () => { + it('put refuses non-allowEnvironmentOverlay types (datasource)', async () => { await expect( repo.put( { org: 'org_alpha', type: 'datasource', name: 'analytics' }, @@ -361,7 +361,7 @@ describe('SysMetadataRepository', () => { it('put still refuses plugin-registered type without runtime-only intent', async () => { // intent defaults to 'override-artifact'; that path requires - // allowOrgOverride, which plugin-registered types do not get + // allowEnvironmentOverlay, which plugin-registered types do not get // automatically. Keeps the artifact-shadowing security boundary // intact for plugin types until they explicitly opt in. await expect( @@ -702,7 +702,7 @@ describe('SysMetadataRepository', () => { // `intent: 'runtime-only'` — a whole-app build stages a BRAND-NEW app // (nothing artifact-backed under this name), which is exactly the // intent the protocol layer computes for it. Required since commit ee58392e1 - // rolled `app`'s `allowOrgOverride` back to `false` (ADR-0005): the + // rolled `app`'s `allowEnvironmentOverlay` back to `false` (ADR-0005): the // repository's default `override-artifact` intent now 403s for `app`, // while the runtime-create tier this write actually belongs to stays // open (`allowRuntimeCreate: true`). diff --git a/packages/platform-objects/src/audit/sys-job.global-unique.test.ts b/packages/platform-objects/src/audit/sys-job.global-unique.test.ts index ab34a3bb865..a464852293b 100644 --- a/packages/platform-objects/src/audit/sys-job.global-unique.test.ts +++ b/packages/platform-objects/src/audit/sys-job.global-unique.test.ts @@ -33,7 +33,7 @@ import { SysJob } from './sys-job.object.js'; * key; a per-organization constraint would make that lookup ambiguous * (an arbitrary org's row would win) rather than fix anything. * 2. **The `job` metadata type is closed to tenants on all three flags** — - * `allowOrgOverride: false`, `allowRuntimeCreate: false`, + * `allowEnvironmentOverlay: false`, `allowRuntimeCreate: false`, * `supportsOverlay: false`. The registry comment says the deciding half in * words: *"no per-org job fork"*. * 3. **No generic write path exists at all.** `enable.apiMethods` is @@ -43,7 +43,7 @@ import { SysJob } from './sys-job.object.js'; * installation-scoped**: `AppPlugin` on `kernel:ready` from the app * bundle's declared jobs, `JobServicePlugin` replaying those registrations, * the schedule / time-relative flow triggers (keyed `:${flowName}`, - * and `flow` is itself `allowOrgOverride: false`), the approvals and + * and `flow` is itself `allowEnvironmentOverlay: false`), the approvals and * reports plugins' fixed names, and wait-node timers keyed * `flow-wait:${runId}:${nodeId}` on a server-minted run id. * 5. **ADR-0120 names this exact key.** Its S5 row — "engine idempotency keys @@ -131,12 +131,13 @@ describe('sys_job — declared uniqueness is installation-wide (#8578)', () => { }); it('the `job` metadata type is closed to tenants on all three flags', () => { - // The deciding fact, quoted from the registry: "no per-org job fork". - // Opening `allowOrgOverride` or `allowRuntimeCreate` is exactly the + // The deciding fact, quoted from the registry: "no environment fork of a + // packaged job". + // Opening `allowEnvironmentOverlay` or `allowRuntimeCreate` is exactly the // "a tenant can register or shadow a job" branch the card named. const job = DEFAULT_METADATA_TYPE_REGISTRY.find((t: any) => t.type === 'job'); expect(job).toBeDefined(); - expect((job as any).allowOrgOverride).toBe(false); + expect((job as any).allowEnvironmentOverlay).toBe(false); expect((job as any).allowRuntimeCreate).toBe(false); expect((job as any).supportsOverlay).toBe(false); }); @@ -149,7 +150,7 @@ describe('sys_job — declared uniqueness is installation-wide (#8578)', () => { // into the cross-tenant oracle after all. const flow = DEFAULT_METADATA_TYPE_REGISTRY.find((t: any) => t.type === 'flow'); expect(flow).toBeDefined(); - expect((flow as any).allowOrgOverride).toBe(false); + expect((flow as any).allowEnvironmentOverlay).toBe(false); }); it("carries NO tenant column (ADR-0131 D7) — so `'global'` is the only scope that states the truth", () => { diff --git a/packages/plugins/plugin-security/src/packaged-permission-set-restore-leg.test.ts b/packages/plugins/plugin-security/src/packaged-permission-set-restore-leg.test.ts index 2a805d75a26..84c9aef4175 100644 --- a/packages/plugins/plugin-security/src/packaged-permission-set-restore-leg.test.ts +++ b/packages/plugins/plugin-security/src/packaged-permission-set-restore-leg.test.ts @@ -31,8 +31,9 @@ * / 403 AND the lock's own message (it names the clone path). The code * alone would not identify the gate: ADR-0005's tier gate inside * `saveMetaItem` answers with the same code for the same row, and its - * message instead says the type "has not opted into per-org overlay - * writes". Without this leg, case 2 below could pass for the wrong reason + * message instead says the item "is provided by a managed package and is + * sealed" (the double below throws the producer's sentence, ADR-0131 D6's + * managed-content seal). Without this leg, case 2 below could pass for the wrong reason * — a probe answered by a gate that is not the one under test. * * 2. THE LOCK AT THE RESTORE LEG — the same fixture, the same set, through @@ -207,8 +208,10 @@ function makeProtocol(ql: any, declaredNames: string[], tierGate: 'open' | 'clos async saveMetaItem(req: { type: string; name: string; item: any }) { if (tierGate === 'closed' && declaredNames.includes(req.name)) { const err: any = new Error( - `[not_overridable] Metadata item 'permission/${req.name}' is provided by a code package ` - + 'and the type has not opted into per-org overlay writes (allowOrgOverride=false).', + `Metadata item 'permission/${req.name}' is provided by a managed package and is sealed ` + + 'against in-place edits. Clone it under a new name to customize it (the "Clone" action on ' + + 'the permission set, or POST /api/v1/data/sys_permission_set with a new name). ' + + 'See docs/adr/0126-packaged-metadata-customization-model.md.', ); err.code = 'NOT_OVERRIDABLE'; err.status = 403; diff --git a/packages/plugins/plugin-security/src/permission-set-projection.test.ts b/packages/plugins/plugin-security/src/permission-set-projection.test.ts index 278322d28ea..ddab6b9aa30 100644 --- a/packages/plugins/plugin-security/src/permission-set-projection.test.ts +++ b/packages/plugins/plugin-security/src/permission-set-projection.test.ts @@ -8,7 +8,7 @@ * bootstrap-declared-permissions.test.ts. * * [#6858 / ADR-0094 D5-R] `makeProtocol` models ADR-0005's TIER GATE, which - * this suite used to be blind to: `permission` is `allowOrgOverride: false` + * this suite used to be blind to: `permission` is `allowEnvironmentOverlay: false` * since commit ee58392e1, so a metadata write whose (type, name) is backed by * a code ARTIFACT is refused 403 `NOT_OVERRIDABLE`, while an artifact-free * name rides `allowRuntimeCreate: true` and still lands. Commit ee58392e1 records the @@ -152,8 +152,10 @@ function makeProtocol(ql: any, declared: Record = {}) { // and packages/qa/dogfood/test/two-doors-permission.dogfood.test.ts). if (declared[req.name] !== undefined) { const err: any = new Error( - `[not_overridable] Metadata item 'permission/${req.name}' is provided by a code package ` - + 'and the type has not opted into per-org overlay writes (allowOrgOverride=false).', + `Metadata item 'permission/${req.name}' is provided by a managed package and is sealed ` + + 'against in-place edits. Clone it under a new name to customize it (the "Clone" action on ' + + 'the permission set, or POST /api/v1/data/sys_permission_set with a new name). ' + + 'See docs/adr/0126-packaged-metadata-customization-model.md.', ); err.code = 'NOT_OVERRIDABLE'; err.status = 403; @@ -555,7 +557,7 @@ describe('package-owned set customization lifecycle (ADR-0094 D5-R)', () => { it('an env-scope save on an ARTIFACT-BACKED package name is REFUSED (403 NOT_OVERRIDABLE) — no overlay, record untouched', async () => { // Was: "a Studio env-scope save on a PACKAGE name customizes the record // and keeps provenance" — the 2026-07-14 direction. Commit ee58392e1 rolled - // `permission` back to `allowOrgOverride: false` and ADR-0094 D5-R's ruling A + // `permission` back to `allowEnvironmentOverlay: false` and ADR-0094 D5-R's ruling A // accepted the tightening, so the write this case used to assert is the // write production now refuses. Rejection-class: the ENVELOPE is the // claim (`code` AND `status`), because a bare "it threw" would stay green diff --git a/packages/plugins/plugin-security/src/permission-set-projection.ts b/packages/plugins/plugin-security/src/permission-set-projection.ts index abf0d9e4932..d964a746ad8 100644 --- a/packages/plugins/plugin-security/src/permission-set-projection.ts +++ b/packages/plugins/plugin-security/src/permission-set-projection.ts @@ -1390,8 +1390,8 @@ export function createPermissionSetWriteThrough( // the producer's gate is exactly what the documented // `OS_METADATA_WRITABLE=permission` hatch switches off — so on the // deployments this card came from there is no refusal at all — and its - // message ("the type has not opted into per-org overlay writes") names - // nothing an admin can act on. It is NOT a second copy of + // message then ("the type has not opted into per-org overlay writes") + // named nothing an admin can act on. It is NOT a second copy of // `isArtifactBacked` (Prime Directive #8): it reads the engine // SchemaRegistry, the one source this plugin already calls // "package-declared" in `readDeclaredBody` above. See `packaged-permission-set-lock.ts`. diff --git a/packages/qa/dogfood/test/managed-content-sealed.dogfood.test.ts b/packages/qa/dogfood/test/managed-content-sealed.dogfood.test.ts index 91d584029be..7a2d6a09b0c 100644 --- a/packages/qa/dogfood/test/managed-content-sealed.dogfood.test.ts +++ b/packages/qa/dogfood/test/managed-content-sealed.dogfood.test.ts @@ -129,7 +129,7 @@ describe('[ADR-0131 D6] managed content is sealed — the hatch opens nothing a const r = await call('GET', '/meta/types'); const entries = (r.json.data?.entries ?? r.json.entries ?? []) as Json[]; const flow = entries.find((e) => e?.type === 'flow'); - expect(flow).toMatchObject({ allowOrgOverride: true, overrideSource: 'env' }); + expect(flow).toMatchObject({ allowEnvironmentOverlay: true, overrideSource: 'env' }); }); it('PUT of a managed flow, object (a field relabelled), field, permission set and position — 403, no row', async () => { diff --git a/packages/qa/dogfood/test/showcase-permission-projection.dogfood.test.ts b/packages/qa/dogfood/test/showcase-permission-projection.dogfood.test.ts index 0c88b8c2dbc..63972bfb8be 100644 --- a/packages/qa/dogfood/test/showcase-permission-projection.dogfood.test.ts +++ b/packages/qa/dogfood/test/showcase-permission-projection.dogfood.test.ts @@ -9,7 +9,7 @@ // the record is re-derived by the AWAITED projector — consistent on the // very next read, no race. // 2. [commit ee58392e1 inverted this pin] A data-door edit of a CODE-DECLARED set is -// REFUSED — `permission` rolled back to `allowOrgOverride: false` +// REFUSED — `permission` rolled back to `allowEnvironmentOverlay: false` // (ADR-0005 security row: "Authorization correctness; overlays would // create silent privilege drift"), so overriding an artifact-backed set // answers 403 `not_overridable` instead of minting an overlay. The @@ -104,7 +104,7 @@ describe('sys_permission_set pure projection (ADR-0094)', () => { it('editing a declared set through the data door is refused — no overlay is minted', async () => { // member_default is a platform-declared set (an artifact baseline // exists), so the write-through's `saveMetaItem` hits the ADR-0005 type - // gate: `permission` is no longer `allowOrgOverride` (commit ee58392e1, the + // gate: `permission` is no longer `allowEnvironmentOverlay` (commit ee58392e1, the // security row's "silent privilege drift"). The refusal must be LOUD — // an error status, not a 2xx that quietly skipped the metadata write — // and must leave no overlay behind (#6190's phantom-write shape is the @@ -152,7 +152,7 @@ describe('sys_permission_set pure projection (ADR-0094)', () => { const baseline = layeredBefore?.code ?? null; expect(baseline, 'the packaged declaration is the code layer').toBeTruthy(); - // Commit ee58392e1 — `permission` rolled back to `allowOrgOverride: false` + // Commit ee58392e1 — `permission` rolled back to `allowEnvironmentOverlay: false` // (ADR-0005 security row). The overlay ADR-0094's 2026-07-14 direction // used here is exactly the per-org shadowing of a code-shipped // authorization contract the ADR forbids, so the save refuses LOUDLY at diff --git a/packages/qa/dogfood/test/two-doors-permission.dogfood.test.ts b/packages/qa/dogfood/test/two-doors-permission.dogfood.test.ts index a7178dcdd40..7492c9d7300 100644 --- a/packages/qa/dogfood/test/two-doors-permission.dogfood.test.ts +++ b/packages/qa/dogfood/test/two-doors-permission.dogfood.test.ts @@ -14,7 +14,7 @@ // 2026-08-09 — see ADR-0094 D5-R): a data-plane edit of a // package-managed, ARTIFACT-BACKED row is REFUSED with 403 // `not_overridable` and no overlay is minted — commit ee58392e1 rolled -// `permission` back to `allowOrgOverride: false`, and ADR-0086 names +// `permission` back to `allowEnvironmentOverlay: false`, and ADR-0086 names // the supported channel instead: edit the package and re-publish. // A "delete" through this door still degrades to a RESET — a packaged // definition can never be removed from the environment — though with @@ -82,7 +82,7 @@ describe('two-doors permission separation (ADR-0086 P2)', () => { it('块2: an admin edit of a package-managed set is refused — no env overlay is minted (#6483)', async () => { // Before commit ee58392e1 this edit became an env-scope overlay (ADR-0094's 2026-07-14 // direction). `permission` has since rolled back to - // `allowOrgOverride: false` (ADR-0005 security row: "Authorization + // `allowEnvironmentOverlay: false` (ADR-0005 security row: "Authorization // correctness; overlays would create silent privilege drift"), so the // write-through's `saveMetaItem` refuses the artifact-backed target with // 403 `not_overridable` — loudly, at the moment of the write. ADR-0086 @@ -130,7 +130,7 @@ describe('two-doors permission separation (ADR-0086 P2)', () => { // The specimen was `member_default` until commit ee58392e1: its record is env-owned // (`managed_by` ≠ 'package'), but its METADATA identity is a platform // artifact (`defaultPermissionSets`), so with `permission` rolled back - // to `allowOrgOverride: false` the write-through's `saveMetaItem` now + // to `allowEnvironmentOverlay: false` the write-through's `saveMetaItem` now // refuses to overlay it — the record's provenance column was never what // the gate reads. A truly env-AUTHORED set (created through the data // door, definition living only in `sys_metadata`) rides the diff --git a/packages/rest/src/execctx-consumer-census.test.ts b/packages/rest/src/execctx-consumer-census.test.ts index b5253bbd213..d28b99daf93 100644 --- a/packages/rest/src/execctx-consumer-census.test.ts +++ b/packages/rest/src/execctx-consumer-census.test.ts @@ -1059,18 +1059,19 @@ async function sweepSelective(failOrdinal: number, doc: any, type: string) { const listRow = (rows: Row[]) => rows.find((r) => r.route.startsWith('GET ') && r.route.endsWith('/meta/:type')); -// An ORG-OVERRIDABLE type, so the org-scope question is not vacuous — asserted -// against the registry in the first CONTROL rather than trusted here. +// One of the five overlay-enabled types — the set an organization reached a +// read for before ADR-0131 D6 — so the org-scope question is not vacuous; +// asserted against the registry in the first CONTROL rather than trusted here. const ORG_SCOPED_TYPE = 'dashboard'; const ORG_SCOPED_DOC = { name: 'ops', type: 'dashboard', widgets: [] }; describe('[#13538] §9 a PARTIAL outage is not served as an org-unscoped 200', () => { - it('CONTROL: this section drives an ORG-OVERRIDABLE type; the type §8 drives is env-wide BY DESIGN', () => { + it('CONTROL: this section drives an overlay-enabled type (org-scoped before D6); the type §8 drives is env-wide BY DESIGN', () => { // The second half is why §8's green is not coverage of this card: on // `object` no organization was ever read, so no fault model whatsoever // could make §8 observe an org-scope difference on the type it sweeps. const declares = (type: string) => - DEFAULT_METADATA_TYPE_REGISTRY.find((e) => e.type === type)?.allowOrgOverride === true; + DEFAULT_METADATA_TYPE_REGISTRY.find((e) => e.type === type)?.allowEnvironmentOverlay === true; expect({ driven: declares(ORG_SCOPED_TYPE), section8: declares(OBJECT_DOC.type) }) .toEqual({ driven: true, section8: false }); }); diff --git a/packages/rest/src/meta-publish-package-scope.test.ts b/packages/rest/src/meta-publish-package-scope.test.ts index de4982efa99..1d5e90850bf 100644 --- a/packages/rest/src/meta-publish-package-scope.test.ts +++ b/packages/rest/src/meta-publish-package-scope.test.ts @@ -65,7 +65,7 @@ const PUBLISH = `${META}/:type/:name/publish`; const ORG = 'org_alpha'; const PKG = 'app.projects'; -/** `allowOrgOverride: true`, so the org limb is stable across these cases. */ +/** One of the five overlay-enabled types, so the org limb is stable across these cases. */ const TYPE = 'views'; function mockServer() { diff --git a/packages/rest/src/rest-meta-managed-seal-hatch.test.ts b/packages/rest/src/rest-meta-managed-seal-hatch.test.ts index 4f460fe93ad..70d017ebe1f 100644 --- a/packages/rest/src/rest-meta-managed-seal-hatch.test.ts +++ b/packages/rest/src/rest-meta-managed-seal-hatch.test.ts @@ -92,7 +92,7 @@ function boot(environmentId: string | undefined) { const entries: any[] = res._json?.entries ?? []; return Object.fromEntries(types.map((t) => { const e = entries.find((x) => x.type === t); - return [t, e ? { allowOrgOverride: e.allowOrgOverride, overrideSource: e.overrideSource } : 'absent']; + return [t, e ? { allowEnvironmentOverlay: e.allowEnvironmentOverlay, overrideSource: e.overrideSource } : 'absent']; })); }; return { call, listing }; @@ -154,7 +154,7 @@ describe('[#22411] the removed spelling OBJECTSTACK_METADATA_WRITABLE is not rea // The types this registry lists (`field` is named in the variable but is no registered type here: // its items live inside an object, so the listing carries no entry for it). const HATCH_TYPES = ['flow', 'object', 'permission', 'position'] as const; - const SHUT = { allowOrgOverride: false, overrideSource: 'registry' }; + const SHUT = { allowEnvironmentOverlay: false, overrideSource: 'registry' }; for (const environmentId of [undefined, 'env_1']) { const kernel = environmentId ? 'environment' : 'host-config'; @@ -182,7 +182,7 @@ describe('[#22411] the removed spelling OBJECTSTACK_METADATA_WRITABLE is not rea open(); const { listing } = boot(environmentId); expect(await listing(HATCH_TYPES)).toEqual( - Object.fromEntries(HATCH_TYPES.map((t) => [t, { allowOrgOverride: true, overrideSource: 'env' }])), + Object.fromEntries(HATCH_TYPES.map((t) => [t, { allowEnvironmentOverlay: true, overrideSource: 'env' }])), ); }); } diff --git a/packages/rest/src/rest-meta-packaged-flow-refusal.test.ts b/packages/rest/src/rest-meta-packaged-flow-refusal.test.ts index 1891737c516..a1a5064d2fd 100644 --- a/packages/rest/src/rest-meta-packaged-flow-refusal.test.ts +++ b/packages/rest/src/rest-meta-packaged-flow-refusal.test.ts @@ -114,7 +114,7 @@ describe('PUT / DELETE /api/v1/meta/flow/:name on a packaged flow — the refusa expect(r.code).toBe('NOT_OVERRIDABLE'); expect(r.message).toBe( `Metadata item 'page/${PACKAGED_PAGE}' is provided by a managed package and is sealed against in-place edits: ` - + 'its type takes no environment overlay (allowOrgOverride=false), and OS_METADATA_WRITABLE does not open ' + + 'its type takes no environment overlay (allowEnvironmentOverlay=false), and OS_METADATA_WRITABLE does not open ' + 'a managed item. Edit the source artifact and redeploy. ' + 'See docs/adr/0131-total-organization-ownership-no-null-organization-id.md.', ); diff --git a/packages/rest/src/rest-server-meta-cached-etag-door-scope.test.ts b/packages/rest/src/rest-server-meta-cached-etag-door-scope.test.ts index c897b28f0a8..d8eafacab9a 100644 --- a/packages/rest/src/rest-server-meta-cached-etag-door-scope.test.ts +++ b/packages/rest/src/rest-server-meta-cached-etag-door-scope.test.ts @@ -61,10 +61,10 @@ const META = '/api/v1/meta'; const ORG_A = 'org_alpha'; const ORG_B = 'org_beta'; -/** `allowOrgOverride: false` — the door must reduce the organization away. */ +/** Outside the five overlay-enabled types — the door must reduce the organization away. */ const NON_OVERRIDABLE = 'object'; -/** `allowOrgOverride: true`, and the type that takes the CACHED arm. */ +/** One of the five overlay-enabled types, and the type that takes the CACHED arm. */ const OVERRIDABLE = 'view'; const NAME = 'shared_document'; diff --git a/packages/rest/src/rest-server-meta-history-diff-org-scope.test.ts b/packages/rest/src/rest-server-meta-history-diff-org-scope.test.ts index 9a451605e2a..167e149d5b4 100644 --- a/packages/rest/src/rest-server-meta-history-diff-org-scope.test.ts +++ b/packages/rest/src/rest-server-meta-history-diff-org-scope.test.ts @@ -28,11 +28,11 @@ const META = '/api/v1/meta'; const ORG_A = 'org_alpha'; const ORG_B = 'org_beta'; -/** The five types the registry declares `allowOrgOverride: true`. */ -const ORG_OVERRIDABLE = ['view', 'dashboard', 'report', 'translation', 'email_template'] as const; +/** The five overlay-enabled types — the doors' org-scoped set before ADR-0131 D6. */ +const PRE_D6_ORG_SCOPED = ['view', 'dashboard', 'report', 'translation', 'email_template'] as const; /** - * `allowOrgOverride: false` **and** `allowRuntimeCreate: true` — the + * Outside the five **and** `allowRuntimeCreate: true` — the * combination that makes this the discriminating control rather than * decoration. Its writes have always landed ENV-WIDE, even for a session with * an active org, so its history lives in the env partition. A type that could @@ -353,7 +353,7 @@ describe('#13406 · ADR-0131 D6 the /history and /diff read doors read the envir beforeEach(() => { b = boot(); }); describe('⭐ fixture first — an org-active author\'s PUT logs environment-wide', () => { - it.each(ORG_OVERRIDABLE)( + it.each(PRE_D6_ORG_SCOPED)( '%s: a PUT under an active org appends an ENV history row, and none org-scoped', async (type) => { const written = await b.put(type, 'authored_at_runtime'); @@ -366,7 +366,7 @@ describe('#13406 · ADR-0131 D6 the /history and /diff read doors read the envir }); describe('/history serves the environment change log to every caller', () => { - it.each(ORG_OVERRIDABLE)('%s: the events come back for the author\'s org, another org and none', async (type) => { + it.each(PRE_D6_ORG_SCOPED)('%s: the events come back for the author\'s org, another org and none', async (type) => { await b.put(type, 'authored_at_runtime'); await b.put(type, 'authored_at_runtime', MARKER_2); expect(b.historyRowsFor(type, 'authored_at_runtime', null).length).toBe(2); @@ -401,7 +401,7 @@ describe('#13406 · ADR-0131 D6 the /history and /diff read doors read the envir }); describe('/diff resolves the environment revisions', () => { - it.each(ORG_OVERRIDABLE)('%s: ?from=1&to=2 compares the two revisions, for every caller', async (type) => { + it.each(PRE_D6_ORG_SCOPED)('%s: ?from=1&to=2 compares the two revisions, for every caller', async (type) => { await b.put(type, 'two_revisions'); await b.put(type, 'two_revisions', MARKER_2); expect(b.historyRowsFor(type, 'two_revisions', null).map((h) => h.version)).toEqual([1, 2]); diff --git a/packages/rest/src/rest-server-meta-org-scope-url-spelling.test.ts b/packages/rest/src/rest-server-meta-org-scope-url-spelling.test.ts index 5bc0c056ecd..84b3f2c9f79 100644 --- a/packages/rest/src/rest-server-meta-org-scope-url-spelling.test.ts +++ b/packages/rest/src/rest-server-meta-org-scope-url-spelling.test.ts @@ -56,7 +56,7 @@ const ORG = 'org_alpha'; // The two measured members of the disagreement set (both named in commit 26f3588fb, // re-derived at head by execution): URL-only spellings of -// `allowOrgOverride: true` types, invisible to the manifest map. +// overlay-enabled types, invisible to the manifest map. const MEMBERS = [ { plural: 'translations', singular: 'translation' }, { plural: 'email_templates', singular: 'email_template' }, @@ -239,8 +239,8 @@ describe('#10340 · ADR-0131 D6 — no spelling of any type carries an organizat it('⛔ still leaves a non-overridable type env-wide under every spelling', async () => { // The guard against over-folding: the fold must change WHICH type - // is judged, never the judgement itself. `object` is - // `allowOrgOverride: false`, and an org-scoped write of it is the + // is judged, never the judgement itself. `object` is outside the + // five overlay-enabled types, and an org-scoped write of it is the // phantom row #6190 stopped minting. for (const spelling of ['object', 'objects']) { const request = await writeWith(spelling); diff --git a/packages/rest/src/rest-server-meta-read-org-scope.test.ts b/packages/rest/src/rest-server-meta-read-org-scope.test.ts index 7d06bb77125..74c66665d55 100644 --- a/packages/rest/src/rest-server-meta-read-org-scope.test.ts +++ b/packages/rest/src/rest-server-meta-read-org-scope.test.ts @@ -34,18 +34,20 @@ const ORG_A = 'org_alpha'; const ORG_B = 'org_beta'; /** - * The five types the registry declares `allowOrgOverride: true`. Listed as + * The five overlay-enabled types — those the registry declared + * `allowOrgOverride: true` while that flag scoped a read to an organization + * (`allowEnvironmentOverlay` since 18, which decides no organization). Listed as * literals rather than derived, deliberately: the point of the card is that all * five must be SERVED, so a registry change that drops one should turn this red * and be looked at, not silently shrink the pin's coverage. */ -const ORG_OVERRIDABLE = ['view', 'dashboard', 'report', 'translation', 'email_template'] as const; +const PRE_D6_ORG_SCOPED = ['view', 'dashboard', 'report', 'translation', 'email_template'] as const; /** The two whose REST branches differ — the trap, named. */ const CACHED_ARM = 'view'; // takes `getMetaItemCached` const UNCACHED_ARM = 'dashboard'; // bypasses it via `isDashboardType` -/** `allowOrgOverride: false` — must keep reading env-wide, never org-scoped. */ +/** Outside the five — must keep reading env-wide, never org-scoped. */ const NON_OVERRIDABLE = 'object'; /** The value every read assertion looks for. */ @@ -419,7 +421,7 @@ describe('#9454 · ADR-0131 D6 every REST /meta read door serves what the write beforeEach(() => { b = boot(); }); describe('write-then-read agreement on one boot, both branches', () => { - it.each(ORG_OVERRIDABLE)( + it.each(PRE_D6_ORG_SCOPED)( '%s: the 200 state:active receipt is answered by the direct GET', async (type) => { const written = await b.put(type, 'authored_at_runtime'); @@ -433,7 +435,7 @@ describe('#9454 · ADR-0131 D6 every REST /meta read door serves what the write }, ); - it.each(ORG_OVERRIDABLE)( + it.each(PRE_D6_ORG_SCOPED)( '%s: the listing contains it too', async (type) => { await b.put(type, 'authored_at_runtime'); @@ -456,7 +458,7 @@ describe('#9454 · ADR-0131 D6 every REST /meta read door serves what the write }); describe('⭐ the row is environment-wide, and so is who it is served to', () => { - it.each(ORG_OVERRIDABLE)('%s: an org-active author\'s write persists with organization_id NULL', async (type) => { + it.each(PRE_D6_ORG_SCOPED)('%s: an org-active author\'s write persists with organization_id NULL', async (type) => { const written = await b.put(type, 'partitioned'); expect(written.status, `PUT answered ${written.status} ${JSON.stringify(written.body)}`).toBe(200); expect(storedRowsFor(b.rows, type, 'partitioned', null).length, 'nothing landed env-wide').toBe(1); @@ -561,7 +563,7 @@ describe('#13753 · ADR-0131 D6 GET /meta/diagnostics sweeps the environment par let b: ReturnType; beforeEach(() => { b = boot(); }); - it.each(ORG_OVERRIDABLE)('%s: an org-active author\'s item is counted by the ?type= arm', async (type) => { + it.each(PRE_D6_ORG_SCOPED)('%s: an org-active author\'s item is counted by the ?type= arm', async (type) => { const written = await b.put(type, 'authored_at_runtime'); expect(written.status, `PUT /${type} was not accepted`).toBe(200); const swept = await b.diagnostics({ type }); diff --git a/packages/rest/src/rest-server-meta-write-org-scope.test.ts b/packages/rest/src/rest-server-meta-write-org-scope.test.ts index 03b2a89ff63..826e9b166fc 100644 --- a/packages/rest/src/rest-server-meta-write-org-scope.test.ts +++ b/packages/rest/src/rest-server-meta-write-org-scope.test.ts @@ -30,9 +30,9 @@ import { RestServer } from './rest-server.js'; const META = '/api/v1/meta'; const ORG = 'org_alpha'; -/** `allowOrgOverride: true` — until ADR-0131 D6, written into the caller's organization. */ +/** One of the five overlay-enabled types — until ADR-0131 D6, written into the caller's organization. */ const OVERRIDABLE = 'views'; -/** `allowOrgOverride: false` — always written environment-wide. */ +/** Outside the five — always written environment-wide. */ const ENV_WIDE = 'object'; function mockServer() { diff --git a/packages/runtime/src/domains/meta-read-org-scope-parity.test.ts b/packages/runtime/src/domains/meta-read-org-scope-parity.test.ts index ffc7348cc42..9b8a0ba1c20 100644 --- a/packages/runtime/src/domains/meta-read-org-scope-parity.test.ts +++ b/packages/runtime/src/domains/meta-read-org-scope-parity.test.ts @@ -37,9 +37,10 @@ * ## The protocol double * * It models the read gate `metadata-protocol` applied before ADR-0131 D6 - * (an organization reached a read only for a type the registry declared - * `allowOrgOverride`), so a transport that still handed down an organization - * would be answered that organization's overlay — the double is the detector. + * (an organization reached a read only for one of the five overlay-enabled + * types, whose registry flag was then spelled `allowOrgOverride`), so a + * transport that still handed down an organization would be answered that + * organization's overlay — the double is the detector. * The real protocol reads no organization at all since D6; what this file * measures is that neither transport sends one. */ @@ -76,13 +77,18 @@ const ENV_DRAFTS: any[] = [{ type: 'view', name: 'env_board', label: 'Env board const clone = (v: T): T => JSON.parse(JSON.stringify(v)); -/** The types the registry declares `allowOrgOverride` — the pre-D6 gate the double models. */ -const ORG_OVERRIDABLE = new Set(DEFAULT_METADATA_TYPE_REGISTRY.filter((e) => e.allowOrgOverride).map((e) => e.type)); +/** + * The set the pre-D6 gate the double models scoped to an organization: the five + * overlay-enabled types. Read off today's `allowEnvironmentOverlay`, which marks + * the same five and decides no organization (ADR-0131 D6) — it is the set, not + * the meaning, that the double borrows. + */ +const PRE_D6_ORG_SCOPED = new Set(DEFAULT_METADATA_TYPE_REGISTRY.filter((e) => e.allowEnvironmentOverlay).map((e) => e.type)); function protocolDouble() { const gate = (type: string, organizationId: unknown) => { const t = canonicalMetaUrlType(type); - return typeof organizationId === 'string' && ORG_OVERRIDABLE.has(t) ? organizationId : undefined; + return typeof organizationId === 'string' && PRE_D6_ORG_SCOPED.has(t) ? organizationId : undefined; }; const resolve = (type: string, name: string, organizationId: unknown) => { const t = canonicalMetaUrlType(type); diff --git a/packages/runtime/src/domains/packages-seed-apply-org-scope.test.ts b/packages/runtime/src/domains/packages-seed-apply-org-scope.test.ts index 139bc7827b4..bdd4241b2ab 100644 --- a/packages/runtime/src/domains/packages-seed-apply-org-scope.test.ts +++ b/packages/runtime/src/domains/packages-seed-apply-org-scope.test.ts @@ -25,9 +25,11 @@ * * `getMetaItem` reads no organization at all (ADR-0131 D6: every metadata * read is environment → code; before it, commit d5cbb44f3's gate already - * dropped an organization for `seed`, which declares `allowOrgOverride: - * false`). ⇒ `{ type:'seed', name, organizationId }` and `{ type:'seed', name }` - * differ in a field the callee provably drops. + * dropped an organization for `seed`, one of the types outside the five that + * gate scoped to an organization: `seed` declares `allowEnvironmentOverlay: + * false`, then spelled `allowOrgOverride`). ⇒ `{ type:'seed', name, + * organizationId }` and `{ type:'seed', name }` differ in a field the callee + * provably drops. * * ⛔ The repair is NOT to restore org-awareness to this read. An org-scoped * `seed` row is the unhydratable phantom `reportUnhydratableOrgScopedRows` @@ -403,7 +405,7 @@ describe('#15068 · 1 · the two rungs resolve to one read', () => { it('`seed` is non-overridable — and since ADR-0131 D6 no read takes an organization', () => { // The registry fact the card rests on, read from the registry rather // than restated. - expect(DEFAULT_METADATA_TYPE_REGISTRY.find((e) => e.type === 'seed')?.allowOrgOverride) + expect(DEFAULT_METADATA_TYPE_REGISTRY.find((e) => e.type === 'seed')?.allowEnvironmentOverlay) .toBe(false); }); diff --git a/packages/runtime/src/meta-field-overlay-lock.test.ts b/packages/runtime/src/meta-field-overlay-lock.test.ts index 5b03b07146f..a4a30ec2317 100644 --- a/packages/runtime/src/meta-field-overlay-lock.test.ts +++ b/packages/runtime/src/meta-field-overlay-lock.test.ts @@ -3,7 +3,7 @@ /** * #7743 — the `field` overlay lock, pinned at the LIVE ROUTE. * - * The registry declares `field` with `allowOrgOverride: false`, and a field a + * The registry declares `field` with `allowEnvironmentOverlay: false`, and a field a * code package ships is an artifact. Yet * `PUT /api/v1/meta/field/showcase_task.title` answered **200** * `state:'active'`, and the row read back with `_diagnostics.valid=true` — @@ -549,7 +549,7 @@ describe('#7743 — PUT /meta/field/. honours the registry overla expect(metaRow(engine, 'object', PACKAGED_OBJECT.name)).toBeUndefined(); }); - it('CONTROL — `view` is still ACCEPTED (allowOrgOverride: true)', async () => { + it('CONTROL — `view` is still ACCEPTED (allowEnvironmentOverlay: true)', async () => { const { engine, dispatcher } = makeStack(); const res = responseOf(await dispatcher.handleMetadata( @@ -560,7 +560,7 @@ describe('#7743 — PUT /meta/field/. honours the registry overla expect(metaRow(engine, 'view', VIEW.name)).toBeDefined(); }); - it('CONTROL — `dashboard` is still ACCEPTED (allowOrgOverride: true)', async () => { + it('CONTROL — `dashboard` is still ACCEPTED (allowEnvironmentOverlay: true)', async () => { const { engine, dispatcher } = makeStack(); const res = responseOf(await dispatcher.handleMetadata( @@ -573,7 +573,7 @@ describe('#7743 — PUT /meta/field/. honours the registry overla it('CONTROL — `job` is still refused as code-only, with its own sentence', async () => { // `job` is the OTHER tier (`allowRuntimeCreate: false` AND - // `allowOrgOverride: false`), refused by #5086's block before this + // `allowEnvironmentOverlay: false`), refused by #5086's block before this // card's predicate is consulted at all. Same code, different reason — // pinned so a change to one tier cannot quietly move the other. const { engine, dispatcher } = makeStack(); diff --git a/packages/runtime/src/meta-managed-content-seal.test.ts b/packages/runtime/src/meta-managed-content-seal.test.ts index 34a51504419..867b9346e21 100644 --- a/packages/runtime/src/meta-managed-content-seal.test.ts +++ b/packages/runtime/src/meta-managed-content-seal.test.ts @@ -326,7 +326,7 @@ describe('[#22411] the dispatcher /meta door: the type listing and the save door const listed = (type: string) => { const entry = entries.find((e) => e.type === type); expect(entry, `${type} is listed`).toBeDefined(); - return { allowOrgOverride: entry.allowOrgOverride, overrideSource: entry.overrideSource }; + return { allowEnvironmentOverlay: entry.allowEnvironmentOverlay, overrideSource: entry.overrideSource }; }; const save = async (type: string, body: { name: string }) => { const res = responseOf(await dispatcher.handleMetadata(`/${type}/${body.name}`, ctx(), 'PUT', body)); @@ -338,7 +338,7 @@ describe('[#22411] the dispatcher /meta door: the type listing and the save door }; } - const SHUT = { allowOrgOverride: false, overrideSource: 'registry' }; + const SHUT = { allowEnvironmentOverlay: false, overrideSource: 'registry' }; const REFUSED = { status: 403, code: 'NOT_CREATABLE', row: false }; for (const environmentId of [undefined, 'env_1']) { @@ -359,7 +359,7 @@ describe('[#22411] the dispatcher /meta door: the type listing and the save door ObjectStackProtocolImplementation.resetEnvWritableCache(); resetEnvWritableMetadataTypes(); const both = await readBoth(environmentId); - expect(both.job.listed).toEqual({ allowOrgOverride: true, overrideSource: 'env' }); + expect(both.job.listed).toEqual({ allowEnvironmentOverlay: true, overrideSource: 'env' }); expect({ status: both.job.saved.status, row: both.job.saved.row }).toEqual({ status: 200, row: true }); expect(both.picklist).toEqual({ listed: SHUT, saved: REFUSED }); }); diff --git a/packages/runtime/src/meta-overlay-read-your-writes.test.ts b/packages/runtime/src/meta-overlay-read-your-writes.test.ts index 0c26eb9cd1b..83bb58e6669 100644 --- a/packages/runtime/src/meta-overlay-read-your-writes.test.ts +++ b/packages/runtime/src/meta-overlay-read-your-writes.test.ts @@ -202,7 +202,7 @@ describe('#4521 — read-your-writes between saveMeta and the dispatch path', () // `restoreArtifactRegistryView` removes on delete. Pinned here because // the write-through is what makes that separation load-bearing. // - // Commit ee58392e1 rolled `action`'s `allowOrgOverride` back to `false` + // Commit ee58392e1 rolled `action`'s `allowEnvironmentOverlay` back to `false` // (ADR-0005: page/app/action are ❌ in the amendment table), so // overriding this PACKAGED action needs the one documented door that // remains — the `OS_METADATA_WRITABLE` operator escape hatch. The @@ -367,7 +367,7 @@ describe('#5079 — list / get / dispatch agree immediately after deleteMeta', ( // "reset to artifact default" would delete the artifact instead of // revealing it. // - // Commit ee58392e1 rolled `action`'s `allowOrgOverride` back to `false` + // Commit ee58392e1 rolled `action`'s `allowEnvironmentOverlay` back to `false` // (ADR-0005: page/app/action are ❌ in the amendment table), so // overriding this PACKAGED action needs the one documented door that // remains — the `OS_METADATA_WRITABLE` operator escape hatch, exactly diff --git a/packages/runtime/src/meta-write-org-scope.test.ts b/packages/runtime/src/meta-write-org-scope.test.ts index 1b52976651f..06432d13ea5 100644 --- a/packages/runtime/src/meta-write-org-scope.test.ts +++ b/packages/runtime/src/meta-write-org-scope.test.ts @@ -2,7 +2,7 @@ /** * [ADR-0131 D6, C5 stage S3] The dispatcher threads NO organization into a - * metadata write: every type, an `allowOrgOverride: true` one included, lands + * metadata write: every type, one of the five overlay-enabled types included, lands * environment-wide (`organization_id` NULL) whatever the session's active * organization. The per-organization overlay axis is retired; the CONTROL cases * that used to pin `view` landing org-scoped are the stage's pins now, and they @@ -10,7 +10,8 @@ * * History, kept below for the reverse-verification record it carries: * #7018 — the runtime threads the session's organization into a metadata WRITE - * only for types the registry declares `allowOrgOverride: true`. + * only for types the registry declared `allowOrgOverride: true` (the flag is + * `allowEnvironmentOverlay` since 18 and decides no organization). * * This is the runtime half of the #6190 ruling (2026-08-09, Option A). Before * it, both dispatcher write sites threaded `resolveActiveOrganizationId` @@ -297,7 +298,7 @@ const metaRow = (engine: any, type: string, name: string) => // decision is ever reached, which would pass these tests for the wrong reason. // --------------------------------------------------------------------------- -/** `allowOrgOverride: false`, `allowRuntimeCreate: true` — the #6190 specimen. */ +/** Outside the five overlay-enabled types, `allowRuntimeCreate: true` — the #6190 specimen. */ const FLOW = { name: 'escalate_overdue', label: 'Escalate overdue tasks', @@ -315,7 +316,7 @@ const FLOW = { edges: [{ id: 'e1', source: 'start', target: 'end' }], }; -/** `allowOrgOverride: true` — the control. Its org scoping must NOT change. */ +/** One of the five overlay-enabled types — the control. Its org scoping must NOT change. */ const VIEW = { name: 'overdue_grid', label: 'Overdue', @@ -324,7 +325,7 @@ const VIEW = { columns: [{ field: 'name', label: 'Name' }], }; -/** `allowOrgOverride: false` — the ADR-0045 publish-visibility specimen. */ +/** Outside the five overlay-enabled types — the ADR-0045 publish-visibility specimen. */ const APP = { name: 'crm', label: 'CRM' }; /** @@ -412,7 +413,7 @@ describe('#7018 — the registry decides whether a metadata write carries the se expect(a.body.data).toMatchObject({ success: true, state: 'active' }); }); - it('⭐ [ADR-0131 D6] an `allowOrgOverride: true` type lands env-wide too, and the read serves it', async () => { + it('⭐ [ADR-0131 D6] an overlay-enabled type lands env-wide too, and the read serves it', async () => { const { engine, dispatcher } = makeStack(ACTIVE_ORG); const res = responseOf(await dispatcher.handleMetadata(`/view/${VIEW.name}`, ctx(ACTIVE_ORG), 'PUT', VIEW)); @@ -513,7 +514,8 @@ describe('#7018 — the registry decides whether a metadata write carries the se * spelling map, for STORAGE. The dispatcher handed the same string RAW to * `organizationIdForMetaWrite`, whose `declaresOrgOverride` tolerates only the * MANIFEST-collection spellings — incomplete by design. For the two URL-only - * spellings of `allowOrgOverride: true` types the two answers diverged: + * spellings of the types the registry then declared `allowOrgOverride: true` + * the two answers diverged: * * PUT /metadata/translation/:name → org-scoped row (correct) * PUT /metadata/translations/:name → env-wide row (the defect) @@ -523,7 +525,7 @@ describe('#7018 — the registry decides whether a metadata write carries the se * One item, two partitions, addressed by spelling. `translations` has no * manifest key at all; `email_template`'s manifest key is the camelCase * `emailTemplates`, so the snake_case plural the registry derivation adds is - * URL-only too. Both fold to an `allowOrgOverride: true` type in storage. + * URL-only too. Both fold to one of those types in storage. * * The `/published` branch is the smaller second site of the same class: after * the layered overlay consult misses, the fallback reads the code/package @@ -569,7 +571,7 @@ describe('#10503 · ADR-0131 D6 no spelling carries an organization through the }); // The two measured members of the disagreement set: URL-only spellings of - // `allowOrgOverride: true` types, invisible to the manifest map. Each + // overlay-enabled types, invisible to the manifest map. Each // carries a schema-VALID body — a minimal one 422s before the scoping // decision is ever reached, which would pass these tests for the wrong // reason (the lesson the specimens above this block were built on). @@ -665,7 +667,7 @@ describe('#10503 · ADR-0131 D6 no spelling carries an organization through the it('⛔ a NON-overridable type stays env-wide under its plural spelling too', async () => { // The fold must change which type is judged, never the judgement. - // `object` is `allowOrgOverride: false`, and an org-scoped write of it + // `object` is outside the five overlay-enabled types, and an org-scoped write of it // is exactly the phantom row #6190 stopped minting — a row cold boot // walks past, after which every record 404s. The singular is pinned by // the #7018 case above; this is the spelling the fold newly reaches. diff --git a/packages/runtime/src/package-duplicate-adopt-org-scope.integration.test.ts b/packages/runtime/src/package-duplicate-adopt-org-scope.integration.test.ts index 2f5ae965c99..66af5f4424a 100644 --- a/packages/runtime/src/package-duplicate-adopt-org-scope.integration.test.ts +++ b/packages/runtime/src/package-duplicate-adopt-org-scope.integration.test.ts @@ -211,7 +211,9 @@ describe('#7819 tier 2 — duplicatePackage must copy the source’s env-wide ro // case that makes the defect a corruption rather than an omission. // // ⭐ And this case is why widening the SCAN alone was not a fix. `object` - // is declared `allowOrgOverride=false`, so stamping the request's org on + // was outside the five types the pre-D6 doors let an organization write + // (its registry flag, `allowEnvironmentOverlay` since 18, was false), so + // stamping the request's org on // the copy is refused outright with `NOT_OVERRIDABLE` — measured, with the // scan widened and the write still request-scoped, this answered // `failed: [{type: 'object', name: 'iojn_widget', …}]`. An `object` cannot diff --git a/packages/spec/authorable-defaults/kernel.json b/packages/spec/authorable-defaults/kernel.json index c06defe52a9..be3bbedbb4b 100644 --- a/packages/spec/authorable-defaults/kernel.json +++ b/packages/spec/authorable-defaults/kernel.json @@ -67,7 +67,7 @@ "kernel/MetadataQuery:pageSize = 50", "kernel/MetadataQuery:sortBy = \"name\"", "kernel/MetadataQuery:sortOrder = \"asc\"", - "kernel/MetadataTypeRegistryEntry:allowOrgOverride = false", + "kernel/MetadataTypeRegistryEntry:allowEnvironmentOverlay = false", "kernel/MetadataTypeRegistryEntry:allowRuntimeCreate = true", "kernel/MetadataTypeRegistryEntry:executionPinned = false", "kernel/MetadataTypeRegistryEntry:loadOrder = 100", diff --git a/packages/spec/authorable-surface/kernel.json b/packages/spec/authorable-surface/kernel.json index 2d331ad6c24..ba66c3024b9 100644 --- a/packages/spec/authorable-surface/kernel.json +++ b/packages/spec/authorable-surface/kernel.json @@ -344,7 +344,8 @@ "kernel/MetadataQueryResult:pageSize", "kernel/MetadataQueryResult:total", "kernel/MetadataTypeRegistryEntry:actions", - "kernel/MetadataTypeRegistryEntry:allowOrgOverride", + "kernel/MetadataTypeRegistryEntry:allowEnvironmentOverlay", + "kernel/MetadataTypeRegistryEntry:allowOrgOverride [RETIRED]", "kernel/MetadataTypeRegistryEntry:allowRuntimeCreate", "kernel/MetadataTypeRegistryEntry:description", "kernel/MetadataTypeRegistryEntry:domain", diff --git a/packages/spec/src/data/picklist.test.ts b/packages/spec/src/data/picklist.test.ts index 9e5f4c333f5..f6e969d42ca 100644 --- a/packages/spec/src/data/picklist.test.ts +++ b/packages/spec/src/data/picklist.test.ts @@ -200,7 +200,7 @@ describe('picklist — a registered kind and a stack collection', () => { it('is package-owned: no runtime create, no per-org overlay; `*.picklist.ts` is the prescription', () => { expect(entry!.allowRuntimeCreate).toBe(false); - expect(entry!.allowOrgOverride).toBe(false); + expect(entry!.allowEnvironmentOverlay).toBe(false); expect(entry!.filePatterns[0]).toBe('**/*.picklist.ts'); }); diff --git a/packages/spec/src/kernel/capability-metadata-kind.test.ts b/packages/spec/src/kernel/capability-metadata-kind.test.ts index 57209950e84..cd39679d338 100644 --- a/packages/spec/src/kernel/capability-metadata-kind.test.ts +++ b/packages/spec/src/kernel/capability-metadata-kind.test.ts @@ -37,7 +37,7 @@ * resources REQUIRE them. An administrator minting a brand-new capability at * runtime has no place in that separation — nothing in code would ever require * the name, so the row is an unreferenced grant target in the authorization - * namespace. Paired with `allowOrgOverride: false` this is #5086's CODE-ONLY + * namespace. Paired with `allowEnvironmentOverlay: false` this is #5086's CODE-ONLY * declaration, which `saveMetaItem` enforces on every kernel. * * The enforcement half lives in @@ -80,13 +80,13 @@ describe('capability is a registered metadata kind', () => { it('has a registry entry, and it declares the kind CODE-ONLY', () => { // The pair is the declaration: `allowRuntimeCreate: false` AND - // `allowOrgOverride: false` together are #5086's "no runtime write channel + // `allowEnvironmentOverlay: false` together are #5086's "no runtime write channel // at all". Either one alone leaves a door open — which is why they are // asserted together rather than in two cases. expect(entry('capability')).toMatchObject({ domain: 'security', supportsOverlay: false, - allowOrgOverride: false, + allowEnvironmentOverlay: false, allowRuntimeCreate: false, }); }); diff --git a/packages/spec/src/kernel/metadata-create-seeds.test.ts b/packages/spec/src/kernel/metadata-create-seeds.test.ts index 625ca4d020c..015ea546d8d 100644 --- a/packages/spec/src/kernel/metadata-create-seeds.test.ts +++ b/packages/spec/src/kernel/metadata-create-seeds.test.ts @@ -57,7 +57,7 @@ describe('metadata create seeds validate against their spec schemas', () => { 'app', 'seed', 'job', 'datasource', 'doc', 'book', 'permission', 'position', 'agent', 'tool', 'skill', 'email_template', // [#7893] `field` is code-only by declaration as of 2026-08-12 - // (`allowRuntimeCreate: false` + `allowOrgOverride: false`, ADR-0049 + // (`allowRuntimeCreate: false` + `allowEnvironmentOverlay: false`, ADR-0049 // remove side): a standalone `field` write minted a row no read path // ever composed into its parent object, so there is no runtime create // surface for a create seed to seed. A pre-filled "New Field" form whose @@ -67,7 +67,7 @@ describe('metadata create seeds validate against their spec schemas', () => { // `capability` / `api`, not deferred work. 'field', // [#5961] `capability` is code-only by declaration - // (`allowRuntimeCreate: false` + `allowOrgOverride: false`, ADR-0066 D1): + // (`allowRuntimeCreate: false` + `allowEnvironmentOverlay: false`, ADR-0066 D1): // there is no runtime create surface for a create seed to seed. It is on // this list for `job`/`agent`'s reason, not as deferred work. 'capability', diff --git a/packages/spec/src/kernel/metadata-customization-retirement.test.ts b/packages/spec/src/kernel/metadata-customization-retirement.test.ts index 795f19d4b0b..86d77abae82 100644 --- a/packages/spec/src/kernel/metadata-customization-retirement.test.ts +++ b/packages/spec/src/kernel/metadata-customization-retirement.test.ts @@ -55,7 +55,7 @@ describe('paper metadata-customization protocol retirement', () => { 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(/allowEnvironmentOverlay/s); expect(issue!.message).toMatch(/ADR-0126/s); }); diff --git a/packages/spec/src/kernel/metadata-type-api-registration.test.ts b/packages/spec/src/kernel/metadata-type-api-registration.test.ts index d3acf15602f..180ecb443c5 100644 --- a/packages/spec/src/kernel/metadata-type-api-registration.test.ts +++ b/packages/spec/src/kernel/metadata-type-api-registration.test.ts @@ -26,7 +26,7 @@ * * ## …and WHICH channel the shape door stands in (updated by #5488) * - * `api` is now CODE-ONLY (`allowRuntimeCreate: false` + `allowOrgOverride: + * `api` is now CODE-ONLY (`allowRuntimeCreate: false` + `allowEnvironmentOverlay: * false`, maintainer ruling 2026-08-07T16:59Z). The declaration schema is * therefore consulted on the ARTIFACT route — stack compile, `publishPackage`, * loader ingest — and no longer on `PUT /api/v1/meta/api/:name`, which the @@ -141,7 +141,7 @@ describe('`api` registry flags — the authorization verdict, written down', () // 403 `NOT_CREATABLE` instead of 200 "Saved" — in draft mode too, since the // inlet runs before the draft/publish branch and does not look at `mode`. const entry = apiEntry()!; - const codeOnly = entry.allowRuntimeCreate === false && entry.allowOrgOverride === false; + const codeOnly = entry.allowRuntimeCreate === false && entry.allowEnvironmentOverlay === false; expect(codeOnly).toBe(true); }); @@ -154,11 +154,11 @@ describe('`api` registry flags — the authorization verdict, written down', () expect(apiEntry()!.filePatterns[0]).toMatch(/\.api\./); }); - it('declares `allowOrgOverride: false` — an endpoint is the publisher’s outward URL', () => { - // A per-org fork could move `path`, flip `authRequired` or drop + it('declares `allowEnvironmentOverlay: false` — an endpoint is the publisher’s outward URL', () => { + // An environment fork could move `path`, flip `authRequired` or drop // `rateLimit` on a URL third parties have already integrated against. // ADR-0005 defaults the flag to false so opting in is deliberate. - expect(apiEntry()!.allowOrgOverride).toBe(false); + expect(apiEntry()!.allowEnvironmentOverlay).toBe(false); }); it('declares `executionPinned: false` — the pinned artifact is the target flow', () => { diff --git a/packages/spec/src/kernel/metadata-type-field-registration.test.ts b/packages/spec/src/kernel/metadata-type-field-registration.test.ts index b6173164c2c..e55a056b761 100644 --- a/packages/spec/src/kernel/metadata-type-field-registration.test.ts +++ b/packages/spec/src/kernel/metadata-type-field-registration.test.ts @@ -87,15 +87,15 @@ describe('`field` registry flags — the authorization verdict, written down', ( // 403 `NOT_CREATABLE` instead of 200 "Saved" — in draft mode too, since the // inlet runs before the draft/publish branch and does not look at `mode`. const entry = fieldEntry()!; - const codeOnly = entry.allowRuntimeCreate === false && entry.allowOrgOverride === false; + const codeOnly = entry.allowRuntimeCreate === false && entry.allowEnvironmentOverlay === false; expect(codeOnly).toBe(true); }); - it('declares `allowOrgOverride: false` — UNCHANGED, the field overlay refusal stays', () => { + it('declares `allowEnvironmentOverlay: false` — UNCHANGED, the field overlay refusal stays', () => { // Binding carry-over of the 2026-08-12 ruling. This card closed the create // tier; the override tier was already closed and must not move in either // direction as a side effect. - expect(fieldEntry()!.allowOrgOverride).toBe(false); + expect(fieldEntry()!.allowEnvironmentOverlay).toBe(false); }); it('leaves `supportsOverlay: false` alone — flipping it would widen the DELETE gate', () => { diff --git a/packages/spec/src/kernel/metadata-type-registry-allow-org-override-absence.test.ts b/packages/spec/src/kernel/metadata-type-registry-allow-org-override-absence.test.ts new file mode 100644 index 00000000000..623ba2f8ec3 --- /dev/null +++ b/packages/spec/src/kernel/metadata-type-registry-allow-org-override-absence.test.ts @@ -0,0 +1,140 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import ts from 'typescript'; +import { describe, expect, it } from 'vitest'; + +// The read-side leg of the `allowOrgOverride` → `allowEnvironmentOverlay` +// rename; the parse, registry and registration legs are +// `metadata-type-registry-allow-org-override-retirement.test.ts`, which says +// why this one exists. It reads outside `packages/spec`, so it is listed in +// `vitest.repo-tests.json` and runs under `test:repo`. + +// ─── Tree-scoped absence of a CODE reference, with a DECLARED radius ───────── +// +// The walk reads `packages/**/*.ts` and `examples/*/src/**/*.ts`, both declared +// for `@objectstack/spec#test:repo` in `scripts/cross-package-test-inputs.mjs` +// and mirrored in `turbo.json`, so a reader of the old key anywhere in the +// radius puts this suite into `turbo ls --affected`. `.tsx` is not scanned and +// not declared, for the reason the compliance-families pin records (no +// cross-package hint may reach a `.tsx` test); a `.tsx` reader belongs to the +// sibling Studio repository, which declares its own response type. +// +// What counts is an IDENTIFIER, or a quoted property key / element access, +// read off the TypeScript AST — never a comment or a string, which record the +// key's history (CHANGELOG-shaped prose, quoted former refusal sentences, the +// tombstone's own prescription, the retired-keys entry). One reference is +// legitimate by SHAPE, not by site: the tombstone declaration itself, a +// property whose initializer is a `retiredKey(...)` call. This file is +// excluded because its refusal leg authors the old key on purpose. +describe('tree-scoped absence: no TypeScript source in the declared radius reads or writes `allowOrgOverride`', () => { + const SPEC_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..'); + const REPO_ROOT = path.resolve(SPEC_ROOT, '../..'); + const THIS_FILE = path.relative(REPO_ROOT, fileURLToPath(import.meta.url)).split(path.sep).join('/'); + const OLD = 'allowOrgOverride'; + const NEW = 'allowEnvironmentOverlay'; + + /** The walked roots — declared in `scripts/cross-package-test-inputs.mjs` under `@objectstack/spec`. */ + const WALK_ROOTS = ['packages', 'examples']; + const IN_RADIUS = (rel: string) => + (rel.startsWith('packages/') && rel.endsWith('.ts')) + || /^examples\/[^/]+\/src\/.+\.ts$/.test(rel); + /** Build, SCM and cache state — not authored sources. */ + const SKIPPED_DIRS = new Set(['node_modules', 'dist', '.git', '.turbo', '.cache', '.objectstack', 'coverage', '.next', '.source']); + + /** The tombstone declaration: `allowOrgOverride: retiredKey(...)`. Exempt by shape. */ + const isTombstoneDeclaration = (node: ts.Node): boolean => + ts.isPropertyAssignment(node.parent) + && node.parent.name === node + && ts.isCallExpression(node.parent.initializer) + && ts.isIdentifier(node.parent.initializer.expression) + && node.parent.initializer.expression.text === 'retiredKey'; + + /** Every CODE reference to `name` in `text`, as `line:col` — identifiers and quoted keys, never comments or prose strings. */ + const codeReferences = (fileName: string, text: string, name: string): string[] => { + const sf = ts.createSourceFile(fileName, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS); + const found: string[] = []; + const at = (node: ts.Node) => { + const { line, character } = sf.getLineAndCharacterOfPosition(node.getStart(sf)); + found.push(`${line + 1}:${character + 1}`); + }; + const visit = (node: ts.Node) => { + if (ts.isIdentifier(node) && node.text === name && !isTombstoneDeclaration(node)) at(node); + else if (ts.isStringLiteral(node) && node.text === name && node.parent) { + const p = node.parent; + // A quoted key or an element access is a reference; any other string is prose. + if ((ts.isPropertyAssignment(p) && p.name === node) + || (ts.isElementAccessExpression(p) && p.argumentExpression === node) + || (ts.isPropertySignature(p) && p.name === node)) at(node); + } + ts.forEachChild(node, visit); + }; + visit(sf); + return found; + }; + + it('the matcher sees a code reference and ignores a mention (anti-vacuity)', () => { + const refs = (src: string) => codeReferences('probe.ts', src, OLD); + expect(refs('const open = types.filter((e) => e.allowOrgOverride);')).toHaveLength(1); + expect(refs('if (!entry?.allowOrgOverride) continue;')).toHaveLength(1); + expect(refs("expect(e).toMatchObject({ allowOrgOverride: true });")).toHaveLength(1); + expect(refs("const v = e['allowOrgOverride'];")).toHaveLength(1); + expect(refs("const { allowOrgOverride } = entry;")).toHaveLength(1); + expect(refs('// the registry declared `allowOrgOverride: true` before 18')).toHaveLength(0); + expect(refs("const s = 'renamed from allowOrgOverride';")).toHaveLength(0); + expect(refs("expect(e).not.toHaveProperty('allowOrgOverride');")).toHaveLength(0); + expect(refs('const t = `kernel/MetadataTypeRegistryEntry:allowOrgOverride`;')).toHaveLength(0); + // The one legitimate reference, exempt by shape — and only that shape. + expect(refs("const S = z.object({ allowOrgOverride: retiredKey('…') });")).toHaveLength(0); + expect(refs("const S = z.object({ allowOrgOverride: z.boolean() });")).toHaveLength(1); + }); + + it('no code reference survives inside the declared radius', () => { + const offenders: string[] = []; + const newReaders = new Set(); + let visited = 0; + const walk = (dir: string) => { + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, entry.name); + const rel = path.relative(REPO_ROOT, full).split(path.sep).join('/'); + if (entry.isDirectory()) { + if (SKIPPED_DIRS.has(entry.name) || entry.name.startsWith('.')) continue; + walk(full); + continue; + } + if (!entry.isFile() || !IN_RADIUS(rel) || rel === THIS_FILE) continue; + visited += 1; + let text: string; + try { + text = fs.readFileSync(full, 'utf-8'); + } catch (err) { + // A path that vanished mid-walk (tsup's transient bundled config) + // cannot be a reference that survives; every other fault is real. + if ((err as NodeJS.ErrnoException)?.code === 'ENOENT') continue; + throw err; + } + if (text.includes(NEW) && codeReferences(rel, text, NEW).length > 0) newReaders.add(rel); + if (!text.includes(OLD)) continue; + for (const where of codeReferences(rel, text, OLD)) offenders.push(`${rel}:${where}`); + } + }; + for (const root of WALK_ROOTS) walk(path.join(REPO_ROOT, root)); + + // Anti-vacuity: the walk covered the tree, and it SEES code references — + // the hand-renamed readers outside this package are found by the same + // matcher under the new name. + expect(visited).toBeGreaterThan(1000); + expect([...newReaders]).toEqual(expect.arrayContaining([ + 'packages/metadata-protocol/src/protocol.ts', + 'packages/metadata-protocol/src/sys-metadata-repository.ts', + 'packages/spec/src/kernel/metadata-plugin.zod.ts', + ])); + expect( + offenders, + 'a code reference to `allowOrgOverride` reads `undefined` against the tombstone and closes every type — read `allowEnvironmentOverlay`', + ).toEqual([]); + }); +}); diff --git a/packages/spec/src/kernel/metadata-type-registry-allow-org-override-retirement.test.ts b/packages/spec/src/kernel/metadata-type-registry-allow-org-override-retirement.test.ts new file mode 100644 index 00000000000..df44dc6e9aa --- /dev/null +++ b/packages/spec/src/kernel/metadata-type-registry-allow-org-override-retirement.test.ts @@ -0,0 +1,97 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +import { describe, expect, it } from 'vitest'; + +import { + DEFAULT_METADATA_TYPE_REGISTRY, + MetadataTypeRegistryEntrySchema, +} from './metadata-plugin.zod'; +import { RETIRED_KEYS_BY_MAJOR } from '../migrations/registry'; + +// ─── `MetadataTypeRegistryEntry.allowOrgOverride` → `allowEnvironmentOverlay` ── +// +// ADR-0131 C5's spec half. ADR-0131 D6 retired the per-organization overlay +// axis, so the registry flag that said "per-org" now decides one +// environment-scope question at every door — whether an environment may +// overlay an item a managed package ships — and is renamed to say that. Ruling +// C on the C5 split: no second key, and not the old name with a new meaning. +// The maintainer's ruling on the mechanism (Q1 A): a `retiredKey()` tombstone, +// the retired-keys entry and a D3 semantic entry, no D2 conversion — no +// manifest, stack or stored row can carry a registry entry. The wire field of +// `GET /meta/types` moves in the same landing (Q2 A); its pin is +// `packages/objectql/src/protocol-meta-types-rich.test.ts`. +// +// The four legs below, and why each exists: +// +// 1. The PARSE refuses the old key with the rename (the +// `customizationPolicies` pin's shape: the issue code, the path naming +// WHICH key, and the prescription text). +// 2. The REGISTRY: every row spells the new key, none spells the old one, +// and the true set is exactly the five that opted in before the rename. +// 3. The REGISTRATION: the retired-keys entry is the exact string the +// build's gate reads. +// 4. The READ-SIDE TRAP, tree-scoped. The tombstone fails `tsc` for every +// WRITER of the old key, but a truthiness READER (`if (!e.allowOrgOverride)`, +// a `filter`, a `toMatchObject`) still compiles — the tombstone's output +// is an optional `undefined` — and reads `undefined`, which treats every +// type as closed: measured as 403 `NOT_OVERRIDABLE` on all five types. +// No type error, gate or consumer would say so. So this leg walks every +// TypeScript source in the declared radius and refuses ANY code +// reference to the old name — a comment or a string is a mention, not a +// reference, and is left to the history it records. That leg reads +// outside this package, so it lives in its own `repo`-project file: +// `metadata-type-registry-allow-org-override-absence.test.ts`. +describe('MetadataTypeRegistryEntry.allowOrgOverride is renamed to allowEnvironmentOverlay', () => { + const base = { type: 'view', label: 'View', filePatterns: ['**/*.view.ts'], domain: 'ui' } as const; + + it('REJECTS an authored `allowOrgOverride`, naming the key and carrying the rename', () => { + const result = MetadataTypeRegistryEntrySchema.safeParse({ ...base, allowOrgOverride: true }); + + 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] === 'allowOrgOverride'); + expect(issue, 'the refusal must name `allowOrgOverride`').toBeDefined(); + expect(issue!.code).toBe('invalid_type'); + expect(issue!.path).toEqual(['allowOrgOverride']); + // The prescription IS the migration doc for whoever hits it. + expect(issue!.message).toMatch( + /`MetadataTypeRegistryEntry\.allowOrgOverride` was renamed to `allowEnvironmentOverlay` in @objectstack\/spec 18/s, + ); + expect(issue!.message).toMatch(/Rename the key to `allowEnvironmentOverlay`; the value is unchanged/s); + // `false` is refused exactly like `true`: the key is gone, not one value of it. + expect(MetadataTypeRegistryEntrySchema.safeParse({ ...base, allowOrgOverride: false }).success).toBe(false); + }); + + it('parses the new key, defaults it to false, and grows no old key back', () => { + const opted = MetadataTypeRegistryEntrySchema.parse({ ...base, allowEnvironmentOverlay: true }); + expect(opted.allowEnvironmentOverlay).toBe(true); + expect(opted).not.toHaveProperty('allowOrgOverride'); + + const defaulted = MetadataTypeRegistryEntrySchema.parse({ ...base }); + expect(defaulted.allowEnvironmentOverlay).toBe(false); // control: defaults still apply + expect(defaulted).not.toHaveProperty('allowOrgOverride'); + }); + + it('every built-in row spells the new key, none the old one, and the true set is the five', () => { + const lacking = DEFAULT_METADATA_TYPE_REGISTRY + .filter((e) => typeof e.allowEnvironmentOverlay !== 'boolean') + .map((e) => e.type); + expect(lacking, 'every row states the flag as a boolean').toEqual([]); + const oldSpelling = DEFAULT_METADATA_TYPE_REGISTRY + .filter((e) => Object.prototype.hasOwnProperty.call(e, 'allowOrgOverride')) + .map((e) => e.type); + expect(oldSpelling, 'no row carries the retired spelling').toEqual([]); + // The values did not move with the name: exactly the five that opted in. + expect( + DEFAULT_METADATA_TYPE_REGISTRY.filter((e) => e.allowEnvironmentOverlay === true).map((e) => e.type).sort(), + ).toEqual(['dashboard', 'email_template', 'report', 'translation', 'view']); + // Control: the registry is the whole universe, not an empty array. + expect(DEFAULT_METADATA_TYPE_REGISTRY.length).toBeGreaterThan(20); + }); + + it('is registered in RETIRED_KEYS_BY_MAJOR[18] under the exact surface spelling', () => { + expect(RETIRED_KEYS_BY_MAJOR[18]).toContain('kernel/MetadataTypeRegistryEntry:allowOrgOverride'); + expect(RETIRED_KEYS_BY_MAJOR[18]).not.toContain('kernel/MetadataTypeRegistryEntry:allowEnvironmentOverlay'); + }); +}); diff --git a/packages/spec/vitest.repo-tests.json b/packages/spec/vitest.repo-tests.json index aded290beee..c1bc81d5162 100644 --- a/packages/spec/vitest.repo-tests.json +++ b/packages/spec/vitest.repo-tests.json @@ -39,6 +39,7 @@ "src/integration/connector-resilience-keys-retirement.test.ts", "src/integration/connector-sync-retirement.test.ts", "src/integration/connector-triggers-retirement.test.ts", + "src/kernel/metadata-type-registry-allow-org-override-absence.test.ts", "src/qa/requires-plugins-retirement.test.ts", "src/security/rls-tags-retirement.test.ts", "src/shared/retired-key-migrate-sentence.test.ts", From 22a0bdc3106bbf48ee0d52527e97106e9a0234d0 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 11 Oct 2026 13:45:07 +0000 Subject: [PATCH 03/15] docs,tooling: the overlay whitelist table, checker column and docs name allowEnvironmentOverlay; changeset Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude --- ...2340-registry-allow-environment-overlay.md | 39 +++++++++++++ content/docs/ai/agents.mdx | 4 +- content/docs/api/declarative-endpoints.mdx | 2 +- content/docs/api/metadata-api.mdx | 2 +- content/docs/automation/jobs.mdx | 2 +- content/docs/concepts/metadata-lifecycle.mdx | 24 ++++---- .../docs/deployment/environment-variables.mdx | 2 +- .../kernel/contracts/metadata-service.mdx | 10 ++-- content/docs/permissions/authorization.mdx | 2 +- content/docs/permissions/capabilities.mdx | 4 +- .../docs/plugins/adding-a-metadata-type.mdx | 6 +- content/docs/protocol/objectui/concept.mdx | 9 +-- docs/qa/platform-checklist/FOLLOW-UPS.md | 4 +- docs/qa/platform-checklist/RUNNER.md | 2 +- docs/qa/platform-checklist/areas/ai.json | 4 +- .../platform-checklist/areas/automation.json | 2 +- .../areas/integration-system.json | 2 +- .../areas/platform-core.json | 10 ++-- .../areas/records-forms.json | 4 +- .../areas/studio-authoring.json | 32 +++++----- packages/spec/liveness/README.md | 4 +- packages/spec/liveness/capability.json | 2 +- packages/spec/liveness/job.json | 2 +- ...ty__src__permission-set-projection.ts.json | 2 +- ...__src__kernel__metadata-plugin.zod.ts.json | 2 +- scripts/check-overlay-whitelist-table.mjs | 58 ++++++++++--------- 26 files changed, 141 insertions(+), 95 deletions(-) create mode 100644 .changeset/22340-registry-allow-environment-overlay.md diff --git a/.changeset/22340-registry-allow-environment-overlay.md b/.changeset/22340-registry-allow-environment-overlay.md new file mode 100644 index 00000000000..d3aa9b528bb --- /dev/null +++ b/.changeset/22340-registry-allow-environment-overlay.md @@ -0,0 +1,39 @@ +--- +'@objectstack/spec': major +'@objectstack/metadata-protocol': major +--- + +feat(spec,metadata-protocol)!: the metadata type registry's `allowOrgOverride` is renamed to `allowEnvironmentOverlay`, on the registry and on `GET /meta/types` + +Clause-②: yes (narrowing) + + + +**BREAKING**: a renamed authorable key and a renamed response field, shipped as `major` on the v18 line (`.changeset/pre.json` is open on `main` in `next` pre mode, so the release is `18.0.0-next.*`). + +**Why.** ADR-0131 D6 retired the per-organization overlay axis: every metadata write and read is environment-wide. The registry flag kept the name that said "per-org" while it decided one environment-scope question at every door — whether an environment may overlay an item of the type that a managed package ships: the metadata protocol's save door, the managed-content seal on write and on the served read, the repository's put gate, and Studio's write gate through `GET /meta/types`. It is renamed to say that. One key, not two, and not the old name with a new meaning; no window in which both spellings are accepted or served. + +## FROM → TO + +| where | you wrote / read | write / read instead | +|:--|:--|:--| +| a `DEFAULT_METADATA_TYPE_REGISTRY` entry (`MetadataTypeRegistryEntry`, `@objectstack/spec/kernel`) | `allowOrgOverride: true` | `allowEnvironmentOverlay: true` | +| a reader of a registry entry | `entry.allowOrgOverride` | `entry.allowEnvironmentOverlay` | +| each `entries[]` item of `GET /api/v1/meta/types` (`GetMetaTypesResponse`) | `allowOrgOverride` | `allowEnvironmentOverlay` | + +**The one-line fix: rename the key, and every read of it; the value is unchanged.** + +Every value is unchanged: `true` on `view`, `dashboard`, `report`, `translation` and `email_template`, `false` on every other built-in type. On the wire the value is still the registry's grant OR the `OS_METADATA_WRITABLE` hatch naming the type (`overrideSource: 'env'`), and the hatch still opens only items no managed package ships, never a managed one. `overrideSource` keeps its `registry` / `env` values; its describe now names the new field. + +⚠️ **Read the old key nowhere.** Writing `allowOrgOverride` fails to compile (its input type is `never`) and fails to parse with the rename prescription (issue code `invalid_type` at path `allowOrgOverride`). A READER does not fail: `if (!entry.allowOrgOverride)`, a filter on it or a partial-match assertion still compiles against the tombstone and reads `undefined`, which treats every type as closed to environment overlays. A client that declares its own type for the `/meta/types` response, instead of importing `GetMetaTypesResponse`, sees no compile error at all and reads every type as locked. Search for the old name, do not wait for `tsc`. + +**`supportsOverlay` keeps its name; its describes now say what it gates.** It decides one thing: a stored overlay row of an item a managed package ships may be removed through the ordinary delete door without the type's environment-overlay permission, because removing it restores the managed definition. It opens no save and it decides no read. The registry's describe, the `GET /meta/types` describe and `MetadataTypeInfoResponse`'s describe used to promise that "the loader can merge overlays", in the retired per-organization wording. + +**Who is affected, measured.** No manifest, stack, plugin configuration or stored row can carry a registry entry (`contributes.kinds` is closed, `config.additionalTypes` is a tombstone, the registry is a TypeScript literal), so no authored metadata changes. The readers are code: every reader in this repository moved in this change, and a reader of the `/meta/types` field outside it moves with it — the Studio console reads the field and moves in its own repository's paired change, which the console pin carries. + +### The kit + +- **The tombstone.** `MetadataTypeRegistryEntry.allowOrgOverride` is a `retiredKey()` naming `allowEnvironmentOverlay` (`kernel/metadata-plugin.zod.ts`); the 28 built-in rows spell the new key. +- **The ledger.** The retired-keys entry `kernel/MetadataTypeRegistryEntry:allowOrgOverride` and the D3 semantic entry `metadata-type-registry-allow-org-override-renamed` (protocol 18), which records the registry key and the wire field together. No D2 conversion: there is no authored or stored source a conversion could rewrite. +- **The wire.** `getMetaTypes` writes the hatch elevation onto `allowEnvironmentOverlay` on both of its arms — a registry type and a runtime-registered type with no registry row — and no entry carries `allowOrgOverride`. +- **The refusal sentences** that named the flag name the new key: `… takes no environment overlay (allowEnvironmentOverlay=false) …`, `… has neither allowEnvironmentOverlay nor allowRuntimeCreate …`, `… allowRuntimeCreate=false and allowEnvironmentOverlay=false …`. Codes and statuses are unchanged. diff --git a/content/docs/ai/agents.mdx b/content/docs/ai/agents.mdx index a87100160d6..fe41842b49c 100644 --- a/content/docs/ai/agents.mdx +++ b/content/docs/ai/agents.mdx @@ -117,7 +117,7 @@ assistant to *write* your metadata and never run. Same word, two layers. `*.agent.ts` is **closed to third parties** — the `agent` metadata type is -`allowRuntimeCreate:false, allowOrgOverride:false`, reserved for the two platform +`allowRuntimeCreate:false, allowEnvironmentOverlay:false`, reserved for the two platform agents and platform-owned subagents (ADR-0063 §2). To give the `ask` agent a new capability you author an agent **skill** (`*.skill.ts`). Every skill declares `surface: 'ask' | 'build' | 'both'`, and an agent's tool set is the **union of its @@ -240,7 +240,7 @@ LLM-callable. The agent definitions below illustrate agent **anatomy** — how an `AgentSchema` record is shaped (that schema and the `defineAgent` factory are part of the open `@objectstack/spec/ai` package). Note that the `agent` metadata type is -`allowRuntimeCreate:false` / `allowOrgOverride:false` (ADR-0063 §2): the **cloud / +`allowRuntimeCreate:false` / `allowEnvironmentOverlay:false` (ADR-0063 §2): the **cloud / Enterprise** in-product runtime honors only the two platform agents `ask` and `build`, so you do not ship a custom agent *to that runtime* — you extend it by authoring a **skill** (open metadata) that attaches to `ask`. In the open edition, diff --git a/content/docs/api/declarative-endpoints.mdx b/content/docs/api/declarative-endpoints.mdx index 041baf5bfbc..c04765211db 100644 --- a/content/docs/api/declarative-endpoints.mdx +++ b/content/docs/api/declarative-endpoints.mdx @@ -280,7 +280,7 @@ PUT /api/v1/meta/api/acme_lead_feed → 403 {"code":"NOT_CREATABLE"} ``` The `api` metadata type is registered with `allowRuntimeCreate: false` and -`allowOrgOverride: false`, and that refusal runs **before any body validation** — so a +`allowEnvironmentOverlay: false`, and that refusal runs **before any body validation** — so a perfectly valid endpoint body gets the same `403`, in `?mode=draft` as well as direct writes. There is no per-organization overlay of an endpoint either. diff --git a/content/docs/api/metadata-api.mdx b/content/docs/api/metadata-api.mdx index 895ffccdb17..efcd29fbb03 100644 --- a/content/docs/api/metadata-api.mdx +++ b/content/docs/api/metadata-api.mdx @@ -59,7 +59,7 @@ Create or update a metadata item. Creates are gated per type: `MetadataTypeRegistryEntry.allowRuntimeCreate` decides whether a **brand-new** name may be minted under that type at all, separately from -whether an item that already exists may be overlaid (`allowOrgOverride`). A type that +whether an item that already exists may be overlaid (`allowEnvironmentOverlay`). A type that declines the create half still accepts updates to names that already exist, and its refusal — `403 NOT_CREATABLE` — names the route that does work. The flags are documented on diff --git a/content/docs/automation/jobs.mdx b/content/docs/automation/jobs.mdx index 5b2cc6e048f..e6e23b61b4b 100644 --- a/content/docs/automation/jobs.mdx +++ b/content/docs/automation/jobs.mdx @@ -42,7 +42,7 @@ writes carry*: | | `job` | `schedule`-type flow | |:---|:---|:---| | What runs | a sandboxed `body`, a mapping `pull`, or (deprecated) one TypeScript function from `defineStack({ functions })` | a node graph — record operations, `notify`, `http`, approvals, subflows | -| Changeable after deploy | **No.** `job` is `allowRuntimeCreate: false` and `allowOrgOverride: false` — there is no "create job" in Studio and no per-tenant fork | Yes — a new flow can be authored through Studio / `PUT /meta` (`allowRuntimeCreate: true`) | +| Changeable after deploy | **No.** `job` is `allowRuntimeCreate: false` and `allowEnvironmentOverlay: false` — there is no "create job" in Studio and no per-tenant fork | Yes — a new flow can be authored through Studio / `PUT /meta` (`allowRuntimeCreate: true`) | | Identity of its data writes | system, in the job's declared [`organization`](#the-organization-a-job-runs-as) | declared by [`runAs`](/docs/automation/flows) — and a `user` run that resolves no trigger user has its data operations **refused**, so a scheduled flow normally declares `runAs: 'system'` | | Retry / time limit | `retryPolicy` + `timeoutMs` on the job, honoured by the job adapter | the flow's own error handling | | Run history | `sys_job` + `sys_job_run` | `sys_automation_run` | diff --git a/content/docs/concepts/metadata-lifecycle.mdx b/content/docs/concepts/metadata-lifecycle.mdx index f6123e63565..39f3a18b4a7 100644 --- a/content/docs/concepts/metadata-lifecycle.mdx +++ b/content/docs/concepts/metadata-lifecycle.mdx @@ -73,7 +73,7 @@ any production path: every `new LayeredRepository(...)` call site is a test When a user edits a view in Studio: 1. Studio calls `PUT /api/v1/meta/view/case_grid` (REST). -2. `protocol.ts:saveMetaItem()` runs the two-tier gate (see [overlay whitelist](#overlay-whitelist-shared-db-tenancy-invariant)): an item that already exists as a packaged artifact requires `MetadataTypeRegistryEntry.allowOrgOverride`; a brand-new item requires `allowRuntimeCreate` **or** `allowOrgOverride`. +2. `protocol.ts:saveMetaItem()` runs the two-tier gate (see [overlay whitelist](#overlay-whitelist-shared-db-tenancy-invariant)): an item that already exists as a packaged artifact requires `MetadataTypeRegistryEntry.allowEnvironmentOverlay`; a brand-new item requires `allowRuntimeCreate` **or** `allowEnvironmentOverlay`. 3. If allowed, the call lands on `MetadataRepository.put(ref, body, { parentVersion, actor })`. 4. The repository: - Verifies `parentVersion` matches the current head (`ConflictError` on mismatch). @@ -104,29 +104,29 @@ later ships. See [ADR-0070](https://github.com/objectstack-ai/objectstack/blob/m ## Overlay whitelist (shared-DB tenancy invariant) -In shared-database multi-tenancy, **most metadata types must not be per-org customizable** — overriding them would break the physical schema. The whitelist lives in **one** place: `MetadataTypeRegistryEntry.allowOrgOverride` in `packages/spec/src/kernel/metadata-plugin.zod.ts`. +**Most metadata types must not be overlaid by an environment** — overlaying a packaged item would break the physical schema or the behaviour its package ships. The whitelist lives in **one** place: `MetadataTypeRegistryEntry.allowEnvironmentOverlay` in `packages/spec/src/kernel/metadata-plugin.zod.ts` (spelled `allowOrgOverride` before protocol 18; `GET /api/v1/meta/types` serves it per entry as `allowEnvironmentOverlay`). > **ADR-0131 D6 — the per-organization overlay axis is retired.** The `/meta` doors (REST and the runtime dispatcher) carry no organization into a metadata write or read: a Studio or `PUT /api/v1/meta/...` write of the five ✅ types below lands **environment-wide** (`organization_id` NULL) and is served to every organization, and an organization admin holding only the retired `manage_org_presentation` capability is refused (`403`). The protocol itself refuses every organization-scoped write, and every read — item, list, layers, cached, history, diff, audit, drafts and the commit timeline — is environment → code, whatever organization a caller names. An overlay row an earlier release stored under an organization — under the `single` posture, every Studio save of these types, filed under the Default Organization — is served by no read: boot names each such row, per type (`[metadata_org_scoped_unserved]`), and the v18 migration ceremony (ADR-0131 C7, `os migrate`) carries it to the environment layer; nothing deletes or rewrites it before then. Re-save the item in Studio to make the edit live now. Public forms are the exception: until that ceremony the anonymous form doors read a form `view` in the Default Organization and prefer its overlay for the form's body, while a withdrawal in either layer closes the form, fail-closed. So a legacy organization overlay of a public form keeps serving its body there: a Studio re-save of that body (an environment row) does not change the body the public form serves, and a Studio withdrawal (an environment row) still closes it. The table still records which types take an overlay of a shipped item. -| Type | `allowOrgOverride` | Rationale | +| Type | `allowEnvironmentOverlay` | Rationale | | :--- | :---: | :--- | -| `view`, `dashboard`, `report`, `email_template`, `translation` | ✅ | Pure rendering / render-time content. Per-org customization is safe. | -| `flow` | ❌ | Flows carry execution side-effects (events, jobs, audit), and the `flow` entry declares `supportsOverlay: false` — the loader cannot merge a per-org flow overlay, so the write permission granted a write nothing could read back: an org-scoped flow overlay wrote successfully and **lost its binding on the next cold start**. Rolled back (#6283) so the silent phantom is a loud `403 not_overridable` at the moment of the write. **`allowRuntimeCreate` stays `true`** — a tenant may still author a *brand-new* flow through the runtime API (ADR-0070 package-first authoring); what is closed is overlaying a *packaged* flow per org. | -| `agent` | ❌ | Agents are platform-owned and closed to third parties (ADR-0063 §2) — no per-org agent fork. | -| `permission`, `position` | ❌ | Authorization correctness — a per-org overlay of a packaged permission set *is* silent privilege drift (ADR-0005's security row). Rolled back from an unratified `true` by the 2026-08-08 maintainer ruling (#6483); `position` takes the amendment's `false` default for new types. **`allowRuntimeCreate` stays `true`**: runtime-created sets (including package-bound rows materialized through the metadata door) keep working, while an admin-door edit of a *code-declared* set refuses with `403 not_overridable`, naming the sanctioned path: clone the set under a new name and edit the clone ([ADR-0126](#packaged-flows-actions-and-permission-sets-adr-0126)). The package's own author still changes the declaration in the package and re-publishes (ADR-0086 two-doors). | +| `view`, `dashboard`, `report`, `email_template`, `translation` | ✅ | Pure rendering / render-time content. Customizing it in an environment is safe. | +| `flow` | ❌ | Flows carry execution side-effects (events, jobs, audit), and the `flow` entry declares `supportsOverlay: false` — the loader cannot merge a per-org flow overlay, so the write permission granted a write nothing could read back: an org-scoped flow overlay wrote successfully and **lost its binding on the next cold start**. Rolled back (#6283) so the silent phantom is a loud `403 not_overridable` at the moment of the write. **`allowRuntimeCreate` stays `true`** — a tenant may still author a *brand-new* flow through the runtime API (ADR-0070 package-first authoring); what is closed is overlaying a *packaged* flow. | +| `agent` | ❌ | Agents are platform-owned and closed to third parties (ADR-0063 §2) — no environment fork of a packaged agent. | +| `permission`, `position` | ❌ | Authorization correctness — an overlay of a packaged permission set *is* silent privilege drift (ADR-0005's security row). Rolled back from an unratified `true` by the 2026-08-08 maintainer ruling (#6483); `position` takes the amendment's `false` default for new types. **`allowRuntimeCreate` stays `true`**: runtime-created sets (including package-bound rows materialized through the metadata door) keep working, while an admin-door edit of a *code-declared* set refuses with `403 not_overridable`, naming the sanctioned path: clone the set under a new name and edit the clone ([ADR-0126](#packaged-flows-actions-and-permission-sets-adr-0126)). The package's own author still changes the declaration in the package and re-publishes (ADR-0086 two-doors). | | `object` | ❌ | Defines the table schema. Overriding a packaged object would break existing data — but `allowRuntimeCreate: true`, so tenants *can* author brand-new objects, **and this is also how a new field is added** (write the object with the field in `fields`). | | `field` | ❌ | **Also `allowRuntimeCreate: false` since protocol 17** (#7893). A field is not a standalone artifact — fields are authored inside their object (`ObjectSchema.fields`), so a `field` write minted a *separate* `sys_metadata` row keyed `('field','.')` that nothing ever composed into the parent: `PUT /meta/field/showcase_task.zz_probe` answered 200 `state=active`, the row read back `_diagnostics.valid: true`, and `GET /meta/object/showcase_task` never listed the field. The door is closed rather than bridged: add a field by writing its **object** (`PUT /meta/object/:name`, or `**/*.object.ts` and redeploy), which both persists and composes. Existing rows are untouched — they were inert before the change too — and remain deletable. | | `datasource` | ❌ | Connection strings; multi-tenant isolation is enforced at a higher layer. (`allowRuntimeCreate: true` — the datasource wizard persists `origin: 'runtime'` rows.) | | `job` | ❌ | **Also `allowRuntimeCreate: false` since protocol 17** (#4509). `JobSchema.handler` names a function in the compiled bundle's function table, which a runtime writer has no way to reach — so a job created in Studio or through `PUT /meta` parsed, saved, reported success and was never scheduled. The door is closed rather than bridged: `job` stays first-class through `*.job.ts` / `defineStack({ jobs, functions })`, where every schedule shape, `retryPolicy` and `timeout` does reach the scheduler. Existing rows are untouched — they were never scheduled — and `migrateStoredMetadata` reports them `skipped`. | -Those five are the **complete** `allowOrgOverride: true` set: of the 28 types in `DEFAULT_METADATA_TYPE_REGISTRY`, every other one is `false`. The ❌ rows above are the `false` types whose *second* tier (`allowRuntimeCreate`) is worth calling out; any type not listed is `allowOrgOverride: false`. +Those five are the **complete** `allowEnvironmentOverlay: true` set: of the 28 types in `DEFAULT_METADATA_TYPE_REGISTRY`, every other one is `false`. The ❌ rows above are the `false` types whose *second* tier (`allowRuntimeCreate`) is worth calling out; any type not listed is `allowEnvironmentOverlay: false`. There is no `workflow` metadata type (per [ADR-0020](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0020-state-machine-converge-and-enforce.md), record state machines are a `state_machine` validation). Nor is there a standalone `validation` type any more — it was retired in protocol 17 under [ADR-0088](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0088-metadata-kind-admission-and-retirement.md) because `ValidationRuleSchema` carries no object-binding key, so a rule authored through that door could never say what it protected; author rules in the object's own `validations[]` instead. The runtime gate is implemented in `OVERLAY_ALLOWED_TYPES` (derived from the registry) and enforced by `SysMetadataRepository.put()`. -The gate is **two-tier** — `allowOrgOverride: false` is not the same as "no runtime writes": +The gate is **two-tier** — `allowEnvironmentOverlay: false` is not the same as "no runtime writes": -- Overwriting an item that ships from a code package requires `allowOrgOverride`. Denied → `403 not_overridable`. -- Creating a brand-new item that has no artifact backing requires `allowRuntimeCreate` (or `allowOrgOverride`). Denied → `403 not_creatable`. +- Overwriting an item that ships from a code package requires `allowEnvironmentOverlay`. Denied → `403 not_overridable`. +- Creating a brand-new item that has no artifact backing requires `allowRuntimeCreate` (or `allowEnvironmentOverlay`). Denied → `403 not_creatable`. See [ADR-0005](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0005-metadata-customization-overlay.md) for the full design and amendments. @@ -234,7 +234,7 @@ The hash is `sha256:` + 64-hex of a canonical JSON serialization of the body, wi |:---|:---|:---| | `defineView(...)` / `defineFlow(...)` / any source file → compiled into `dist/objectstack.json` | ❌ Never. Loaded into the in-memory registry on boot; refreshed via HMR in dev. | ❌ The artifact's own version history *is* Git. The metadata layer does not duplicate it. | | Editing a `.json` under `//.json` (FS overlay, e.g. `/view/case_grid.json`) | ❌ FS layer is independent of DB. | ✅ Appended to the change log at `/.objectstack/.log/main.jsonl` by `FileSystemRepository`. | -| Studio inline edit, or `PUT /api/v1/meta/...` (REST) on an `allowOrgOverride: true` type | ✅ Written by `SysMetadataRepository.put()` as an **overlay row**, environment-wide (`organization_id` NULL) since ADR-0131 D6. | ✅ Appended to `sys_metadata_history` in the **same transaction** as the `sys_metadata` write. No-op puts (identical hash) skip the history row entirely. | +| Studio inline edit, or `PUT /api/v1/meta/...` (REST) on an `allowEnvironmentOverlay: true` type | ✅ Written by `SysMetadataRepository.put()` as an **overlay row**, environment-wide (`organization_id` NULL) since ADR-0131 D6. | ✅ Appended to `sys_metadata_history` in the **same transaction** as the `sys_metadata` write. No-op puts (identical hash) skip the history row entirely. | | Deploying a new build (new `dist/objectstack.json`) | ❌ The artifact is loaded into memory, not synced into `sys_metadata`. | ❌ Use Git tags / your deployment platform's release log; that's where artifact "version history" lives. | ### Why artifact never enters the database diff --git a/content/docs/deployment/environment-variables.mdx b/content/docs/deployment/environment-variables.mdx index 29f244bfdbe..6b9be300853 100644 --- a/content/docs/deployment/environment-variables.mdx +++ b/content/docs/deployment/environment-variables.mdx @@ -306,7 +306,7 @@ that bypassed write hooks, `rebuildSearchCompanion` (from | `OS_MARKETPLACE_CACHE` | enum | `on` | `off` disables the in-memory marketplace listing cache. | | `OS_MARKETPLACE_PUBLIC_BASE_URL` | url | — | Public base URL of the marketplace registry (proxied from this runtime when set). | | `OS_ALLOW_UNMASKED_OBJECT_METADATA` | boolean | `false` | Escape hatch for the metadata-plane field-level security mask ([ADR-0106](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0106-metadata-plane-fls-object-schema-masking.md) D8). By default every object schema served by `/meta` and `/metadata` is projected onto the fields the **calling user** may read, so a field they cannot read does not appear at all — not its name, label, type, picklist options, formula, `visibleWhen` predicate, `defaultValue`, or the `requiredPermissions` capability guarding it. Set to `1` to serve the full schema to every authenticated caller, as releases before this one did. This changes **disclosure only**: the data plane still masks values and refuses forbidden writes either way, and the console reads field affordances from `/auth/me/permissions`, so toggling it never changes UI correctness. The REST layer also honours a per-server `metadata.maskObjectFields: false`; this variable is the deployment-wide knob and covers the runtime `/metadata` dispatcher, which has no REST config to read. | -| `OS_METADATA_WRITABLE` | csv | — (none) | Comma-separated metadata type names (e.g. `hook,job`) granted a runtime escape hatch that treats them as `allowOrgOverride: true` for items **no managed package ships**: it opens runtime creation of a type whose registry entry allows none. It never opens an organization-scoped write: since [ADR-0131](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0131-total-organization-ownership-no-null-organization-id.md) D6 the metadata protocol refuses every organization-scoped write with `403 NOT_OVERRIDABLE`. It **never opens an item a managed package ships**: managed content is sealed ([ADR-0131](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0131-total-organization-ownership-no-null-organization-id.md) D6), so an overlay or a removal of a shipped flow, object, field, permission set, position or any other type without an environment overlay is refused with `403 NOT_OVERRIDABLE` whether the hatch is set or not. Customize managed content through its type's own route instead: an environment overlay for `view`, `dashboard`, `report`, `translation` and `email_template`; disable, or clone under a new name, for a flow; clone for a permission set. An overlay row this hatch wrote before over a managed item is kept at rest but **not served**: every read takes the package's definition, and boot names each such row, per type (`[metadata_sealed_overlay_unserved]`), with the two remedies — re-express the change as a new item under a new name, or delete the row. A stored fork of a packaged permission set is the one exception: the deployment does not boot while it holds one (ADR-0048); `os migrate security-catalog-overlays` lists such rows and, with `--apply`, deletes them, which restores the package's definition. A stored row over a managed item of the other types that merge overlays at read (`position`, `page`, `app`, `dataset`, `book`, `tool`, `skill`) can still be removed, but removing it changes nothing that is served: no read serves it, so the package's definition is already served. See [ADR-0005](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0005-metadata-customization-overlay.md). | +| `OS_METADATA_WRITABLE` | csv | — (none) | Comma-separated metadata type names (e.g. `hook,job`) granted a runtime escape hatch that treats them as `allowEnvironmentOverlay: true` for items **no managed package ships**: it opens runtime creation of a type whose registry entry allows none. It never opens an organization-scoped write: since [ADR-0131](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0131-total-organization-ownership-no-null-organization-id.md) D6 the metadata protocol refuses every organization-scoped write with `403 NOT_OVERRIDABLE`. It **never opens an item a managed package ships**: managed content is sealed ([ADR-0131](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0131-total-organization-ownership-no-null-organization-id.md) D6), so an overlay or a removal of a shipped flow, object, field, permission set, position or any other type without an environment overlay is refused with `403 NOT_OVERRIDABLE` whether the hatch is set or not. Customize managed content through its type's own route instead: an environment overlay for `view`, `dashboard`, `report`, `translation` and `email_template`; disable, or clone under a new name, for a flow; clone for a permission set. An overlay row this hatch wrote before over a managed item is kept at rest but **not served**: every read takes the package's definition, and boot names each such row, per type (`[metadata_sealed_overlay_unserved]`), with the two remedies — re-express the change as a new item under a new name, or delete the row. A stored fork of a packaged permission set is the one exception: the deployment does not boot while it holds one (ADR-0048); `os migrate security-catalog-overlays` lists such rows and, with `--apply`, deletes them, which restores the package's definition. A stored row over a managed item of the other types that merge overlays at read (`position`, `page`, `app`, `dataset`, `book`, `tool`, `skill`) can still be removed, but removing it changes nothing that is served: no read serves it, so the package's definition is already served. See [ADR-0005](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0005-metadata-customization-overlay.md). | --- diff --git a/content/docs/kernel/contracts/metadata-service.mdx b/content/docs/kernel/contracts/metadata-service.mdx index 9a7785a903b..1b45fc0f27f 100644 --- a/content/docs/kernel/contracts/metadata-service.mdx +++ b/content/docs/kernel/contracts/metadata-service.mdx @@ -310,11 +310,11 @@ const validation = await metadataService.validate('object', definition); 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 +ever served, and ADR-0126 supersedes it on the record. Environment 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`). +— opt-in per type via `allowEnvironmentOverlay`, written environment-wide +through the metadata write doors (ADR-0131 D6), and read back through the +layered read (`code` / `overlay` / `effective`). --- @@ -404,7 +404,7 @@ const dashboard = await metadataService.get('dashboard', 'sales_overview'); ### Environment Customization View customization rides ADR-0005's metadata overlay (opt-in per type via -`allowOrgOverride`, `view` among the overlay types): a write through the +`allowEnvironmentOverlay`, `view` among the overlay types): a write through the `/meta` doors stores a `sys_metadata` row, and the layered read returns `code` / `overlay` / `effective` for it. Since ADR-0131 D6 the per-organization overlay axis is retired: the doors carry no organization, so the overlay is diff --git a/content/docs/permissions/authorization.mdx b/content/docs/permissions/authorization.mdx index 96a8524aaa8..f9dbae3c33c 100644 --- a/content/docs/permissions/authorization.mdx +++ b/content/docs/permissions/authorization.mdx @@ -254,7 +254,7 @@ one of two doors, each writing only what it owns: admin-door **edit of a set whose definition ships as a code artifact is refused with `403 not_overridable`**, loudly, at the moment of the write: the ADR-0094 write-through still translates the edit into a metadata save, - but `permission` declares `allowOrgOverride: false` (ADR-0005's security + but `permission` declares `allowEnvironmentOverlay: false` (ADR-0005's security row — overlays of the authorization surface would create silent privilege drift), so the tier gate refuses it and no env-scope overlay is minted. The supported channel is the one ADR-0086 two-doors always named: **edit the diff --git a/content/docs/permissions/capabilities.mdx b/content/docs/permissions/capabilities.mdx index 2ac0247ee4d..70c318f8aab 100644 --- a/content/docs/permissions/capabilities.mdx +++ b/content/docs/permissions/capabilities.mdx @@ -223,14 +223,14 @@ withdrawing the **grant**. ## `capability` is code-only `capability` is a registered metadata kind, and its registry entry declares -`allowRuntimeCreate: false` **and** `allowOrgOverride: false`. Together those +`allowRuntimeCreate: false` **and** `allowEnvironmentOverlay: false`. Together those are the code-only declaration, so: ``` PUT /api/v1/meta/capability/:name → 403 NOT_CREATABLE "Metadata type 'capability' is code-only: the metadata-type registry - declares allowRuntimeCreate=false and allowOrgOverride=false, so it + declares allowRuntimeCreate=false and allowEnvironmentOverlay=false, so it cannot be created through the runtime metadata API … on any kernel. Declare it in source (**/*.capability.ts) and redeploy." ``` diff --git a/content/docs/plugins/adding-a-metadata-type.mdx b/content/docs/plugins/adding-a-metadata-type.mdx index c67860ad555..18ff0ec50b2 100644 --- a/content/docs/plugins/adding-a-metadata-type.mdx +++ b/content/docs/plugins/adding-a-metadata-type.mdx @@ -42,7 +42,7 @@ The single source of truth for built-in types is the domain: 'ui', // one of: data | ui | automation | system | security | ai filePatterns: ['**/*.my-widget.ts', '**/*.my-widget.yml'], // required: globs used to discover files of this type supportsOverlay: true, // false = no 3-state diff in the editor - allowOrgOverride: true, // false = compile-time only, true = runtime overlays accepted via the metadata API + allowEnvironmentOverlay: true, // false = packaged items locked; true = an environment may overlay a packaged item via the metadata API allowRuntimeCreate: true, // allow admin to create new instances at runtime } ``` @@ -56,7 +56,7 @@ The single source of truth for built-in types is the > verbatim as the `:type` path param (`/api/v1/meta/my_widget`) — there is no > automatic pluralization. -The `allowOrgOverride` flag is the **only** place that controls whether the +The `allowEnvironmentOverlay` flag is the **only** place that controls whether the overlay store accepts writes for this type. The runtime env-var `OS_METADATA_WRITABLE=foo,bar` flips the flag on at runtime for the listed types — useful for opt-in writable @@ -182,7 +182,7 @@ registry.registerEditPage('my_widget', (params) => ( - Dirty tracking (`JSON.stringify` diff) - Save with destructive-change confirmation dialog - Reset overlay / Refresh / History buttons -- Read-only fallback when `allowOrgOverride` is false +- Read-only fallback when `allowEnvironmentOverlay` is false You only have to render the value-bound part. diff --git a/content/docs/protocol/objectui/concept.mdx b/content/docs/protocol/objectui/concept.mdx index 3314a823166..96fb34a1a35 100644 --- a/content/docs/protocol/objectui/concept.mdx +++ b/content/docs/protocol/objectui/concept.mdx @@ -436,10 +436,11 @@ fields: (ADR-0126 §6 wall 3: an upgrade rewrites the packaged BASE; a tenant's choices live separately and are never merged into it). - What Acme Corp actually customizes at runtime is a real, declared shape: a - per-org **overlay write on the `view` metadata type** - (`allowOrgOverride: true` on its `DEFAULT_METADATA_TYPE_REGISTRY` entry, - ADR-0005) — the org's own `FormView` document, authored with the same shape + What Acme Corp actually customizes at runtime is a real, declared shape: an + environment **overlay write on the `view` metadata type** + (`allowEnvironmentOverlay: true` on its `DEFAULT_METADATA_TYPE_REGISTRY` + entry, ADR-0005; environment-wide since ADR-0131 D6) — the deployment's own + `FormView` document, authored with the same shape Layer 1 and the Server Response above already use, not a diff against the base. The merged layout below reflects only what Layers 1 and 3 (both real, declared shapes) actually produce. diff --git a/docs/qa/platform-checklist/FOLLOW-UPS.md b/docs/qa/platform-checklist/FOLLOW-UPS.md index 6f00a6dcd5c..81551777fbf 100644 --- a/docs/qa/platform-checklist/FOLLOW-UPS.md +++ b/docs/qa/platform-checklist/FOLLOW-UPS.md @@ -361,7 +361,7 @@ posture, then decide which shape is wanted). - **`content/docs/kernel/contracts/metadata-service.mdx`** teaches the superseded three-layer overlay protocol as the customization architecture — which ADR-0126 §6.4 forbids citing — and its worked example overlays an **`object`** (tier B, - `allowOrgOverride:false`): the exact `NOT_OVERRIDABLE` phantom write the §6.1 wall refuses. + `allowEnvironmentOverlay:false`): the exact `NOT_OVERRIDABLE` phantom write the §6.1 wall refuses. - **`content/docs/protocol/objectui/concept.mdx`** (+ `index.mdx`) promises per-tenant field-level object customization ("Make phone required", "Add custom field vip_status") — contradicts Regime E and the sentence now shipped at @@ -414,7 +414,7 @@ posture, then decide which shape is wanted). - **E1's three-tier language landed verbatim** at `capabilities/integrations.mdx` and `build-without-code.mdx`; repo-wide, no unconditional "install then customize in Studio" claim remains in `content/docs`. -- **The dashboard overlay door exists** (`dashboard allowOrgOverride: true`, +- **The dashboard overlay door exists** (`dashboard allowEnvironmentOverlay: true`, `packages/spec/src/kernel/metadata-plugin.zod.ts`) — the display-class item asserts the tier-1 promise on both view and dashboard. - **`TenancyPostureSchema` is enumSource-pinnable** (direct inline `z.enum`, 3 members) — diff --git a/docs/qa/platform-checklist/RUNNER.md b/docs/qa/platform-checklist/RUNNER.md index aa720bf5679..6ffbc6dc018 100644 --- a/docs/qa/platform-checklist/RUNNER.md +++ b/docs/qa/platform-checklist/RUNNER.md @@ -125,7 +125,7 @@ contradicts it, and correct it here when it does. showcase package is NOT blocked and needs no escape hatch.** The org-overridable types are derived from the metadata-type registry, not a hand-written list, and are exactly **`view`, `dashboard`, `report`, `translation`, `email_template`** - (`packages/spec/src/kernel/metadata-plugin.zod.ts` — the `allowOrgOverride: true` + (`packages/spec/src/kernel/metadata-plugin.zod.ts` — the `allowEnvironmentOverlay: true` entries; pinned by `protocol.org-scoped-write-refused.test.ts` G5). So `PUT /api/v1/meta/view/` answers 2xx on stock showcase with no `OS_METADATA_WRITABLE` and no `?package=` trick, while the same shape on `object`, diff --git a/docs/qa/platform-checklist/areas/ai.json b/docs/qa/platform-checklist/areas/ai.json index 89072f00a27..c088ae1c9ba 100644 --- a/docs/qa/platform-checklist/areas/ai.json +++ b/docs/qa/platform-checklist/areas/ai.json @@ -42,7 +42,7 @@ "boot; sign in as admin; GET /api/v1/meta (meta.getTypes) and GET /api/v1/meta/types — capture which kinds are registered", "GET /api/v1/meta/agent, /api/v1/meta/tool, /api/v1/meta/skill (route ledger: meta.getItems) and locate the three authored probes", "GET /api/v1/meta/skill/qa_probe_skill (meta.getItem) and field-diff against the authored source, noting applied defaults (surface:'ask', active:true)", - "PUT /api/v1/meta/skill/qa_runtime_skill?mode=draft (and /meta/tool/qa_runtime_tool?mode=draft) with a NAME NO PACKAGE SHIPS — a runtime-created item (skill/tool are allowRuntimeCreate:true) — and re-read it with ?state=draft; then PUT /api/v1/meta/skill/qa_probe_skill?mode=draft against the PACKAGE-SHIPPED probe and capture the 403 NOT_OVERRIDABLE (skill/tool are allowOrgOverride:false since ee58392e1 — a package-shipped tool/skill has no per-org overlay); then attempt the same runtime save for the agent kind and capture the outcome (agent is allowRuntimeCreate:false + allowOrgOverride:false — ADR-0063 §2, metadata-plugin.zod.ts)", + "PUT /api/v1/meta/skill/qa_runtime_skill?mode=draft (and /meta/tool/qa_runtime_tool?mode=draft) with a NAME NO PACKAGE SHIPS — a runtime-created item (skill/tool are allowRuntimeCreate:true) — and re-read it with ?state=draft; then PUT /api/v1/meta/skill/qa_probe_skill?mode=draft against the PACKAGE-SHIPPED probe and capture the 403 NOT_OVERRIDABLE (skill/tool are allowEnvironmentOverlay:false since ee58392e1 — a package-shipped tool/skill takes no environment overlay); then attempt the same runtime save for the agent kind and capture the outcome (agent is allowRuntimeCreate:false + allowEnvironmentOverlay:false — ADR-0063 §2, metadata-plugin.zod.ts)", "repeat one meta list unauthenticated; capture status", "POST /api/v1/ai/tools/qa_probe_tool/execute (any payload) on the open-framework boot and capture the 501 — evidence for the read-only-projection clause, NOT an expected execution" ], @@ -66,7 +66,7 @@ "evidence": "list/get responses + diff" }, { - "clause": "the runtime write door matches the declared registry posture — ALL sides: PUT ?mode=draft succeeds for a RUNTIME-CREATED skill (and tool) name — one no package ships (allowRuntimeCreate:true); the same PUT against a PACKAGE-SHIPPED skill/tool is refused 403 NOT_OVERRIDABLE (allowOrgOverride:false, ee58392e1 — ADR-0005 ai row); and the agent kind has NO governed runtime write path (allowRuntimeCreate:false; 'for agents, the code is the record', metadata-plugin.zod.ts — migrateStoredMetadata reports agent rows 'skipped' by design)", + "clause": "the runtime write door matches the declared registry posture — ALL sides: PUT ?mode=draft succeeds for a RUNTIME-CREATED skill (and tool) name — one no package ships (allowRuntimeCreate:true); the same PUT against a PACKAGE-SHIPPED skill/tool is refused 403 NOT_OVERRIDABLE (allowEnvironmentOverlay:false, ee58392e1 — ADR-0005 ai row); and the agent kind has NO governed runtime write path (allowRuntimeCreate:false; 'for agents, the code is the record', metadata-plugin.zod.ts — migrateStoredMetadata reports agent rows 'skipped' by design)", "oracle": "api", "verify": "the runtime-created skill draft PUT (qa_runtime_skill) returns 200 and the draft is readable via GET /api/v1/meta/skill/qa_runtime_skill?state=draft — ⚠️ the READ parameter is ?state=draft, not ?mode=draft: rest-server.ts keys the draft projection on req.query.state, while ?mode=draft is the WRITE-side parameter, so re-reading with ?mode=draft serves the active item (or 404s when none is active) and reads like the draft never landed. The draft PUT against the package-shipped qa_probe_skill answers 403 code NOT_OVERRIDABLE (sys-metadata-repository.ts assertAllowed, override-artifact intent) — CORRECT, not a bug: a 200 there would be a per-org overlay the registry row forbids. The agent runtime save is refused/unsupported (capture the actual status), and that refusal is recorded as CORRECT, not filed as a bug", "evidence": "the three PUT responses (runtime-created skill 200 + its ?state=draft read, package-shipped skill 403, agent refusal)" diff --git a/docs/qa/platform-checklist/areas/automation.json b/docs/qa/platform-checklist/areas/automation.json index edfe6ad936f..2dfb06458a5 100644 --- a/docs/qa/platform-checklist/areas/automation.json +++ b/docs/qa/platform-checklist/areas/automation.json @@ -332,7 +332,7 @@ { "clause": "the child-row filter is settable in Studio's visual filter editor on a writable base package (create a scratch base via POST /api/v1/packages and author the parent+summary there), and persists per the meta API; duplicate of the code showcase package is refused DUPLICATE_SOURCE_NOT_A_BASE (#14451).", "oracle": "screenshot", - "verify": "POST /api/v1/packages creates the scratch base; author the parent with a filtered summary field and its child there; open the summary field in Studio's visual filter editor, edit its child-row filter, save, and confirm the persisted metadata via the meta API. An org overlay is NOT an alternative route: the object type declares supportsOverlay and allowOrgOverride false (DEFAULT_METADATA_TYPE_REGISTRY). The duplicate route's 422 DUPLICATE_SOURCE_NOT_A_BASE on the code showcase package is the expected refusal (requireDuplicableSource), not a blocker", + "verify": "POST /api/v1/packages creates the scratch base; author the parent with a filtered summary field and its child there; open the summary field in Studio's visual filter editor, edit its child-row filter, save, and confirm the persisted metadata via the meta API. An org overlay is NOT an alternative route: the object type declares supportsOverlay and allowEnvironmentOverlay false (DEFAULT_METADATA_TYPE_REGISTRY). The duplicate route's 422 DUPLICATE_SOURCE_NOT_A_BASE on the code showcase package is the expected refusal (requireDuplicableSource), not a blocker", "evidence": "editor screenshot + saved metadata read" } ], diff --git a/docs/qa/platform-checklist/areas/integration-system.json b/docs/qa/platform-checklist/areas/integration-system.json index 15492fa6e02..ad35a681b76 100644 --- a/docs/qa/platform-checklist/areas/integration-system.json +++ b/docs/qa/platform-checklist/areas/integration-system.json @@ -441,7 +441,7 @@ "a flow that is EDITABLE in Studio — use a shipped one (showcase_declarative_connector_ping) or create it from the console's own create page; see the knownGap before authoring one over the metadata API" ], "knownGaps": [ - "A FLOW AUTHORED OVER THE METADATA API IS NOT EDITABLE IN STUDIO (run #7690, cross-observed by run #7695 / #7753 item 3) — with or without `?package=`, it opens behind 'This flow is provided by an installed package, so it is read-only at runtime', so a run that provisions its fixture flow via PUT /api/v1/meta/flows/... cannot then drive the picker on it. objectui's ResourceEditPage treats an item as artifact-backed when `layered.code != null && _packageId !== 'sys_metadata'`, and `flow` declares allowOrgOverride:false — so for THIS type the banner is arguably telling the truth (the server would refuse the overlay write anyway). The editable path is the console's own create page. Recorded, not filed: the polarity-reversed case, where the same heuristic locks a WRITABLE-package object it should not, is the real defect and lives at objectui#4308" + "A FLOW AUTHORED OVER THE METADATA API IS NOT EDITABLE IN STUDIO (run #7690, cross-observed by run #7695 / #7753 item 3) — with or without `?package=`, it opens behind 'This flow is provided by an installed package, so it is read-only at runtime', so a run that provisions its fixture flow via PUT /api/v1/meta/flows/... cannot then drive the picker on it. objectui's ResourceEditPage treats an item as artifact-backed when `layered.code != null && _packageId !== 'sys_metadata'`, and `flow` declares allowEnvironmentOverlay:false — so for THIS type the banner is arguably telling the truth (the server would refuse the overlay write anyway). The editable path is the console's own create page. Recorded, not filed: the polarity-reversed case, where the same heuristic locks a WRITABLE-package object it should not, is the real defect and lives at objectui#4308" ] }, "steps": [ diff --git a/docs/qa/platform-checklist/areas/platform-core.json b/docs/qa/platform-checklist/areas/platform-core.json index f21261bf6a1..b115082011e 100644 --- a/docs/qa/platform-checklist/areas/platform-core.json +++ b/docs/qa/platform-checklist/areas/platform-core.json @@ -427,7 +427,7 @@ "publish: POST /api/v1/packages//publish-drafts; then GET /api/v1/meta/view/os_qa_view_/published and capture it", "verify in the console that the published view is live for showcase_task (reload, open the view switcher)", "denied side A: attempt the same runtime create targeting the read-only CODE package; capture the refusal (expect writable_package_required — ADR-0070 D1)", - "denied side B: attempt a PUT overlay against a PACKAGED object item (type 'object' has allowOrgOverride: false); capture the refusal (expect HTTP 403 not_overridable)", + "denied side B: attempt a PUT overlay against a PACKAGED object item (type 'object' has allowEnvironmentOverlay: false); capture the refusal (expect HTTP 403 not_overridable)", "Studio browser half: create a new record page via Studio bound to an object and capture the PUT /api/v1/meta/page/... it issues" ], "acceptance": [ @@ -456,9 +456,9 @@ "evidence": "the refusal response" }, { - "clause": "DENY side of the overlay gate: a per-org overlay write against a type with allowOrgOverride unset (object/field) answers HTTP 403 not_overridable, while view/dashboard (the ADR-0005 Phase 1 opt-ins) accept", + "clause": "DENY side of the overlay gate: an overlay write against a type with allowEnvironmentOverlay unset (object/field) answers HTTP 403 not_overridable, while view/dashboard (the ADR-0005 Phase 1 opt-ins) accept", "oracle": "api", - "verify": "the object-targeting PUT returns 403 not_overridable (contract stated on allowOrgOverride in packages/spec/src/kernel/metadata-plugin.zod.ts); the view PUT from the happy path succeeded", + "verify": "the object-targeting PUT returns 403 not_overridable (contract stated on allowEnvironmentOverlay in packages/spec/src/kernel/metadata-plugin.zod.ts); the view PUT from the happy path succeeded", "evidence": "both responses side by side" }, { @@ -489,7 +489,7 @@ }, "source": [ "packages/runtime/src/route-ledger.ts (PUT /meta/:type/:name, GET /meta/_drafts, GET /meta/:type/:name/published, POST /packages/:id/publish-drafts)", - "packages/spec/src/kernel/metadata-plugin.zod.ts#allowOrgOverride (allowOrgOverride 403 not_overridable contract; validateOnWrite; registry flags per type)", + "packages/spec/src/kernel/metadata-plugin.zod.ts#allowEnvironmentOverlay (allowEnvironmentOverlay 403 not_overridable contract; validateOnWrite; registry flags per type)", "ADR-0070 via packages/qa/dogfood/test/package-first-authoring.dogfood.test.ts#WRITABLE_PACKAGE_REQUIRED (the runtime create into a loaded code package refused, with its writable-base control beside it — re-pointed #18104: the lower-case spelling survives here only inside a test title and comments; the code this test actually pins is the SCREAMING_SNAKE one, ADR-0112)", "ADR-0033 (drafts / publish)" ], @@ -1684,7 +1684,7 @@ "steps": [ "additive half — CROSS-REF, do not duplicate: platform-core.metadata-registry-serving owns the overlay steps (its steps 7-8 / clauses 5-6): the three AccountExtension fields present in the MERGED GET /api/v1/meta/object/showcase_account, rendering on the form, and a loyalty_tier + csat_score write round-tripping through /api/v1/data/showcase_account. Cite that item's verdicts when the same run scored them; otherwise drive exactly those steps here", "capture the BEFORE image for the refusal half: GET /api/v1/meta/object/showcase_account and keep the full served field inventory", - "in-place refusal, door 1 (org-overlay): PUT /api/v1/meta/object/showcase_account with the served body plus ONE added field qa_inplace_probe_ and NO ?package= → expect 403 NOT_OVERRIDABLE (type 'object' is not in the ADR-0005 overlay opt-ins; the refusal names allowRuntimeCreate/allowOrgOverride and the OS_METADATA_WRITABLE escape hatch — packages/metadata-protocol/src/protocol.ts)", + "in-place refusal, door 1 (org-overlay): PUT /api/v1/meta/object/showcase_account with the served body plus ONE added field qa_inplace_probe_ and NO ?package= → expect 403 NOT_OVERRIDABLE (type 'object' is not in the ADR-0005 overlay opt-ins; the refusal names allowRuntimeCreate/allowEnvironmentOverlay and the OS_METADATA_WRITABLE escape hatch — packages/metadata-protocol/src/protocol.ts)", "in-place refusal, door 2 (package write): repeat the same PUT with ?package=com.example.showcase (the showcase's read-only code package) → expect the read-only-base refusal (403 ITEM_LOCKED class — the same door access-security.owd-save-gate's control leg measured on stock, #9477); record WHICH door answered and the exact status + code, reading declaredCode where the wire code is a mapped generic", "after both refusals: re-GET /api/v1/meta/object/showcase_account and diff against step 2 — byte-identical; qa_inplace_probe_ absent, the three extension fields still present", "record the Regime E provenance posture: the extension arrived AS A PACKAGE — it registers as an 'extend' contributor (packages/objectql/src/engine.ts) folded over the base at resolution (mergeObjectDefinitions), the base package's authored definition is never edited, and package identity supplies provenance and upgrade isolation (ADR-0126 §3: 'the customization IS a package', install-grain not per-org)" diff --git a/docs/qa/platform-checklist/areas/records-forms.json b/docs/qa/platform-checklist/areas/records-forms.json index 0ba6c9bc8a4..707de16a36d 100644 --- a/docs/qa/platform-checklist/areas/records-forms.json +++ b/docs/qa/platform-checklist/areas/records-forms.json @@ -4386,7 +4386,7 @@ "two organizations on one environment, each with its own signed-in persona, sharing one app whose object has a picklist-bound field" ], "knownGaps": [ - "NOT DECLARED: the per-organization overlay is phase 2 of the picklist design. The picklist row of packages/spec/src/kernel/metadata-plugin.zod.ts#DEFAULT_METADATA_TYPE_REGISTRY carries `allowOrgOverride: false`, the one opt-in ADR-0005 allows for an organization overlay, and ruling 5904864936 on #18164 defers the overlay to its own sub-issue, filed when a customer requirement measures it. Nothing here can run until that sub-issue lands the overlay. Package-level extension (phase 1) is records-forms.picklist-shared-across-objects" + "NOT DECLARED: the per-organization overlay is phase 2 of the picklist design. The picklist row of packages/spec/src/kernel/metadata-plugin.zod.ts#DEFAULT_METADATA_TYPE_REGISTRY carries `allowEnvironmentOverlay: false`, the one opt-in ADR-0005 allows for an organization overlay, and ruling 5904864936 on #18164 defers the overlay to its own sub-issue, filed when a customer requirement measures it. Nothing here can run until that sub-issue lands the overlay. Package-level extension (phase 1) is records-forms.picklist-shared-across-objects" ] }, "negative": [ @@ -4397,7 +4397,7 @@ "source": [ "#18164 design of record (comment 5715762696), item 4 (a per-organization overlay of the picklist shape, additive only, whose allowance was left to the maintainer as an open point) and item 6 (the verification sentence: org A adds a value, A writes it, B is refused)", "#18164 ruling 5904864936 (the overlay stays deferred to its own sub-issue on a measured customer requirement) and #19518 Scope 5 (not declared in the spec layer)", - "docs/adr/0005-metadata-customization-overlay.md (`allowOrgOverride` on the type registry is the only org-overlay opt-in)" + "docs/adr/0005-metadata-customization-overlay.md (`allowEnvironmentOverlay` on the type registry is the only org-overlay opt-in)" ], "history": [ { diff --git a/docs/qa/platform-checklist/areas/studio-authoring.json b/docs/qa/platform-checklist/areas/studio-authoring.json index e59ed265061..eb2182d5eac 100644 --- a/docs/qa/platform-checklist/areas/studio-authoring.json +++ b/docs/qa/platform-checklist/areas/studio-authoring.json @@ -526,7 +526,7 @@ }, { "id": "studio-authoring.org-override-registry-gate", - "title": "The metadata type registry gates runtime writes: allowOrgOverride=false kinds refuse overlay (403 not_overridable), allowRuntimeCreate=false kinds refuse creation — both sides", + "title": "The metadata type registry gates runtime writes: allowEnvironmentOverlay=false kinds refuse overlay (403 not_overridable), allowRuntimeCreate=false kinds refuse creation — both sides", "since": "v16", "status": "active", "revision": 2, @@ -539,14 +539,14 @@ "the stock showcase artifact (artifact-backed objects like showcase_task and packaged views are the locked targets; a scratch name serves the allowed-create side)" ], "knownGaps": [ - "CORRECTION TO RUN #7637 (recorded by run #7695): the Studio read-only badge is HONEST on this build/pin — it is not asserting a lock the server declines to apply. With OS_METADATA_WRITABLE=permission set, GET /meta reports permission { allowOrgOverride: true, overrideSource: 'env' } (the enum is registry|env — packages/spec/src/api/protocol.zod.ts) and the editor becomes fully writable; the badge tracks the real writable computation, proven BOTH ways in that run. #7637's contrary observation does not reproduce here — do not act on it as written, and do not re-file it", - "ROUTE NOTE (run #7695 / #7690): a flow authored over the metadata API is NOT editable in Studio — it opens behind 'This flow is provided by an installed package, so it is read-only at runtime', with or without `?package=`. objectui's ResourceEditPage treats an item as artifact-backed when `layered.code != null && _packageId !== 'sys_metadata'` (packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx / 1311-1323) and `flow` declares allowOrgOverride:false, so for THIS kind the banner is arguably telling the truth: the server would refuse the overlay write anyway. The editable path is the console's own create page. Recorded as a route note, NOT a defect — the polarity-reversed case (the same heuristic locking an object published into a WRITABLE package, which Studio and the server both treat as editable) IS the defect and is filed at objectui#4308; whoever works that card should confirm this case stays correct" + "CORRECTION TO RUN #7637 (recorded by run #7695): the Studio read-only badge is HONEST on this build/pin — it is not asserting a lock the server declines to apply. With OS_METADATA_WRITABLE=permission set, GET /meta reports permission { allowEnvironmentOverlay: true, overrideSource: 'env' } (the enum is registry|env — packages/spec/src/api/protocol.zod.ts) and the editor becomes fully writable; the badge tracks the real writable computation, proven BOTH ways in that run. #7637's contrary observation does not reproduce here — do not act on it as written, and do not re-file it", + "ROUTE NOTE (run #7695 / #7690): a flow authored over the metadata API is NOT editable in Studio — it opens behind 'This flow is provided by an installed package, so it is read-only at runtime', with or without `?package=`. objectui's ResourceEditPage treats an item as artifact-backed when `layered.code != null && _packageId !== 'sys_metadata'` (packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx / 1311-1323) and `flow` declares allowEnvironmentOverlay:false, so for THIS kind the banner is arguably telling the truth: the server would refuse the overlay write anyway. The editable path is the console's own create page. Recorded as a route note, NOT a defect — the polarity-reversed case (the same heuristic locking an object published into a WRITABLE package, which Studio and the server both treat as editable) IS the defect and is filed at objectui#4308; whoever works that card should confirm this case stays correct" ] }, "steps": [ - "read the authority first: DEFAULT_METADATA_TYPE_REGISTRY in packages/spec/src/kernel/metadata-plugin.zod.ts declares per-kind allowOrgOverride and allowRuntimeCreate — if the flags have changed, revise this item's variants before running", - "positive overlay: PUT /api/v1/meta/view/ with a modified label (view: allowOrgOverride true) → expect acceptance; GET the name and confirm the overlay wins at read", - "negative overlay: PUT /api/v1/meta/object/showcase_task (artifact-backed; object: allowOrgOverride false) with any modified body → capture the refusal", + "read the authority first: DEFAULT_METADATA_TYPE_REGISTRY in packages/spec/src/kernel/metadata-plugin.zod.ts declares per-kind allowEnvironmentOverlay and allowRuntimeCreate — if the flags have changed, revise this item's variants before running", + "positive overlay: PUT /api/v1/meta/view/ with a modified label (view: allowEnvironmentOverlay true) → expect acceptance; GET the name and confirm the overlay wins at read", + "negative overlay: PUT /api/v1/meta/object/showcase_task (artifact-backed; object: allowEnvironmentOverlay false) with any modified body → capture the refusal", "negative create: PUT /api/v1/meta/job/qa_probe_job (job: allowRuntimeCreate false — a runtime-authored job could never be scheduled, #4509) → capture the refusal", "positive create: PUT /api/v1/meta/object/qa_org_probe as a brand-NEW object (object: allowRuntimeCreate true) → expect acceptance — the two flags gate different doors", "cleanup + reset semantics: DELETE /api/v1/meta/view/ and confirm reset:true, then GET returns the artifact default again", @@ -554,7 +554,7 @@ ], "acceptance": [ { - "clause": "a kind with allowOrgOverride=false refuses the org-level override of an artifact-backed item with 403 not_overridable — the lock is server-side", + "clause": "a kind with allowEnvironmentOverlay=false refuses the environment overlay of an artifact-backed item with 403 not_overridable — the lock is server-side", "oracle": "api", "verify": "PUT /api/v1/meta/object/showcase_task answers 403 with the not_overridable code (the runtime behavior the registry's own doc comment declares)", "evidence": "the 403 response body" @@ -597,7 +597,7 @@ "traps": ["dispatcher-vs-hono-route"], "automated": { "kind": "unit", "ref": "packages/objectql/src/overlay-precedence.test.ts" }, "source": [ - "packages/spec/src/kernel/metadata-plugin.zod.ts#DEFAULT_METADATA_TYPE_REGISTRY (DEFAULT_METADATA_TYPE_REGISTRY per-kind flags; allowOrgOverride doc: 'runtime returns HTTP 403 not_overridable'; the object/field lock rationale; job's #4509 create lock)", + "packages/spec/src/kernel/metadata-plugin.zod.ts#DEFAULT_METADATA_TYPE_REGISTRY (DEFAULT_METADATA_TYPE_REGISTRY per-kind flags; allowEnvironmentOverlay doc: 'runtime returns HTTP 403 not_overridable'; the object/field lock rationale; job's #4509 create lock)", "packages/objectql/src/overlay-precedence.test.ts#not_overridable ('denied — must throw 403 (not_overridable or not_creatable)')", "packages/metadata-protocol/src/protocol.ts#isRuntimeCreateAllowed (isRuntimeCreateAllowed — the write-gate authority)", "ADR-0005 (metadata customization opt-in), ADR-0049 (enforce-or-remove — the job rationale)", @@ -1199,9 +1199,9 @@ "a vendored console matching .objectui-sha" ], "knownGaps": [ - "DASHBOARD OVERLAY DOOR VERIFIED PRESENT at authoring time (this worktree): DEFAULT_METADATA_TYPE_REGISTRY declares dashboard allowOrgOverride:true (packages/spec/src/kernel/metadata-plugin.zod.ts), same as view — so the dashboard leg is ASSERTED, not waived. If a run finds the flag flipped, revise this item (ADR-0005 admission-pair change) before scoring", + "DASHBOARD OVERLAY DOOR VERIFIED PRESENT at authoring time (this worktree): DEFAULT_METADATA_TYPE_REGISTRY declares dashboard allowEnvironmentOverlay:true (packages/spec/src/kernel/metadata-plugin.zod.ts), same as view — so the dashboard leg is ASSERTED, not waived. If a run finds the flag flipped, revise this item (ADR-0005 admission-pair change) before scoring", "dashboards.system-overview-live-counts drives the SAME system_overview board expecting its stock shape — the dashboard leg here MUST end with the reset-overlay step; an overlay left behind corrupts that item's next run", - "the neighbouring failure class is objectui#4308 mis-tiering (an item in a WRITABLE package shown behind the installed-package lock): if the packaged view/dashboard opens LOCKED, read the ResourceEditPage tiering (isArtifactItem; artifact-backed canWrite = allowOrgOverride) and the layered envelope's provenance, and forge the raw PUT, before filing a registry defect" + "the neighbouring failure class is objectui#4308 mis-tiering (an item in a WRITABLE package shown behind the installed-package lock): if the packaged view/dashboard opens LOCKED, read the ResourceEditPage tiering (isArtifactItem; artifact-backed canWrite = allowEnvironmentOverlay) and the layered envelope's provenance, and forge the raw PUT, before filing a registry defect" ] }, "steps": [ @@ -1210,15 +1210,15 @@ "edit DIRECTLY on the packaged artifact — change the default list's label and add/remove a column — save draft and publish through the designer chrome", "as an end user, open the showcase task list (client reload only, no server restart): the edited label/column set renders", "server truth: GET the view again — the overlay body is served over the shipped base (overlay wins at read; the code layer remains underneath in the layered read)", - "dashboard leg: open the designer on the built-in system_overview dashboard — it opens EDITABLE the same way (dashboard allowOrgOverride:true); edit a widget title; publish; open the rendered System Overview board and confirm the edited title renders (its live-count semantics stay dashboards.system-overview-live-counts' business)", + "dashboard leg: open the designer on the built-in system_overview dashboard — it opens EDITABLE the same way (dashboard allowEnvironmentOverlay:true); edit a widget title; publish; open the rendered System Overview board and confirm the edited title renders (its live-count semantics stay dashboards.system-overview-live-counts' business)", "reset BOTH overlays via the designer's destructive control: it must carry the 'Reset overlay' verb (resettable:true on the layered envelope — a package baseline exists), confirm; the follow-up GET serves the SHIPPED body again and the end-user surfaces render the base", - "negative, same surface: open the designer route on the PACKAGED flow — it opens read-only behind the installed-package banner (artifact-backed + flow allowOrgOverride:false) with no operating save/publish path" + "negative, same surface: open the designer route on the PACKAGED flow — it opens read-only behind the installed-package banner (artifact-backed + flow allowEnvironmentOverlay:false) with no operating save/publish path" ], "acceptance": [ { "clause": "a PACKAGED view opens directly editable in the designer — no installed-package lock — and the edit persists as an org overlay served over the shipped base (the tier-1 claim 'views … are yours to edit directly', on the packaged artifact itself)", "oracle": "api", - "verify": "the editor renders editable (no artifactLockedBanner — artifact-backed canWrite is allowOrgOverride, which view declares true); after publish, GET /api/v1/meta/view?object=showcase_task serves the edited label/columns while the layered read still carries the code base underneath", + "verify": "the editor renders editable (no artifactLockedBanner — artifact-backed canWrite is allowEnvironmentOverlay, which view declares true); after publish, GET /api/v1/meta/view?object=showcase_task serves the edited label/columns while the layered read still carries the code base underneath", "evidence": "the editable-designer screenshot + the pre/post meta reads" }, { @@ -1242,7 +1242,7 @@ { "clause": "a Regime-C artifact in the SAME surface stays locked: the packaged flow opens read-only behind the installed-package banner with no operating save/publish path — 'yours to edit directly' is the Regime-O display classes (view, dashboard), never flow (ADR-0126 §3)", "oracle": "dom", - "verify": "after a screenshot confirms render, the flow editor shows the installed-package lock banner and its inputs are read-only (isArtifactItem + flow allowOrgOverride:false ⇒ canWrite false); no save/publish affordance operates", + "verify": "after a screenshot confirms render, the flow editor shows the installed-package lock banner and its inputs are read-only (isArtifactItem + flow allowEnvironmentOverlay:false ⇒ canWrite false); no save/publish affordance operates", "evidence": "the locked-editor screenshot + the disabled-state DOM read" } ], @@ -1254,11 +1254,11 @@ ], "traps": ["stale-console-bundle", "hydration-race", "automation-input", "cache-staleness"], "source": [ - "packages/spec/src/kernel/metadata-plugin.zod.ts#allowOrgOverride (view allowOrgOverride:true), (dashboard allowOrgOverride:true — the overlay door the dashboard leg rides, verified present), (flow allowOrgOverride:false — the same-surface Regime-C negative)", + "packages/spec/src/kernel/metadata-plugin.zod.ts#allowEnvironmentOverlay (view allowEnvironmentOverlay:true), (dashboard allowEnvironmentOverlay:true — the overlay door the dashboard leg rides, verified present), (flow allowEnvironmentOverlay:false — the same-surface Regime-C negative)", "docs/adr/0126-packaged-metadata-customization-model.md §3 (Regime O = view, dashboard, report, translation, email_template; flow = Regime C)", "content/docs/capabilities/integrations.mdx + content/docs/build-without-code.mdx (the E1 tier-1 claim under test: 'views and dashboards are yours to edit directly' on installed/packaged apps)", "packages/platform-objects/src/apps/dashboards/system_overview.dashboard.ts (the code-shipped built-in board the dashboard leg edits)", - "objectui packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx (isArtifactItem tiering), (artifact-backed canWrite = allowOrgOverride — why the view/dashboard open editable and the flow does not), (resettable → the Reset-overlay verb), (the installed-package lock banner) — objectui owns the designer and end-user markup, so locator fixes land there (ADR-0054); the framework owns the registry flags and the overlay/reset doors", + "objectui packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx (isArtifactItem tiering), (artifact-backed canWrite = allowEnvironmentOverlay — why the view/dashboard open editable and the flow does not), (resettable → the Reset-overlay verb), (the installed-package lock banner) — objectui owns the designer and end-user markup, so locator fixes land there (ADR-0054); the framework owns the registry flags and the overlay/reset doors", "#12438 (the ADR-0126 sweep this item lands from)", "studio-authoring.org-override-registry-gate (the raw-PUT registry-gate half — cross-referenced, not duplicated), studio-authoring.view-authoring-live (the non-packaged authoring loop this item complements), dashboards.system-overview-live-counts (the same board's live-count semantics — and the reason the overlay must be reset)" ], diff --git a/packages/spec/liveness/README.md b/packages/spec/liveness/README.md index 08d61a088b1..81927766793 100644 --- a/packages/spec/liveness/README.md +++ b/packages/spec/liveness/README.md @@ -977,7 +977,7 @@ marker where the Notes cell goes, never a guess at what belongs there. | book | seeded 2026-08-01 (#4488). ADR-0046 §6 spine; `audience` is ENFORCED and fail-closed (tree 401/403 + per-doc effective-audience union on both list and tree). Dead 2 = BOTH inline `translations` maps (book-level and per-group): no resolver reads them and the bundle translator doesn't cover `book` — the trap is that `doc.translations` two files over works on every read path. Also recorded: the `include: { tag }` rule variant is live — `tags` was DECLARED on DocSchema in 17.0.0 (#4509, ADR-0049); see the `doc` row below for the fix's full history. **#4667**: both inline translation maps retired — book-level row DELETED (BookSchema is strictObject), group-level row KEPT as a tombstone (BookGroupSchema is a plain z.object with no .strict(), so a bare delete would have zod silently strip it). No resolver read either; the trap was proximity to `doc.translations`, which is live on every doc render path. | | doc | seeded 2026-08-01 (#4488). Fully live: the kernel stores `content` unparsed, but the REST read layer localizes (resolveDocLocale), audience-gates, list-strips `content`, and the book resolver consumes name/label/description/order/group — plus the objectui console portal renders it all. The schema's own "docs are inert data" header describes the kernel, not the type. **`tags` DECLARED in 17.0.0 (#4509)** — the enforce half of enforce-or-remove: the book resolver's `include: { tag }` matcher, the REST transport and `ResolverDoc.tags` all already existed, but DocSchema is strict and had no `tags` key, so authoring one was a parse error and the variant could never match. Live on arrival | | email_template | this row read 8/–/13/– for one day (seeded 2026-08-01, #4488: "every authorable property is dead", the webhook shape on AUTH mail) and #4509 CLOSED it by ENFORCING — the second worked example, after `webhook`, that a dead verdict is a worklist entry rather than a tombstone. `bootstrapDeclaredEmailTemplates` materializes declared items into the `sys_email_template` rows `sendTemplate` reads, sharing `mapTemplateToRow` with the built-in seeder so the two doors cannot drift, and re-materializes on live metadata writes (`email_template` is `allowRuntimeCreate: true`, so boot-only would have left Studio saves inert). Three breaks had to close, not one: the engine never registered `emailTemplates:` into the registry, built-in seeds masqueraded as `managed_by: admin` and outranked declared templates, and nothing materialized. ADR-0054 proof bound on `subject` (`email-template-materialization`) | -| job | seeded 2026-08-01 (#4488). The file-authored path is fully enforced: all three schedule shapes honored by the adapters, `retryPolicy`/`timeout` enforced since #3494 (this is the retryPolicy the datasource ledger warns about confusing with its dead namesake), `enabled: false` skips scheduling. Dead 3 = `id` (authorWarn — `name` is the identity everywhere) + label/description (docs-kept). The type-level gap CLOSED 2026-08-02 (#4509) by closing the door rather than bridging it: `handler` names a function in the compiled bundle's function table, which a runtime writer cannot name, so `allowRuntimeCreate` **and** `allowOrgOverride` are now false and `*.job.ts` / `defineStack({ jobs })` are the supported doors. The kind stays registered — its file loader is genuinely consumed (ADR-0088 admission test) **#4667**: `id` REMOVED (row deleted, strict removal) — nothing read it and its own describe() ("defaults to `name` when omitted") advertised an identity override that never existed; `name` is the scheduling key, the sys_job row key and the JobExecution.jobId stamp, so two jobs differing only in `id` were one job. **#7131** (PR #7425) takes the remaining two: `label` and `description` re-grade `dead` → `live` under the 2026-08-10 maintainer ruling that **designer previews count as consumers** — objectui's `JobPreview` had been reading `d.label` and `d.description` and rendering them as the preview card's title and subtitle the whole time, so the old "no runtime consumer" was a true statement about the *scheduler* and a false one about the system. **This row now has zero dead and the ADR-0033 exemption is still in force**, which is worth saying out loud because it is the first row in this table where those two facts hold together: the keys are still docs-shaped, still deliberately KEPT, still not `authorWarn`'d, and enforce-or-remove still has nothing to chase here. What changed is only that the exemption no longer has to carry the verdict — the measurement does. | +| job | seeded 2026-08-01 (#4488). The file-authored path is fully enforced: all three schedule shapes honored by the adapters, `retryPolicy`/`timeout` enforced since #3494 (this is the retryPolicy the datasource ledger warns about confusing with its dead namesake), `enabled: false` skips scheduling. Dead 3 = `id` (authorWarn — `name` is the identity everywhere) + label/description (docs-kept). The type-level gap CLOSED 2026-08-02 (#4509) by closing the door rather than bridging it: `handler` names a function in the compiled bundle's function table, which a runtime writer cannot name, so `allowRuntimeCreate` **and** `allowEnvironmentOverlay` are now false and `*.job.ts` / `defineStack({ jobs })` are the supported doors. The kind stays registered — its file loader is genuinely consumed (ADR-0088 admission test) **#4667**: `id` REMOVED (row deleted, strict removal) — nothing read it and its own describe() ("defaults to `name` when omitted") advertised an identity override that never existed; `name` is the scheduling key, the sys_job row key and the JobExecution.jobId stamp, so two jobs differing only in `id` were one job. **#7131** (PR #7425) takes the remaining two: `label` and `description` re-grade `dead` → `live` under the 2026-08-10 maintainer ruling that **designer previews count as consumers** — objectui's `JobPreview` had been reading `d.label` and `d.description` and rendering them as the preview card's title and subtitle the whole time, so the old "no runtime consumer" was a true statement about the *scheduler* and a false one about the system. **This row now has zero dead and the ADR-0033 exemption is still in force**, which is worth saying out loud because it is the first row in this table where those two facts hold together: the keys are still docs-shaped, still deliberately KEPT, still not `authorWarn`'d, and enforce-or-remove still has nothing to chase here. What changed is only that the exemption no longer has to carry the verdict — the measurement does. | | mapping | seeded 2026-08-01 (#4488) at 8/11 live; **0 dead since #4509** retired the three that were not. The import half (#2611) is loudly enforced — unsupported transforms/formats are 400s, `mode`/`upsertKey` default the request, the wizard picker renders `label`. RETIRED 17.0.0: `extractQuery` (authorWarn — "for export only" promised an export path no exporter implements) + `errorPolicy`/`batchSize`, which were dead AND **unwarnable** (schema defaults materialize at parse, so presence ≠ authored — `_authorWarnSkipped`, the non-boolean instance of the default(true) rule). That unwarnability is why they went out in the 17.0.0 window rather than after a deprecation cycle: removal was the only channel that could ever reach the author. Rows DELETED, not tombstoned — MappingSchema is strict, so the keys left the walked shape. `connectorSource` (2026-09-30) is this type's first `planned` row: the pull binding the connector-attached sync family MOVED here when the connector's `syncConfig` / `fieldMappings` retired to tombstones, seeded contract-first with `authorWarn` (its seven drilled rows — five children and `watermark`'s two — `planned` with it) because the pull executor is the next stage. **Live since 2026-10-01 (#20919)**: the connector sync executor reads every key. The container's `authorWarn` left with that re-grade (#21127) — kept at first, it made the author-side lint throw, so `os validate` / `os lint` exited 1 on every stack that authored the binding; the caveat it carried (nothing schedules a pull until the `job` stage lands) is on the key's description | | picklist | seeded 2026-09-30 (#19518) with every key `planned`, ahead of its runtime reader by ruling; **live since the runtime layer (#19519)**: the ObjectQL registry merges `picklistExtensions` into the list (additive only, a repeated value refused) and resolves it onto every field that names it, the write door judges against that set, and a field naming an undeclared list fails the boot. `field.picklist` left `authorWarn` with it. Display keys (`label`, `description`, `options.color`) are graded by their served form. | | seed | seeded 2026-08-01 (#4488). Fully live via SeedLoaderService on both doors (boot/per-org replay + runtime-draft publish). `records` is the z.record walk boundary: the keys an author writes are the target object's fields, governed by that object's own definitions — recorded in the entry, not silently skipped | @@ -985,7 +985,7 @@ marker where the Notes cell goes, never a guess at what belongs there. | qa | seeded 2026-08-10 (#6247) — **not a metadata type**: `TestSuiteSchema` is the FILE surface of the shipped `os test` command (`qa/*.test.json`), governed through the same `SPEC_ONLY_SCHEMAS` override as `query`/`webhook`/`validation`. It is in the table as the clearest worked example of a **false `dead` measurement**: #6247 reported the whole domain declared-but-inert on a grep that scanned only `*Schema` identifiers, and every consumer here reads the **type** names (`QA.TestSuite`, `QA.TestStep`, `QA.TestAction`) — so an entire execution chain (core's `TestRunner` + `HttpTestAdapter`, published via `export * as QA`, driven by a documented CLI command) read as zero consumers, and a retire ruling was issued on it before being withdrawn. The `evidenceScope` table one section up says no amount of specifier matching is sufficient for a negative claim; this is the same lesson for **identifier** matching. What was really wrong was narrower and real: the type was the contract and the schema had no `parse` site, so the CLI's `JSON.parse(content) as QA.TestSuite` cast admitted anything — ENFORCED in the same change (`TestSuiteSchema.safeParse` at the load site, pinned). The seeding recorded Dead 5 — `name`, `scenarios.name`, `scenarios.description`, `scenarios.tags` and `scenarios.requires` — and #20289 (family `qa-runner`, verdict ENFORCE) made all FIVE live: the suite `name` heads the suite in `os test`'s report and is stamped as `suiteName` on every TestResult the suite produces; `scenarios.name` is printed beside the id and carried as `scenarioName`; `scenarios.description` is carried and printed under a failed scenario; `scenarios.tags` is read by `os test --tags` (any-of, exact; deselected scenarios are counted and never passed); and `scenarios.requires` is judged by the TestRunner before a scenario's first step — `params` against the environment of the process running `os test`, `services` against the target's discovery `services` (enabled and `available`) — so an unmet entry SKIPS the scenario with its reason, counted apart and never passed. `requires` was put to the maintainer first rather than guessed (ruled B): over HTTP no server surface lists the loaded plugins, so `requires.plugins` is a `retiredKey()` tombstone inside the block whose prescription names `requires.services`. It does not carry `authorWarn` and the omission is deliberate (`_authorWarnSkipped`): the lint walks stack **collections**, a QA suite is a loose file in no stack, so a warn flag here would emit nothing — a silent no-op inside the mechanism built to catch silent no-ops | | validation | seeded 2026-08-01 (#4488). The ADR-0020 carrier: the evaluator honors active/events/priority/severity/type/condition/message (the zod header's "only reads type/condition/…" prose is STALE — trust the ledger). Dead 3 = label/description/tags, declared governance metadata, kept unmarked. Union walk boundary recorded: only base + `script` keys walked; per-variant keys are governed by the evaluator's tests, not ledger rows. **No longer a registered metadata kind** — #4509 retired it under ADR-0088 (a standalone rule had no object-binding key and every variant is `.strict()`, so it bound to nothing and gated no write; a state machine authored that way saved cleanly and did nothing). The rule VOCABULARY is untouched and fully live via `object.validations[]`, so the ledger keeps governing it through the gate's spec-only override, alongside `webhook` and `query`. The contrast with the two bridges in the same batch is the point: enforce-or-remove picked ENFORCE where the feature existed and only the wiring was missing, and REMOVE where the shape itself could not carry the feature | | api | seeded 2026-08-04 (#5271, part of #5206; PR #5312) — **not a metadata type until that same change made it one**, which is the row's point: governance and registration landed together, the treatment `datasource` did not get (#4487) and paid for with six inert keys found by hand. What #5206 measured before the fix: `api` was in neither `DEFAULT_METADATA_TYPE_REGISTRY` nor `BUILTIN_METADATA_TYPE_SCHEMAS`, so `saveMetaItem`'s `resolveOverlaySchema('api', …)` → `getMetadataTypeSchema('api')` returned `undefined` and took its own documented branch — an unregistered type is stored **unvalidated** — while `getMetaTypes()` could not enumerate the type at all, so Studio rendered neither list nor form. That issue names the shape precisely and it is the inverse of this ledger's usual one: **enforced but undeclared** (the matcher was already indexing these entries, #5089), where `dead` is declared-but-unenforced. The seeding pass classified 27 keys — live 25 / planned 2 / dead 0 — each cited `file:line` at the consumer layer that reads it: the MATCHER (`packages/metadata/src/endpoint-matcher.ts`) indexes `name`/`path`/`method`; the EXECUTOR (`packages/runtime/src/endpoint-executor.ts`) dispatches on `type` and reads `target`/`objectParams`; the POLICY chain (`packages/runtime/src/endpoint-policy.ts` + `security/inbound-rate-limit.ts`) enforces `authRequired`/`rateLimit`/`cacheTtl`; the MAPPING layer (`packages/runtime/src/api-mapping.ts`) applies `inputMapping`/`outputMapping`; and OpenAPI enrichment (`packages/rest/src/openapi-endpoints.ts`) emits `summary`/`description`. Timing was the reason it was cheap: #5040's E-series had built every one of those consumers and all of it was on main, so each key had a real evidence path rather than a promise. **Planned 2 = `inputMapping.transform` + `outputMapping.transform`, and `planned` rather than `dead` is load-bearing**: `dead` here means parsed with no consumer — a silent no-op — and these are the opposite, parsed and then LOUDLY REFUSED at publish (`endpoint-publish-gate.ts` mappingGate) and again at runtime, because no transformation-function registry exists anywhere in the platform. An author who writes one is told so and told what to do instead, so there is nothing for enforce-or-remove to chase; they stay in the vocabulary because admitting them needs a function registry **and** a sandbox ruling (#5040 §3.4), which is a design decision, not a key to quietly delete. Zero dead | -| capability | seeded 2026-08-08 (#5961; commit ae31a1912) — `CapabilityDeclarationSchema`, the DECLARATION side of ADR-0066 D1's three-way separation: packages DEFINE a capability, permission sets GRANT it via `systemPermissions`, resources REQUIRE it via `requiredPermissions`. **The gate's 12 and the seeding PR's 5 are the same measurement at two granularities** — commit ae31a1912 call-graph-closed **5 authorable properties**, every one to a real reader in `packages/plugins/plugin-security/src/bootstrap-declared-capabilities.ts` (the one consumer that turns a declaration into a `sys_capability` row), all `live`, with no `PENDING_GOVERNANCE` debt recorded; the other 7 are the ADR-0010 protection-envelope keys the gate auto-classifies `live` and which carry `null` verdicts in the file, exactly as on `permission`/`position`. The same worked example as `api` above and commit ae31a1912 says so in those words — **enforced but undeclared**, the mirror of the hole #5271 closed. What #5961 measured: absent from `DEFAULT_METADATA_TYPE_REGISTRY`, `BUILTIN_METADATA_TYPE_SCHEMAS` and `HAND_CRAFTED_SCHEMAS`, so `isRuntimeCreateAllowed()` took its no-static-entry fallback (permanently true) and `saveMetaItem` its no-schema branch — `PUT /api/v1/meta/capability/:name` accepted **arbitrary JSON** onto an authorization surface whose names `systemPermissions`/`requiredPermissions` resolve by string, while `/meta/types` synthesised a false `allowRuntimeCreate: true` descriptor Studio drew a raw-JSON create form from. #5870 did not open that path (the write gate reads the registry, not the item store); it only made the type visible in `getMetaTypes()`, and both the issue and this row say so to stop the next reader filing it as a regression. Landed as ruling A on ADR-0066 D1's own authority: `allowRuntimeCreate: false` **and** `allowOrgOverride: false`, the second self-judged inside the ruling's rationale and flagged for veto — a tenant overlay of a package declaration would lift `scope` from `org` to `platform`, which is the one field on this type that is an escalation rather than display. Its reverse verification is worth copying: deleting the registry entry gave 7 red / 3 green and measured something **sharper than predicted** — a garbage payload turned 422 rather than resolving, i.e. the schema binding is a real second line of defence behind the registry row, not a restatement of it; deleting the schema binding alone gave exactly 3 red. `packageId` is the one key that reads oddly: deliberately a FALLBACK, not the primary, since #5870 added `capabilities` to the ObjectQL stamped-collection list so `_packageId` now reaches a declaration and wins — it stays `live` because the fallback branch still decides materialization for any declaration arriving unstamped. Zero dead | +| capability | seeded 2026-08-08 (#5961; commit ae31a1912) — `CapabilityDeclarationSchema`, the DECLARATION side of ADR-0066 D1's three-way separation: packages DEFINE a capability, permission sets GRANT it via `systemPermissions`, resources REQUIRE it via `requiredPermissions`. **The gate's 12 and the seeding PR's 5 are the same measurement at two granularities** — commit ae31a1912 call-graph-closed **5 authorable properties**, every one to a real reader in `packages/plugins/plugin-security/src/bootstrap-declared-capabilities.ts` (the one consumer that turns a declaration into a `sys_capability` row), all `live`, with no `PENDING_GOVERNANCE` debt recorded; the other 7 are the ADR-0010 protection-envelope keys the gate auto-classifies `live` and which carry `null` verdicts in the file, exactly as on `permission`/`position`. The same worked example as `api` above and commit ae31a1912 says so in those words — **enforced but undeclared**, the mirror of the hole #5271 closed. What #5961 measured: absent from `DEFAULT_METADATA_TYPE_REGISTRY`, `BUILTIN_METADATA_TYPE_SCHEMAS` and `HAND_CRAFTED_SCHEMAS`, so `isRuntimeCreateAllowed()` took its no-static-entry fallback (permanently true) and `saveMetaItem` its no-schema branch — `PUT /api/v1/meta/capability/:name` accepted **arbitrary JSON** onto an authorization surface whose names `systemPermissions`/`requiredPermissions` resolve by string, while `/meta/types` synthesised a false `allowRuntimeCreate: true` descriptor Studio drew a raw-JSON create form from. #5870 did not open that path (the write gate reads the registry, not the item store); it only made the type visible in `getMetaTypes()`, and both the issue and this row say so to stop the next reader filing it as a regression. Landed as ruling A on ADR-0066 D1's own authority: `allowRuntimeCreate: false` **and** `allowEnvironmentOverlay: false`, the second self-judged inside the ruling's rationale and flagged for veto — a tenant overlay of a package declaration would lift `scope` from `org` to `platform`, which is the one field on this type that is an escalation rather than display. Its reverse verification is worth copying: deleting the registry entry gave 7 red / 3 green and measured something **sharper than predicted** — a garbage payload turned 422 rather than resolving, i.e. the schema binding is a real second line of defence behind the registry row, not a restatement of it; deleting the schema binding alone gave exactly 3 red. `packageId` is the one key that reads oddly: deliberately a FALLBACK, not the primary, since #5870 added `capabilities` to the ObjectQL stamped-collection list so `_packageId` now reaches a declaration and wins — it stays `live` because the fallback branch still decides materialization for any declaration arriving unstamped. Zero dead | | manifest | seeded 2026-08-23 (commit 3fc606687) — **not a metadata type and not a stack collection either**, which is the row's whole point. `ManifestSchema` (`packages/spec/src/kernel/manifest.zod.ts:132`) is what an author writes as `objectstack.config.ts` or a packaged manifest; it is parsed at `packages/objectql/src/registry.ts:2950` and by `os plugin build`, and it sat outside the ratchet's universe entirely — `GOVERNED` listed no `plugin`/`manifest`/`package`, `SPEC_ONLY_SCHEMAS` covered only webhook/query/validation/qa, `PENDING_GOVERNANCE` was empty so the gate reported itself **complete**, and `liveness/` had no file for it. A ratchet extended only to unregistered KINDS would not have reached it either: the retired-key entry `17.kernel__Manifest__loading.ts` records that `PLURAL_TO_SINGULAR` has no `packages`/`plugins` entry, so a manifest is never walked as a stack collection member. That blind spot was paid for twice, by hand and after the fact — `loading`'s ten inert keys (#4914, one of them `sandboxing`, which isolated nothing while looking like isolation) and the `contributes` census (recorded in commit be21955ba), which found exactly ONE reader of the 11-member block monorepo-wide. Dead 20 = the ten dead `contributes` members (`kinds` is the sole live one — `engine.ts:4504` → `registerKind`), the five `capabilities.*` and two `configuration.*` keys (all three containers have ZERO reads of the container itself, so no key beneath one can be read), plus `extensions`, `integrity`, and the tombstoned `loading` whose row must stay because `retiredKey()` keeps the key in the walked shape. **`integrity` is security-shaped and not retired here**: it declares per-file digests the future runtime loader is designed to re-verify at unpack (ADR-0025 §3.5 steps 4–7) while nothing re-verifies them today (since #13464 the publisher computes the map at build and self-checks it at the `os plugin publish` preflight, but nothing re-checks it after publish; the leg is the loader's, NOT the cloud control plane's). `runtime` — the ADR-0025 §3.6 trust tier, the other security-shaped key, read locally only by two CLI lines that ECHO the value with no `runtime === 'sandbox'` branch anywhere, while `loading`'s own tombstone used to redirect upgrading authors to it as something "which [is] enforced" — is the family's SPLIT verdict and the ledger's first `live-elsewhere` row (#13483): #12400 measured the cloud leg (cloud @15f55df, 2026-08-29) and found a real consumer — the marketplace publish gate hard-rejects (HTTP 422) an unverified publisher requesting the `node` tier — so the maintainer ruling of 2026-08-30 (executed by commit a9ee98992) took "say it truthfully" over retirement, and retirement is ruled OUT because deleting the key would tear out that gate's input. The tombstone, both `runtime` describes and the D3 entry state the split (publish-gate enforced; load-side NOT implemented); the row's verdict now says it as data rather than as a qualifying note, under the elsewhere criteria (foreign pointer + cross-repo scope + attestation with a 180d expiry), and load-side enforcement is a v18 direction rather than a removal. The `contributes` dispositions have since MOVED: the cloud leg was measured CLEAN 2026-08-24 (recorded in commit be21955ba; cloud `origin/main` @ 5b5925a, zero `manifest.contributes` reads, controls held), and commit be21955ba then executed the retirement — the nine mechanically-dead members are `retiredKey()` tombstones (D3 `plugin-manifest-contributes-dead-members-retired`), their rows staying because a tombstone keeps the key in the walked shape. Commit bc56e1881 then executed the second retirement too (ruled B 2026-08-22; D3 `plugin-manifest-contributes-routes-retired`), tombstoning `routes` and leaving `kinds` the block's sole live member. The remaining non-`contributes` `dead` rows keep their three-repo census verdicts as an enforce-or-remove worklist, not a licence to delete. `permissions` stays `live` on corrected evidence after its legacy flat-list arm was retired (ADR-0049, conversion `manifest-permissions-string-list-removed`): the block is the structured ADR-0025 one alone, its measured consumer is objectui's install-consent disclosure (it refuses nothing at load), and the `collectDeclaredSuggestions` evidence it used to cite was the assembled-stage `PermissionSet[]` reader, which only reports an authoring-stage value as skipped | | crud_endpoints | seeded 2026-09-02 (commit a3d5724c8) — one of the FOUR `RestServerConfig` sub-objects, and the family that made the `SPEC_ONLY_SCHEMAS` boundary explicit: SERVER CONFIGURATION. An author writes `RestServerConfigSchema` (`packages/spec/src/api/rest-server.zod.ts`) as the REST server's construction argument — not a metadata item, not a request body, not a manifest — so no registry has ever held it and no ratchet rooted in one could ask who reads it. Rooted on the four sub-schemas rather than on the whole config on purpose: the walk drilled exactly ONE level when this was rooted (it recurses as of #17424; the rooting stands), so with `RestServerConfigSchema` as the root the sub-objects would BE the drilled level and `metadata.endpoints.schema` / `batch.operations.upsertMany` would have no row of their own, their container's blanket `live` silently covering a dead key — #4956's shape, in the ledger written to end it. `RestApiConfigSchema` (the fifth sub-object, `api`) is now enrolled SEPARATELY as `rest_api` (#14640, 2026-09-21): when these four landed its consumption seam was still validate-only, so a census of it would have recorded a half that was about to move — that half has moved (`normalizeConfig` builds the `api` block from its parsed output, released), so the fence expired and the fifth sub-object was measured on the settled seam. ⛔ Its ledger is `rest_api.json`, never `api.json`, which is a different surface entirely. **What #11984 settled and what it did not**: that PR made `RestServer.normalizeConfig` PARSE and CONSUME these four instead of casting them, so an out-of-enum or out-of-range value is now refused at construction — accept/reject. Executing a declared contract does not give a key a consumer, and this family is that distinction's worked example. Dead 5 = `objectParamStyle` (every CRUD route takes the object name as a PATH segment; `'query'` is validated against the enum and mounts exactly what `'path'` mounts) and the four members of `patterns` (custom URL patterns are normalized and never read — every route is mounted from hard-coded method/path pairs in `registerCrudEndpoints`). Live 6 = the five `operations.*` switches, each gating a route mount, and `dataPrefix`, which has five independent consumers and moves the mounted paths and the advertised discovery document together. A different question was open about `patterns` (its `z.record` input type demands all five operations) — a declaration defect, not a liveness one, not re-derived here **Commit b3a63d32c RETIRED the dead set (2026-09-03, ADR-0049 enforce-or-remove)**: `patterns` and `objectParamStyle` are `retiredKey()` tombstones (the schema is a non-strict `z.object`, so the rows STAY `dead` with a REMOVED note — the rls.priority precedent) and the four `patterns.*` child rows collapse into the one `patterns` row because the value def `CrudEndpointPattern` left the shape with its key (RETIRED_DEFS_BY_MAJOR[18]). REMOVE, not ENFORCE, because the mounted CRUD paths are the contract the client SDK, the discovery document and /openapi.json all describe — a per-operation pattern knob could only make them lie — and an endpoint on a custom path is a declarative `api` endpoint. `evidenceScope` widened to `cross-repo` on the cloud reading (#14796 @9b6abe0f2fd5: zero, structural). The `z.partialRecord` question closes with the record | | metadata_endpoints | seeded 2026-09-02 (commit a3d5724c8) — one of the FOUR `RestServerConfig` sub-objects, and the family that made the `SPEC_ONLY_SCHEMAS` boundary explicit: SERVER CONFIGURATION. An author writes `RestServerConfigSchema` (`packages/spec/src/api/rest-server.zod.ts`) as the REST server's construction argument — not a metadata item, not a request body, not a manifest — so no registry has ever held it and no ratchet rooted in one could ask who reads it. Rooted on the four sub-schemas rather than on the whole config on purpose: the walk drilled exactly ONE level when this was rooted (it recurses as of #17424; the rooting stands), so with `RestServerConfigSchema` as the root the sub-objects would BE the drilled level and `metadata.endpoints.schema` / `batch.operations.upsertMany` would have no row of their own, their container's blanket `live` silently covering a dead key — #4956's shape, in the ledger written to end it. `RestApiConfigSchema` (the fifth sub-object, `api`) is now enrolled SEPARATELY as `rest_api` (#14640, 2026-09-21): when these four landed its consumption seam was still validate-only, so a census of it would have recorded a half that was about to move — that half has moved (`normalizeConfig` builds the `api` block from its parsed output, released), so the fence expired and the fifth sub-object was measured on the settled seam. ⛔ Its ledger is `rest_api.json`, never `api.json`, which is a different surface entirely. **What #11984 settled and what it did not**: that PR made `RestServer.normalizeConfig` PARSE and CONSUME these four instead of casting them, so an out-of-enum or out-of-range value is now refused at construction — accept/reject. Executing a declared contract does not give a key a consumer, and this family is that distinction's worked example. Dead 2 = `cacheTtl` and `endpoints.schema`. `enableCache` is live and `cacheTtl` is not, which is the pair worth reading together: the cached branch delegates to the protocol's `getMetaItemCached`, whose signature takes no TTL, and no cache header anywhere is built from this value. Its negative-bound observation travels in that row by triage ruling rather than as a separate defect — the schema declares `z.number().int()` with no lower bound, so `-1` is accepted, and #11984 pins it as accepted because that is what the contract says. `endpoints.schema` is the sharpest case in the family for per-key rows: its three siblings each gate a route mount and it gates nothing, because `GET /meta/:type/:name/schema` does not exist — `packages/rest/src` mounts no path ending in `/schema` at all **Commit b3a63d32c RETIRED both (2026-09-03, ADR-0049)**: `cacheTtl` and `endpoints.schema` are `retiredKey()` tombstones, rows kept `dead` with a REMOVED note. The negative-bound observation dies with `cacheTtl` (its #11984 acceptance pin is reversed to a refusal pin); `endpoints.schema` had no route to gate, so there was nothing to enforce. `evidenceScope` widened to `cross-repo` (#14796) | diff --git a/packages/spec/liveness/capability.json b/packages/spec/liveness/capability.json index b052f032c3e..6500912f575 100644 --- a/packages/spec/liveness/capability.json +++ b/packages/spec/liveness/capability.json @@ -24,7 +24,7 @@ "status": "live", "verifiedAt": "2026-10-10", "evidence": "objectui: packages/app-shell/src/views/metadata-admin/SchemaForm.tsx (the item page renders each property of the type's served schema, `scope` included, for a capability read from `GET /api/v1/meta/capability/:name`; read at the pinned `.objectui-sha` 20c6d351ad) — display", - "note": "platform vs org decides whether holding the capability is a global power or one scoped to the caller's organization — the same distinction PLATFORM_CAPABILITIES carries on the curated side. Not display: it is the field that would be escalated if a tenant could overlay a package-shipped declaration, which is why the #5961 registry entry sets `supportsOverlay: false` / `allowOrgOverride: false`. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — `:124` had rotted onto `unchanged: number;`, the counter field itself (see the `label` entry). Re-closed by hand against 8cb96ec41." + "note": "platform vs org decides whether holding the capability is a global power or one scoped to the caller's organization — the same distinction PLATFORM_CAPABILITIES carries on the curated side. Not display: it is the field that would be escalated if a tenant could overlay a package-shipped declaration, which is why the #5961 registry entry sets `supportsOverlay: false` / `allowEnvironmentOverlay: false`. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — `:124` had rotted onto `unchanged: number;`, the counter field itself (see the `label` entry). Re-closed by hand against 8cb96ec41." }, "packageId": { "status": "live", diff --git a/packages/spec/liveness/job.json b/packages/spec/liveness/job.json index 7cb0c6cf550..37d5356a7de 100644 --- a/packages/spec/liveness/job.json +++ b/packages/spec/liveness/job.json @@ -1,6 +1,6 @@ { "type": "job", - "_note": "JobSchema. The file-authored path is healthy: `defineStack({ jobs })` → `AppPlugin.start`'s `kernel:ready` hook → IJobService.schedule → the service-job adapters honor every schedule shape (`CronJobAdapter.schedule` / `DbJobAdapter.schedule`) and `runWithPolicy` enforces retryPolicy/timeout (#3494 — these used to be parsed-but-ignored). `retryPolicy` here is the ENFORCED spelling ({maxRetries, backoffMs, backoffMultiplier}); do not confuse it with the datasource `retryPolicy`, which is dead and spells its delay differently. TYPE-LEVEL GAP CLOSED 2026-08-02 (#4509) by CLOSING THE DOOR, not building a bridge: `job` was registered `allowRuntimeCreate: true` while only the compiled bundle's `jobs` ever reached the scheduler, so a Studio-created job saved cleanly and never ran. Unlike the webhook (#3461) and email_template (#4509 item 1) disconnects, this one could not be bridged: `handler` names a function in the compiled bundle's function table (`collectBundleFunctions`), which a runtime writer does not have and cannot name — the missing piece is a handler-binding design, not an ingestion path. So `allowRuntimeCreate` AND `allowOrgOverride` are now both false (metadata-plugin.zod.ts, with the rationale block), leaving `*.job.ts` / `defineStack({ jobs })` as the supported doors. The kind stays registered: its file loader is genuinely consumed, so it still passes the ADR-0088 admission test. Seeded 2026-08-01. 2026-08-28 (commit 8cb96ec41): every LOCAL citation re-anchored to its consuming symbol — and this file is its own best argument for doing so. The 2026-08-02 note recorded that the SEEDED lines had already drifted ~25 lines and were restamped with fresh numbers; twenty-six days later every one of those fresh numbers had drifted again, this time by ~70-100 lines, onto `} else {`, `try {`, a bare `}` and a line about registering ACTIONS. Restamping is not a fix for line rot, it is the same claim with a newer date — which is the case this whole worklist rests on. The two objectui-cited entries (`label`, `description`) are left BYTE-FOR-BYTE UNTOUCHED: their evidence and producer are pinned at `objectui @aeb8424b`, a commit this container cannot reproduce, and foreign anchors are never collected by the scanner. 2026-10-03 (#21489): a job's sandboxed `body` is scheduled by the binder's job half (`scheduleAppArtifactJobs`, `packages/runtime/src/app-artifact-handlers.ts`) on every door that brings an artifact in — the boot and install-local (install and rehydrate) — and the install-local door refuses a package whose enabled job has no `body`, the one shape no JSON door can run. 2026-10-04 (#20281 stage ③, rulings Q1-B + Q2-O1): two keys land — `pull`, the declarative run form (`{ mapping }`: the platform pulls that mapping's `connectorSource` on the job's schedule through the `automation` service's contract method `pullConnectorSource`, bound by the same binder's job half), and `organization`, the organization every run form runs as, judged at bind by the scheduled flows' posture rule (`resolveScheduledWorkPolicy`).", + "_note": "JobSchema. The file-authored path is healthy: `defineStack({ jobs })` → `AppPlugin.start`'s `kernel:ready` hook → IJobService.schedule → the service-job adapters honor every schedule shape (`CronJobAdapter.schedule` / `DbJobAdapter.schedule`) and `runWithPolicy` enforces retryPolicy/timeout (#3494 — these used to be parsed-but-ignored). `retryPolicy` here is the ENFORCED spelling ({maxRetries, backoffMs, backoffMultiplier}); do not confuse it with the datasource `retryPolicy`, which is dead and spells its delay differently. TYPE-LEVEL GAP CLOSED 2026-08-02 (#4509) by CLOSING THE DOOR, not building a bridge: `job` was registered `allowRuntimeCreate: true` while only the compiled bundle's `jobs` ever reached the scheduler, so a Studio-created job saved cleanly and never ran. Unlike the webhook (#3461) and email_template (#4509 item 1) disconnects, this one could not be bridged: `handler` names a function in the compiled bundle's function table (`collectBundleFunctions`), which a runtime writer does not have and cannot name — the missing piece is a handler-binding design, not an ingestion path. So `allowRuntimeCreate` AND `allowEnvironmentOverlay` are now both false (metadata-plugin.zod.ts, with the rationale block), leaving `*.job.ts` / `defineStack({ jobs })` as the supported doors. The kind stays registered: its file loader is genuinely consumed, so it still passes the ADR-0088 admission test. Seeded 2026-08-01. 2026-08-28 (commit 8cb96ec41): every LOCAL citation re-anchored to its consuming symbol — and this file is its own best argument for doing so. The 2026-08-02 note recorded that the SEEDED lines had already drifted ~25 lines and were restamped with fresh numbers; twenty-six days later every one of those fresh numbers had drifted again, this time by ~70-100 lines, onto `} else {`, `try {`, a bare `}` and a line about registering ACTIONS. Restamping is not a fix for line rot, it is the same claim with a newer date — which is the case this whole worklist rests on. The two objectui-cited entries (`label`, `description`) are left BYTE-FOR-BYTE UNTOUCHED: their evidence and producer are pinned at `objectui @aeb8424b`, a commit this container cannot reproduce, and foreign anchors are never collected by the scanner. 2026-10-03 (#21489): a job's sandboxed `body` is scheduled by the binder's job half (`scheduleAppArtifactJobs`, `packages/runtime/src/app-artifact-handlers.ts`) on every door that brings an artifact in — the boot and install-local (install and rehydrate) — and the install-local door refuses a package whose enabled job has no `body`, the one shape no JSON door can run. 2026-10-04 (#20281 stage ③, rulings Q1-B + Q2-O1): two keys land — `pull`, the declarative run form (`{ mapping }`: the platform pulls that mapping's `connectorSource` on the job's schedule through the `automation` service's contract method `pullConnectorSource`, bound by the same binder's job half), and `organization`, the organization every run form runs as, judged at bind by the scheduled flows' posture rule (`resolveScheduledWorkPolicy`).", "props": { "name": { "status": "live", diff --git a/scripts/adr-anchors/packages__plugins__plugin-security__src__permission-set-projection.ts.json b/scripts/adr-anchors/packages__plugins__plugin-security__src__permission-set-projection.ts.json index 7a6a248e5ad..43d5b6592d2 100644 --- a/scripts/adr-anchors/packages__plugins__plugin-security__src__permission-set-projection.ts.json +++ b/scripts/adr-anchors/packages__plugins__plugin-security__src__permission-set-projection.ts.json @@ -5,5 +5,5 @@ "ADR-0005", "ADR-0086" ], - "invariant": "This file IS ADR-0094's machinery (write-through + awaited projector + boot reconciliation), and since 2026-08-09 it is also the site of ADR-0094 D5-R. The 2026-07-14 direction confirmation that once sat in its header — package sets are customized through an ADR-0005 env overlay — is RETIRED: #6483 rolled `permission` back to `allowOrgOverride: false`, so a metadata write against a CODE-DECLARED (artifact-backed) set is refused 403 NOT_OVERRIDABLE, while a set whose definition lives only in `sys_metadata` rides `allowRuntimeCreate` and still works. Two rules follow and must not be quietly undone: (1) the refusal is LEFT TO THE PRODUCER — never re-derive artifact-backing here to pre-empt it, and never add a tolerant fallback (`??`, catch-and-continue) around the refused `saveMetaItem`; `isArtifactBacked` is the protocol's rule and the `managed_by` column is measurably not that fact; (2) the two write points that DO catch (the `restore` leg and the reconcile backfill) catch because the record already exists by then — they report on the durability channel (#4632) and must keep doing so rather than being 'fixed' into throwing. Restoring the overlay channel needs an ADR-0005 revision ratifying an admission pair, not an edit here or in the registry." + "invariant": "This file IS ADR-0094's machinery (write-through + awaited projector + boot reconciliation), and since 2026-08-09 it is also the site of ADR-0094 D5-R. The 2026-07-14 direction confirmation that once sat in its header — package sets are customized through an ADR-0005 env overlay — is RETIRED: #6483 rolled `permission` back to `allowOrgOverride: false` (the flag is `allowEnvironmentOverlay` since protocol 18), so a metadata write against a CODE-DECLARED (artifact-backed) set is refused 403 NOT_OVERRIDABLE, while a set whose definition lives only in `sys_metadata` rides `allowRuntimeCreate` and still works. Two rules follow and must not be quietly undone: (1) the refusal is LEFT TO THE PRODUCER — never re-derive artifact-backing here to pre-empt it, and never add a tolerant fallback (`??`, catch-and-continue) around the refused `saveMetaItem`; `isArtifactBacked` is the protocol's rule and the `managed_by` column is measurably not that fact; (2) the two write points that DO catch (the `restore` leg and the reconcile backfill) catch because the record already exists by then — they report on the durability channel (#4632) and must keep doing so rather than being 'fixed' into throwing. Restoring the overlay channel needs an ADR-0005 revision ratifying an admission pair, not an edit here or in the registry." } diff --git a/scripts/adr-anchors/packages__spec__src__kernel__metadata-plugin.zod.ts.json b/scripts/adr-anchors/packages__spec__src__kernel__metadata-plugin.zod.ts.json index 34fe2a93e0f..0f94feab28b 100644 --- a/scripts/adr-anchors/packages__spec__src__kernel__metadata-plugin.zod.ts.json +++ b/scripts/adr-anchors/packages__spec__src__kernel__metadata-plugin.zod.ts.json @@ -3,5 +3,5 @@ "adrs": [ "ADR-0005" ], - "invariant": "The `allowOrgOverride` column of DEFAULT_METADATA_TYPE_REGISTRY is the machine-readable form of ADR-0005's tenant-customizable whitelist (docs/adr/0005-metadata-customization-overlay.md:53-64) — `view`/`dashboard`/`report` (plus `email_template`/`translation` per the system row) opt in; every other type is `false` until an ADR-0005 revision ratifies its admission pair (an overlay schema `resolveOverlaySchema()` accepts AND a written render-only rationale). The 2026-08-08 maintainer ruling on #6483 rolled back nine unratified `true` flags (`page`/`app`/`action`/`dataset`/`book`/`permission`/`position`/`tool`/`skill`; `flow` fell the same way in #6283), so a flag flip here is an ADR-0005 whitelist change and needs the ADR revised, not just this file edited. `OVERLAY_ALLOWED_TYPES` (metadata-protocol/src/protocol.ts) and the repository's `assertAllowed` allow-list both DERIVE from this column (Prime Directive #8) — never re-grow a parallel list." + "invariant": "The `allowEnvironmentOverlay` column of DEFAULT_METADATA_TYPE_REGISTRY (spelled `allowOrgOverride` before protocol 18; ADR-0131 D6 retired the per-organization axis, so the whitelist now governs the environment overlay of a packaged item) is the machine-readable form of ADR-0005's customizable whitelist (docs/adr/0005-metadata-customization-overlay.md:53-64) — `view`/`dashboard`/`report` (plus `email_template`/`translation` per the system row) opt in; every other type is `false` until an ADR-0005 revision ratifies its admission pair (an overlay schema `resolveOverlaySchema()` accepts AND a written render-only rationale). The 2026-08-08 maintainer ruling on #6483 rolled back nine unratified `true` flags (`page`/`app`/`action`/`dataset`/`book`/`permission`/`position`/`tool`/`skill`; `flow` fell the same way in #6283), so a flag flip here is an ADR-0005 whitelist change and needs the ADR revised, not just this file edited. `OVERLAY_ALLOWED_TYPES` (metadata-protocol/src/protocol.ts) and the repository's `assertAllowed` allow-list both DERIVE from this column (Prime Directive #8) — never re-grow a parallel list." } diff --git a/scripts/check-overlay-whitelist-table.mjs b/scripts/check-overlay-whitelist-table.mjs index ac2d332e246..d27fbe06117 100644 --- a/scripts/check-overlay-whitelist-table.mjs +++ b/scripts/check-overlay-whitelist-table.mjs @@ -8,6 +8,12 @@ * `DEFAULT_METADATA_TYPE_REGISTRY`: the TABLE in both directions (legs 1-2), * and the two registry-derived COUNTS in the sentence under it (leg 3). * + * The flag this reads is `allowEnvironmentOverlay`, renamed from + * `allowOrgOverride` in protocol 18 (ADR-0131 C5); the page's column header, + * this file's `COL_FLAG` and the self-test fixtures moved in one change. The + * measurements quoted below were taken under the old spelling and read the + * same under the new one. + * * node scripts/check-overlay-whitelist-table.mjs # judge the checked-in tree * node scripts/check-overlay-whitelist-table.mjs --list # every type and where it is asserted * node scripts/check-overlay-whitelist-table.mjs --self-test # prove the battery can go red @@ -15,7 +21,7 @@ * ## The gap this closes * * The section states its own contract: "The whitelist lives in **one** place: - * `MetadataTypeRegistryEntry.allowOrgOverride`". The table under that sentence + * `MetadataTypeRegistryEntry.allowEnvironmentOverlay`". The table under that sentence * is hand-maintained -- no generator writes the page, there is no `DO NOT EDIT` * marker -- so the single source of truth had a hand-kept copy sitting directly * beneath it with nothing holding the two together. @@ -35,7 +41,7 @@ * ## Why BOTH directions (constraint 1, established by measurement on #11750) * * `translation` was a false negative BY OMISSION -- a type the registry marks - * `allowOrgOverride: true` that the table simply did not list. A one-directional + * `allowEnvironmentOverlay: true` that the table simply did not list. A one-directional * table→registry gate ("every row I can see agrees with the registry") passes * on a table that is missing a whole row. Three of the four defects were * findable from the table side; the fourth was findable ONLY from the registry @@ -47,7 +53,7 @@ * * LEG 1 table → registry: every type named in the table carries the * registry's verdict for it (and is a type the registry declares). - * LEG 2 registry → table: every registry entry with `allowOrgOverride: true` + * LEG 2 registry → table: every registry entry with `allowEnvironmentOverlay: true` * is named somewhere in the table. * * Leg 2 is deliberately asymmetric: it demands rows only for the `true` set. @@ -61,7 +67,7 @@ * #11750 corrected the table and, in the same PR, added a sentence under it so * a human could check the completeness claim without opening the registry: * - * "Those five are the **complete** `allowOrgOverride: true` set: of the 27 + * "Those five are the **complete** `allowEnvironmentOverlay: true` set: of the 27 * types in `DEFAULT_METADATA_TYPE_REGISTRY`, every other one is `false`." * * Both numbers are derived from the registry, and both were hand-kept. That is @@ -81,7 +87,7 @@ * Scanned over the WHOLE page, not just the whitelist section: a claim ABOUT * the registry is no less a hand-kept copy for having drifted into a * neighbouring section. Both patterns are anchored on backticked identifiers - * (`DEFAULT_METADATA_TYPE_REGISTRY`, `allowOrgOverride: true`), so they cannot + * (`DEFAULT_METADATA_TYPE_REGISTRY`, `allowEnvironmentOverlay: true`), so they cannot * collide with ordinary English. * * ### "five" is a WORD, and the prose keeps it @@ -120,7 +126,7 @@ * this file's AST walk -> 27 * * The missed entry is `datasource`, whose object literal opens `{` on its own - * line, so `type:` and `allowOrgOverride:` land on separate lines. A + * line, so `type:` and `allowEnvironmentOverlay:` land on separate lines. A * regex-built gate would have been born with one type invisible to it -- and * invisible in the direction that matters, because an entry the gate never * parsed can never be reported as an omission from the table. It would have @@ -128,9 +134,9 @@ * * Two more shapes in the same file that a regex reads wrong, both present today: * - * grep -c 'allowOrgOverride: true' -> 6, but only 5 are ENTRIES; the sixth + * grep -c 'allowEnvironmentOverlay: true' -> 6, but only 5 are ENTRIES; the sixth * is prose inside a code comment. - * grep -c 'allowOrgOverride: false' -> 33 inside the registry region, but + * grep -c 'allowEnvironmentOverlay: false' -> 33 inside the registry region, but * only 22 are entries; the other 11 are * comments discussing the flags. * @@ -144,13 +150,13 @@ * Every way this gate can lose track of its subject is a finding: * * - the `## Overlay whitelist ...` heading is gone or renamed; - * - the table's header row no longer reads `Type | allowOrgOverride | ...` + * - the table's header row no longer reads `Type | allowEnvironmentOverlay | ...` * (a column swap would otherwise make the gate read the WRONG column and * keep printing green); * - a verdict cell holds anything but a bare ✅ / ❌; * - a type cell holds anything but backticked type names; * - the registry is not an array literal of object literals with literal - * `type` / `allowOrgOverride` values (a spread, a computed key, an + * `type` / `allowEnvironmentOverlay` values (a spread, a computed key, an * identifier flag, a helper call). * * That last one is the route-around clause. If the registry ever grows a shape @@ -163,13 +169,13 @@ * * Sibling pages state the same facts correctly in PROSE, not in a machine-shaped * type→flag table: `content/docs/permissions/authorization.mdx` - * ("`permission` declares `allowOrgOverride: false`"), + * ("`permission` declares `allowEnvironmentOverlay: false`"), * `content/docs/automation/jobs.mdx` (a job-vs-flow comparison table whose * flag mention sits inside an English cell), `content/docs/ai/agents.mdx`, * `content/docs/permissions/capabilities.mdx`, * `content/docs/references/system/email-template.mdx`. * - * Measured before deciding: `grep -rn allowOrgOverride content/docs/` hits 22 + * Measured before deciding: `grep -rn allowEnvironmentOverlay content/docs/` hits 22 * lines across 13 files. Of those, ~5 are genuine " is " assertions; * the rest are the schema-field reference row, a code sample using an invented * type, and sentences ABOUT the flag rather than about any type @@ -252,7 +258,7 @@ const TABLE_LINE = /^\s*\|/; /** The two columns this gate reads, by name. A rename or reorder is a finding. */ const COL_TYPE = 'Type'; -const COL_FLAG = 'allowOrgOverride'; +const COL_FLAG = 'allowEnvironmentOverlay'; const YES = '✅'; const NO = '❌'; @@ -269,7 +275,7 @@ const BACKTICKED = /`([^`]*)`/g; /** * Read `DEFAULT_METADATA_TYPE_REGISTRY` by AST. * - * @returns {{ entries: Array<{type: string, allowOrgOverride: boolean, line: number}>, + * @returns {{ entries: Array<{type: string, allowEnvironmentOverlay: boolean, line: number}>, * findings: Array<{kind: string, message: string, line: number}> }} * `findings` non-empty means the registry could not be read honestly. It is * never "read what we could and carry on" -- see the header. @@ -397,10 +403,10 @@ export function readRegistry(text, fileLabel = REGISTRY_FILE) { }); continue; } - // `allowOrgOverride` is optional in the schema and defaults to false + // `allowEnvironmentOverlay` is optional in the schema and defaults to false // (see the reference row for MetadataTypeRegistryEntry). An entry that // omits it is a legitimate `false`. - entries.push({ type, allowOrgOverride: flag === true, line }); + entries.push({ type, allowEnvironmentOverlay: flag === true, line }); } const seen = new Map(); @@ -422,7 +428,7 @@ export function readRegistry(text, fileLabel = REGISTRY_FILE) { message: `\`${REGISTRY_CONST}\` parsed to ZERO entries — the gate has no input and must not report clean.`, }); } - if (findings.length === 0 && !entries.some((e) => e.allowOrgOverride)) { + if (findings.length === 0 && !entries.some((e) => e.allowEnvironmentOverlay)) { findings.push({ kind: 'structure', line: lineOf(array), @@ -741,21 +747,21 @@ export function compare(entries, rows) { }); continue; } - if (entry.allowOrgOverride !== row.allow) { + if (entry.allowEnvironmentOverlay !== row.allow) { leg1.push({ kind: 'mismatch', type, line: row.line, message: `the table says \`${type}\` is ${row.allow ? YES : NO}, the registry declares ` + - `\`${COL_FLAG}: ${entry.allowOrgOverride}\` (${REGISTRY_FILE}:${entry.line}).`, + `\`${COL_FLAG}: ${entry.allowEnvironmentOverlay}\` (${REGISTRY_FILE}:${entry.line}).`, }); } } } for (const entry of entries) { - if (entry.allowOrgOverride && !tableTypes.has(entry.type)) { + if (entry.allowEnvironmentOverlay && !tableTypes.has(entry.type)) { leg2.push({ kind: 'missing-row', type: entry.type, @@ -784,7 +790,7 @@ export function compare(entries, rows) { export function compareProse(entries, claims) { const actual = new Map([ ['total', entries.length], - ['true-set', entries.filter((e) => e.allowOrgOverride).length], + ['true-set', entries.filter((e) => e.allowEnvironmentOverlay).length], ]); const leg3 = []; for (const claim of claims) { @@ -963,14 +969,14 @@ export function selfTest() { ); check( 'fixture/AST reads the multi-line `datasource` entry', - before.entries.some((e) => e.type === 'datasource' && e.allowOrgOverride === false), + before.entries.some((e) => e.type === 'datasource' && e.allowEnvironmentOverlay === false), ); // ---- 5. Comments are not entries. `grep -c` over the fixture inflates both // flags; the AST count must be the smaller, correct one. battery('5. Comments are not entries. `grep -c` over the fixture inflates both'); const grepTrue = (FIXTURE_REGISTRY.match(new RegExp(`${COL_FLAG}: true`, 'g')) || []).length; - const astTrue = before.entries.filter((e) => e.allowOrgOverride).length; + const astTrue = before.entries.filter((e) => e.allowEnvironmentOverlay).length; check('fixture/ast true-set = 5', astTrue === 5, `got ${astTrue}`); check( 'fixture/grep over-counts true because of comment prose', @@ -1084,7 +1090,7 @@ export function selfTest() { 'fixture.ts', ); check('omitted flag defaults to false', omitted.findings.length === 0 && - omitted.entries.some((e) => e.type === 'agent' && e.allowOrgOverride === false), + omitted.entries.some((e) => e.type === 'agent' && e.allowEnvironmentOverlay === false), JSON.stringify(omitted.findings)); // ---- 13. LEG 3 (#11763): the two registry counts stated in the prose. @@ -1307,7 +1313,7 @@ function main() { const { leg1, leg2, tableTypes } = compare(reg.entries, tab.rows); const leg3 = compareProse(reg.entries, prose.claims); - const trueSet = reg.entries.filter((e) => e.allowOrgOverride).map((e) => e.type); + const trueSet = reg.entries.filter((e) => e.allowEnvironmentOverlay).map((e) => e.type); const claimList = prose.claims.map((c) => `${c.what}=${c.value} ("${c.token}" @:${c.line})`); if (process.argv.includes('--list')) { @@ -1315,7 +1321,7 @@ function main() { for (const e of reg.entries.slice().sort((a, b) => a.type.localeCompare(b.type))) { const row = tableTypes.get(e.type); console.log( - ` ${(e.allowOrgOverride ? YES : NO)} ${e.type.padEnd(20)} ` + + ` ${(e.allowEnvironmentOverlay ? YES : NO)} ${e.type.padEnd(20)} ` + `registry:${String(e.line).padEnd(5)} ` + `${row ? `table:${row.line}` : 'table: (not listed)'}`, ); From 968679363d538022c6aaed950029305d152cfb49 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 11 Oct 2026 13:54:19 +0000 Subject: [PATCH 04/15] docs(references): regenerate for allowEnvironmentOverlay and the corrected supportsOverlay describes Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude --- content/docs/references/api/metadata.mdx | 2 +- content/docs/references/api/protocol.mdx | 6 +++--- content/docs/references/kernel/metadata-plugin.mdx | 9 +++++---- content/docs/references/system/email-template.mdx | 4 ++-- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/content/docs/references/api/metadata.mdx b/content/docs/references/api/metadata.mdx index af1b841c0d9..22a3c7ac27d 100644 --- a/content/docs/references/api/metadata.mdx +++ b/content/docs/references/api/metadata.mdx @@ -796,7 +796,7 @@ Metadata query with filtering, sorting, and pagination | **label** | `string` | ✅ | Display label | | **description** | `string` | optional | Description | | **filePatterns** | `string[]` | ✅ | File glob patterns | -| **supportsOverlay** | `boolean` | ✅ | Overlay support | +| **supportsOverlay** | `boolean` | ✅ | Whether a stored overlay row of an item a managed package ships may be removed through the ordinary delete door without the type's environment-overlay permission, because removing it restores the managed definition; it opens no save and it decides no read | | **domain** | `string` | ✅ | Protocol domain | | **actions** | `{ name: string; label: string \| Record; description?: string \| Record; objectName?: string; … }[]` | optional | Declarative type-level actions (buttons) the metadata-admin UI renders for this type | diff --git a/content/docs/references/api/protocol.mdx b/content/docs/references/api/protocol.mdx index 8cbe205f780..52f43fe9d64 100644 --- a/content/docs/references/api/protocol.mdx +++ b/content/docs/references/api/protocol.mdx @@ -1434,14 +1434,14 @@ Enable package response | **label** | `string` | ✅ | Human-readable label | | **description** | `string` | optional | Brief description | | **filePatterns** | `string[]` | ✅ | Glob patterns used to discover artifacts of this type | -| **supportsOverlay** | `boolean` | ✅ | Loader can merge per-org overlays on top of artifact | -| **allowOrgOverride** | `boolean` | ✅ | Per-org overlay writes accepted at runtime (may be env-elevated) | +| **supportsOverlay** | `boolean` | ✅ | Whether a stored overlay row of an item a managed package ships may be removed through the ordinary delete door without the type's environment-overlay permission, because removing it restores the managed definition; it opens no save and it decides no read | +| **allowEnvironmentOverlay** | `boolean` | ✅ | True when the registry grants environment overlays of this type's packaged items, or when OS_METADATA_WRITABLE names the type (overrideSource env), which opens the type's writes of items no managed package ships and never a managed item (ADR-0131 D6) | | **allowRuntimeCreate** | `boolean` | ✅ | New artifacts of this type can be created via runtime API | | **supportsVersioning** | `boolean` | ✅ | History is tracked for this type | | **executionPinned** | `boolean` | ✅ | Runtime transactions pin a specific historical version_hash (ADR-0009) | | **loadOrder** | `integer` | ✅ | Loading priority (lower = earlier) | | **domain** | `Enum<'data' \| 'ui' \| 'automation' \| 'system' \| 'security' \| 'ai'>` | ✅ | Protocol domain | -| **overrideSource** | `Enum<'registry' \| 'env'>` | ✅ | Whether allowOrgOverride is set in the static registry or via OS_METADATA_WRITABLE env var | +| **overrideSource** | `Enum<'registry' \| 'env'>` | ✅ | Whether allowEnvironmentOverlay comes from the static registry or is elevated by the OS_METADATA_WRITABLE environment variable | | **createSeed** | `any` | optional | Authoritative minimal valid create seed for this type — Studio/CLI/API derive create defaults from it (single source of truth in @objectstack/spec). Absent for canvas-create types whose shape is built interactively. | diff --git a/content/docs/references/kernel/metadata-plugin.mdx b/content/docs/references/kernel/metadata-plugin.mdx index 34817087bee..c177cbd50a9 100644 --- a/content/docs/references/kernel/metadata-plugin.mdx +++ b/content/docs/references/kernel/metadata-plugin.mdx @@ -101,7 +101,7 @@ const result = MetadataBulkResultSchema.parse(data); | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **storage** | `{ datasource?: string; tableName?: string; fallback?: Enum<'filesystem' \| 'memory' \| 'none'>; rootDir?: string; … }` | ✅ | Storage backend configuration | -| **customizationPolicies** | `never` | optional | [REMOVED] `config.customizationPolicies` was removed from `MetadataPluginConfig` in @objectstack/spec 17 (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). | +| **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 environment overlay (opt-in via `allowEnvironmentOverlay` on `DEFAULT_METADATA_TYPE_REGISTRY`, enforced at the metadata protocol's write doors and its served read) 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 | @@ -161,7 +161,7 @@ const result = MetadataBulkResultSchema.parse(data); | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **storage** | `{ datasource?: string; tableName?: string; fallback?: Enum<'filesystem' \| 'memory' \| 'none'>; rootDir?: string; … }` | ✅ | Storage backend configuration | -| **customizationPolicies** | `never` | optional | [REMOVED] `config.customizationPolicies` was removed from `MetadataPluginConfig` in @objectstack/spec 17 (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). | +| **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 environment overlay (opt-in via `allowEnvironmentOverlay` on `DEFAULT_METADATA_TYPE_REGISTRY`, enforced at the metadata protocol's write doors and its served read) 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 | @@ -267,8 +267,9 @@ const result = MetadataBulkResultSchema.parse(data); | **label** | `string` | ✅ | Display label for the metadata type | | **description** | `string` | optional | Description of the metadata type | | **filePatterns** | `string[]` | ✅ | Glob patterns to discover files of this type | -| **supportsOverlay** | `boolean` | optional (default: `true`) | Whether overlay customization is supported | -| **allowOrgOverride** | `boolean` | optional (default: `false`) | Allow per-org overlay writes via runtime metadata API | +| **supportsOverlay** | `boolean` | optional (default: `true`) | Whether a stored overlay row of an item a managed package ships may be removed through the ordinary delete door without the type's environment-overlay permission, because removing it restores the managed definition; it opens no save and it decides no read | +| **allowEnvironmentOverlay** | `boolean` | optional (default: `false`) | Whether an environment may overlay an item of this type that a managed package ships (ADR-0131 D6 regime O). Decides the save door, the managed-content seal on write and on read, and Studio's write gate; the OS_METADATA_WRITABLE hatch does not reach a managed item. Default false. | +| **allowOrgOverride** | `never` | optional | [REMOVED] `MetadataTypeRegistryEntry.allowOrgOverride` was renamed to `allowEnvironmentOverlay` in @objectstack/spec 18 (ADR-0131 D6) — the per-organization overlay axis was retired, and the key now names the one scope it governs: whether an environment may overlay an item of this type that a managed package ships. Rename the key to `allowEnvironmentOverlay`; the value is unchanged. | | **allowRuntimeCreate** | `boolean` | optional (default: `true`) | Allow runtime creation via API | | **supportsVersioning** | `boolean` | optional (default: `false`) | Whether version history is tracked | | **executionPinned** | `boolean` | optional (default: `false`) | Transaction rows reference a specific version_hash; history GC is disabled and getByHash() MUST resolve old hashes (ADR-0009) | diff --git a/content/docs/references/system/email-template.mdx b/content/docs/references/system/email-template.mdx index 2441cd0907e..0d5797ba317 100644 --- a/content/docs/references/system/email-template.mdx +++ b/content/docs/references/system/email-template.mdx @@ -11,8 +11,8 @@ Email Template Metadata Protocol Declarative template definition consumed by `IEmailService.sendTemplate()` to render outbound mail. Persisted as rows of `sys_email_template` so administrators can author/edit/translate templates in Studio without -shipping code, and tenants can override the built-in defaults -(`allowOrgOverride: true` in the metadata registry). +shipping code, and an environment can overlay the packaged defaults +(`allowEnvironmentOverlay: true` in the metadata registry). Aligned with Salesforce `EmailTemplate` and ServiceNow `sysevent_email_action` conventions: a single named template is From d98067c5ff13ff9f5e975edaa0d276fed0e8aa2e Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 11 Oct 2026 13:55:29 +0000 Subject: [PATCH 05/15] changeset: the ADR-0087 marker names the D3 id the gate resolves Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude --- .changeset/22340-registry-allow-environment-overlay.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/22340-registry-allow-environment-overlay.md b/.changeset/22340-registry-allow-environment-overlay.md index d3aa9b528bb..7121308339d 100644 --- a/.changeset/22340-registry-allow-environment-overlay.md +++ b/.changeset/22340-registry-allow-environment-overlay.md @@ -7,7 +7,7 @@ feat(spec,metadata-protocol)!: the metadata type registry's `allowOrgOverride` i Clause-②: yes (narrowing) - + **BREAKING**: a renamed authorable key and a renamed response field, shipped as `major` on the v18 line (`.changeset/pre.json` is open on `main` in `next` pre mode, so the release is `18.0.0-next.*`). From 6b3de41c98e155b7336d57dee7ce207e903b023d Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 11 Oct 2026 13:57:57 +0000 Subject: [PATCH 06/15] spec(contracts): MetadataTypeInfo.supportsOverlay says what it gates Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude --- packages/spec/src/contracts/metadata-service.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/spec/src/contracts/metadata-service.ts b/packages/spec/src/contracts/metadata-service.ts index 35a705e88f7..6da7ee244fe 100644 --- a/packages/spec/src/contracts/metadata-service.ts +++ b/packages/spec/src/contracts/metadata-service.ts @@ -131,7 +131,12 @@ export interface MetadataTypeInfo { description?: string; /** File glob patterns */ filePatterns: string[]; - /** Supports overlay customization */ + /** + * The registry's removal carve-out (`MetadataTypeRegistryEntry` + * `supportsOverlay`): a stored overlay row of an item a managed package + * ships may be removed without the type's environment-overlay permission. + * It opens no save and decides no read. + */ supportsOverlay: boolean; /** Protocol domain */ domain: string; From 077a4ded22b2d83b8fafbc28125ae7a35cd7aed1 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 11 Oct 2026 14:18:31 +0000 Subject: [PATCH 07/15] spec,metadata-protocol: supportsOverlay's carve-out takes the landed seal wording (removes no managed definition; changes what is served for permission alone) Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude --- .../22340-registry-allow-environment-overlay.md | 2 +- packages/metadata-protocol/src/protocol.ts | 13 ++++++++----- .../src/sys-metadata-repository.ts | 5 +++-- packages/spec/src/api/metadata.zod.ts | 2 +- packages/spec/src/api/protocol.zod.ts | 2 +- packages/spec/src/kernel/metadata-plugin.zod.ts | 12 +++++++----- 6 files changed, 21 insertions(+), 15 deletions(-) diff --git a/.changeset/22340-registry-allow-environment-overlay.md b/.changeset/22340-registry-allow-environment-overlay.md index 7121308339d..eb150c25bcb 100644 --- a/.changeset/22340-registry-allow-environment-overlay.md +++ b/.changeset/22340-registry-allow-environment-overlay.md @@ -27,7 +27,7 @@ Every value is unchanged: `true` on `view`, `dashboard`, `report`, `translation` ⚠️ **Read the old key nowhere.** Writing `allowOrgOverride` fails to compile (its input type is `never`) and fails to parse with the rename prescription (issue code `invalid_type` at path `allowOrgOverride`). A READER does not fail: `if (!entry.allowOrgOverride)`, a filter on it or a partial-match assertion still compiles against the tombstone and reads `undefined`, which treats every type as closed to environment overlays. A client that declares its own type for the `/meta/types` response, instead of importing `GetMetaTypesResponse`, sees no compile error at all and reads every type as locked. Search for the old name, do not wait for `tsc`. -**`supportsOverlay` keeps its name; its describes now say what it gates.** It decides one thing: a stored overlay row of an item a managed package ships may be removed through the ordinary delete door without the type's environment-overlay permission, because removing it restores the managed definition. It opens no save and it decides no read. The registry's describe, the `GET /meta/types` describe and `MetadataTypeInfoResponse`'s describe used to promise that "the loader can merge overlays", in the retired per-organization wording. +**`supportsOverlay` keeps its name; its describes now say what it gates.** It decides one thing: a stored overlay row of an item a managed package ships may be removed through the ordinary delete door without the type's environment-overlay permission: the removal removes no managed definition, and changes what is served for `permission` alone. It opens no save and it decides no read. The registry's describe, the `GET /meta/types` describe and `MetadataTypeInfoResponse`'s describe used to promise that "the loader can merge overlays", in the retired per-organization wording. **Who is affected, measured.** No manifest, stack, plugin configuration or stored row can carry a registry entry (`contributes.kinds` is closed, `config.additionalTypes` is a tombstone, the registry is a TypeScript literal), so no authored metadata changes. The readers are code: every reader in this repository moved in this change, and a reader of the `/meta/types` field outside it moves with it — the Studio console reads the field and moves in its own repository's paired change, which the console pin carries. diff --git a/packages/metadata-protocol/src/protocol.ts b/packages/metadata-protocol/src/protocol.ts index 102421abe77..eff1a12c83e 100644 --- a/packages/metadata-protocol/src/protocol.ts +++ b/packages/metadata-protocol/src/protocol.ts @@ -15792,8 +15792,10 @@ export class ObjectStackProtocolImplementation implements * overlay an item a managed package ships, on the save AND on the served * read ({@link isSealedManagedItem}, {@link declinesStoredRow}); * `supportsOverlay` decides the removal carve-out alone — such a row may be - * removed without that permission, because removing it restores the - * managed definition. It opens no save and it decides no read. Commit + * removed without that permission: the removal removes no managed + * definition, and changes what is served for `permission` alone + * ({@link refusePackagedBaseRemoval}). It opens no save and it decides no + * read. Commit * ee58392e1 rolled the permission back for six types — `permission`, * `position`, `page`, `app`, `dataset`, `book` — and deliberately left * this flag alone, which is exactly the state #6960 was filed about: rows @@ -25962,9 +25964,10 @@ export class ObjectStackProtocolImplementation implements // • Artifact-backed item → delete becomes a tombstone overlay, // requires `allowEnvironmentOverlay`… // • …EXCEPT when the type's entry carries `supportsOverlay: true` — - // the removal carve-out: removing the row restores the managed - // definition, so it is repair and is allowed without it. The flag - // opens no save and decides no read. [#6960, delete-only; see below.] + // the removal carve-out: removing the row removes no managed + // definition (and changes what is served for `permission` alone), + // so it is repair and is allowed without it. The flag opens no save + // and decides no read. [#6960, delete-only; see below.] // • DB-only item → hard delete of a user-created row, // requires `allowRuntimeCreate` (or `allowEnvironmentOverlay`). if (this.environmentId !== undefined) { diff --git a/packages/metadata-protocol/src/sys-metadata-repository.ts b/packages/metadata-protocol/src/sys-metadata-repository.ts index df5af81fac5..14674b6d55b 100644 --- a/packages/metadata-protocol/src/sys-metadata-repository.ts +++ b/packages/metadata-protocol/src/sys-metadata-repository.ts @@ -385,8 +385,9 @@ const RUNTIME_CREATE_ALLOWED_TYPES: ReadonlySet = new Set( * above (Prime Directive #8), and deliberately a different question from * {@link OVERLAY_ALLOWED_TYPES}: `allowEnvironmentOverlay` decides whether an * environment may overlay a packaged item at all, `supportsOverlay` only that - * removing such a row restores the managed definition, so it is repair. It - * opens no save and decides no read. Read only by + * removing such a row removes no managed definition (and changes what is + * served for `permission` alone), so it is repair. It opens no save and + * decides no read. Read only by * {@link SysMetadataRepository.assertDeleteAllowed}. */ const OVERLAY_CAPABLE_TYPES: ReadonlySet = new Set( diff --git a/packages/spec/src/api/metadata.zod.ts b/packages/spec/src/api/metadata.zod.ts index 8e2a65c15fe..c0e902853bc 100644 --- a/packages/spec/src/api/metadata.zod.ts +++ b/packages/spec/src/api/metadata.zod.ts @@ -299,7 +299,7 @@ export const MetadataTypeInfoResponseSchema = lazySchema(() => BaseResponseSchem label: z.string().describe('Display label'), description: z.string().optional().describe('Description'), filePatterns: z.array(z.string()).describe('File glob patterns'), - supportsOverlay: z.boolean().describe('Whether a stored overlay row of an item a managed package ships may be removed through the ordinary delete door without the type\'s environment-overlay permission, because removing it restores the managed definition; it opens no save and it decides no read'), + supportsOverlay: z.boolean().describe('Whether a stored overlay row of an item a managed package ships may be removed through the ordinary delete door without the type\'s environment-overlay permission: the removal removes no managed definition, and changes what is served for permission alone; it opens no save and it decides no read'), domain: z.string().describe('Protocol domain'), actions: z.array(ActionSchema).optional().describe('Declarative type-level actions (buttons) the metadata-admin UI renders for this type'), }).optional().describe('Type info'), diff --git a/packages/spec/src/api/protocol.zod.ts b/packages/spec/src/api/protocol.zod.ts index 765f086389c..7119c42df42 100644 --- a/packages/spec/src/api/protocol.zod.ts +++ b/packages/spec/src/api/protocol.zod.ts @@ -207,7 +207,7 @@ export const GetMetaTypesResponseSchema = lazySchema(() => z.object({ label: z.string().describe('Human-readable label'), description: z.string().optional().describe('Brief description'), filePatterns: z.array(z.string()).describe('Glob patterns used to discover artifacts of this type'), - supportsOverlay: z.boolean().describe('Whether a stored overlay row of an item a managed package ships may be removed through the ordinary delete door without the type\'s environment-overlay permission, because removing it restores the managed definition; it opens no save and it decides no read'), + supportsOverlay: z.boolean().describe('Whether a stored overlay row of an item a managed package ships may be removed through the ordinary delete door without the type\'s environment-overlay permission: the removal removes no managed definition, and changes what is served for permission alone; it opens no save and it decides no read'), allowEnvironmentOverlay: z.boolean().describe('True when the registry grants environment overlays of this type\'s packaged items, or when OS_METADATA_WRITABLE names the type (overrideSource env), which opens the type\'s writes of items no managed package ships and never a managed item (ADR-0131 D6)'), allowRuntimeCreate: z.boolean().describe('New artifacts of this type can be created via runtime API'), supportsVersioning: z.boolean().describe('History is tracked for this type'), diff --git a/packages/spec/src/kernel/metadata-plugin.zod.ts b/packages/spec/src/kernel/metadata-plugin.zod.ts index 4d07fc8d880..7752e5e3bd8 100644 --- a/packages/spec/src/kernel/metadata-plugin.zod.ts +++ b/packages/spec/src/kernel/metadata-plugin.zod.ts @@ -249,9 +249,10 @@ const MetadataTypeRegistryEntryBaseSchema = z.object({ /** * Whether a stored overlay row of an item a managed package ships may be * REMOVED through the ordinary delete door without the type's - * environment-overlay permission ({@link allowEnvironmentOverlay}), because - * removing it restores the managed definition (the repair carve-out for - * overlay rows stored before a type's permission was rolled back). + * environment-overlay permission ({@link allowEnvironmentOverlay}): the + * removal removes no managed definition, and changes what is served for + * `permission` alone (the repair carve-out for overlay rows stored before a + * type's permission was rolled back). * * It opens no save and it decides no read: whether an environment overlay of * a packaged item is written or served is decided by @@ -260,8 +261,9 @@ const MetadataTypeRegistryEntryBaseSchema = z.object({ */ supportsOverlay: z.boolean().default(true).describe( 'Whether a stored overlay row of an item a managed package ships may be removed through the ' - + 'ordinary delete door without the type\'s environment-overlay permission, because removing it ' - + 'restores the managed definition; it opens no save and it decides no read' + + 'ordinary delete door without the type\'s environment-overlay permission: the removal removes ' + + 'no managed definition, and changes what is served for permission alone; it opens no save and ' + + 'it decides no read' ), /** From a16954175812a6a67be381ae23db50c82eea6a19 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 11 Oct 2026 14:23:46 +0000 Subject: [PATCH 08/15] docs(references): regenerate for the landed carve-out wording Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude --- content/docs/references/api/metadata.mdx | 2 +- content/docs/references/api/protocol.mdx | 2 +- content/docs/references/kernel/metadata-plugin.mdx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/content/docs/references/api/metadata.mdx b/content/docs/references/api/metadata.mdx index 22a3c7ac27d..c8eccc648cf 100644 --- a/content/docs/references/api/metadata.mdx +++ b/content/docs/references/api/metadata.mdx @@ -796,7 +796,7 @@ Metadata query with filtering, sorting, and pagination | **label** | `string` | ✅ | Display label | | **description** | `string` | optional | Description | | **filePatterns** | `string[]` | ✅ | File glob patterns | -| **supportsOverlay** | `boolean` | ✅ | Whether a stored overlay row of an item a managed package ships may be removed through the ordinary delete door without the type's environment-overlay permission, because removing it restores the managed definition; it opens no save and it decides no read | +| **supportsOverlay** | `boolean` | ✅ | Whether a stored overlay row of an item a managed package ships may be removed through the ordinary delete door without the type's environment-overlay permission: the removal removes no managed definition, and changes what is served for permission alone; it opens no save and it decides no read | | **domain** | `string` | ✅ | Protocol domain | | **actions** | `{ name: string; label: string \| Record; description?: string \| Record; objectName?: string; … }[]` | optional | Declarative type-level actions (buttons) the metadata-admin UI renders for this type | diff --git a/content/docs/references/api/protocol.mdx b/content/docs/references/api/protocol.mdx index 52f43fe9d64..31095cf9522 100644 --- a/content/docs/references/api/protocol.mdx +++ b/content/docs/references/api/protocol.mdx @@ -1434,7 +1434,7 @@ Enable package response | **label** | `string` | ✅ | Human-readable label | | **description** | `string` | optional | Brief description | | **filePatterns** | `string[]` | ✅ | Glob patterns used to discover artifacts of this type | -| **supportsOverlay** | `boolean` | ✅ | Whether a stored overlay row of an item a managed package ships may be removed through the ordinary delete door without the type's environment-overlay permission, because removing it restores the managed definition; it opens no save and it decides no read | +| **supportsOverlay** | `boolean` | ✅ | Whether a stored overlay row of an item a managed package ships may be removed through the ordinary delete door without the type's environment-overlay permission: the removal removes no managed definition, and changes what is served for permission alone; it opens no save and it decides no read | | **allowEnvironmentOverlay** | `boolean` | ✅ | True when the registry grants environment overlays of this type's packaged items, or when OS_METADATA_WRITABLE names the type (overrideSource env), which opens the type's writes of items no managed package ships and never a managed item (ADR-0131 D6) | | **allowRuntimeCreate** | `boolean` | ✅ | New artifacts of this type can be created via runtime API | | **supportsVersioning** | `boolean` | ✅ | History is tracked for this type | diff --git a/content/docs/references/kernel/metadata-plugin.mdx b/content/docs/references/kernel/metadata-plugin.mdx index c177cbd50a9..75fc636b74b 100644 --- a/content/docs/references/kernel/metadata-plugin.mdx +++ b/content/docs/references/kernel/metadata-plugin.mdx @@ -267,7 +267,7 @@ const result = MetadataBulkResultSchema.parse(data); | **label** | `string` | ✅ | Display label for the metadata type | | **description** | `string` | optional | Description of the metadata type | | **filePatterns** | `string[]` | ✅ | Glob patterns to discover files of this type | -| **supportsOverlay** | `boolean` | optional (default: `true`) | Whether a stored overlay row of an item a managed package ships may be removed through the ordinary delete door without the type's environment-overlay permission, because removing it restores the managed definition; it opens no save and it decides no read | +| **supportsOverlay** | `boolean` | optional (default: `true`) | Whether a stored overlay row of an item a managed package ships may be removed through the ordinary delete door without the type's environment-overlay permission: the removal removes no managed definition, and changes what is served for permission alone; it opens no save and it decides no read | | **allowEnvironmentOverlay** | `boolean` | optional (default: `false`) | Whether an environment may overlay an item of this type that a managed package ships (ADR-0131 D6 regime O). Decides the save door, the managed-content seal on write and on read, and Studio's write gate; the OS_METADATA_WRITABLE hatch does not reach a managed item. Default false. | | **allowOrgOverride** | `never` | optional | [REMOVED] `MetadataTypeRegistryEntry.allowOrgOverride` was renamed to `allowEnvironmentOverlay` in @objectstack/spec 18 (ADR-0131 D6) — the per-organization overlay axis was retired, and the key now names the one scope it governs: whether an environment may overlay an item of this type that a managed package ships. Rename the key to `allowEnvironmentOverlay`; the value is unchanged. | | **allowRuntimeCreate** | `boolean` | optional (default: `true`) | Allow runtime creation via API | From e684a2ec8942cf99e383dba0e346301277aae10d Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 11 Oct 2026 16:27:34 +0000 Subject: [PATCH 09/15] test(spec): the absence pin counts the refusal pin apart as its live positive control Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude --- ...egistry-allow-org-override-absence.test.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/spec/src/kernel/metadata-type-registry-allow-org-override-absence.test.ts b/packages/spec/src/kernel/metadata-type-registry-allow-org-override-absence.test.ts index 623ba2f8ec3..a1021572602 100644 --- a/packages/spec/src/kernel/metadata-type-registry-allow-org-override-absence.test.ts +++ b/packages/spec/src/kernel/metadata-type-registry-allow-org-override-absence.test.ts @@ -28,12 +28,17 @@ import { describe, expect, it } from 'vitest'; // key's history (CHANGELOG-shaped prose, quoted former refusal sentences, the // tombstone's own prescription, the retired-keys entry). One reference is // legitimate by SHAPE, not by site: the tombstone declaration itself, a -// property whose initializer is a `retiredKey(...)` call. This file is -// excluded because its refusal leg authors the old key on purpose. +// property whose initializer is a `retiredKey(...)` call. One file is +// legitimate by PURPOSE: the sibling refusal pin, which authors the old key to +// prove the parse refuses it. It is not skipped — its references are counted +// apart and REQUIRED, which makes it this leg's live positive control: the +// walker reached it and the matcher saw real code. describe('tree-scoped absence: no TypeScript source in the declared radius reads or writes `allowOrgOverride`', () => { const SPEC_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..'); const REPO_ROOT = path.resolve(SPEC_ROOT, '../..'); const THIS_FILE = path.relative(REPO_ROOT, fileURLToPath(import.meta.url)).split(path.sep).join('/'); + /** The refusal pin: authors the old key on purpose (see above). Counted apart, never skipped. */ + const REFUSAL_PIN = THIS_FILE.replace(/-absence\.test\.ts$/, '-retirement.test.ts'); const OLD = 'allowOrgOverride'; const NEW = 'allowEnvironmentOverlay'; @@ -94,6 +99,7 @@ describe('tree-scoped absence: no TypeScript source in the declared radius reads it('no code reference survives inside the declared radius', () => { const offenders: string[] = []; + const refusalPinReferences: string[] = []; const newReaders = new Set(); let visited = 0; const walk = (dir: string) => { @@ -118,7 +124,9 @@ describe('tree-scoped absence: no TypeScript source in the declared radius reads } if (text.includes(NEW) && codeReferences(rel, text, NEW).length > 0) newReaders.add(rel); if (!text.includes(OLD)) continue; - for (const where of codeReferences(rel, text, OLD)) offenders.push(`${rel}:${where}`); + for (const where of codeReferences(rel, text, OLD)) { + (rel === REFUSAL_PIN ? refusalPinReferences : offenders).push(`${rel}:${where}`); + } } }; for (const root of WALK_ROOTS) walk(path.join(REPO_ROOT, root)); @@ -127,6 +135,11 @@ describe('tree-scoped absence: no TypeScript source in the declared radius reads // the hand-renamed readers outside this package are found by the same // matcher under the new name. expect(visited).toBeGreaterThan(1000); + expect(REFUSAL_PIN).not.toBe(THIS_FILE); + expect(fs.existsSync(path.join(REPO_ROOT, REFUSAL_PIN)), `${REFUSAL_PIN} exists`).toBe(true); + // Live positive control: the refusal pin's deliberate `allowOrgOverride: true` + // and `: false` are real code references, and the walk found them. + expect(refusalPinReferences.length).toBeGreaterThanOrEqual(2); expect([...newReaders]).toEqual(expect.arrayContaining([ 'packages/metadata-protocol/src/protocol.ts', 'packages/metadata-protocol/src/sys-metadata-repository.ts', From 088afc679843744f5e15568536336090f400cf37 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 11 Oct 2026 17:40:35 +0000 Subject: [PATCH 10/15] spec: the rename prescription names the published npm major (check:future-spec-major) Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude --- content/docs/references/kernel/metadata-plugin.mdx | 2 +- packages/spec/src/kernel/metadata-plugin.zod.ts | 2 +- ...metadata-type-registry-allow-org-override-retirement.test.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/content/docs/references/kernel/metadata-plugin.mdx b/content/docs/references/kernel/metadata-plugin.mdx index 75fc636b74b..b70457c9817 100644 --- a/content/docs/references/kernel/metadata-plugin.mdx +++ b/content/docs/references/kernel/metadata-plugin.mdx @@ -269,7 +269,7 @@ const result = MetadataBulkResultSchema.parse(data); | **filePatterns** | `string[]` | ✅ | Glob patterns to discover files of this type | | **supportsOverlay** | `boolean` | optional (default: `true`) | Whether a stored overlay row of an item a managed package ships may be removed through the ordinary delete door without the type's environment-overlay permission: the removal removes no managed definition, and changes what is served for permission alone; it opens no save and it decides no read | | **allowEnvironmentOverlay** | `boolean` | optional (default: `false`) | Whether an environment may overlay an item of this type that a managed package ships (ADR-0131 D6 regime O). Decides the save door, the managed-content seal on write and on read, and Studio's write gate; the OS_METADATA_WRITABLE hatch does not reach a managed item. Default false. | -| **allowOrgOverride** | `never` | optional | [REMOVED] `MetadataTypeRegistryEntry.allowOrgOverride` was renamed to `allowEnvironmentOverlay` in @objectstack/spec 18 (ADR-0131 D6) — the per-organization overlay axis was retired, and the key now names the one scope it governs: whether an environment may overlay an item of this type that a managed package ships. Rename the key to `allowEnvironmentOverlay`; the value is unchanged. | +| **allowOrgOverride** | `never` | optional | [REMOVED] `MetadataTypeRegistryEntry.allowOrgOverride` was renamed to `allowEnvironmentOverlay` in @objectstack/spec 17 (ADR-0131 D6) — the per-organization overlay axis was retired, and the key now names the one scope it governs: whether an environment may overlay an item of this type that a managed package ships. Rename the key to `allowEnvironmentOverlay`; the value is unchanged. | | **allowRuntimeCreate** | `boolean` | optional (default: `true`) | Allow runtime creation via API | | **supportsVersioning** | `boolean` | optional (default: `false`) | Whether version history is tracked | | **executionPinned** | `boolean` | optional (default: `false`) | Transaction rows reference a specific version_hash; history GC is disabled and getByHash() MUST resolve old hashes (ADR-0009) | diff --git a/packages/spec/src/kernel/metadata-plugin.zod.ts b/packages/spec/src/kernel/metadata-plugin.zod.ts index 7752e5e3bd8..b4f3244f028 100644 --- a/packages/spec/src/kernel/metadata-plugin.zod.ts +++ b/packages/spec/src/kernel/metadata-plugin.zod.ts @@ -315,7 +315,7 @@ const MetadataTypeRegistryEntryBaseSchema = z.object({ */ allowOrgOverride: retiredKey( '`MetadataTypeRegistryEntry.allowOrgOverride` was renamed to `allowEnvironmentOverlay` in ' - + '@objectstack/spec 18 (ADR-0131 D6) — the per-organization overlay axis was retired, and ' + + '@objectstack/spec 17 (ADR-0131 D6) — the per-organization overlay axis was retired, and ' + 'the key now names the one scope it governs: whether an environment may overlay an item ' + 'of this type that a managed package ships. Rename the key to `allowEnvironmentOverlay`; ' + 'the value is unchanged.', diff --git a/packages/spec/src/kernel/metadata-type-registry-allow-org-override-retirement.test.ts b/packages/spec/src/kernel/metadata-type-registry-allow-org-override-retirement.test.ts index df44dc6e9aa..61356c4b3de 100644 --- a/packages/spec/src/kernel/metadata-type-registry-allow-org-override-retirement.test.ts +++ b/packages/spec/src/kernel/metadata-type-registry-allow-org-override-retirement.test.ts @@ -56,7 +56,7 @@ describe('MetadataTypeRegistryEntry.allowOrgOverride is renamed to allowEnvironm expect(issue!.path).toEqual(['allowOrgOverride']); // The prescription IS the migration doc for whoever hits it. expect(issue!.message).toMatch( - /`MetadataTypeRegistryEntry\.allowOrgOverride` was renamed to `allowEnvironmentOverlay` in @objectstack\/spec 18/s, + /`MetadataTypeRegistryEntry\.allowOrgOverride` was renamed to `allowEnvironmentOverlay` in @objectstack\/spec 17/s, ); expect(issue!.message).toMatch(/Rename the key to `allowEnvironmentOverlay`; the value is unchanged/s); // `false` is refused exactly like `true`: the key is gone, not one value of it. From c0d91e964388c71cf8bd7480716819fcb7f8e730 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 12 Oct 2026 03:10:48 +0000 Subject: [PATCH 11/15] regen: content/docs/references/system/email-template.mdx from the merged tree (os-regen-merge step 3) Main's side of the generated reference, re-derived from the merged email-template.zod.ts header (the registry flag cited as allowEnvironmentOverlay). Discharges the merge commit's deferral. Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude --- content/docs/references/system/email-template.mdx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/content/docs/references/system/email-template.mdx b/content/docs/references/system/email-template.mdx index 0d5797ba317..3a19ca702dc 100644 --- a/content/docs/references/system/email-template.mdx +++ b/content/docs/references/system/email-template.mdx @@ -9,10 +9,13 @@ description: "Declarative template definition consumed by IEmailService.sendTemp Email Template Metadata Protocol Declarative template definition consumed by `IEmailService.sendTemplate()` -to render outbound mail. Persisted as rows of `sys_email_template` so -administrators can author/edit/translate templates in Studio without -shipping code, and an environment can overlay the packaged defaults -(`allowEnvironmentOverlay: true` in the metadata registry). +to render outbound mail. Read from the metadata registry by +`(name, locale)` — the environment's item over each code package's +declaration, the built-in auth templates included — so administrators can +author/edit/translate templates in Studio without shipping code, and a +Studio save overrides a built-in default at environment level +(`allowEnvironmentOverlay: true` in the metadata registry). No +`sys_email_template` row is read or written on the way. Aligned with Salesforce `EmailTemplate` and ServiceNow `sysevent_email_action` conventions: a single named template is From fa5dcbd066d6adb27992204558529452fd6c1bb7 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 12 Oct 2026 03:18:20 +0000 Subject: [PATCH 12/15] spec: the rename prescription carries no version word; a history comment keeps its era's name - The allowOrgOverride tombstone's prescription reads "was renamed to `allowEnvironmentOverlay` (ADR-0131 D6)": the changeset ships this as major on the 18.0.0-next line, so naming an npm major there contradicts one of the two. The TSDoc's "in 18" names the protocol major and stays. The refusal pin and the generated reference follow. - sys-metadata-repository.ts: the comment recording commit ee58392e1 names the key that commit rolled back, `allowOrgOverride`. Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude --- content/docs/references/kernel/metadata-plugin.mdx | 2 +- packages/metadata-protocol/src/sys-metadata-repository.ts | 2 +- packages/spec/src/kernel/metadata-plugin.zod.ts | 4 ++-- ...tadata-type-registry-allow-org-override-retirement.test.ts | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/content/docs/references/kernel/metadata-plugin.mdx b/content/docs/references/kernel/metadata-plugin.mdx index b70457c9817..edef4c74253 100644 --- a/content/docs/references/kernel/metadata-plugin.mdx +++ b/content/docs/references/kernel/metadata-plugin.mdx @@ -269,7 +269,7 @@ const result = MetadataBulkResultSchema.parse(data); | **filePatterns** | `string[]` | ✅ | Glob patterns to discover files of this type | | **supportsOverlay** | `boolean` | optional (default: `true`) | Whether a stored overlay row of an item a managed package ships may be removed through the ordinary delete door without the type's environment-overlay permission: the removal removes no managed definition, and changes what is served for permission alone; it opens no save and it decides no read | | **allowEnvironmentOverlay** | `boolean` | optional (default: `false`) | Whether an environment may overlay an item of this type that a managed package ships (ADR-0131 D6 regime O). Decides the save door, the managed-content seal on write and on read, and Studio's write gate; the OS_METADATA_WRITABLE hatch does not reach a managed item. Default false. | -| **allowOrgOverride** | `never` | optional | [REMOVED] `MetadataTypeRegistryEntry.allowOrgOverride` was renamed to `allowEnvironmentOverlay` in @objectstack/spec 17 (ADR-0131 D6) — the per-organization overlay axis was retired, and the key now names the one scope it governs: whether an environment may overlay an item of this type that a managed package ships. Rename the key to `allowEnvironmentOverlay`; the value is unchanged. | +| **allowOrgOverride** | `never` | optional | [REMOVED] `MetadataTypeRegistryEntry.allowOrgOverride` was renamed to `allowEnvironmentOverlay` (ADR-0131 D6) — the per-organization overlay axis was retired, and the key now names the one scope it governs: whether an environment may overlay an item of this type that a managed package ships. Rename the key to `allowEnvironmentOverlay`; the value is unchanged. | | **allowRuntimeCreate** | `boolean` | optional (default: `true`) | Allow runtime creation via API | | **supportsVersioning** | `boolean` | optional (default: `false`) | Whether version history is tracked | | **executionPinned** | `boolean` | optional (default: `false`) | Transaction rows reference a specific version_hash; history GC is disabled and getByHash() MUST resolve old hashes (ADR-0009) | diff --git a/packages/metadata-protocol/src/sys-metadata-repository.ts b/packages/metadata-protocol/src/sys-metadata-repository.ts index 14674b6d55b..e21ab559e64 100644 --- a/packages/metadata-protocol/src/sys-metadata-repository.ts +++ b/packages/metadata-protocol/src/sys-metadata-repository.ts @@ -2031,7 +2031,7 @@ export class SysMetadataRepository implements MetadataRepository { * kernel (`environmentId === undefined`) skips the protocol's own two-tier * block entirely and lands here instead. That made it the second of the two * refusal points #6960 measured: on an environment carrying an overlay row - * authored BEFORE commit ee58392e1 rolled `allowEnvironmentOverlay` back to + * authored BEFORE commit ee58392e1 rolled `allowOrgOverride` back to * `false`, the row kept merging overlay-wins at read time while the ordinary * "Reset to package default" answered 403 — the removal reachable only * through `OS_METADATA_WRITABLE`. Maintainer ruling, 2026-08-10: the delete diff --git a/packages/spec/src/kernel/metadata-plugin.zod.ts b/packages/spec/src/kernel/metadata-plugin.zod.ts index b4f3244f028..86961718d34 100644 --- a/packages/spec/src/kernel/metadata-plugin.zod.ts +++ b/packages/spec/src/kernel/metadata-plugin.zod.ts @@ -314,8 +314,8 @@ const MetadataTypeRegistryEntryBaseSchema = z.object({ * `undefined`, which closes every type. Read `allowEnvironmentOverlay`. */ allowOrgOverride: retiredKey( - '`MetadataTypeRegistryEntry.allowOrgOverride` was renamed to `allowEnvironmentOverlay` in ' - + '@objectstack/spec 17 (ADR-0131 D6) — the per-organization overlay axis was retired, and ' + '`MetadataTypeRegistryEntry.allowOrgOverride` was renamed to `allowEnvironmentOverlay` ' + + '(ADR-0131 D6) — the per-organization overlay axis was retired, and ' + 'the key now names the one scope it governs: whether an environment may overlay an item ' + 'of this type that a managed package ships. Rename the key to `allowEnvironmentOverlay`; ' + 'the value is unchanged.', diff --git a/packages/spec/src/kernel/metadata-type-registry-allow-org-override-retirement.test.ts b/packages/spec/src/kernel/metadata-type-registry-allow-org-override-retirement.test.ts index 61356c4b3de..64bc0561213 100644 --- a/packages/spec/src/kernel/metadata-type-registry-allow-org-override-retirement.test.ts +++ b/packages/spec/src/kernel/metadata-type-registry-allow-org-override-retirement.test.ts @@ -56,7 +56,7 @@ describe('MetadataTypeRegistryEntry.allowOrgOverride is renamed to allowEnvironm expect(issue!.path).toEqual(['allowOrgOverride']); // The prescription IS the migration doc for whoever hits it. expect(issue!.message).toMatch( - /`MetadataTypeRegistryEntry\.allowOrgOverride` was renamed to `allowEnvironmentOverlay` in @objectstack\/spec 17/s, + /`MetadataTypeRegistryEntry\.allowOrgOverride` was renamed to `allowEnvironmentOverlay` \(ADR-0131 D6\)/s, ); expect(issue!.message).toMatch(/Rename the key to `allowEnvironmentOverlay`; the value is unchanged/s); // `false` is refused exactly like `true`: the key is gone, not one value of it. From 67560edfaadbd3fd48ab82fbe8ecb60c9fe13cc5 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 12 Oct 2026 03:29:40 +0000 Subject: [PATCH 13/15] chore(objectui): move the console pin to e5311165a8d6 (objectui#12168, carries objectui#12141); re-measure the spec's pin citations `.objectui-sha` 4997995022f8 -> e5311165a8d6, the merge of objectstack-ai/objectui#12168: objectui main from that commit on reads the `GET /meta/types` field under its new name, `allowEnvironmentOverlay`. The 55 asserting pin citations in packages/spec/src (49 hand-written, 6 in the generated migrations registry) are re-measured over the objectui diff 4997995022f8..e5311165a8d6 (287 paths, 24 commits): five cited files changed (ObjectGrid.tsx, data-table.tsx, form.tsx, record-related-list.tsx, data-objectstack/src/index.ts) and every cited line in them moved or held byte-identical; each record carries a hop sentence, and the anchors that moved are re-pointed in the records' at-this-pin text. The six migration entries' corpus counts are re-taken with git grep -o -F (8506 tracked files). check:objectui-pin-citations --verify-anchors: 55 asserting citations match, 13 anchor content assertions verified at e5311165a. Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude --- .../objectui-pin-citations-e5311165a8d6.md | 18 ++ .objectui-sha | 2 +- .../api-methods-batch-conformance.test.ts | 15 +- .../src/kernel/functional-completeness.ts | 22 ++- ...ck-and-hot-reload-durations-unit-in-key.ts | 6 +- ...rnel-runtime-config-timeout-unit-in-key.ts | 6 +- .../18.logging-durations-unit-in-key.ts | 6 +- ...tem-metrics-jsdoc-durations-unit-in-key.ts | 9 +- ...ing-otel-exporter-durations-unit-in-key.ts | 11 +- .../18.tenant-schema-cache-ttl-unit-in-key.ts | 6 +- .../src/ui/action-outcome-messages.test.ts | 3 + packages/spec/src/ui/action.zod.ts | 3 + packages/spec/src/ui/component.test.ts | 30 ++- packages/spec/src/ui/component.zod.ts | 182 ++++++++++++++---- packages/spec/src/ui/dataset.zod.ts | 3 + packages/spec/src/ui/view.zod.ts | 15 +- 16 files changed, 268 insertions(+), 69 deletions(-) create mode 100644 .changeset/objectui-pin-citations-e5311165a8d6.md diff --git a/.changeset/objectui-pin-citations-e5311165a8d6.md b/.changeset/objectui-pin-citations-e5311165a8d6.md new file mode 100644 index 00000000000..aed08ffeb0d --- /dev/null +++ b/.changeset/objectui-pin-citations-e5311165a8d6.md @@ -0,0 +1,18 @@ +--- +'@objectstack/spec': patch +--- + +The spec's objectui citations, and the shipped description text that names the `.objectui-sha` pin (the `FormField.span` describe and six migration-entry descriptions), are re-measured against the new console pin, objectui `e5311165a8d6`. + +Clause-②: no + +Every anchor was mapped through the objectui diff `4997995022f8..e5311165a8d6`: 287 paths over 24 commits, four deleted (`StudioHomePage.tsx`, its two tests and `permission-set-clone-dispatch.ts`, none of which a record cites) and none renamed. Five files a record cites changed on the hop, plus the `en` / `zh` / `de` packs (whose `calendar.configRequired` strings did not change): `ObjectGrid.tsx` (objectui#12110 and objectui#12143), `data-table.tsx` (objectui#12124 and objectui#12145), `form.tsx` (objectui#12110, objectui#12130 and objectui#12155), `record-related-list.tsx` (objectui#12082) and `data-objectstack/src/index.ts` (objectui#12112). Every cited line in them moved or held with its text byte-identical, and each record that cites one is re-pointed with a hop sentence that says by how much. The records that cite only byte-identical files gain a hop sentence that says so. + +Two readings sit beside a cited read, and each hop sentence says what it found: + +- `record-related-list.tsx`: the list's fallback remove now reads the affordance-to-grant map's `relatedRowDelete` row (objectui#12082). The hunks sit above and below the `requiredPermissions` gate; the gate and the related object's read gate are byte-identical, 17 lines down. +- `form.tsx`: the built-in branches now settle the widget a field hint renders and the props they hand their control (objectui#12110, objectui#12155, objectui#12130). `spanLadderFor` and its one call site are byte-identical, so the `span` describe's reading holds. + +The six migration entries' corpus counts were re-taken with `git grep -o -F`, the method that reproduces every `4997995022f8` number. The corpus is now 8506 tracked files. Every token an entry counts as zero still reads zero: 124 candidate tokens (every export of the four spec files the entries name, and every key they name) read zero at both pins. `Span` reads 519 against 517, both new hits `colSpan`, and `SpanSchema` reads 57 at both pins, as the tracing entry records. The controls moved with the corpus, for example `objectstack` from 18271 to 18355 and `timeout` from 1704 to 1706. + +No key, default, enum member or export moves. diff --git a/.objectui-sha b/.objectui-sha index 97325989491..28c065313fd 100644 --- a/.objectui-sha +++ b/.objectui-sha @@ -1 +1 @@ -4997995022f8e65946f6362b7434685539cc72b7 +e5311165a8d6aa77b5cb7d9fd05da8451a2c21aa diff --git a/packages/spec/src/data/api-methods-batch-conformance.test.ts b/packages/spec/src/data/api-methods-batch-conformance.test.ts index 05043cc6ba0..b520af02c0e 100644 --- a/packages/spec/src/data/api-methods-batch-conformance.test.ts +++ b/packages/spec/src/data/api-methods-batch-conformance.test.ts @@ -61,7 +61,20 @@ const SINGLE_RECORD_WRITE_ONLY: Record = { // `revoked` on ONE key. The multi-select surface this rule protects does not // exist for API keys, and the shape a future one would take does not need // `bulk` either — both read off the console build this release pins - // (`.objectui-sha` = `499799502`, re-read there 2026-10-11: + // (`.objectui-sha` = `e5311165a`, re-read there 2026-10-12: + // in `packages/plugin-grid`, on the hop off `499799502`, `ObjectGrid.tsx` + // changed again (27 insertions, 11 deletions: objectui#12110's authored + // column label and objectui#12143's grouped-pager button names), none of it + // inside the selection block, which only MOVED by +16: + // `ObjectGrid.tsx:4997-5024` here (`4981-5008` at `499799502`), still hashing to + // `c88443302d40c2db739ddb235470bafa29056e2e` (hash-object of the block), + // re-READ with the same reading below. No hunk sits beside that reading: the + // `delete` verdict line MOVED `:1787` -> `:1793` and the promoted def's params + // `:5165-5184` -> `:5181-5200` (called at `:5200` -> `:5216`), each + // byte-identical. `hooks/useBulkExecutor.ts` is byte-identical to `499799502` + // and its `:298-303` still hashes to + // `01083348330f10a201cdf1078b4c21c236402b6a`. At + // `499799502`, re-read there 2026-10-11: // in `packages/plugin-grid`, on the hop off `0df67f237`, `ObjectGrid.tsx` // changed again (89 insertions, 44 deletions: objectui#12081 item 3's // whole-set footer and objectui#12082 leg A), none of it inside the diff --git a/packages/spec/src/kernel/functional-completeness.ts b/packages/spec/src/kernel/functional-completeness.ts index b9d740f225c..04240cf6316 100644 --- a/packages/spec/src/kernel/functional-completeness.ts +++ b/packages/spec/src/kernel/functional-completeness.ts @@ -298,7 +298,14 @@ export function checkFieldCompleteness(def: unknown): CompletenessFinding[] { * English sentence. So the loss is total rather than partial — every * record, on every object — and the author is told at render time as well * as here. Both reads hold at the pin this repo builds against - * (`.objectui-sha` = `499799502`, re-read there 2026-10-11: + * (`.objectui-sha` = `e5311165a`, re-read there 2026-10-12: + * on the hop off `499799502` `ObjectCalendar.tsx` and `ListView.tsx` are + * byte-identical (`git diff --quiet`), so `getCalendarConfig` `:311`, the + * `if (!calendarConfig)` arm `:1354` with its `tt(…)` lines `:1361-1362` and + * the `case 'calendar'` at `3813` did not move; and the `en` / `zh` / `de` + * packs changed (five commits: objectui#12124, objectui#12145, objectui#12146 + * and two parts of objectui#7611) but not their `calendar.configRequired` + * strings. At `499799502`, re-read there 2026-10-11: * on the hop off `0df67f237` `ObjectCalendar.tsx` and `ListView.tsx` are * byte-identical (`git diff --quiet`), so `getCalendarConfig` `:311`, the * `if (!calendarConfig)` arm `:1354` with its `tt(…)` lines `:1361-1362` and @@ -425,7 +432,10 @@ export function checkFieldCompleteness(def: unknown): CompletenessFinding[] { * This repo already records the same deletion one door over: the #13817 * check in `../ui/view.zod.ts` names objectui#7029 as its runtime half. * - `gantt` → NO fallback, and no silence [#19630]. Measured at the pin - * this repo builds against (`.objectui-sha` = `499799502`, re-read there 2026-10-11: + * this repo builds against (`.objectui-sha` = `e5311165a`, re-read there 2026-10-12: + * every objectui file this record cites is byte-identical across the hop from + * `499799502` (`git diff --quiet`), so every anchor held unmoved. At + * `499799502`, re-read there 2026-10-11: * on the hop off `0df67f237` `ObjectGantt.tsx` changed (+27/-4, * objectui#12082, objectui `752504520`: the chart's in-chart writes read the * affordance-to-grant map), none of it in `getGanttConfig` or the refusal arm, @@ -524,6 +534,9 @@ export function checkFieldCompleteness(def: unknown): CompletenessFinding[] { * requires. So the view does not draw a blank chart: it refuses, by name. * - `timeline` → date axis: NO fallback [#19630]; title: `titleField || 'name'`, * which still stands. Measured at the same pin (`.objectui-sha` = + * `e5311165a`, re-read there 2026-10-12: + * every objectui file this record cites is byte-identical across the hop from + * `499799502` (`git diff --quiet`), so every anchor held unmoved. At * `499799502`, re-read there 2026-10-11: * every objectui file this record cites is byte-identical across the hop from * `0df67f237` (`git diff --quiet`), so every anchor held unmoved. At @@ -620,7 +633,10 @@ export function checkFieldCompleteness(def: unknown): CompletenessFinding[] { * block does render; the warning still fires there, because the block the * view TYPE names is the one that is missing. Unchanged by this row. * - `map` → NO fallback, and no silence [#19630]. Measured at the same - * pin (`.objectui-sha` = `499799502`, re-read there 2026-10-11: + * pin (`.objectui-sha` = `e5311165a`, re-read there 2026-10-12: + * every objectui file this record cites is byte-identical across the hop from + * `499799502` (`git diff --quiet`), so every anchor held unmoved. At + * `499799502`, re-read there 2026-10-11: * every objectui file this record cites is byte-identical across the hop from * `0df67f237` (`git diff --quiet`), so every anchor held unmoved. At * `0df67f237`, re-read there 2026-10-11: diff --git a/packages/spec/src/migrations/entries/semantic/18.kernel-health-check-and-hot-reload-durations-unit-in-key.ts b/packages/spec/src/migrations/entries/semantic/18.kernel-health-check-and-hot-reload-durations-unit-in-key.ts index a84ca1a448c..90016600957 100644 --- a/packages/spec/src/migrations/entries/semantic/18.kernel-health-check-and-hot-reload-durations-unit-in-key.ts +++ b/packages/spec/src/migrations/entries/semantic/18.kernel-health-check-and-hot-reload-durations-unit-in-key.ts @@ -52,13 +52,13 @@ export const entry: SemanticMigration = { + 'the audience that does not parse. Measured on 884e8347d: the only in-repo readers are ' + 'packages/core/src/health-monitor.ts and packages/core/src/hot-reload.ts, both moved in ' + 'this same change; and the pinned objectui checkout — the pin this repo builds ' - + 'against, `.objectui-sha` = `4997995022f8e65946f6362b7434685539cc72b7` — names ' + + 'against, `.objectui-sha` = `e5311165a8d6aa77b5cb7d9fd05da8451a2c21aa` — names ' + 'neither def and neither key: all thirteen exports of plugin-lifecycle-advanced.zod.ts and ' - + 'the string debounceDelay each occur 0 times across its 8441 tracked files (0 across the 8424 at 0df67f237, the 8351 at 20c6d351a, the 8281 at 47b1f0bb7, the 8234 at f0268ad78, the 7754 at a58626c88, the 7650 at 0abd4f9f8, the 7632 at 9dfaca654, the 7579 at 2e818d0b5, the 10267 at ab1879721, the 10071 at 89cad75d5, the 9912 at 31971ff1e, the 9800 at e420df310, the 9546 at db11afd49, the 9283 at dd3f7e1be, the ' + + 'the string debounceDelay each occur 0 times across its 8506 tracked files (0 across the 8441 at 499799502, the 8424 at 0df67f237, the 8351 at 20c6d351a, the 8281 at 47b1f0bb7, the 8234 at f0268ad78, the 7754 at a58626c88, the 7650 at 0abd4f9f8, the 7632 at 9dfaca654, the 7579 at 2e818d0b5, the 10267 at ab1879721, the 10071 at 89cad75d5, the 9912 at 31971ff1e, the 9800 at e420df310, the 9546 at db11afd49, the 9283 at dd3f7e1be, the ' + '8512 at f8a9d0fb0 and the 8303 at 62597c588 too), against lit ' + 'controls objectstack 12966 and @objectstack/spec 4997 on the same corpus at 87af769e9, ' + 'which re-count to 13125 and 5043 respectively at 62597c588, to 13347 and 5123 at ' - + 'f8a9d0fb0, to 13745 and 5466 at dd3f7e1be, to 14704 and 5545 at db11afd49, to 15352 and 6024 at e420df310, to 15691 and 6206 at 31971ff1e, to 16044 and 6461 at 89cad75d5, to 16377 and 6665 at ab1879721, to 17227 and 7134 at 2e818d0b5, to 17313 and 7186 at 9dfaca654, to 17390 and 7209 at 0abd4f9f8, to 17468 and 7246 at a58626c88, to 17956 and 7522 at f0268ad78, to 17980 and 7523 at 47b1f0bb7, to 18047 and 7545 at 20c6d351a, to 18248 and 7582 at 0df67f237 and to 18271 and 7587 at this pin (git grep -o -F, the method that reproduces ' + + 'f8a9d0fb0, to 13745 and 5466 at dd3f7e1be, to 14704 and 5545 at db11afd49, to 15352 and 6024 at e420df310, to 15691 and 6206 at 31971ff1e, to 16044 and 6461 at 89cad75d5, to 16377 and 6665 at ab1879721, to 17227 and 7134 at 2e818d0b5, to 17313 and 7186 at 9dfaca654, to 17390 and 7209 at 0abd4f9f8, to 17468 and 7246 at a58626c88, to 17956 and 7522 at f0268ad78, to 17980 and 7523 at 47b1f0bb7, to 18047 and 7545 at 20c6d351a, to 18248 and 7582 at 0df67f237, to 18271 and 7587 at 499799502 and to 18355 and 7599 at this pin (git grep -o -F, the method that reproduces ' + 'every earlier count).', acceptanceCriteria: 'Every producer and reader of a PluginHealthCheck spells intervalMs and timeoutMs, and every ' diff --git a/packages/spec/src/migrations/entries/semantic/18.kernel-runtime-config-timeout-unit-in-key.ts b/packages/spec/src/migrations/entries/semantic/18.kernel-runtime-config-timeout-unit-in-key.ts index d3729356a49..553e5d17a66 100644 --- a/packages/spec/src/migrations/entries/semantic/18.kernel-runtime-config-timeout-unit-in-key.ts +++ b/packages/spec/src/migrations/entries/semantic/18.kernel-runtime-config-timeout-unit-in-key.ts @@ -46,10 +46,10 @@ export const entry: SemanticMigration = { + 'spells timeout 0 times; outside the zod file and its test the only live occurrences are the ' + 'generated rows in content/docs/references/kernel/plugin-security-advanced.mdx, which this ' + 'rename regenerates. The pinned objectui checkout — this is the pin we build against, ' - + '`.objectui-sha` = `4997995022f8e65946f6362b7434685539cc72b7`, re-read from this tree — ' + + '`.objectui-sha` = `e5311165a8d6aa77b5cb7d9fd05da8451a2c21aa`, re-read from this tree — ' + 'spells resourceLimits.timeout 0 times across ' - + '8441 tracked files, against lit controls timeout 1704, RuntimeConfig 352 and resourceLimits ' - + '2 on the same corpus (0 across 8424, and 1705 / 347 / 2, at 0df67f237; 0 across 8351, and 1694 / 337 / 2, at 20c6d351a; 0 across 8281, and 1674 / 337 / 2, at 47b1f0bb7; 0 across 8234, and 1658 / 337 / 2, at f0268ad78; 0 across 7754, and 1431 / 299 / 2, at a58626c88; 0 across 7650, and 1360 / 293 / 2, at 0abd4f9f8; 0 across 7632, and 1360 / 293 / 2, at 9dfaca654; 0 across 7579, and 1351 / 276 / 2, at 2e818d0b5; 0 across 10267, and 1348 / 273 / 2, at ab1879721; 0 across 10071, and 1331 / 273 / 2, at 89cad75d5; 0 across 9912, and 1303 / 273 / 2, at 31971ff1e; 0 across 9800, and 1293 / 273 / 2, at e420df310; 0 across 9546, and 1197 / 273 / 2, at db11afd49; 0 across 9283, and 1172 / 263 / 2, at dd3f7e1be; 0 across 8512, and 1096 / 245 / 2, at f8a9d0fb0; 0 across 8303, and ' + + '8506 tracked files, against lit controls timeout 1706, RuntimeConfig 357 and resourceLimits ' + + '2 on the same corpus (0 across 8441, and 1704 / 352 / 2, at 499799502; 0 across 8424, and 1705 / 347 / 2, at 0df67f237; 0 across 8351, and 1694 / 337 / 2, at 20c6d351a; 0 across 8281, and 1674 / 337 / 2, at 47b1f0bb7; 0 across 8234, and 1658 / 337 / 2, at f0268ad78; 0 across 7754, and 1431 / 299 / 2, at a58626c88; 0 across 7650, and 1360 / 293 / 2, at 0abd4f9f8; 0 across 7632, and 1360 / 293 / 2, at 9dfaca654; 0 across 7579, and 1351 / 276 / 2, at 2e818d0b5; 0 across 10267, and 1348 / 273 / 2, at ab1879721; 0 across 10071, and 1331 / 273 / 2, at 89cad75d5; 0 across 9912, and 1303 / 273 / 2, at 31971ff1e; 0 across 9800, and 1293 / 273 / 2, at e420df310; 0 across 9546, and 1197 / 273 / 2, at db11afd49; 0 across 9283, and 1172 / 263 / 2, at dd3f7e1be; 0 across 8512, and 1096 / 245 / 2, at f8a9d0fb0; 0 across 8303, and ' + '1086 / 240 / 2, at 62597c588); both resourceLimits hits are prose in packages/app-shell recording ' + 'that objectui\'s own AppShellRuntimeConfig shares not one key with the spec\'s ' + 'RuntimeConfig, so nothing there authors this key and no pin bump is owed. ADR-0087.', diff --git a/packages/spec/src/migrations/entries/semantic/18.logging-durations-unit-in-key.ts b/packages/spec/src/migrations/entries/semantic/18.logging-durations-unit-in-key.ts index 9f6fd13981e..f2433a5af67 100644 --- a/packages/spec/src/migrations/entries/semantic/18.logging-durations-unit-in-key.ts +++ b/packages/spec/src/migrations/entries/semantic/18.logging-durations-unit-in-key.ts @@ -47,10 +47,10 @@ export const entry: SemanticMigration = { + 'no in-repo runtime reads any of the four — outside `packages/spec/src/system/logging.zod.ts` ' + 'and its test the only occurrences are the generated rows in ' + '`content/docs/references/system/logging.mdx`, which this rename regenerates; and the pinned ' - + 'objectui checkout — `.objectui-sha` = `4997995022f8e65946f6362b7434685539cc72b7` — spells ' + + 'objectui checkout — `.objectui-sha` = `e5311165a8d6aa77b5cb7d9fd05da8451a2c21aa` — spells ' + '`flushInterval` 0 times, `initialDelay` 0, `HttpDestinationConfig` 0 and `LoggingConfig` 0 ' - + 'across its 8441 tracked files, against lit controls `useState` 2648 and `timeout` 1704 on ' - + 'the same corpus (all four 0 across 8424, against 2645 and 1705, at 0df67f237, 0 across 8351, against 2630 and 1694, at 20c6d351a, 0 across 8281, against 2630 and 1674, at 47b1f0bb7, 0 across 8234, against 2622 and 1658, at f0268ad78, 0 across 7754, against 2491 and 1431, at a58626c88, 0 across 7650, against 2478 and 1360, at 0abd4f9f8, 0 across 7632, against 2477 and 1360, at 9dfaca654, 0 across 7579, against 2477 and 1351, at 2e818d0b5, 0 across 10267, against 2476 and 1348, at ab1879721, 0 across 10071, against 2470 and 1331, at 89cad75d5, 0 across 9912, against 2469 and 1303, at 31971ff1e, 0 across 9800, against 2464 and 1293, at e420df310, 0 across 9546, against 2449 and 1197, at db11afd49, 0 across 9283, against 2435 and 1172, at dd3f7e1be, 0 across 8512, ' + + 'across its 8506 tracked files, against lit controls `useState` 2659 and `timeout` 1706 on ' + + 'the same corpus (all four 0 across 8441, against 2648 and 1704, at 499799502, 0 across 8424, against 2645 and 1705, at 0df67f237, 0 across 8351, against 2630 and 1694, at 20c6d351a, 0 across 8281, against 2630 and 1674, at 47b1f0bb7, 0 across 8234, against 2622 and 1658, at f0268ad78, 0 across 7754, against 2491 and 1431, at a58626c88, 0 across 7650, against 2478 and 1360, at 0abd4f9f8, 0 across 7632, against 2477 and 1360, at 9dfaca654, 0 across 7579, against 2477 and 1351, at 2e818d0b5, 0 across 10267, against 2476 and 1348, at ab1879721, 0 across 10071, against 2470 and 1331, at 89cad75d5, 0 across 9912, against 2469 and 1303, at 31971ff1e, 0 across 9800, against 2464 and 1293, at e420df310, 0 across 9546, against 2449 and 1197, at db11afd49, 0 across 9283, against 2435 and 1172, at dd3f7e1be, 0 across 8512, ' + 'against 2391 and 1096, at f8a9d0fb0, and 0 across ' + '8303, against 2389 and 1086, at 62597c588).', acceptanceCriteria: diff --git a/packages/spec/src/migrations/entries/semantic/18.system-metrics-jsdoc-durations-unit-in-key.ts b/packages/spec/src/migrations/entries/semantic/18.system-metrics-jsdoc-durations-unit-in-key.ts index 5c7b5f1613a..e19824f8376 100644 --- a/packages/spec/src/migrations/entries/semantic/18.system-metrics-jsdoc-durations-unit-in-key.ts +++ b/packages/spec/src/migrations/entries/semantic/18.system-metrics-jsdoc-durations-unit-in-key.ts @@ -73,10 +73,11 @@ export const entry: SemanticMigration = { + 'against a lit control of 1195 defineStack occurrences on that same corpus at fc28c1d38 ' + '(1195 again at 9b62f54671); and the objectui ' + 'checkout this repo builds against — this is the pin, ' - + '`.objectui-sha` = `4997995022f8e65946f6362b7434685539cc72b7`, re-read from this tree — ' - + 'spells all six metrics def names and both distinctive keys 0 times across 8441 tracked ' - + 'files at that sha, against lit controls window 4604, timeout 1704, period 251, ' - + 'interval 238 and metrics 456 on that same corpus and sha (0 across 8424, against 4596 / ' + + '`.objectui-sha` = `e5311165a8d6aa77b5cb7d9fd05da8451a2c21aa`, re-read from this tree — ' + + 'spells all six metrics def names and both distinctive keys 0 times across 8506 tracked ' + + 'files at that sha, against lit controls window 4614, timeout 1706, period 251, ' + + 'interval 238 and metrics 456 on that same corpus and sha (0 across 8441, against 4604 / ' + + '1704 / 251 / 238 / 456, at 499799502, 0 across 8424, against 4596 / ' + '1705 / 251 / 233 / 456, at 0df67f237, 0 across 8351, against 4470 / ' + '1694 / 249 / 213 / 456, at 20c6d351a, 0 across 8281, against 4449 / ' + '1674 / 249 / 213 / 455, at 47b1f0bb7, 0 across 8234, against 4430 / ' diff --git a/packages/spec/src/migrations/entries/semantic/18.system-tracing-otel-exporter-durations-unit-in-key.ts b/packages/spec/src/migrations/entries/semantic/18.system-tracing-otel-exporter-durations-unit-in-key.ts index 319dc12f085..cad84f0151d 100644 --- a/packages/spec/src/migrations/entries/semantic/18.system-tracing-otel-exporter-durations-unit-in-key.ts +++ b/packages/spec/src/migrations/entries/semantic/18.system-tracing-otel-exporter-durations-unit-in-key.ts @@ -69,12 +69,13 @@ export const entry: SemanticMigration = { + 'dark control of 0; inside packages/spec the ' + 'only occurrences are tracing.zod.ts, its test, and the generated rows in ' + 'content/docs/references/system/tracing.mdx, which this rename regenerates. And the ' - + 'pinned objectui checkout — `.objectui-sha` = `4997995022f8e65946f6362b7434685539cc72b7` — names none of it: all 37 exports of ' - + 'tracing.zod.ts and each of the four key names occur 0 times across the 8441 files ' - + 'tracked at that sha (the 517 Span and 57 SpanSchema hits are objectui\'s own HTML ' + + 'pinned objectui checkout — `.objectui-sha` = `e5311165a8d6aa77b5cb7d9fd05da8451a2c21aa` — names none of it: all 37 exports of ' + + 'tracing.zod.ts and each of the four key names occur 0 times across the 8506 files ' + + 'tracked at that sha (the 519 Span and 57 SpanSchema hits are objectui\'s own HTML ' + 'text-span component, TextSpanSchema, an unrelated name, plus colSpan and prose), against ' - + 'two lit controls on that same corpus and sha: 18271 hits for the bare token objectstack, ' - + 'and 7587 for the package specifier @objectstack/spec (at 0df67f237: 0 across 8424, Span 517, ' + + 'two lit controls on that same corpus and sha: 18355 hits for the bare token objectstack, ' + + 'and 7599 for the package specifier @objectstack/spec (at 499799502: 0 across 8441, Span 517, ' + + '18271 and 7587; at 0df67f237: 0 across 8424, Span 517, ' + '18248 and 7582; at 20c6d351a: 0 across 8351, Span 517, ' + '18047 and 7545; at 47b1f0bb7: 0 across 8281, Span 517, ' + '17980 and 7523; at f0268ad78: 0 across 8234, Span 517, ' diff --git a/packages/spec/src/migrations/entries/semantic/18.tenant-schema-cache-ttl-unit-in-key.ts b/packages/spec/src/migrations/entries/semantic/18.tenant-schema-cache-ttl-unit-in-key.ts index 1bc6e935bae..ba320e0e24d 100644 --- a/packages/spec/src/migrations/entries/semantic/18.tenant-schema-cache-ttl-unit-in-key.ts +++ b/packages/spec/src/migrations/entries/semantic/18.tenant-schema-cache-ttl-unit-in-key.ts @@ -31,9 +31,9 @@ export const entry: SemanticMigration = { + 'bd25e897dc: no in-repo runtime reads the key — outside `packages/spec/src/system/tenant.zod.ts` ' + 'and its test the only occurrences are the four generated rows in ' + '`content/docs/references/system/tenant.mdx`, which this rename regenerates; and the pinned ' - + 'objectui checkout — `.objectui-sha` = `4997995022f8e65946f6362b7434685539cc72b7` — spells it 0 ' - + 'times across 8441 tracked files, against lit controls `TTL` 184 and `tenant` 1362 on the ' - + 'same corpus (0 across 8424, against 184 and 1357, at 0df67f237; 0 across 8351, against 184 and 1340, at 20c6d351a; 0 across 8281, against 184 and 1338, at 47b1f0bb7; 0 across 8234, against 184 and 1338, at f0268ad78; 0 across 7754, against 184 and 1319, at a58626c88; 0 across 7650, against 182 and 1318, at 0abd4f9f8; 0 across 7632, against 182 and 1318, at 9dfaca654; 0 across 7579, against 182 and 1317, at 2e818d0b5; 0 across 10267, against 180 and 1238, at ab1879721; 0 across 10071, against 181 and 1237, at 89cad75d5; 0 across 9912, against 181 and 1237, at 31971ff1e; 0 across 9800, against 181 and 1235, at e420df310; 0 across 9546, against 181 and 1200, at db11afd49; 0 across 9283, against 181 and 1185, at dd3f7e1be; 0 across 8512, against 156 and 1034, at f8a9d0fb0; 0 across 8303, against 156 ' + + 'objectui checkout — `.objectui-sha` = `e5311165a8d6aa77b5cb7d9fd05da8451a2c21aa` — spells it 0 ' + + 'times across 8506 tracked files, against lit controls `TTL` 184 and `tenant` 1365 on the ' + + 'same corpus (0 across 8441, against 184 and 1362, at 499799502; 0 across 8424, against 184 and 1357, at 0df67f237; 0 across 8351, against 184 and 1340, at 20c6d351a; 0 across 8281, against 184 and 1338, at 47b1f0bb7; 0 across 8234, against 184 and 1338, at f0268ad78; 0 across 7754, against 184 and 1319, at a58626c88; 0 across 7650, against 182 and 1318, at 0abd4f9f8; 0 across 7632, against 182 and 1318, at 9dfaca654; 0 across 7579, against 182 and 1317, at 2e818d0b5; 0 across 10267, against 180 and 1238, at ab1879721; 0 across 10071, against 181 and 1237, at 89cad75d5; 0 across 9912, against 181 and 1237, at 31971ff1e; 0 across 9800, against 181 and 1235, at e420df310; 0 across 9546, against 181 and 1200, at db11afd49; 0 across 9283, against 181 and 1185, at dd3f7e1be; 0 across 8512, against 156 and 1034, at f8a9d0fb0; 0 across 8303, against 156 ' + 'and 987, at 62597c588).', acceptanceCriteria: 'Every schema-level tenant isolation source spells `performance.schemaCacheTtlSeconds`; ' diff --git a/packages/spec/src/ui/action-outcome-messages.test.ts b/packages/spec/src/ui/action-outcome-messages.test.ts index 8d020f59c83..fad5fa7af12 100644 --- a/packages/spec/src/ui/action-outcome-messages.test.ts +++ b/packages/spec/src/ui/action-outcome-messages.test.ts @@ -12,6 +12,9 @@ * runner default) and the `${result.*}` INTERPOLATION are the console's — the * reader is objectstack-ai/objectui#11344, a later link of the same ruling, * carried from the pin this repo builds against (`.objectui-sha` = + * `e5311165a`, re-read there 2026-10-12: + * every objectui file this record cites is byte-identical across the hop from + * `499799502` (`git diff --quiet`), so every anchor held unmoved. At * `499799502`, re-read there 2026-10-11: * every objectui file this record cites is byte-identical across the hop from * `0df67f237` (`git diff --quiet`), so every anchor held unmoved. At diff --git a/packages/spec/src/ui/action.zod.ts b/packages/spec/src/ui/action.zod.ts index 6ed83465a72..0d610aa0e9e 100644 --- a/packages/spec/src/ui/action.zod.ts +++ b/packages/spec/src/ui/action.zod.ts @@ -1364,6 +1364,9 @@ const actionObject = () => strictObject({ * * Liveness: the ledger row was `planned` until the console reader landed, * and it is `live` from the pin this repo builds against (`.objectui-sha` = + * `e5311165a`, re-read there 2026-10-12: + * every objectui file this record cites is byte-identical across the hop from + * `499799502` (`git diff --quiet`), so every anchor held unmoved. At * `499799502`, re-read there 2026-10-11: * every objectui file this record cites is byte-identical across the hop from * `0df67f237` (`git diff --quiet`), so every anchor held unmoved. At diff --git a/packages/spec/src/ui/component.test.ts b/packages/spec/src/ui/component.test.ts index 4b3a11fdcd9..d8bd8f1e34d 100644 --- a/packages/spec/src/ui/component.test.ts +++ b/packages/spec/src/ui/component.test.ts @@ -288,7 +288,10 @@ describe('PageAccordionProps variant — declared because the accordion renderer // same file's `ComponentRegistry.register('accordion', …)` publishes the key to // the Studio block designer at `:1222` (the `items` input, documented as // `[{ label, icon?, collapsed?, children }]`). Measured at the pin this repo -// builds against — `.objectui-sha` = `499799502`, re-read there 2026-10-11: +// builds against — `.objectui-sha` = `e5311165a`, re-read there 2026-10-12: +// every objectui file this record cites is byte-identical across the hop from +// `499799502` (`git diff --quiet`), so every anchor held unmoved. At +// `499799502`, re-read there 2026-10-11: // every objectui file this record cites is byte-identical across the hop from // `0df67f237` (`git diff --quiet`), so every anchor held unmoved. At // `0df67f237`, re-read there 2026-10-11: @@ -457,7 +460,10 @@ describe('PageTabsProps items[].value / items[].count — declared because the t // same file's `ComponentRegistry.register('tabs', …)` publishes the key to the // Studio block designer at `:1007` (the `items` input, documented as // `[{ label, value?, icon?, count?, visibleWhen?, children }]`). Measured at -// the pin this repo builds against — `.objectui-sha` = `499799502`, re-read there 2026-10-11: +// the pin this repo builds against — `.objectui-sha` = `e5311165a`, re-read there 2026-10-12: +// every objectui file this record cites is byte-identical across the hop from +// `499799502` (`git diff --quiet`), so every anchor held unmoved. At +// `499799502`, re-read there 2026-10-11: // every objectui file this record cites is byte-identical across the hop from // `0df67f237` (`git diff --quiet`), so every anchor held unmoved. At // `0df67f237`, re-read there 2026-10-11: @@ -3499,7 +3505,10 @@ describe('object-* block props schemas — declared, so the props gate has a sch // #16503 — the spec half of objectui#8172 (decision batch #68, 2026-09-07, // option A: the contract declares the capability that already ships, is // documented and is in use). Measured at the objectui pin this repo builds -// against (`.objectui-sha` = `499799502`, re-read there 2026-10-11: +// against (`.objectui-sha` = `e5311165a`, re-read there 2026-10-12: +// every objectui file this record cites is byte-identical across the hop from +// `499799502` (`git diff --quiet`), so every anchor held unmoved. At +// `499799502`, re-read there 2026-10-11: // every objectui file this record cites is byte-identical across the hop from // `0df67f237` (`git diff --quiet`), so every anchor held unmoved. At // `0df67f237`, re-read there 2026-10-11: @@ -3713,7 +3722,10 @@ describe('ObjectKanbanPropsSchema limit — the row cap four objectui faces alre // since retired that block, objectui#8257). Unlike `limit` above — a key four // objectui faces already implemented, so the spec was the half that was wrong // — `quickAdd` was FORWARDED and never read: at the pin this repo builds -// against (`.objectui-sha` = `499799502`, re-read there 2026-10-11: +// against (`.objectui-sha` = `e5311165a`, re-read there 2026-10-12: +// every objectui file this record cites is byte-identical across the hop from +// `499799502` (`git diff --quiet`), so every anchor held unmoved. At +// `499799502`, re-read there 2026-10-11: // every objectui file this record cites is byte-identical across the hop from // `0df67f237` (`git diff --quiet`), so every anchor held unmoved. At // `0df67f237`, re-read there 2026-10-11: @@ -3916,7 +3928,10 @@ describe('ObjectKanbanPropsSchema quickAdd is retired', () => { // #9881 and commit 60e0f900a recorded the accordion and tab items; these two close the set. // // The button record re-measured at the pin this repo builds against — -// `.objectui-sha` = `499799502`, re-read there 2026-10-11: +// `.objectui-sha` = `e5311165a`, re-read there 2026-10-12: +// every objectui file this record cites is byte-identical across the hop from +// `499799502` (`git diff --quiet`), so every anchor held unmoved. At +// `499799502`, re-read there 2026-10-11: // every objectui file this record cites is byte-identical across the hop from // `0df67f237` (`git diff --quiet`), so every anchor held unmoved. At // `0df67f237`, re-read there 2026-10-11: @@ -4102,7 +4117,10 @@ describe('ObjectMetricPropsSchema icon liveness', () => { // // The acceptance the card names, pinned: each row's KEY SET is the one the // renderer's read points support at the pin this repo builds against -// (`.objectui-sha` = `499799502`, re-read there 2026-10-11: +// (`.objectui-sha` = `e5311165a`, re-read there 2026-10-12: +// every objectui file this record cites is byte-identical across the hop from +// `499799502` (`git diff --quiet`), so every anchor held unmoved. At +// `499799502`, re-read there 2026-10-11: // `ObjectTree.tsx`, `ObjectMap.tsx` and `record-source.ts` are byte-identical // across the hop from `0df67f237` (`git diff --quiet`), so every anchor in them // held unmoved; `ObjectGantt.tsx` +27/-4 changed for objectui#12082 (objectui diff --git a/packages/spec/src/ui/component.zod.ts b/packages/spec/src/ui/component.zod.ts index c0d62b35a80..2530a86b209 100644 --- a/packages/spec/src/ui/component.zod.ts +++ b/packages/spec/src/ui/component.zod.ts @@ -840,7 +840,10 @@ export const PageTabsProps = strictObject({ * false candidate a component over). * * The key is LIVE at the objectui pin this repo builds against - * (`.objectui-sha` = `499799502`, re-read there 2026-10-11: + * (`.objectui-sha` = `e5311165a`, re-read there 2026-10-12: + * every objectui file this record cites is byte-identical across the hop from + * `499799502` (`git diff --quiet`), so every anchor held unmoved. At + * `499799502`, re-read there 2026-10-11: * every objectui file this record cites is byte-identical across the hop from * `0df67f237` (`git diff --quiet`), so every anchor held unmoved. At * `0df67f237`, re-read there 2026-10-11: @@ -1083,7 +1086,17 @@ export const PageCardProps = strictObject({ * declarations identical. * * Each clause is read off the objectui pin this repo builds against - * (`.objectui-sha` = `4997995022f8`, re-read there 2026-10-11: + * (`.objectui-sha` = `e5311165a8d6`, re-read there 2026-10-12: + * across the hop from `4997995022f8` `record-details.tsx`, + * `record-highlights.tsx`, `record-quick-actions.tsx` and the three + * `permissions` files are byte-identical (`git diff --quiet`), so NO anchor in + * those six moved; `record-related-list.tsx` changed (+22/-3, objectui#12082, + * objectui `158c75d90`: the list's fallback remove reads the + * affordance-to-grant map's `relatedRowDelete` row) in one import line and + * above and below the gate without touching a cited line, so every anchor in + * it MOVED by 17 with its cited text byte-identical — `361` -> `378`, `379` -> + * `396`, `415` -> `432`, `428` -> `445`, `428-436` -> `445-453` — and each is + * cited below at this pin. At `4997995022f8`, re-read there 2026-10-11: * every objectui file this record cites is byte-identical across the hop from * `0df67f237c5a` (`git diff --quiet`), so every anchor held unmoved. At * `0df67f237c5a`, re-read there 2026-10-11: @@ -1182,22 +1195,22 @@ export const PageCardProps = strictObject({ * * 1. CAPABILITIES, NOT OBJECT ACTIONS. Every block gates through * `perms.hasCapabilities(required)` — `record-details.tsx:347`, - * `record-highlights.tsx:102`, `record-related-list.tsx:428`, + * `record-highlights.tsx:102`, `record-related-list.tsx:445`, * `record-quick-actions.tsx:282` — and never `perms.can(objectName, …)`; * each renderer's docblock states the capability "is not object-scoped" * (`record-details.tsx:336`, `record-highlights.tsx:81`, - * `record-related-list.tsx:415`, `record-quick-actions.tsx:271`). `read` + * `record-related-list.tsx:432`, `record-quick-actions.tsx:271`). `read` * is looked up in the capability set like any other name. * 2. ALL OF THEM. `MePermissionsProvider.tsx:447` is * `required.every((p) => held.has(p))`. * 3. THE OUTCOME. Each block returns a `role="status"` "Insufficient * permissions to view …" notice instead of its content — * `record-details.tsx:347-355`, `record-highlights.tsx:160-173`, - * `record-related-list.tsx:428-436`, `record-quick-actions.tsx:282-290`. + * `record-related-list.tsx:445-453`, `record-quick-actions.tsx:282-290`. * Checks that already withhold the content run first on two of them (no * record bound, `record-details.tsx:291`; no object, - * `record-related-list.tsx:361`; the related object's read gate, - * `record-related-list.tsx:379`), which is why the text says "wherever it + * `record-related-list.tsx:378`; the related object's read gate, + * `record-related-list.tsx:396`), which is why the text says "wherever it * would otherwise render" rather than promising the notice unconditionally. * 4. PRESENTATION ONLY. The gate is renderer code: nothing in this repo's * server packages reads a page component's `requiredPermissions`. The data @@ -2477,7 +2490,10 @@ export const PageAccordionProps = strictObject({ * re-derive the same false candidate). * * The key is LIVE at the objectui pin this repo builds against - * (`.objectui-sha` = `499799502`, re-read there 2026-10-11: + * (`.objectui-sha` = `e5311165a`, re-read there 2026-10-12: + * every objectui file this record cites is byte-identical across the hop from + * `499799502` (`git diff --quiet`), so every anchor held unmoved. At + * `499799502`, re-read there 2026-10-11: * every objectui file this record cites is byte-identical across the hop from * `0df67f237` (`git diff --quiet`), so every anchor held unmoved. At * `0df67f237`, re-read there 2026-10-11: @@ -2911,7 +2927,10 @@ export const ElementButtonPropsSchema = lazySchema(() => strictObject({ * the button. * * The key is LIVE at the objectui pin this repo builds against - * (`.objectui-sha` = `499799502`, re-read there 2026-10-11: + * (`.objectui-sha` = `e5311165a`, re-read there 2026-10-12: + * every objectui file this record cites is byte-identical across the hop from + * `499799502` (`git diff --quiet`), so every anchor held unmoved. At + * `499799502`, re-read there 2026-10-11: * every objectui file this record cites is byte-identical across the hop from * `0df67f237` (`git diff --quiet`), so every anchor held unmoved. At * `0df67f237`, re-read there 2026-10-11: @@ -3386,7 +3405,18 @@ export const ElementTextInputPropsSchema = lazySchema(() => strictObject({ * had no row for — `action:button`, `action:group`, `action:menu`, * `action:icon`, `element:definition-list`, `element:repeater` * (`core/src/registry/public-blocks.ts:117-122` at the pin this repo builds - * against, `.objectui-sha` = `499799502`, re-read there 2026-10-11: + * against, `.objectui-sha` = `e5311165a`, re-read there 2026-10-12: + * `public-blocks.ts`, `auto-trigger.ts`, `static-params.ts`, `ui/button.tsx`, + * the four `action-*.tsx` renderers, `basic/data-list.tsx` and + * `previews/block-config.ts` are byte-identical across the hop from + * `499799502` (`git diff --quiet`), so their anchors held unmoved; and + * `data-objectstack/src/index.ts` changed (+59/-13, objectui#12112, objectui + * `43e150518`: a refused dashboard or list view opened by URL says it was + * refused) in a type declaration between the two cited reads and in the + * adapter's meta-item probe below both, which left `serializeOrderBy` + * `869-895` unmoved and MOVED the `translateFilterArray` arm `5051-5062` -> + * `5066-5077` byte-identical; each moved anchor is cited below at this pin. At + * `499799502`, re-read there 2026-10-11: * every objectui file this record cites is byte-identical across the hop from * `0df67f237` (`git diff --quiet`), so every anchor held unmoved. At * `0df67f237`, re-read there 2026-10-11: @@ -3956,7 +3986,10 @@ export type ActionIconPropsParsed = z.infer; * inventory. A member's `params` is its `ActionParam[]` input list: both * containers forward an array as `actionParams` (group `:346-348`, menu * `:270-272`, re-read at the pin this repo builds against, `.objectui-sha` = - * `499799502`, 2026-10-11: `action-group.tsx`, `action-menu.tsx` and + * `e5311165a`, 2026-10-12: `action-group.tsx`, `action-menu.tsx` and + * `static-params.ts` are byte-identical across the hop from `499799502` (`git + * diff --quiet`), so both held unmoved; at `499799502`, 2026-10-11: + * `action-group.tsx`, `action-menu.tsx` and * `static-params.ts` are byte-identical across the hop from `0df67f237` (`git * diff --quiet`), so both held unmoved; at `0df67f237`, 2026-10-11: * `action-group.tsx`, `action-menu.tsx` and @@ -4378,7 +4411,7 @@ export type ElementDefinitionListProps = z.input strictObject({ * control `schema.editable` in `ObjectGrid.tsx`. It was declared ahead of its * reader on purpose (the BUILD objectui#11068 chose), and its describe carried * the `[EXPERIMENTAL — not enforced]` marker that said so. Re-measured at the - * pin this repo builds against (`.objectui-sha` = `499799502`, re-read there 2026-10-11: + * pin this repo builds against (`.objectui-sha` = `e5311165a`, re-read there 2026-10-12: + * `ObjectGrid.tsx` changed across the hop from `499799502` (+27/-11: + * objectui#12110, objectui `5330038f4`, and objectui#12143, objectui + * `2381c07f3`) and `data-table.tsx` changed too (+12/-1: objectui#12124 and + * objectui#12145, the pager's and the row menu's accessible names), neither + * on the read; the read MOVED `5615` -> `5631` with its line byte-identical, + * and the same method still finds 15 hit lines against 3 for the control. At + * `499799502`, re-read there 2026-10-11: * `ObjectGrid.tsx` changed across the hop from `0df67f237` (+89/-44: * objectui#12081 item 3, objectui `69bca0af6`, and objectui#12082, objectui * `752504520`) and `data-table.tsx` is byte-identical (`git diff --quiet`); the @@ -4696,7 +4736,7 @@ const GridOperationsSchema = lazySchema(() => strictObject({ * `ab1879721`): the BUILD landed — objectui `154075ab1` (objectui#11068), inside * `89cad75d5570..ab1879721595` — and the grid reads the key. The same method * finds 0 hit lines at `89cad75d5570` and 15 at `ab1879721595`, at `2e818d0b51ec` and at this pin, against 3 for the - * control `schema.editable` in `ObjectGrid.tsx` at each: `ObjectGrid.tsx:5615` + * control `schema.editable` in `ObjectGrid.tsx` at each: `ObjectGrid.tsx:5631` * hands `schema.keyboardNavigation ?? inlineEditable` to the `data-table` it * renders, and `components/src/renderers/complex/data-table.tsx` acts on it. * So the marker is gone, as the member's record prescribed; see the member. @@ -4775,7 +4815,12 @@ export const ObjectGridPropsSchema = lazySchema(() => strictObject({ * same members, and objectui's grid follows this declaration. * * ⚠️ That gap is closed at the pin this repo builds against (`.objectui-sha` - * = `499799502`, re-read there 2026-10-11: + * = `e5311165a`, re-read there 2026-10-12: + * `ObjectGrid.tsx` changed across the hop from `499799502` (+27/-11: + * objectui#12110 and objectui#12143), every hunk above both reads (the last + * at `:6592`, a pager button's name written in place), so both MOVED by 16 + * with their lines byte-identical: `6632-6633` -> `6648-6649`, `6649-6650` -> + * `6665-6666`. At `499799502`, re-read there 2026-10-11: * `ObjectGrid.tsx` changed across the hop from `0df67f237` (+89/-44: * objectui#12081 item 3 and objectui#12082), every hunk above both reads * (the last at `:5321-5343`), so both MOVED by 45 with their lines @@ -4814,7 +4859,7 @@ export const ObjectGridPropsSchema = lazySchema(() => strictObject({ * `6158e4c93`) resolves both members against the display locale before they * reach `DataEmptyState` — * `resolveInlineI18nLabel(authoredEmptyState?.title, displayLocale)` and the - * same for `message` (`ObjectGrid.tsx:6632-6633`, drawn at `:6649-6650`) — so + * same for `message` (`ObjectGrid.tsx:6648-6649`, drawn at `:6665-6666`) — so * both `I18nLabel` forms draw, and a locale map with no usable entry keeps * that member's default. At `89cad75d5570` the two still reached * `DataEmptyState` raw. @@ -5178,7 +5223,19 @@ export const ObjectGridPropsSchema = lazySchema(() => strictObject({ * `ComponentPropsMap` key. This record said to drop the marker in the change * that lands the BUILD at the pin, and it is dropped here. * - * Read at the pin this repo builds against (`.objectui-sha` = `499799502`, re-read there 2026-10-11: + * Read at the pin this repo builds against (`.objectui-sha` = `e5311165a`, re-read there 2026-10-12: + * `ObjectGrid.tsx` changed across the hop from `499799502` (+27/-11: + * objectui#12110 and objectui#12143) and `data-table.tsx` changed (+12/-1: + * objectui#12124 and objectui#12145: seven lines of default translations + * above every cited read, one `aria-label` rewritten in place and four added + * below them), so the five `data-table.tsx` anchors MOVED by 7 with their + * lines byte-identical — `1018` -> `1025`, `2607` -> `2614`, `3101` -> `3108`, + * `2257` -> `2264`, `2283` -> `2290` — and the two grid anchors MOVED with + * their lines byte-identical — `5615` -> `5631`, `1851` -> `1857`; no hunk + * lands between the viewer's write verdict `objectInlineEditable` and `:1857`, + * nor in the arrow-key handling between `navigationTarget` and the end of + * `handleCellKeyDown`, so `inlineEditable` is still the authored `editable` + * AND that verdict. At `499799502`, re-read there 2026-10-11: * `ObjectGrid.tsx` changed across the hop from `0df67f237` (+89/-44: * objectui#12081 item 3 and objectui#12082) and `data-table.tsx` is * byte-identical (`git diff --quiet`), so the five `data-table.tsx` anchors @@ -5233,17 +5290,17 @@ export const ObjectGridPropsSchema = lazySchema(() => strictObject({ * and `data-table.tsx` were * byte-identical across the hop from `ab1879721`, where they were measured * 2026-10-03; the BUILD is objectui `154075ab1`, objectui#11068): - * `plugin-grid/src/ObjectGrid.tsx:5615` hands the data table + * `plugin-grid/src/ObjectGrid.tsx:5631` hands the data table * `keyboardNavigation: schema.keyboardNavigation ?? inlineEditable`, where - * `inlineEditable` (`:1851`) is the authored `editable` AND the viewer's + * `inlineEditable` (`:1857`) is the authored `editable` AND the viewer's * write verdict — so an absent key follows whether the grid RENDERS * editable, `true` turns the navigation on for a read-only grid, and `false` * turns it off on an editable one. `components/src/renderers/complex/data-table.tsx` - * (`keyboardNavigation = false` by default, `:1018`) then gives the table - * `role="grid"` (`:2607`) and makes the data cells one roving Tab stop - * (`tabIndex` 0 on one cell and -1 on the rest, `:3101`) that the arrow + * (`keyboardNavigation = false` by default, `:1025`) then gives the table + * `role="grid"` (`:2614`) and makes the data cells one roving Tab stop + * (`tabIndex` 0 on one cell and -1 on the rest, `:3108`) that the arrow * keys, Home / End and Ctrl+Home / Ctrl+End move (`navigationTarget`, - * `:2257`; `handleCellKeyDown`, `:2283`). A widget a cell renders keeps its + * `:2264`; `handleCellKeyDown`, `:2290`). A widget a cell renders keeps its * own Tab stop. Off, every data cell is its own Tab stop, as before. */ keyboardNavigation: z.boolean().optional() @@ -5619,7 +5676,10 @@ export const ObjectMetricPropsSchema = lazySchema(() => strictObject({ * same record for the metric tile. * * The key is LIVE at the objectui pin this repo builds against - * (`.objectui-sha` = `499799502`, re-read there 2026-10-11: + * (`.objectui-sha` = `e5311165a`, re-read there 2026-10-12: + * every objectui file this record cites is byte-identical across the hop from + * `499799502` (`git diff --quiet`), so every anchor held unmoved. At + * `499799502`, re-read there 2026-10-11: * every objectui file this record cites is byte-identical across the hop from * `0df67f237` (`git diff --quiet`), so every anchor held unmoved. At * `0df67f237`, re-read there 2026-10-11: @@ -5879,7 +5939,10 @@ const ObjectKanbanLaneSchema = lazySchema(() => strictObject({ * DESIGNER's spelling with * zero read points (#7973 class) — aliased to the `groupBy` the board reads. * `limit` (#16503) was measured later, at the pin this repo builds against - * (`.objectui-sha` = `499799502`, re-read there 2026-10-11: + * (`.objectui-sha` = `e5311165a`, re-read there 2026-10-12: + * every objectui file this record cites is byte-identical across the hop from + * `499799502` (`git diff --quiet`), so every anchor held unmoved. At + * `499799502`, re-read there 2026-10-11: * every objectui file this record cites is byte-identical across the hop from * `0df67f237` (`git diff --quiet`), so every anchor held unmoved. At * `0df67f237`, re-read there 2026-10-11: @@ -6000,7 +6063,10 @@ export const ObjectKanbanPropsSchema = lazySchema(() => strictObject({ * Row cap (#16503 — the spec half of objectui#8172; decision batch #68, * 2026-09-07, option A: the contract declares the capability that already * ships, is documented and is in use). Measured at the objectui pin this - * repo builds against (`.objectui-sha` = `499799502`, re-read there 2026-10-11: + * repo builds against (`.objectui-sha` = `e5311165a`, re-read there 2026-10-12: + * every objectui file this record cites is byte-identical across the hop from + * `499799502` (`git diff --quiet`), so every anchor held unmoved. At + * `499799502`, re-read there 2026-10-11: * every objectui file this record cites is byte-identical across the hop from * `0df67f237` (`git diff --quiet`), so every anchor held unmoved. At * `0df67f237`, re-read there 2026-10-11: @@ -6293,7 +6359,10 @@ export const ObjectKanbanPropsSchema = lazySchema(() => strictObject({ * quick-add control and no block a document can name offers one either). * * Measured at the objectui pin this repo builds against - * (`.objectui-sha` = `499799502`, re-read there 2026-10-11: + * (`.objectui-sha` = `e5311165a`, re-read there 2026-10-12: + * every objectui file this record cites is byte-identical across the hop from + * `499799502` (`git diff --quiet`), so every anchor held unmoved. At + * `499799502`, re-read there 2026-10-11: * every objectui file this record cites is byte-identical across the hop from * `0df67f237` (`git diff --quiet`), so every anchor held unmoved; * `ObjectKanban.tsx` still names `quickAdd` and `onQuickAdd` 2 times each @@ -6515,6 +6584,9 @@ export const ObjectKanbanPropsSchema = lazySchema(() => strictObject({ * element schemas). * * Measured at the pin this repo builds against (`.objectui-sha` = + * `e5311165a`, re-read there 2026-10-12: + * every objectui file this record cites is byte-identical across the hop from + * `499799502` (`git diff --quiet`), so every anchor held unmoved. At * `499799502`, re-read there 2026-10-11: * every objectui file this record cites is byte-identical across the hop from * `0df67f237` (`git diff --quiet`), so every anchor held unmoved. At @@ -6744,6 +6816,9 @@ export const ObjectCalendarPropsSchema = lazySchema(() => strictObject({ * `object-kanban`'s above. * * Measured at the pin this repo builds against (`.objectui-sha` = + * `e5311165a`, re-read there 2026-10-12: + * every objectui file this record cites is byte-identical across the hop from + * `499799502` (`git diff --quiet`), so every anchor held unmoved. At * `499799502`, re-read there 2026-10-11: * every objectui file this record cites is byte-identical across the hop from * `0df67f237` (`git diff --quiet`), so every anchor held unmoved. At @@ -7970,7 +8045,10 @@ const OBJECT_MAP_FLAT_CONFIG_GUIDANCE: readonly KeySetGuidance[] = [ /** * `object-map` (objectui `plugin-map/src/ObjectMap.tsx` plus the registry shell * `plugin-map/src/index.tsx`, read at the pin this repo builds against — - * `.objectui-sha` = `499799502`, re-read there 2026-10-11: + * `.objectui-sha` = `e5311165a`, re-read there 2026-10-12: + * every objectui file this record cites is byte-identical across the hop from + * `499799502` (`git diff --quiet`), so every anchor held unmoved. At + * `499799502`, re-read there 2026-10-11: * every objectui file this record cites is byte-identical across the hop from * `0df67f237` (`git diff --quiet`), so every anchor held unmoved. At * `0df67f237`, re-read there 2026-10-11: @@ -8171,7 +8249,10 @@ export const ObjectMapPropsSchema = lazySchema(() => strictObject({ * Base query filter — the `ViewFilterRule` ARRAY form, the one filter * orthography every `filter` door in this map shares (ui#6206-B reaching the * `object-*` family: #15449, decision batch #55, option A). Measured at the - * pin this repo builds against (`.objectui-sha` = `499799502`, re-read there 2026-10-11: + * pin this repo builds against (`.objectui-sha` = `e5311165a`, re-read there 2026-10-12: + * every objectui file this record cites is byte-identical across the hop from + * `499799502` (`git diff --quiet`), so every anchor held unmoved. At + * `499799502`, re-read there 2026-10-11: * every objectui file this record cites is byte-identical across the hop from * `0df67f237` (`git diff --quiet`), so every anchor held unmoved. At * `0df67f237`, re-read there 2026-10-11: @@ -8251,7 +8332,10 @@ export const ObjectMapPropsSchema = lazySchema(() => strictObject({ * Marker order — the `SortItem` ARRAY form, the one sort orthography every * DECLARED `sort` door on this platform carries (objectui#8221, decision batch * #77, option B). Measured at the pin this repo builds against - * (`.objectui-sha` = `499799502`, re-read there 2026-10-11: + * (`.objectui-sha` = `e5311165a`, re-read there 2026-10-12: + * every objectui file this record cites is byte-identical across the hop from + * `499799502` (`git diff --quiet`), so every anchor held unmoved. At + * `499799502`, re-read there 2026-10-11: * every objectui file this record cites is byte-identical across the hop from * `0df67f237` (`git diff --quiet`), so every anchor held unmoved. At * `0df67f237`, re-read there 2026-10-11: @@ -8453,7 +8537,10 @@ function objectGanttMarker() { /** * `object-gantt` (objectui `plugin-gantt/src/ObjectGantt.tsx` plus the registry * shell `plugin-gantt/src/index.tsx`, read at the pin this repo builds against - * — `.objectui-sha` = `499799502`, re-read there 2026-10-11: + * — `.objectui-sha` = `e5311165a`, re-read there 2026-10-12: + * every objectui file this record cites is byte-identical across the hop from + * `499799502` (`git diff --quiet`), so every anchor held unmoved. At + * `499799502`, re-read there 2026-10-11: * `record-source.ts`, `plugin-gantt/src/index.tsx` and `ListView.tsx` are * byte-identical across the hop from `0df67f237` (`git diff --quiet`), so every * anchor in them held unmoved; `ObjectGantt.tsx` +27/-4 changed for @@ -8727,7 +8814,10 @@ export type ObjectGanttPropsParsed = z.infer; * they flatten `options.tree` (`ListView.tsx:4120-4144`, `case 'tree'`: the * product carries these keys, the EFFECTIVE `filter` objectui#10250 added, and * NO `tree` key). Both halves re-READ at the - * pin this repo builds against (`.objectui-sha` = `499799502`, re-read there 2026-10-11: + * pin this repo builds against (`.objectui-sha` = `e5311165a`, re-read there 2026-10-12: + * every objectui file this record cites is byte-identical across the hop from + * `499799502` (`git diff --quiet`), so every anchor held unmoved. At + * `499799502`, re-read there 2026-10-11: * every objectui file this record cites is byte-identical across the hop from * `0df67f237` (`git diff --quiet`), so every anchor held unmoved. At * `0df67f237`, re-read there 2026-10-11: @@ -8875,7 +8965,10 @@ const OBJECT_TREE_FLAT_CONFIG_GUIDANCE: readonly KeySetGuidance[] = [ /** * `object-tree` (objectui `plugin-tree/src/ObjectTree.tsx` plus the registry * shell `plugin-tree/src/index.tsx`, read at the pin this repo builds against - * — `.objectui-sha` = `499799502`, re-read there 2026-10-11: + * — `.objectui-sha` = `e5311165a`, re-read there 2026-10-12: + * every objectui file this record cites is byte-identical across the hop from + * `499799502` (`git diff --quiet`), so every anchor held unmoved. At + * `499799502`, re-read there 2026-10-11: * every objectui file this record cites is byte-identical across the hop from * `0df67f237` (`git diff --quiet`), so every anchor held unmoved. At * `0df67f237`, re-read there 2026-10-11: @@ -9074,7 +9167,11 @@ export const ObjectTreePropsSchema = lazySchema(() => strictObject({ * holds for them because each registers through `ElementDataSourceGate`, * which lowers the spec binding onto `objectName` before the renderer sees * the node. `plugin-tree/src/index.tsx` does NOT: at the pin this repo - * builds against (`.objectui-sha` = `499799502`, re-read there 2026-10-11: + * builds against (`.objectui-sha` = `e5311165a`, re-read there 2026-10-12: + * re-COUNTED by the same method, 0 for the tree, 3 each for `plugin-gantt`, + * `plugin-grid` and `plugin-calendar` and 4 for `plugin-map`, all five + * shells byte-identical across the hop from `499799502` (`git diff + * --quiet`). At `499799502`, re-read there 2026-10-11: * re-COUNTED by the same method, 0 for the tree, 3 each for `plugin-gantt`, * `plugin-grid` and `plugin-calendar` and 4 for `plugin-map`, all five * shells byte-identical across the hop from `0df67f237` (`git diff @@ -9485,7 +9582,10 @@ function objectTimelineItemsFitVariant() { * `object-timeline` (objectui `plugin-timeline/src/ObjectTimeline.tsx`, the * presentational `plugin-timeline/src/renderer.tsx` it composes into, and the * registry shell `plugin-timeline/src/index.tsx` — all read at the pin this - * repo builds against (`.objectui-sha` = `499799502`, re-read there 2026-10-11: + * repo builds against (`.objectui-sha` = `e5311165a`, re-read there 2026-10-12: + * every objectui file this record cites is byte-identical across the hop from + * `499799502` (`git diff --quiet`), so every anchor held unmoved. At + * `499799502`, re-read there 2026-10-11: * every objectui file this record cites is byte-identical across the hop from * `0df67f237` (`git diff --quiet`), so every anchor held unmoved. At * `0df67f237`, re-read there 2026-10-11: @@ -9745,6 +9845,9 @@ export const ObjectTimelinePropsSchema = lazySchema(() => strictObject({ * first publishes a refusal for a key the renderer honours. * * Read points at the pin this repo builds against (`.objectui-sha` = + * `e5311165a`, re-read there 2026-10-12: + * every objectui file this record cites is byte-identical across the hop from + * `499799502` (`git diff --quiet`), so every anchor held unmoved. At * `499799502`, re-read there 2026-10-11: * every objectui file this record cites is byte-identical across the hop from * `0df67f237` (`git diff --quiet`), so every anchor held unmoved. At @@ -10111,6 +10214,9 @@ export const ComponentPropsMap = { // set from this map's keys), on the three-part evidence that vocabulary's // string-arm ledger asks of a type admitted without an enum member — all // measured at the pin this repo builds against (`.objectui-sha` = + // `e5311165a`, re-read there 2026-10-12: + // every objectui file this record cites is byte-identical across the hop from + // `499799502` (`git diff --quiet`), so every anchor held unmoved. At // `499799502`, re-read there 2026-10-11: // every objectui file this record cites is byte-identical across the hop from // `0df67f237` (`git diff --quiet`), so every anchor held unmoved. At @@ -10220,6 +10326,9 @@ export const ComponentPropsMap = { // authority for map and gantt while tree's rung-1 `data` read stayed // undeclared on every face. Key sets measured from the renderers' read // points at the pin this repo builds against (`.objectui-sha` = + // `e5311165a`, re-read there 2026-10-12: + // every objectui file this record cites is byte-identical across the hop from + // `499799502` (`git diff --quiet`), so every anchor held unmoved. At // `499799502`, re-read there 2026-10-11: // `ObjectTree.tsx` and `ObjectMap.tsx` are byte-identical to `0df67f237` // (`git diff --quiet`); `ObjectGantt.tsx` (+27/-4) changed for objectui#12082 @@ -10310,6 +10419,9 @@ export const ComponentPropsMap = { // it — so `object-timeline` was unjudged in both directions, a real key and // a typo riding through alike. Key set measured from the renderer's read // points at the pin this repo builds against (`.objectui-sha` = + // `e5311165a`, re-read there 2026-10-12: + // every objectui file this record cites is byte-identical across the hop from + // `499799502` (`git diff --quiet`), so every anchor held unmoved. At // `499799502`, re-read there 2026-10-11: // every objectui file this record cites is byte-identical across the hop from // `0df67f237` (`git diff --quiet`), so every anchor held unmoved. At diff --git a/packages/spec/src/ui/dataset.zod.ts b/packages/spec/src/ui/dataset.zod.ts index 72928729c88..d078c001db4 100644 --- a/packages/spec/src/ui/dataset.zod.ts +++ b/packages/spec/src/ui/dataset.zod.ts @@ -283,6 +283,9 @@ export const DatasetMeasureSchema = lazySchema(() => strictObject({ * and this docblock and the `describe` beneath it both said so. * * Measured at the pin this repo builds against (`.objectui-sha` = + * `e5311165a`, re-read there 2026-10-12: + * every objectui file this record cites is byte-identical across the hop from + * `499799502` (`git diff --quiet`), so every anchor held unmoved. At * `499799502`, re-read there 2026-10-11: * every objectui file this record cites is byte-identical across the hop from * `0df67f237` (`git diff --quiet`), so every anchor held unmoved. At diff --git a/packages/spec/src/ui/view.zod.ts b/packages/spec/src/ui/view.zod.ts index 2fe8f9026c3..93efd15707f 100644 --- a/packages/spec/src/ui/view.zod.ts +++ b/packages/spec/src/ui/view.zod.ts @@ -3429,7 +3429,18 @@ const FormFieldBaseSchema = lazySchema(() => { * inside the `53ded82bf7...87af769e9` range, so the widest-tier-only * under-span this block used to record (#17328: one cell of two at * 720px) no longer reproduces at the pin this repo builds against - * (`.objectui-sha` = `499799502`, re-read there 2026-10-11: + * (`.objectui-sha` = `e5311165a`, re-read there 2026-10-12: + * `autoLayout.ts` and `fields`' `field-type-alias.ts` are byte-identical to + * `499799502` (`git diff --quiet`), so `resolveColSpan` `:154`, + * `WIDE_FIELD_TYPES` `:58-69` and the `repeater` -> `field:grid` mapping held + * unmoved, and `form.tsx` changed on this hop — 228 insertions, 79 deletions: + * objectui#12110 (objectui `5330038f4`), objectui#12155 (objectui `f3d540814`) + * and objectui#12130 (objectui `d2500d8be`), which settle the widget a field + * hint renders, the props a built-in branch hands its control, and a field's + * `step` and `className` — none of it inside `spanLadderFor`, which MOVED + * `:274-301` -> `:275-302` byte-identical under one added import line, and its + * one call site MOVED `:3258` -> `:3326` with the call byte-identical, so it + * still emits the ladder. At `499799502`, re-read there 2026-10-11: * `autoLayout.ts` and `fields`' `field-type-alias.ts` are byte-identical to * `0df67f237` (`git diff --quiet`), so `resolveColSpan` `:154`, * `WIDE_FIELD_TYPES` `:58-69` and the `repeater` -> `field:grid` mapping held @@ -3532,7 +3543,7 @@ const FormFieldBaseSchema = lazySchema(() => { * had changed only in its registration's input list, objectui#9910's * `children` slot; at `62597c588` it was byte-identical to `87af769e9`). */ - span: z.enum(['auto', 'full']).default('auto').describe("Relative field width. 'auto' (default — omit it): the renderer sizes the field from its widget type × the current column count — at the pin this repo builds against (`.objectui-sha` = `4997995022f8`), only textarea, markdown, html, richtext and repeater resolve to the full column count (repeater reaches it through the wide `field:grid` widget it maps to). 'full': resolves to the form grid's full column count. How far down the container-query tiers that span is emitted is the renderer's, not this key's: at that same pin the renderer emits one clamped col-span class per multi-column tier (`@md:col-span-2 @2xl:col-span-3` for a 3-column grid), so the field takes the whole row at every multi-column tier, not just the widest."), + span: z.enum(['auto', 'full']).default('auto').describe("Relative field width. 'auto' (default — omit it): the renderer sizes the field from its widget type × the current column count — at the pin this repo builds against (`.objectui-sha` = `e5311165a8d6`), only textarea, markdown, html, richtext and repeater resolve to the full column count (repeater reaches it through the wide `field:grid` widget it maps to). 'full': resolves to the form grid's full column count. How far down the container-query tiers that span is emitted is the renderer's, not this key's: at that same pin the renderer emits one clamped col-span class per multi-column tier (`@md:col-span-2 @2xl:col-span-3` for a 3-column grid), so the field takes the whole row at every multi-column tier, not just the widest."), /** Custom widget override — only needed when auto-inference is insufficient */ widget: z.string().optional().describe('Custom widget/component name (overrides type-based inference)'), From 2e15086180de818b5db93c7cfeaf5091c8dd8990 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 12 Oct 2026 03:42:13 +0000 Subject: [PATCH 14/15] chore(objectui): the console changeset, the regenerated SDUI manifest and the re-recorded lockstep for e5311165a8d6 - .changeset/console-e5311165a8d6.md: derived by scripts/bump-objectui.sh from the changesets objectui declared over 4997995022f8..e5311165a8d6 (21 releasing, 4 breaking by annotation), with its ADR-0087 disposition answered: not-required (no-migration-prescription), each declared-breaking objectui change answered in turn. - sdui.manifest.json + scripts/sdui-manifest.record.json: regenerated by scripts/gen-sdui-manifest-node.mjs from the built tree at the pin (107 components; one input description changed). - packages/sdui-parser/objectui-lockstep.json: re-recorded at the pin; the grammar region, the 25 diagnostic codes and the containment predicate are unchanged. Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude --- .changeset/console-e5311165a8d6.md | 72 +++++++++++++++++++++ packages/sdui-parser/objectui-lockstep.json | 6 +- scripts/sdui-manifest.record.json | 8 +-- sdui.manifest.json | 2 +- 4 files changed, 80 insertions(+), 8 deletions(-) create mode 100644 .changeset/console-e5311165a8d6.md diff --git a/.changeset/console-e5311165a8d6.md b/.changeset/console-e5311165a8d6.md new file mode 100644 index 00000000000..b79efab1493 --- /dev/null +++ b/.changeset/console-e5311165a8d6.md @@ -0,0 +1,72 @@ +--- +"@objectstack/console": minor +--- + +Console (objectui) refreshed to `e5311165a8d6`. Frontend changes in this range: + +Derived from the changesets objectui declared over the range — 21 releasing of 23 changesets added across 24 non-merge commits; omitted: 2 release-nothing changesets, 1 commit carrying no changeset (they ship no package code). + +- **minor** — **BREAKING** — Studio and the metadata admin read a metadata type's overlay flag under its new name, `allowEnvironmentOverlay`, and the read-only banners name `OS_METADATA_WRITABLE` (objectui#12… (objectui `e5311165a`) +- **minor** — The marketplace package page's **Update** button opens an update dialog, and a package README's raw HTML is no longer printed as text (objectui#12146). (objectui `82bb1d584`) +- **minor** — **BREAKING** — A form field declares `step` and `className` on both faces, so the strict authoring face stops telling authors to remove two keys that work. A built-in form branch now joins an en… (objectui `d2500d8be`) +- **minor** — "Clone to customize" on a packaged permission set calls the server's clone door (objectui#7611, objectstack ADR-0126 §7.1). (objectui `6d5879fe9`) +- **minor** — **A dashboard or a list view the server refuses you, opened by its URL, now says it was refused** (objectui#12112, the console half of objectstack#22611's ruling A). (objectui `43e150518`) +- **minor** — Setup's "Assigned users" on a permission set writes a new grant by the set's name alone (objectui#7611, after objectstack#22863, ADR-0131 stage 6b-2-pre). (objectui `faf4ce952`) +- **minor** — **BREAKING** — A form field declares `avatarField`, the record field each candidate of the search picker draws its avatar from, on both faces, so the strict authoring face stops telling authors… (objectui `c0c2f792b`) +- **minor** — A `position` decision output lists the position registry and commits position names (objectui#7611, objectstack ADR-0131 D3/D4). (objectui `8cc7158b4`) +- **minor** — The capability select lists the capability registry, and the permission-set editor's provenance badge reads the layered envelope (objectui#7611, objectstack ADR-0131 D3/D7). (objectui `53ee2f854`) +- **minor** — **BREAKING** — A form field declares the rest of the field metadata its widget reads off the `fields[]` entry, on both faces, so the strict authoring face stops telling authors to remove keys th… (objectui `0665c3be7`) +- **minor** — Screen readers hear the names of more controls in the viewer's language (objectui#12145). (objectui `e723f58ee`) +- **minor** — The suggested-bindings panel shows each suggestion's real state and can revoke an acceptance (objectui#7611, after objectstack#22854). (objectui `f962cdc09`) +- **minor** — The record feed and a related list's Add existing and fallback remove read the affordance-to-grant map, so a caller without the grant is no longer offered a write the server then… (objectui `158c75d90`) +- **minor** — A paged table's first, previous, next and last page buttons have accessible names (objectui#12124). (objectui `dc6d28e86`) +- **patch** — The Studio app's root now opens the app's first navigation entry, like every other app's root, and `StudioHomePage`, Studio's old metadata overview, is retired (objectui#12113). (objectui `ed12d48ec`) +- **patch** — A form field's own `defaultValue` is no longer drawn in its control, so the form never shows a value it does not save (objectui#12155). (objectui `f3d540814`) +- **patch** — A grouped grid's own first, previous, next and last page buttons have accessible names (objectui#12143). (objectui `2381c07f3`) +- **patch** — Console rendering fixes from the HotCRM browser pass (objectui#12110): (objectui `5330038f4`) +- **patch** — The record page's discussion shows stored replies under their comment, and every comment offers the reply input where the caller may post one (objectui#12144). (objectui `a7d492011`) +- **patch** — A record activity feed shows an object's `activityMilestones` rows of type `completed`, such as "Case resolved", instead of hiding them as completed tasks (objectui#12106). (objectui `1d1cfe0c1`) +- **patch** — A record-source refusal (`RECORD_SOURCE_REQUIRED`, raised by every arm that requires a record source) now reports the same path every time (objectui#5250). (objectui `9b7550999`) + +⚠️ 4 of these carry a breaking change: 4 by the author's own breaking annotation in the changeset body — objectui declares no `major` inside a launch window (`scripts/check-changeset-no-major.mjs`). Each is marked **BREAKING** in the list above — read them before compiling the release record. + +**In this console build, declared nowhere** — objectui merged 1 commit in this range with no `.changeset/*.md`. The code is inside the pin above and ships here, but nothing upstream declared it, so it appears in no objectui CHANGELOG and in no entry above. Listed by subject rather than counted, because a count cannot tell a dependency bump from a form-behaviour change (objectstack#6174); the upstream gate that would prevent this is objectui#3387. + +- _(no changeset)_ feat(scripts): the JSON-fence gate reads the published READMEs (objectui#12131, phase 2) (#12139) (objectui `ce9811c96`) + + + +objectui range: `4997995022f8...e5311165a8d6` diff --git a/packages/sdui-parser/objectui-lockstep.json b/packages/sdui-parser/objectui-lockstep.json index 9ff6726ea5b..b61677b2f45 100644 --- a/packages/sdui-parser/objectui-lockstep.json +++ b/packages/sdui-parser/objectui-lockstep.json @@ -6,8 +6,8 @@ ], "objectui": { "repo": "https://github.com/objectstack-ai/objectui.git", - "rev": "4997995022f8e65946f6362b7434685539cc72b7", - "revDate": "2026-10-11T10:56:33+00:00", + "rev": "e5311165a8d6aa77b5cb7d9fd05da8451a2c21aa", + "revDate": "2026-10-12T01:48:21+00:00", "source": "packages/sdui-parser/src", "files": [ "packages/sdui-parser/src/body-dialect.ts", @@ -21,7 +21,7 @@ "packages/sdui-parser/src/validate.ts" ] }, - "recordedAgainstPin": "4997995022f8e65946f6362b7434685539cc72b7", + "recordedAgainstPin": "e5311165a8d6aa77b5cb7d9fd05da8451a2c21aa", "grammarRegion": { "file": "packages/sdui-parser/src/parse.ts", "delimiter": "/* ---------------------- the JS literal subset (#6614) ---------------------- */", diff --git a/scripts/sdui-manifest.record.json b/scripts/sdui-manifest.record.json index 9089e121ea0..55a009ef5ad 100644 --- a/scripts/sdui-manifest.record.json +++ b/scripts/sdui-manifest.record.json @@ -11,12 +11,12 @@ "a pin bump without regeneration goes RED there — that is the anti-rot half of the freshness gate.", "Regenerate + re-record: pnpm objectui:build && node scripts/gen-sdui-manifest-node.mjs" ], - "objectuiSha": "4997995022f8e65946f6362b7434685539cc72b7", + "objectuiSha": "e5311165a8d6aa77b5cb7d9fd05da8451a2c21aa", "source": "built-tree", - "modulesRoot": ".cache/objectui-4997995022f8/apps/console", + "modulesRoot": ".cache/objectui-e5311165a8d6/apps/console", "objectuiWorkspaceVersion": "17.7.0", "generator": "scripts/gen-sdui-manifest-node.mjs", - "generatedAt": "2026-10-11", - "sha256": "9d87162a3ef60676c6aed69ecf2703693bdbbf66d5ef798c9d770f3112f86217", + "generatedAt": "2026-10-12", + "sha256": "a799c55a18d6854faaffbf3eee8a959bf142e12109cee62890de46a327fe4db4", "components": 107 } diff --git a/sdui.manifest.json b/sdui.manifest.json index a335e13646f..10fefd454a1 100644 --- a/sdui.manifest.json +++ b/sdui.manifest.json @@ -1712,7 +1712,7 @@ { "name": "showCompleted", "type": "boolean", - "description": "Include completed activities (sys_activity type \"completed\", which surfaces as a task item). Off by default." + "description": "Include completed activities (sys_activity type \"completed\", which surfaces as a task item). Off by default. A milestone row (metadata.kind \"milestone\") is not a completed activity and shows either way." }, { "name": "unifiedTimeline", From 49ef13a518dd486eeef521971583f109f098f590 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 12 Oct 2026 03:43:48 +0000 Subject: [PATCH 15/15] =?UTF-8?q?regen:=20content/docs/references/ui/view.?= =?UTF-8?q?mdx=20=E2=80=94=20the=20FormField.span=20describe=20names=20the?= =?UTF-8?q?=20pin=20e5311165a8d6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude --- content/docs/references/ui/view.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/docs/references/ui/view.mdx b/content/docs/references/ui/view.mdx index 11812a8b1a2..0e9d89ff351 100644 --- a/content/docs/references/ui/view.mdx +++ b/content/docs/references/ui/view.mdx @@ -213,7 +213,7 @@ Column footer summary configuration | **required** | `boolean` | optional | Required override | | **hidden** | `boolean` | optional | Hidden override | | **colSpan** | `integer` | optional | Absolute column span (1-4). The renderer clamps it to the form grid's current column count, so the cell starts at a real column boundary at every surface width and never overflows (`colSpan: 4` in a 3-column grid renders as 3); a `colSpan` within the column count renders as authored, and `colSpan: 1` emits no span class at all. | -| **span** | `Enum<'auto' \| 'full'>` | optional (default: `"auto"`) | Relative field width. 'auto' (default — omit it): the renderer sizes the field from its widget type × the current column count — at the pin this repo builds against (`.objectui-sha` = `4997995022f8`), only textarea, markdown, html, richtext and repeater resolve to the full column count (repeater reaches it through the wide `field:grid` widget it maps to). 'full': resolves to the form grid's full column count. How far down the container-query tiers that span is emitted is the renderer's, not this key's: at that same pin the renderer emits one clamped col-span class per multi-column tier (`@md:col-span-2 @2xl:col-span-3` for a 3-column grid), so the field takes the whole row at every multi-column tier, not just the widest. | +| **span** | `Enum<'auto' \| 'full'>` | optional (default: `"auto"`) | Relative field width. 'auto' (default — omit it): the renderer sizes the field from its widget type × the current column count — at the pin this repo builds against (`.objectui-sha` = `e5311165a8d6`), only textarea, markdown, html, richtext and repeater resolve to the full column count (repeater reaches it through the wide `field:grid` widget it maps to). 'full': resolves to the form grid's full column count. How far down the container-query tiers that span is emitted is the renderer's, not this key's: at that same pin the renderer emits one clamped col-span class per multi-column tier (`@md:col-span-2 @2xl:col-span-3` for a 3-column grid), so the field takes the whole row at every multi-column tier, not just the widest. | | **widget** | `string` | optional | Custom widget/component name (overrides type-based inference) | | **language** | `string` | optional | Code editor language (for type=code) | | **keyField** | `{ field?: string; label?: string \| Record; placeholder?: string \| Record; helpText?: string \| Record; … }` | optional | Key column config for record-typed fields | @@ -343,7 +343,7 @@ Form-view select option — the object-field option shape minus the per-option ` | **required** | `boolean` | optional | Required override | | **hidden** | `boolean` | optional | Hidden override | | **colSpan** | `integer` | optional | Absolute column span (1-4). The renderer clamps it to the form grid's current column count, so the cell starts at a real column boundary at every surface width and never overflows (`colSpan: 4` in a 3-column grid renders as 3); a `colSpan` within the column count renders as authored, and `colSpan: 1` emits no span class at all. | -| **span** | `Enum<'auto' \| 'full'>` | optional (default: `"auto"`) | Relative field width. 'auto' (default — omit it): the renderer sizes the field from its widget type × the current column count — at the pin this repo builds against (`.objectui-sha` = `4997995022f8`), only textarea, markdown, html, richtext and repeater resolve to the full column count (repeater reaches it through the wide `field:grid` widget it maps to). 'full': resolves to the form grid's full column count. How far down the container-query tiers that span is emitted is the renderer's, not this key's: at that same pin the renderer emits one clamped col-span class per multi-column tier (`@md:col-span-2 @2xl:col-span-3` for a 3-column grid), so the field takes the whole row at every multi-column tier, not just the widest. | +| **span** | `Enum<'auto' \| 'full'>` | optional (default: `"auto"`) | Relative field width. 'auto' (default — omit it): the renderer sizes the field from its widget type × the current column count — at the pin this repo builds against (`.objectui-sha` = `e5311165a8d6`), only textarea, markdown, html, richtext and repeater resolve to the full column count (repeater reaches it through the wide `field:grid` widget it maps to). 'full': resolves to the form grid's full column count. How far down the container-query tiers that span is emitted is the renderer's, not this key's: at that same pin the renderer emits one clamped col-span class per multi-column tier (`@md:col-span-2 @2xl:col-span-3` for a 3-column grid), so the field takes the whole row at every multi-column tier, not just the widest. | | **widget** | `string` | optional | Custom widget/component name (overrides type-based inference) | | **language** | `string` | optional | Code editor language (for type=code) | | **keyField** | `{ field?: string; label?: string \| Record; placeholder?: string \| Record; helpText?: string \| Record; … }` | optional | Key column config for record-typed fields |