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.'), }), ])