From d8e136bd1b3af68eb633abd75f8c7e45a692c7c4 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 18 Jul 2026 07:35:05 +0000 Subject: [PATCH] fix(spec): drop the dead `systemFields.owner` key (#3175 follow-up) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `ObjectSchema.systemFields` advertised an `owner?: boolean` opt-out that nothing read — `applySystemFields` only consumes `systemFields.tenant` and `systemFields.audit`, and `owner_id` provisioning is governed by the object-level `ownership` property (made first-class in #3185). The key was declared but wired to nothing. - spec: remove `owner` from the `systemFields` object so it only advertises the two opt-outs it honors (`tenant`, `audit`). Runtime-compatible (the key was ignored; it is stripped now — both no-ops). A TS author who set it gets an excess-property error; the fix is to delete it or use `ownership: 'org' | 'none'`. - docs: regenerated the object reference; corrected the stale objectql/security note that called `audit` "reserved" (it is active). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_014343Qv6DFAykuAJc9yjANb --- .changeset/systemfields-drop-owner-key.md | 18 ++++++++++++++++++ content/docs/protocol/objectql/security.mdx | 2 +- content/docs/references/data/object.mdx | 2 +- packages/spec/src/data/object.zod.ts | 1 - 4 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 .changeset/systemfields-drop-owner-key.md diff --git a/.changeset/systemfields-drop-owner-key.md b/.changeset/systemfields-drop-owner-key.md new file mode 100644 index 0000000000..7901cc8f9f --- /dev/null +++ b/.changeset/systemfields-drop-owner-key.md @@ -0,0 +1,18 @@ +--- +"@objectstack/spec": minor +--- + +fix(spec): drop the dead `systemFields.owner` key (#3175 follow-up) + +`ObjectSchema.systemFields` exposed an `owner?: boolean` opt-out key that nothing +read — the registry (`applySystemFields`) only consumes `systemFields.tenant` and +`systemFields.audit`, and `owner_id` provisioning is governed by the object-level +`ownership` property (`'user' | 'org' | 'none'`, made first-class in #3185). The +key was declared but wired to nothing. + +Removed it so the schema only advertises the two opt-outs it actually honors +(`tenant`, `audit`). Backward-compatible at runtime: the key was ignored before and +is stripped now (both no-ops). A TypeScript author who set `systemFields.owner` +will now see an excess-property error — the fix is to delete the key (it never did +anything) or use `ownership: 'org' | 'none'` to skip `owner_id`. Also corrected the +stale `objectql/security` doc that called `audit` "reserved" (it is active). diff --git a/content/docs/protocol/objectql/security.mdx b/content/docs/protocol/objectql/security.mdx index 64e3f077f2..0f71a5002e 100644 --- a/content/docs/protocol/objectql/security.mdx +++ b/content/docs/protocol/objectql/security.mdx @@ -405,7 +405,7 @@ Combine OWD (`sharingModel: private`), an RLS `using` policy, FLS (`readable: fa ### Security by Default -Leave registry-level `systemFields` injection enabled (the object default) so multi-tenant objects auto-stamp `organization_id` on create, and use field `defaultValue` to seed ownership fields — so records are never left unscoped. (`owner`/`audit` injection keys on `systemFields` are reserved for future expansion; only `tenant` is active today — see `packages/spec/src/data/object.zod.ts`.) +Leave registry-level `systemFields` injection enabled (the object default) so multi-tenant objects auto-stamp `organization_id` on create, and use field `defaultValue` to seed ownership fields — so records are never left unscoped. (`systemFields` accepts two opt-out keys — `tenant` and `audit` — both active; `owner_id` provisioning is governed separately by the object-level `ownership` property. See `packages/spec/src/data/object.zod.ts`.) --- diff --git a/content/docs/references/data/object.mdx b/content/docs/references/data/object.mdx index 66116bafda..f84e4110fe 100644 --- a/content/docs/references/data/object.mdx +++ b/content/docs/references/data/object.mdx @@ -107,7 +107,7 @@ const result = ApiMethod.parse(data); | **managedBy** | `Enum<'platform' \| 'config' \| 'system' \| 'append-only' \| 'better-auth'>` | optional | Lifecycle bucket — platform (user CRUD) \| config (admin authored) \| system (engine-managed) \| append-only (audit) \| better-auth (identity). UI clients honour the resolved affordance matrix. | | **ownership** | `Enum<'user' \| 'org' \| 'none'>` | optional | Record-ownership model: user (default — injects reassignable owner_id) \| org \| none (no per-record owner, skips owner_id). Distinct from the package own/extend contribution kind. | | **userActions** | `{ create?: boolean; import?: boolean; edit?: boolean \| { enabled?: boolean; visibleWhen?: string \| { dialect: Enum<'cel' \| 'js' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; disabledWhen?: string \| { dialect: Enum<'cel' \| 'js' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object } }; delete?: boolean \| { enabled?: boolean; visibleWhen?: string \| { dialect: Enum<'cel' \| 'js' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; disabledWhen?: string \| { dialect: Enum<'cel' \| 'js' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object } }; … }` | optional | Per-object override of the resolved CRUD affordance matrix. | -| **systemFields** | `'false' \| { tenant?: boolean; owner?: boolean; audit?: boolean }` | optional | Opt out of, or selectively disable, registry-level system-field auto-injection. | +| **systemFields** | `'false' \| { tenant?: boolean; audit?: boolean }` | optional | Opt out of, or selectively disable, registry-level system-field auto-injection. | | **datasource** | `string` | optional | Target Datasource ID. "default" is the primary DB. | | **external** | `{ remoteName?: string; remoteSchema?: string; writable?: boolean; columnMap?: Record; … }` | optional | Remote table binding for federated (external) objects. | | **fields** | `Record; description?: string; … }>` | ✅ | Field definitions map. Keys must be snake_case identifiers. | diff --git a/packages/spec/src/data/object.zod.ts b/packages/spec/src/data/object.zod.ts index cba76139d2..acd3772e86 100644 --- a/packages/spec/src/data/object.zod.ts +++ b/packages/spec/src/data/object.zod.ts @@ -695,7 +695,6 @@ const ObjectSchemaBase = z.object({ z.literal(false), z.object({ tenant: z.boolean().optional().describe('Inject the organization_id column. Default true (the column is always provisioned; the multi-tenant flag governs only its index).'), - owner: z.boolean().optional().describe('Unwired: owner_id provisioning is governed by the object-level `ownership` property (`user`/`org`/`none`), not this key. Reserved.'), audit: z.boolean().optional().describe('Inject the audit columns (created_at/created_by/updated_at/updated_by). Default true.'), }), ])