Skip to content

Commit 119a338

Browse files
docs(objectql): Multi-Tenant Schemas says the injected organization_id is stamped on insert and NOT NULL on a table the driver creates (#25322)
<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} --> _Requested by **Elon** · [project thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9DPad2G8LxwxKbL6HrM92a5Z)_ Fixes #25137 Clause-②: no Metadata-protocol: none ## What changed One file, docs only: `content/docs/protocol/objectql/schema.mdx`, section "Multi-Tenant Schemas" (+19 / -3). - The parenthetical that said the injected `organization_id` "is server-populated on insert and stays NULL where no organization context exists" now says the platform stamps it on insert, so the author never writes it. - A new paragraph after the first YAML example says what `main` does, each claim with its source: - `required: false` is the authoring surface, not the stored value; NULL is not an organization (ADR-0131 D1). - An insert is stamped with the writer's organization, under `single` with the Default Organization, and a write that names none is refused wherever an organization can exist (ADR-0131 D9). - A table the SQL or Turso driver creates carries the column `NOT NULL` at `CREATE TABLE`, so the database refuses a row with no organization, an `upsert` included (ADR-0131 D1's driver note, and the `tenant-column-not-null-ddl` changeset of `@objectstack/driver-sql` / `@objectstack/driver-turso`). The in-memory driver enforces no `NOT NULL` (the same D1 note). - A table that already existed keeps a nullable column until `os migrate organization-ownership` tightens it; the ceremony cannot tighten a rotated shard or a Turso remote table; `os migrate plan` lists every tenant column still nullable (the changeset's "Unchanged" bullet; ADR-0131 D1 and its 2026-10-22 amendment, item 2). Both are linked to their `deployment/cli` sections. - An object whose rows belong to no organization opts out and has no column (the changeset's remedy; ADR-0131 D1). The opt-out paragraph that already follows is unchanged. **D1's wording, measured before citing it.** The card and the changeset both quote D1 as "There is no nullable tenant column anywhere". That sentence is no longer D1: the 2026-10-22 amendment (ruling of record `6328195425`) replaced it with a paragraph that says where the column is NOT NULL and which nullable columns remain. So the page cites D1 for what the amended text says (NOT NULL at the engine and at every table the driver creates; NULL is not a state), and does not quote the retired sentence. ## Per-hit verdicts (the other pages) Rule applied: a sentence that describes a database from before the ownership ceremony, or history, stays; only a sentence that claims today's behaviour and is wrong changes. None changed. | Page:line (`origin/main` `9dc1f42d5`) | What it says | Verdict | |---|---|---| | `deployment/seed-tenancy-repair.mdx:18` | the repair stamps business rows that carry NULL | stays: the repair acts on rows an existing database already holds | | `deployment/seed-tenancy-repair.mdx:33` | seed rows "land with `organization_id = NULL`" on a first boot | stays: it is in "The defect it repairs", which states it as measured on 17.0.0 GA (`:47`). See Acceptance notes on its tense | | `deployment/seed-tenancy-repair.mdx:162` | the repair's `WHERE organization_id IS NULL` | stays: the repair's SQL over pre-ceremony rows | | `deployment/seed-tenancy-repair.mdx:179` | colliding rows keep `organization_id = NULL` | stays: what the repair leaves on such a database | | `deployment/seed-tenancy-repair.mdx:295` | NULL is the marker the report reads | stays: the ordering rule for a database that holds such rows | | `deployment/seed-tenancy-repair.mdx:304` | colliding rows keep NULL and stay findable | stays: same | | `deployment/seed-tenancy-repair.mdx:344` | the manual remedy's `WHERE organization_id IS NULL` | stays: the manual remedy for such a database | | `deployment/cli.mdx:1904` | a seeded row written before any organization existed carries NULL | stays: history ("predates the repair"); `os migrate duplicates` inventories it | | `deployment/cli.mdx:1934` | the repair leaves colliding rows NULL | stays: same as `seed-tenancy-repair.mdx:304` | | `permissions/permission-sets.mdx:193` | an organization-less grant row "an earlier release's promotion wrote" anchors nothing | stays: history, and still true of such a row | `cli.mdx` is not edited, so this PR has no hunk beside draft PR #25212's (its one `cli.mdx` hunk is at `@@ -756`). ## The PM's readings, measured - **A1 (premise holds)**: holds. `schema.mdx:721-723` at `origin/main` `9dc1f42d5` read "it is server-populated on insert and stays NULL where no organization context exists". - **A2 (no open PR edits these files; #25212 edits `cli.mdx` elsewhere)**: holds. #25212's files list one `cli.mdx` hunk (`@@ -756,7 +756,7 @@`, +1 / -1) and nothing under `content/docs/protocol/**`, `seed-tenancy-repair.mdx` or `permission-sets.mdx`. This PR does not touch `cli.mdx`. - **A3 (#25135 not found open)**: holds, with the reason: #25135 merged on 2026-10-21T20:45Z. It corrected the same sentence in three code comments (`registry.ts`, `registry-tenant-index-declaration.test.ts`, `object.zod.ts`) and named this page under its Acceptance notes, carrier none. No overlap. ## Gates Head `2f9543d`: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 41 commands (29 pnpm, 12 direct node) for the one changed path. Each was run with its exit code captured before any pipe and recorded as `COMMAND :: exit CODE`; `--ran` reconciles 41 derived, 41 run, 0 NOT-MEASURED, 0 UNRUN. All 41 exit 0. One needed a second run: `pnpm --filter @objectstack/spec run check:skill-examples` first exited 3 (PREREQUISITE NOT MET: `packages/client-react/dist` held no declarations in a fresh worktree); after `pnpm --filter '@objectstack/client-react...' build` it exits 0 (262 prose examples type-check across 3 surfaces). `check:doc-anchors` resolves all 573 internal fragment links, the two new ones included. Before the gates, `@objectstack/lint...` and `@objectstack/spec` were built under the verify lock (VERDICT command-exit 0). Not measured locally, CI's: the Build Docs and Test Core jobs, the type-check lanes, and the wide-population families the derivation names. No package source moves, so no test or ablation applies. No published file moves (`content/docs/**` is in no package's `files[]`), so this carries `skip-changeset` and no changeset. ## Acceptance notes - `seed-tenancy-repair.mdx` describes its defect in the present tense (`:30-34`, and `:72` "At `kernel:ready` on a fresh install no organization exists yet"). On `main` the `single` posture creates the Default Organization at boot, before seeds load, and the seed loader refuses an organization-less row on an install that registers the organization object (`packages/metadata-protocol/src/seed-loader.ts`, the `unowned` branch). The section is anchored to 17.0.0 GA, so this PR leaves it; a reader on 18.x may still take it for today's behaviour. Carrier: none. - The page cites the changeset by its name, not its path: a release consumes `.changeset/*.md`, and the sentence then lives in the two drivers' `CHANGELOG.md`. ADR-0131 D1's driver note carries the same fact durably. - The attribution block's first line is an HTML comment the session requires as the body's first line; `os-dev.md` forbids HTML comments in GitHub text. It is the same shape PR #25255 carried through the relay; reported as a deviation. - The branch is the harness-pinned `claude/project-thread-c1stko`, which names no issue; the claim (comment `6354581569`) records it as the identity. --- _Generated by [Claude Code](https://claude.ai/code/session_01Er1tmLgRYyGBSrDcDsUMqQ)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent fc7b53f commit 119a338

