diff --git a/.changeset/namespace-retirement-now-cited.md b/.changeset/namespace-retirement-now-cited.md new file mode 100644 index 0000000000..0483dfd8ff --- /dev/null +++ b/.changeset/namespace-retirement-now-cited.md @@ -0,0 +1,5 @@ +--- +'@objectstack/spec': patch +--- + +The `namespace` rejection messages now cite the decision record that actually exists: ADR-0129 D3 (the enforced object-naming contract — `name` is the canonical id, module prefix embedded literally, no separate namespace declaration). The object tombstone's citation, removed when its previous spelling pointed at a decision letter no ADR declared, comes back pointing at the real record; the translation-contract rejection re-points the same way. The retirement itself is unchanged and still enforced. diff --git a/docs/adr/0028-metadata-naming-and-namespace-isolation.md b/docs/adr/0028-metadata-naming-and-namespace-isolation.md index 93dc1817fd..48c2ef5190 100644 --- a/docs/adr/0028-metadata-naming-and-namespace-isolation.md +++ b/docs/adr/0028-metadata-naming-and-namespace-isolation.md @@ -2,7 +2,7 @@ **Status**: Deferred (2026-07-16; proposed 2026-06-01) — the target model (namespace as identity dimension, short authored names, derived physical names, `namingMode` dual-read, `sys` reservation, namespaced transport segments) is entirely unbuilt, and adopting it now would be a platform-wide migration (physical table derivation, transport routes, dual-read compatibility) with no present pain to justify it: the literal-prefix current-state contract (`spec/kernel/namespace-prefix.ts`) is enforced, collision-proof (ADR-0028/0048), and working. **Re-open trigger**: real multi-package ecosystem naming pain — e.g. a marketplace package whose natural short names are unusable under literal prefixing, or cross-environment portability requiring namespace remapping. Until then this stays a recorded design, not planned work. **Deciders**: ObjectStack Protocol Architects -**Supersedes**: the hand-written object-namespace-prefix authoring rule documented in `packages/spec/src/kernel/manifest.zod.ts` (the `namespace` field) and enforced by `validateNamespacePrefix()` in `packages/spec/src/stack.zod.ts` — there is no standalone ADR for that rule today. +**Supersedes**: the hand-written object-namespace-prefix authoring rule documented in `packages/spec/src/kernel/manifest.zod.ts` (the `namespace` field) and enforced by `validateNamespacePrefix()` in `packages/spec/src/stack.zod.ts` — that rule's standalone record is [ADR-0129](./0129-object-name-is-the-canonical-id.md) (2026-08-29; it governs until this ADR's re-open trigger fires). **Builds on**: [ADR-0005](./0005-metadata-customization-overlay.md) (one Zod source per type, org overlay), [ADR-0008](./0008-metadata-repository-and-change-log.md) (Repository · ChangeLog · Cache · Registry; `MetaRef = org/type/name`), [ADR-0010](./0010-metadata-protection-model.md) (protection model), [ADR-0019](./0019-app-as-consumer-unit.md) (app as the consumer-installable unit), [ADR-0025](./0025-plugin-package-distribution.md) (package distribution), [ADR-0029](./0029-kernel-object-ownership-and-platform-objects-decomposition.md) (**prerequisite** — kernel object ownership; D5/D6 below assume the kernel is properly owned per ADR-0029) **Consumers**: `@objectstack/spec` (manifest + stack validators), `@objectstack/objectql` (`SchemaRegistry`, `StorageNameMapping`, ownership model), `@objectstack/plugins/driver-sql` (physical table derivation), `@objectstack/rest` + `@objectstack/api` (route + generated-surface naming), `@objectstack/services/service-automation` (connector registry), `@objectstack/services/service-ai` (tool registry), `@objectstack/platform-objects` (kernel object ownership), `@objectstack/cli` (`os validate`) diff --git a/docs/adr/0067-commit-history-and-rollback-for-ai-authoring.md b/docs/adr/0067-commit-history-and-rollback-for-ai-authoring.md index b19176ddc5..5afae61270 100644 --- a/docs/adr/0067-commit-history-and-rollback-for-ai-authoring.md +++ b/docs/adr/0067-commit-history-and-rollback-for-ai-authoring.md @@ -1,6 +1,6 @@ # ADR-0067: Commit history and rollback for AI authoring — turns become atomic, revertible commits -**Status**: Accepted (2026-06-24; completed 2026-07-16) · **Amended** (2026-08-06, #5351/#5696 — the D2 join is now distinguishable (`owned`), and Decision-2's atomicity is enforced as a ONE-datasource promise, with audit rows carved out and possibly orphaned; see the Amendment at the end) — fully implemented: commit grouping (`sys_metadata_commit`), `revertCommit`/`rollbackToPackageCommit`/`listCommits`, REST routes; **Decision-2 landed via #3066**: `publishPackageDrafts` runs every promotion + the commit record inside ONE `engine.transaction()` (two-phase — side effects post-commit), so a commit cannot half-land; `engine.transaction()` joins ambient transactions to make nested repository writes participate. Locked by `protocol-publish-package-drafts.test.ts` (all-or-nothing + rollback tracking) and `engine-ambient-transaction.test.ts`. +**Status**: Accepted (2026-06-24; completed 2026-07-16) · **Amended** (2026-08-06, #5351/#5696 — the D2 join is now distinguishable (`owned`), and Decision-2's atomicity is enforced as a ONE-datasource promise, with audit rows carved out and possibly orphaned; see the Amendment at the end) — fully implemented: commit grouping (`sys_metadata_commit`), `revertCommit`/`rollbackToPackageCommit`/`listCommits`, REST routes; **Decision-2 landed via #3066**: `publishPackageDrafts` runs every promotion + the commit record inside ONE `engine.transaction()` (two-phase — side effects post-commit), so a commit cannot half-land; `engine.transaction()` joins ambient transactions to make nested repository writes participate. Locked by `protocol-publish-package-drafts.test.ts` (all-or-nothing + rollback tracking) and `engine-ambient-transaction.test.ts`. **Letter index** (2026-08-29, #12786): the Decision headings are relabeled `1.`–`6.` → `D1`–`D6` so `Dk` citations verify — recognition of the record's own numbering (this status line and the Amendment already say "D2"); no decision is renumbered, reordered, or rewritten. **Deciders**: ObjectStack Protocol Architects **Builds on / amends**: [ADR-0045](./0045-additive-materialization-and-visibility-gate.md) (**amended**: ADR-0045 keeps a *draft + human-confirm* gate on mutations as the safety mechanism; this ADR replaces *confirm-before* with *revert-after* for everything except irreversible data loss, and unifies the two authoring regimes under one primitive — the commit), [ADR-0027](./0027-metadata-authoring-lifecycle.md) (draft workspace — retained as a *review affordance*, demoted from *safety mechanism*), [ADR-0033](./0033-ai-assisted-metadata-authoring.md) ("AI never publishes — it drafts" → **AI commits; commits are revertible**), [ADR-0034](./0034-transactional-writes-and-ambient-transaction.md) (per-write transaction — **extended to span a whole turn**), [ADR-0038](./0038-build-verification-loop.md) (machine gate — runs per commit, before it lands) **Consumers**: `@objectstack/objectql` (commit grouping, atomic turn-apply, `revertCommit`, history query — built on the existing `sys_metadata_history` + `restoreVersion`), `@objectstack/runtime` + `@objectstack/rest` (commit/revert routes), `../cloud/service-ai-studio` (turn = commit; auto-commit policy; data-loss confirmation), `../objectui` (commit timeline + "revert to here") @@ -64,7 +64,7 @@ ADR-0045's own "Consequences/Costs" lists the two regimes ("materialize vs. draf ## Decision -### 1. A turn is a commit +### D1 — A turn is a commit A **commit** is the atomic set of metadata events produced by one apply/turn, identified by a `commit_id` and recorded as a group over `sys_metadata_history`. Minimal mechanism: a `commit_id` column on the history event (plus a thin `sys_metadata_commit` row carrying `{ commit_id, package_id, organization_id, message, actor, ai_model?, event_seq_range, parent_commit_id }`). No new snapshot store — the bodies are already in `sys_metadata_history.metadata`. @@ -72,19 +72,19 @@ The commit's **message is the user's prompt**; its **actor** is the AI principal The server owns commit assembly — the model never constructs it. `apply_blueprint` and the per-item authoring tools (`add_field`, `create_metadata`, …) run inside a turn context that opens a commit, writes through it, and seals it. -### 2. Commits are atomic +### D2 — Commits are atomic All metadata writes in a turn execute inside **one `engine.transaction()`**; any failure rolls the whole commit back — no half-built app, no orphan rows, no partial visibility flip. This replaces `publishPackageDrafts`'s per-item best-effort loop with a turn-spanning transaction (the per-item `withTxn` composes into it). The visibility flip (ADR-0045 `hidden:false`) is part of the same transaction — never "some apps visible, some not." *Driver caveat*: in-memory drivers have no real transaction (`withTxn` no-ops); atomicity is a SQL-driver guarantee. Production is SQL; document the gap, don't paper over it. -### 3. Rollback is first-class and append-only +### D3 — Rollback is first-class and append-only - `revertCommit(commitId)` — restore every item the commit touched to its **pre-commit** body, atomically (reuse `restoreVersion` per item inside one transaction). Recorded as a **new forward commit** (`operation_type='revert'`), so history is never rewritten and a revert is itself revertible. - `rollbackToPackageCommit(commitId)` — revert the package's HEAD back through every commit after the target, as one transaction. This is the "一层一层回退" the founder described — **commit-granular, not per-artifact** (§Resolved-1). - Revert restores **metadata** deterministically. Its effect on **data** is governed by §5. -### 4. The confirm gate, relocated +### D4 — The confirm gate, relocated ADR-0045 gated *publish* (a human confirms before anything visible changes). This ADR relocates the gate to the only place revert can't save you: @@ -93,7 +93,7 @@ ADR-0045 gated *publish* (a human confirms before anything visible changes). Thi The ADR-0027 **draft workspace + `?preview=draft` diff review** is retained as a *review affordance* (a reviewer may still preview a pending change), but it is **no longer the safety mechanism** — revertibility is. Drafts stop being mandatory for mutations in the default path. -### 5. Metadata reverts cleanly; data is made reversible (the crux) +### D5 — Metadata reverts cleanly; data is made reversible (the crux) Metadata is declarative and snapshotted, so it reverts exactly. Data does not: reverting "create object *X*" or "drop field *f*" touches real tables and real rows — including rows a user typed in after publish. The rule that keeps revert safe: @@ -103,7 +103,7 @@ Metadata is declarative and snapshotted, so it reverts exactly. Data does not: r - the new object holds **user-entered rows beyond the seed** → revert requires a **typed confirmation that names the impact** ("revert will remove object *X* and its **N** rows, **M** of them entered after publish") **and auto-cuts a `sys_package_version` named restore point first** (the escape hatch — the revert is itself undoable). - This is the precise boundary of the §4 confirm gate: a human confirms **iff** the operation would destroy real user data that revert cannot resurrect. -### 6. Substrate and restore points +### D6 — Substrate and restore points - **Commit log = `sys_metadata_history`** (framework, per-event, reuse). This is the per-turn substrate. No full-bundle copy per turn. - **`sys_package_version` = named restore points** (cloud, full-bundle, heavy), cut only on: the §5 pre-destructive-revert escape hatch; a user-initiated "name a checkpoint" ("before I let the AI go wide"); and marketplace release. *Not* per turn. diff --git a/docs/adr/0129-object-name-is-the-canonical-id.md b/docs/adr/0129-object-name-is-the-canonical-id.md new file mode 100644 index 0000000000..6cd39e98b6 --- /dev/null +++ b/docs/adr/0129-object-name-is-the-canonical-id.md @@ -0,0 +1,112 @@ +# ADR-0129: Object `name` is the canonical id — literal module prefix, no separate `namespace` field + +**Status**: Accepted (2026-08-29; maintainer ruling on #12918, option 甲 — recorded as one +convoy with #12786). This ADR **records an already-enforced, platform-wide decision**; it +changes no shipped behavior. The contract below has been enforced since the parse path was +closed by #4001 and is restated by Prime Directive #6 in `AGENTS.md` — what was missing was +the decision record itself, and that absence is what produced the phantom citation this ADR +closes (see § History). +**Deciders**: ObjectStack maintainer (2026-08-29), recording a contract in force since #4001. +**Relates to**: [ADR-0028](./0028-metadata-naming-and-namespace-isolation.md) (the **Deferred +target model** pointing the opposite way — reconciled in D4 below), +[ADR-0048](./0048-cross-package-metadata-collision.md) (cross-package collision-proofing of +the literal prefix), ADR-0006 (the record the phantom letter was mis-attributed to — the +project/environment split, which never decided object naming), #4001 (parse-path closure), +#4522 (tombstone), #12913 / #12917 / #12918 (the 2026-08-28 incident and its remediation). +**Consumers**: `@objectstack/spec` (`data/object.zod.ts`, `stack.zod.ts`, +`kernel/namespace-prefix.ts`, `system/translation.zod.ts`), `@objectstack/objectql` +(`SchemaRegistry`, `StorageNameMapping`), every SQL driver, `@objectstack/rest`, the client +SDKs, and every metadata author — human or AI. + +--- + +## TL;DR + +The object `name` is the **only** identity an object has, everywhere. The module prefix is +part of the name the author writes (`sys_user`, `crm_account`), validated against the +package's declared namespace. There is **no** separate `namespace` field on the object +document — the key is retired and **rejected on the parse path** with a prescription. This +page exists because the decision was enforced for months with no ADR recording it, and an +unwritten decision manufactures phantom citations (§ History). + +--- + +## Decision + +### D1 — The object `name` IS the canonical id everywhere + +One string is the object's identity across every surface: the API, ObjectQL, REST routes, +the client SDKs, and the **database table name**. There is no logical/physical split and no +parallel identity dimension: `StorageNameMapping.resolveTableName` is a pass-through +(`packages/spec/src/system/constants/system-names.ts`; every SQL driver routes through it), +and the object document carries no `tableName` field — the table name always equals `name` +(pinned by `packages/spec/src/data/object.test.ts`, "name-as-identity"). + +### D2 — The module prefix is embedded literally in the authored name + +Authors write the full literal name — `sys_user`, `crm_account` — never a short name plus a +namespace. A package declares its prefix once (`manifest.namespace`), and every object it +defines MUST be named `${namespace}_${shortName}` (platform-reserved `sys_*` names exempt). +Enforced at **both** chokepoints from one shared rule +(`packages/spec/src/kernel/namespace-prefix.ts` — the single source, so the two cannot +drift): + +- compile time — `validateNamespacePrefix()` in `packages/spec/src/stack.zod.ts` + (`defineStack()` / `os validate`); +- runtime — `MetadataManager.publishPackage()` (Studio publish). + +The literal prefix is collision-proof across installed packages (ADR-0048) and gives the AI +author exactly one writing style — the property the rule exists for. + +### D3 — There is no separate `namespace` field; the key is retired and rejected loudly + +The object document has **no** `namespace` key. It is retired with a tombstone: the strict +`ObjectSchema` rejects the key on parse, and the rejection message carries the fix +(`UNKNOWN_KEY_GUIDANCE.namespace` in `packages/spec/src/data/object.zod.ts` — +`namespace: "sys", name: "user"` becomes `name: "sys_user"`). Until #4001 closed the parse +path the key was **stripped in silence**, so an object declaring one shipped under the +unprefixed name its author never intended — the rejection is the fix for that failure mode, +not a style preference. The retirement is platform-wide: the translation contract rejects +the key the same way (`packages/spec/src/system/translation.zod.ts`), and Prime Directive #6 +in `AGENTS.md` states the authoring rule for every agent working this repo. + +### D4 — Reconciliation with ADR-0028: this is the governing record until 0028's re-open trigger fires + +[ADR-0028](./0028-metadata-naming-and-namespace-isolation.md) records the **opposite** +model — namespace as an identity dimension, short authored names, derived physical names +(its D1–D6). That model is a **Deferred target design, entirely unbuilt**, by 0028's own +status line, which also records the literal-prefix current state as "enforced, +collision-proof (ADR-0028/0048), and working" and names a re-open trigger (real +multi-package ecosystem naming pain). The two records do not conflict: 0028 is a deferred +design, this ADR is the enforced present. **Until 0028's recorded re-open trigger fires, +this ADR is the governing record of object naming.** If 0028 is ever adopted, the adopting +work must supersede this record explicitly (Prime Directive #13) — never drift past it. + +--- + +## History — why this record exists + +The contract above was enforced with **no ADR recording it**. When #4522 added the +tombstone's rejection message, its author needed a provenance for a real, enforced decision, +found none, and minted one: a phantom decision letter — spelled "D4" — attributed to +ADR-0006, a record that never decided object naming and declares no such letter. The +citation survived for months because the ADR-0006 family declared no letters at all, so +`check:adr-anchors` had nothing to contradict — until #12736 minted D1–D3 for ADR-0006's +API-surface addendum (2026-08-28), at which point the phantom became mechanically +unresolvable and every merge-queue build went red (#12913; emergency one-string fix +#12917). #12918 recorded the root cause: **the decision was real, enforced, and unwritten** +— and the gate's own remedy text says what to do about that: "if the decision is real but +unwritten, it needs an ADR, not a citation." This is that ADR. The surviving spellings +re-point here (the letter to cite for the retirement is **D3**); `packages/spec/CHANGELOG.md` +keeps its historical copies untouched. + +## Consequences + +- The retirement finally has a citable record: rejection messages, tests, and code comments + cite **ADR-0129 D3** instead of a phantom, and `check:adr-anchors` can verify them. +- The next author (or AI) who asks "why can't `namespace` come back?" finds a decision, not + an enforcement with no paper trail — the failure mode that produced the phantom does not + re-arm. +- ADR-0028's Supersedes-line note ("there is no standalone ADR for that rule today") is + corrected by this record's existence; nothing else in 0028 changes, and its Deferred + status and re-open trigger stand exactly as written. diff --git a/packages/objectql/src/engine.ts b/packages/objectql/src/engine.ts index 31f7c1735a..6582ed615e 100644 --- a/packages/objectql/src/engine.ts +++ b/packages/objectql/src/engine.ts @@ -3404,7 +3404,8 @@ export class ObjectQL implements IObjectQLEngine { // Propagate the service-principal label (`ExecutionContext.actor`, // e.g. `svc:flow:`) so a non-user write stays attributable in the // audit log — the writer's `userId ?? session.actor` fallback is dead - // without this hop (ADR-0014 D2, #4366). + // without this hop (the `ExecutionContext.actor` audit-attribution + // contract, `@objectstack/spec` kernel/execution-context.zod.ts; #4366). ...(typeof (execCtx as any).actor === 'string' && (execCtx as any).actor ? { actor: (execCtx as any).actor } : {}), @@ -11166,8 +11167,9 @@ export class ObjectQL implements IObjectQLEngine { ): void { // The triggered-by half. `userId` survives the `sudo()`-shaped elevation, // which is what makes the two halves recordable at all; `actor` is the - // service-principal channel (ADR-0014 D2) that keeps a non-user- - // authenticated caller attributable instead of anonymous. + // service-principal channel (the `ExecutionContext.actor` contract, + // `@objectstack/spec` kernel/execution-context.zod.ts) that keeps a + // non-user-authenticated caller attributable instead of anonymous. const triggeredBy = (context?.userId != null && String(context.userId)) || (typeof (context as any)?.actor === 'string' && (context as any).actor.trim()) || diff --git a/packages/platform-objects/src/identity/sys-member.object.ts b/packages/platform-objects/src/identity/sys-member.object.ts index 5dfe73977a..ce4fe06c04 100644 --- a/packages/platform-objects/src/identity/sys-member.object.ts +++ b/packages/platform-objects/src/identity/sys-member.object.ts @@ -71,7 +71,8 @@ export const SysMember = ObjectSchema.create({ type: 'api', target: '/api/v1/auth/organization/invite-member', // Same gate as the other two mirrors — the org CAPABILITY, not - // multi-org (ADR-0081 D1). + // multi-org (ADR-0093 D9: single-org sessions carry an active org + // via plugin-auth's default-org bootstrap). requiresFeature: 'organization', successMessage: 'Invitation sent', refreshAfter: true, @@ -122,7 +123,7 @@ export const SysMember = ObjectSchema.create({ locations: ['list_toolbar'], type: 'api', target: '/api/v1/auth/organization/add-member', - // Gated on the org CAPABILITY, not multi-org (ADR-0081 D1): the + // Gated on the org CAPABILITY, not multi-org (ADR-0093 D9): the // better-auth endpoints resolve the session's active org, which // single-org mode now guarantees via plugin-auth's default-org // bootstrap. Same gate on every membership mutation below. @@ -254,7 +255,7 @@ export const SysMember = ObjectSchema.create({ // `deleteBehavior:'cascade'` is a suggestion, not an audit: nothing // depends on the restrict. In particular it is NOT an accidental // last-administrator guard — that invariant is enforced by a `beforeDelete` - // hook registered on `sys_member` itself (ADR-0024 D5.2, + // hook registered on `sys_member` itself (cloud ADR-0024 D5.2, // `last-admin-guard.ts`), and the engine's cascade recurses through the // PUBLIC `delete()` precisely so the child's own hooks and events fire. // The guard therefore still refuses a cascade that would take the last diff --git a/packages/plugins/plugin-auth/src/auth-manager.ts b/packages/plugins/plugin-auth/src/auth-manager.ts index 299bc028a3..7a1bd7bccf 100644 --- a/packages/plugins/plugin-auth/src/auth-manager.ts +++ b/packages/plugins/plugin-auth/src/auth-manager.ts @@ -5821,7 +5821,7 @@ export class AuthManager { } /** - * Maintain `sys_user.source` (ADR-0024 D4 provenance) as accounts are linked. + * Maintain `sys_user.source` (cloud ADR-0024 D4 provenance) as accounts are linked. * Drives the managed-vs-native user-mgmt gating: a managed (`idp-provisioned`) * user holds no local credential, so the password / identity-edit actions * hide for them — preventing a managed user from self-minting a local diff --git a/packages/plugins/plugin-auth/src/auth-plugin.ts b/packages/plugins/plugin-auth/src/auth-plugin.ts index f3fdcd5822..4e777dd336 100644 --- a/packages/plugins/plugin-auth/src/auth-plugin.ts +++ b/packages/plugins/plugin-auth/src/auth-plugin.ts @@ -174,7 +174,9 @@ export interface AuthPluginOptions extends Partial { */ /** - * ADR-0081 D1 — single-org default-organization bootstrap. In single-org + * The single-org default-organization bootstrap (a pre-repo decision whose + * old label collides with this repo's ADR series — see the citation note + * under ADR-0093 D9; the membership guarantee it backs is D9). In single-org * mode (`OS_MULTI_ORG_ENABLED` unset/false) nothing else ever creates an * organization, so sessions carry no `activeOrganizationId` and better-auth * `organization/invite-member` has no org to resolve — i.e. no way to add a @@ -1020,7 +1022,8 @@ export class AuthPlugin implements Plugin { } }); - // ADR-0081 D1 — single-org default-organization bootstrap. Every WALLED + // The single-org default-organization bootstrap (pre-repo decision; label + // collision documented under ADR-0093 D9). Every WALLED // posture (`group` and `isolated`) keeps its existing owner: the enterprise // organizations package, which runs the same idempotent helper with the // seed-ownership step injected. One crisp owner per posture, and the open @@ -1134,7 +1137,7 @@ export class AuthPlugin implements Plugin { // level, which BYPASSES `account.create.after` / `stampIdentitySource`. This // ObjectQL `afterInsert` hook stamps `source=idp_provisioned` regardless of // the creation path, so SCIM-provisioned users are correctly marked as the - // managed mirror (ADR-0024 D4 / ADR-0071 verification #1). It mirrors the + // managed mirror (cloud ADR-0024 D4 / cloud ADR-0071 verification #1). It mirrors the // federated branch of `stampIdentitySource`, is idempotent, and never breaks // the insert. Complementary to (not a replacement for) the OAuth-path stamp. ctx.hook('kernel:ready', async () => { diff --git a/packages/plugins/plugin-auth/src/invitation-role-cap.ts b/packages/plugins/plugin-auth/src/invitation-role-cap.ts index 7a07c3c087..c33a9256ea 100644 --- a/packages/plugins/plugin-auth/src/invitation-role-cap.ts +++ b/packages/plugins/plugin-auth/src/invitation-role-cap.ts @@ -98,7 +98,7 @@ export function orgRoleGrade(raw: unknown): number { * a hand-written copy drops the comma-joined (`'owner,member'`) and array * spellings `parseOrgRoles` handles, and on a security path that difference is * silent. Second consumer, and the reason this is exported: the break-glass - * ban guard (`last-admin-ban-guard.ts`, ADR-0024 D5.2), which counts the + * ban guard (`last-admin-ban-guard.ts`, cloud ADR-0024 D5.2), which counts the * administrators an environment would have left after a ban — a guard that * mistook the only owner for an ordinary member would wave the lockout * through. diff --git a/packages/spec/src/data/object.test.ts b/packages/spec/src/data/object.test.ts index c2496b688a..ea1237cefc 100644 --- a/packages/spec/src/data/object.test.ts +++ b/packages/spec/src/data/object.test.ts @@ -1557,7 +1557,7 @@ describe('ObjectSchema.create() forces a required master_detail under controlled }); // ============================================================================ -// Namespace removal (D4) — Object identity is single-sourced on `name`. +// Namespace removal (ADR-0129 D3) — Object identity is single-sourced on `name`. // ============================================================================ describe('ObjectSchema name-as-identity', () => { @@ -1572,7 +1572,7 @@ describe('ObjectSchema name-as-identity', () => { } }); - it('REJECTS legacy `namespace` with the prefix-embedding fix (ADR-0006 D4)', () => { + it('REJECTS legacy `namespace` with the prefix-embedding fix (ADR-0129 D3)', () => { // Until #4001 closed this shape on the parse path, this key was stripped in // silence — so an object written as `{ namespace: 'sys', name: 'user' }` // shipped as plain `user`, under a name its author never intended. The test diff --git a/packages/spec/src/data/object.zod.ts b/packages/spec/src/data/object.zod.ts index 3b59e0aee8..513ff65dce 100644 --- a/packages/spec/src/data/object.zod.ts +++ b/packages/spec/src/data/object.zod.ts @@ -1464,7 +1464,7 @@ const UNKNOWN_KEY_GUIDANCE: Record = { // Tombstones age out too: drop an entry ~two majors after the removal // (by then it's archaeology, not an upgrade; see CHANGELOG.md for history). namespace: - '`namespace` was retired — the object `name` IS the canonical id ' + + '`namespace` was retired (ADR-0129 D3) — the object `name` IS the canonical id ' + 'everywhere (API, ObjectQL, REST, SDK, DB table), so there is no separate namespace ' + 'to declare. Embed the module prefix in the name instead: `namespace: "sys", ' + 'name: "user"` becomes `name: "sys_user"`. Until this shape was closed on the ' + diff --git a/packages/spec/src/system/translation.zod.ts b/packages/spec/src/system/translation.zod.ts index 19724cdb0e..bf32244d86 100644 --- a/packages/spec/src/system/translation.zod.ts +++ b/packages/spec/src/system/translation.zod.ts @@ -359,7 +359,7 @@ const TRANSLATION_KEY_GUIDANCE: Record.fields..options'", _meta: "`_meta` is the retired object-first dialect — use the top-level 'locale' field (on a bundle, the locale is the map key)", - namespace: '`namespace` is not part of the translation contract — omit it (ADR-0006 D4 retired namespaces platform-wide)', + namespace: '`namespace` is not part of the translation contract — omit it (ADR-0129 D3 retired the separate namespace declaration platform-wide)', }; // ────────────────────────────────────────────────────────────────────────────