Skip to content

Commit 863c580

Browse files
docs(objectql,spec): the tenant column is stamped and never NULL on single-tenant stacks (C1) (#25135)
This PR carries **item C1 only** of #25128. Item B6 (narrowing the blanket pre-18 relax rigs) moved to #25132 under the claim's split; #25132 is not addressed here. Fixes #25128 Clause-②: no (comment and docblock text only: no accept set, response, status, error, outcome or export moves) Metadata-protocol: none ## What changes Since 7b-0 (#24940, merged as `5e9b100743`), a table the driver creates carries its tenant column (`organization_id`) NOT NULL, and an insert is stamped (under `single` with the Default Organization). Three comments still said the column stays NULL on single-tenant stacks. Each now uses the wording 7b-0 used for the two sites it corrected (`applySystemFields`' docblock in `registry.ts` and `SystemFieldName.ORGANIZATION_ID` in `system-names.ts`): org-scoping stamps it on insert (under `single` with the Default Organization), and it is never NULL (ADR-0131 D1). Each comment keeps the rest of what it said; in particular the INDEX is still gated on multi-tenant mode. | File | Site | +/- | |---|---|---| | `packages/objectql/src/registry.ts` | the comment at the `organization_id` gate in `applySystemFields` ("it just stays NULL when no tenant context exists") | +3 / -2 | | `packages/objectql/src/registry-tenant-index-declaration.test.ts` | the comment above `expect(def.fields.organization_id).toBeDefined()` ("it just stays NULL") | +2 / -1 | | `packages/spec/src/data/object.zod.ts` | the `organization_id` bullet of the `systemFields` docblock ("It stays NULL on single-tenant stacks"). The multi-tenant attribution to `@objectstack/organizations` (the `org-scoping` service) is kept | +4 / -3 | | `.changeset/25128-tenant-column-never-null-comments.md` | `patch` for `@objectstack/spec` | +10 | No code, assertion, `.describe()` text or CHANGELOG changes. **Cross-lane:** `packages/spec/src/data/object.zod.ts` is a `domain:spec` file. The seat declares it to the spec seat. ## What `main` does (read before editing) - **NOT NULL:** driver-sql `createColumn` takes a `tenantColumn` argument, and `if (declaresColumnNotNull(field) || tenantColumn)` makes the column not-nullable on both exits. Only the CREATE TABLE sites pass it (`initObjects`' create branch, `ensureShardTable`). The driver-turso remote face's `buildCreateTableSQL` does the same. Pinned by `sql-driver-tenant-column-not-null.test.ts` and `turso-remote-tenant-column-not-null.test.ts`. - **The stamp under `single`:** `ObjectQL.resolveSystemInsertOrganization` (`packages/objectql/src/engine.ts`) runs after the beforeInsert hooks. For a system insert that carries no organization, it calls `resolveSystemWriteOrganization` (`packages/core/src/security/system-write-organization.ts`), which answers `derived` when exactly one organization exists under `single` (the Default Organization). The engine threads that id as `DriverOptions.tenantId`, and the driver stamps the column with it. The comment's own block says "stamped with the derived organization or refused below — never left NULL". A session write carries its organization on the context. - **Generator:** nothing committed is generated from the `systemFields` docblock. `build-docs.ts` renders from `json-schema/` (built from `.describe()`) plus each module's file-level header (`renderFileDescription`), never a property docblock. A repo-wide `git grep` of the docblock's text hits only `object.zod.ts`. After `pnpm --filter @objectstack/spec build`, `git status` is clean and `check:generated` reports all 14 artifacts up to date. The text does ship, through the built `dist/` and `src/**/*.zod.ts` in spec's `files[]`, so `@objectstack/spec` takes a `patch` changeset. objectql's `files[]` is `dist` only: after a build, `dist/` holds `applySystemFields` (positive control) and none of the comment text. - **Census** (`git grep -i` for "stays NULL", "NULL on single-tenant", "stays null", "remains NULL" and "left NULL", plus a single-tenant/NULL regexp, over `packages/objectql` and `packages/spec`): only these three tenant-column sites. The other hits are unrelated (`owner_id` "when left NULL", nullable booleans, aggregates, `notNull`). Outside the fence, one hand-written page has the same stale sentence. It is named under Acceptance notes and not edited. ## Verification All on head `ca06c671d`, which includes the merge of `origin/main` `1b3b65e64`, unless marked otherwise. - `pnpm exec turbo run build --concurrency=2` (under the verify lock): 77/77 tasks, VERDICT command-exit 0. - `pnpm --filter @objectstack/objectql typecheck`: exit 0 (tsc and check:test-typecheck OK). - `pnpm --filter @objectstack/spec typecheck`: exit 0 (tsc, scripts-typecheck and check:test-typecheck OK). - `pnpm --filter @objectstack/objectql exec vitest run --maxWorkers=2 src/registry-tenant-index-declaration.test.ts`: 1 file, 12/12 tests passed. - `pnpm --filter @objectstack/spec check:generated`: exit 0, all 14 generated artifacts up to date, and the build left no tracked change. - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`, with no paths: 94 commands derived and 94 run, every exit code captured before any pipe. All 94 exited 0. `--ran` reconciliation: "94 derived famil(ies) accounted for — 94 run, 0 NOT-MEASURED (a DERIVED zero — all 94 recorded an exit code and none of them is 3)", exit 0. - On the pre-merge head `3c3a5759b`, the same derivation ran 94 commands (86, plus 8 once the changeset existed). All exited 0 except `pnpm check:dual-build-cjs-loads`, which exited 3 (PREREQUISITE NOT MET: no `dist/`). After the full build it ran again and exited 0. - Not measured locally (CI's): the path-scheduled CI jobs and type-check lanes the derivation names, Test Core shards, and `test:repo`. - No ablation, because no tested behaviour changes. ## Acceptance notes - `content/docs/protocol/objectql/schema.mdx` (section "Multi-Tenant Schemas") still says the injected `organization_id` "stays NULL where no organization context exists". It is a hand-written docs page outside this card's file surface, so it is named here and not edited. Carrier: none. --- _Generated by [Claude Code](https://claude.ai/code/session_01Xwr1aPkHuFRqE9RSahyogV)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 1b3b65e commit 863c580

4 files changed

Lines changed: 19 additions & 6 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
docs(spec): the `systemFields` docblock no longer says `organization_id` stays NULL on single-tenant stacks
6+
7+
Clause-②: no (comment and docblock text only: no accept set, response, status, error, outcome or export moves)
8+
Metadata-protocol: none
9+
10+
The `organization_id` bullet of `ObjectSchema`'s `systemFields` docblock now says what the runtime does: org-scoping stamps the tenant column on insert (under `single` with the Default Organization), and it is never NULL (ADR-0131 D1). Only the column's index is still gated on multi-tenant mode. No schema, `.describe()` text or behavior changes.

‎packages/objectql/src/registry-tenant-index-declaration.test.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,8 @@ describe('#6810 — a registry-backed object reads back valid at both /meta exit
149149
expect(tenantIndexes(def)).toEqual([]);
150150
// And the COLUMN is still provisioned either way — that decoupling is
151151
// older than this fix and must survive it (sudo writers stamp the column
152-
// on single-tenant stacks; it just stays NULL).
152+
// on single-tenant stacks; org-scoping stamps it on insert under `single`
153+
// with the Default Organization, and it is never NULL, ADR-0131 D1).
153154
expect(def.fields.organization_id).toBeDefined();
154155
});
155156