1 file changed

Lines changed: 19 additions & 3 deletions

File tree

‎content/docs/protocol/objectql/schema.mdx‎

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -719,9 +719,9 @@ full_name:
719719

720720
Row-level isolation runs on **one platform column**: `organization_id`, a lookup to
721721
`sys_organization`. The registry injects it into every registered object that has not
722-
opted out (`hidden`, `readonly`, `required: false` — it is server-populated on insert
723-
and stays NULL where no organization context exists), so a tenant-scoped object
724-
declares **nothing** about tenancy:
722+
opted out (`hidden`, `readonly`, `required: false` — the platform stamps it on
723+
insert, so the author never writes it), so a tenant-scoped object declares **nothing**
724+
about tenancy:
725725

726726
```yaml
727727
name: customer
@@ -731,6 +731,22 @@ fields:
731731
required: true
732732
```
733733

734+
`required: false` describes the authoring surface, not the stored value: NULL is not an
735+
organization (ADR-0131 D1). An insert is stamped with the writer's organization — under
736+
the `single` posture, with the Default Organization — and a write that names none is
737+
refused wherever an organization can exist (ADR-0131 D9). A table the SQL or Turso
738+
driver creates carries the column `NOT NULL` at `CREATE TABLE`, so the database itself
739+
refuses a row with no organization, an `upsert` included (ADR-0131 D1; changeset
740+
`tenant-column-not-null-ddl` of `@objectstack/driver-sql` and `@objectstack/driver-turso`).
741+
The in-memory driver enforces no `NOT NULL`. A table that already existed — created by an
742+
earlier release, or one the driver adds the column to — keeps a nullable column until
743+
[`os migrate organization-ownership`](/docs/deployment/cli#os-migrate-organization-ownership)
744+
tightens it, and that ceremony cannot tighten a rotated shard or a table on the Turso
745+
remote face; [`os migrate plan`](/docs/deployment/cli#tenant-columns-still-nullable-advisory)
746+
lists every tenant column still nullable, with the reason. An object whose rows belong
747+
to no organization does not leave the column empty — it opts out, and has no column at
748+
all.
749+
734750
The column's existence does not depend on the deployment's tenancy posture — only its
735751
*index* does, since nothing filters by organization on a single-organization stack. An
736752
object opts **out** by declaring `tenancy.enabled: false`

0 commit comments

Comments
 (0)