‎packages/objectql/src/registry.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -774,8 +774,9 @@ export function applySystemFields(
774774
// global multi-tenant flag. Decoupling "does the column exist" from "is
775775
// tenancy enabled" is what stops sudo writers (audit / messaging / inbox /
776776
// outbox …) from failing with "no column named organization_id" on
777-
// single-tenant stacks: they can always stamp the column, it just stays NULL
778-
// when no tenant context exists. The multi-tenant flag now governs only
777+
// single-tenant stacks: they can always stamp the column, and org-scoping
778+
// stamps it on insert (under `single` with the Default Organization), so it
779+
// is never NULL (ADR-0131 D1). The multi-tenant flag now governs only
779780
// whether the column is INDEXED — on a single-tenant DB nothing ever filters
780781
// by organization, so the index would be dead weight. That is also why the
781782
// plan takes no `multiTenant` input: the flag cannot change what EXISTS, only

‎packages/spec/src/data/object.zod.ts‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2496,9 +2496,10 @@ const ObjectSchemaBase = strictObject(
24962496
*
24972497
* - `organization_id` — `lookup → sys_organization`. The COLUMN is
24982498
* provisioned unconditionally (subject to the opt-outs below); only its
2499-
* DB index is gated on multi-tenant mode. It stays NULL on single-tenant
2500-
* stacks and is auto-stamped on insert by `@objectstack/organizations`
2501-
* (the `org-scoping` service) in multi-tenant mode.
2499+
* DB index is gated on multi-tenant mode. Org-scoping stamps it on
2500+
* insert (`@objectstack/organizations`, the `org-scoping` service, in
2501+
* multi-tenant mode; under `single` with the Default Organization), and
2502+
* it is never NULL (ADR-0131 D1).
25022503
* - Audit columns — `created_at`, `created_by`, `updated_at`, `updated_by`
25032504
* (`readonly` + `system`). Gated by `audit` below.
25042505
* - `owner_id` — `lookup → sys_user`, auto-provisioned on user-authored

0 commit comments

Comments
 (0)