Repository navigation
feat(driver-sql,driver-turso)!: 18.x step 7 part 7b-0 — a table the driver creates carries its tenant column NOT NULL - #24940
Conversation
…r retired; webhook activation door Claude-Session: https://claude.ai/code/session_01PDSxBZE561TFe1N2iEjKXZ Co-authored-by: Claude <noreply@anthropic.com>
…he 18.0 dual read; door pins for webhook Claude-Session: https://claude.ai/code/session_01PDSxBZE561TFe1N2iEjKXZ Co-authored-by: Claude <noreply@anthropic.com>
…asserts no row and a dispatch Claude-Session: https://claude.ai/code/session_01PDSxBZE561TFe1N2iEjKXZ Co-authored-by: Claude <noreply@anthropic.com>
…the checklist, the webhooks guide and the showcase; changeset Claude-Session: https://claude.ai/code/session_01PDSxBZE561TFe1N2iEjKXZ Co-authored-by: Claude <noreply@anthropic.com>
…tcher's switch read Claude-Session: https://claude.ai/code/session_01PDSxBZE561TFe1N2iEjKXZ Co-authored-by: Claude <noreply@anthropic.com>
…posture builds no wall, as its 18.0 seeded copy stamped with the Default Organization was Claude-Session: https://claude.ai/code/session_01PDSxBZE561TFe1N2iEjKXZ Co-authored-by: Claude <noreply@anthropic.com>
…red writes; the declared-webhook double is drivable by the limit gate Claude-Session: https://claude.ai/code/session_01PDSxBZE561TFe1N2iEjKXZ Co-authored-by: Claude <noreply@anthropic.com>
…does not implement Claude-Session: https://claude.ai/code/session_01PDSxBZE561TFe1N2iEjKXZ Co-authored-by: Claude <noreply@anthropic.com>
…authors; checklist and admitsOrganization docblock say a declared webhook is deployment-wide under single Claude-Session: https://claude.ai/code/session_01PDSxBZE561TFe1N2iEjKXZ Co-authored-by: Claude <noreply@anthropic.com>
…0g-webhooks-registry
…the fixture's customized declared-webhook mirror Claude-Session: https://claude.ai/code/session_01PDSxBZE561TFe1N2iEjKXZ Co-authored-by: Claude <noreply@anthropic.com>
…n fail on a double delivery Claude-Session: https://claude.ai/code/session_01PDSxBZE561TFe1N2iEjKXZ Co-authored-by: Claude <noreply@anthropic.com>
…0g-webhooks-registry # Conflicts: # content/docs/permissions/tenant-audit-census.mdx # docs/audits/2026-08-tenant-audit-write-call-sites.counts.md
…0g-webhooks-registry
…0g-webhooks-registry # Conflicts: # content/docs/permissions/tenant-audit-census.mdx # docs/audits/2026-08-tenant-audit-write-call-sites.counts.md
…-registry' into claude/issue-24581-ceremony-open
…NOT NULL door end to end
…he ceremony, then boot; boot-refusal-order step 2 back on the binary
…he checklist; changeset
…ap, docs and strictness-ledger artifacts
…el — the cipher names no organization Claude-Session: https://claude.ai/code/session_014UATXWqAXqkaZkbG92ox6V Co-authored-by: Claude <noreply@anthropic.com>
…sys_flow_credential leave reach) Claude-Session: https://claude.ai/code/session_014UATXWqAXqkaZkbG92ox6V Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Read, in a detached worktree at the head and read-only: the PR's own diff against its merge-base with origin/main (f46ef4d; 362 files, +3259 / -75), every one of the 75 deleted lines, the two new pins, every testkit, the per-package justification beside every pre-18 relax call (asPre18TenantColumn, initObjectsPre18, relaxTenantColumns, relaxEveryTenantColumn, relaxEveryRegisteredTenantColumn, relaxTenantColumnsOnFile), and 24 changed test files across driver-sql, driver-turso, objectql, runtime, rest, cli, plugin-security, plugin-approvals, plugin-auth, plugin-audit, plugin-sharing, plugin-webhooks, service-messaging, service-storage, service-automation, service-analytics, dogfood and app-showcase (cli orphans.integration.test.ts and rest rest-stored-expression-dropped-fields.test.ts among them). Context read from the GitHub API: the PR body, issue #24583 (the 7b-0 part), execution record 6285122667, claim 6285142472, rulings 6266750393, 6267391172, 6269955919 and 6270035914 on #15194, the dev report 6311401309 and the later spec-seat note 6317289149, the seat note 6309760075, #24948 and #25016 (merged as 688052e, on this branch), the check-runs and annotations on the head, and the Test Core (2/6) job log tail. Nothing was built, run or posted. ① Derived judgments1. Scope of NOT NULL — confined to CREATE TABLE, tenant column only. PASS.
2. 18.0.0-born and ceremonied databases — untouched. PASS.
3. Writers. PASS, with two declared loud paths (B2, B3).
4. Organization delete under NOT NULL. PASS. With #25016 on the branch: a 5. Walled postures (R4) and 6. Spec text. The only spec change is ② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
维护者速读 · 请批准 · epic 席
|
…very-row-master fixtures meet a NOT NULL tenant column Their organization-less permission-set (and, for the update seam, position) catalogue rows are legacy residue, so each suite relaxes only those tables to the pre-18 shape. No assertion changed. Claude-Session: https://claude.ai/code/session_018EszgtA3V2KXBX5tf59Hi4 Co-authored-by: Claude <noreply@anthropic.com>
|
Seat note · epic PM
|
Claude-Session: https://claude.ai/code/session_018EszgtA3V2KXBX5tf59Hi4 Co-authored-by: Claude <noreply@anthropic.com>
…its BREAKING banner; stale NULL-tenant comments corrected - Changeset: `Metadata-protocol: none` (the constraint changes what a data writer can store, not what a metadata author can write), so driver-sql and driver-turso regrade from minor to patch under main's level rule. The launch-window sentence goes; the BREAKING banner, the ADR-0087 marker and the remedy stay. It now also says that, on a database that has not taken the ceremony, a table this release creates is NOT NULL beside nullable siblings, so the lifecycle mover cannot carry an organization-less row into it. - runtime default-organization-statements.golden: the delete-and-recreate case no longer relaxes every tenant table. The engine's cascade no longer sets a tenant column to NULL, and the case passes on the fresh store. - spec system-names and objectql registry docblocks: the tenant column is stamped on insert and never NULL, not "NULL on single-tenant stacks". Claude-Session: https://claude.ai/code/session_018EszgtA3V2KXBX5tf59Hi4 Co-authored-by: Claude <noreply@anthropic.com>
Contract review (delta check after PASS at 4f41fbf)Served-tier: Read, in a detached worktree at the head and read-only: the four commits since 4f41fbf (first-parent: cceb73d, 31189e7, 7fe7c4e, a674501), the two own commits' full diff (201 lines, seven files), the two merges replayed with ① Derived judgments1. The plugin-security fixtures for the three tests from main. PASS.
2. B1 under the new changeset gate (#25114). CLOSED.
3. B4, B5, B8.
4. Main's merges and new writers. PASS: no new NULL writer.
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
|
Seat ACCEPT · epic PM Seat check
|
Part of #24583 (18.x step 7, part 7b-0; claim 6285142472; governing text: execution record 6285122667, ADR-0131 D1, ruling ② A 6269955919).
Clause-②: yes (narrowing) — a table the driver creates refuses a write that names no organization, at the database.
Metadata-protocol: none — the constraint changes what a data writer can store, not what a metadata author can write.
Organization delete: resolved on main (#24948, ruling B)
Earlier on this branch, deleting an organization answered 500. The engine's
set_nullcascade on the injected tenant lookup hit the NOT NULL column. The same defect is live in 18.0.0 on a ceremonied database (measured; see #24948).The fix landed on main as #25016 (688052e), and this branch now carries it. On this head:
409 DELETE_RESTRICTEDwith per-tabledependents.set_nulla tenant column. Pointer columns (active_organization_id,parent_organization_id) keepset_null.The two dogfood pins that were red here are green on that contract, with no 500. #25016 rewrote them:
organization-delete-federated-fixture: 6/6. An organization that owns nothing is deleted with 200 (the positive control). An organization that owns a record is refused with 409 and its counts. The Default Organization cases are kept.org-record-admin-reach: 7/7.What changes
driver-sql
createColumn: a newtenantColumnargument makes the column NOT NULL on both exits (scalar and multi-value). Only the two CREATE TABLE sites pass it:initObjects' create branch:name === tenantField;ensureShardTable's create branch: asked of the base's declaration through the recording resolver. The index sync now reuses that same answer.An ADD COLUMN never passes it, so a column added to a populated table stays nullable. That table is the ceremony's.
driver-turso remote face:
buildCreateTableSQL(used bysyncSchema,syncSchemasBatchandinitObjects) asks the driver'scomputeTenantFieldresolver. It emitsNOT NULLon the tenant column only. ADD COLUMN is unchanged.spec:
TENANT_SCOPE_FIELD_DEFkeeps its shape. Its description no longer says "NULL on single-tenant stacks", and its docblock says why the column has nostorage.notNull.No metadata change and no differ change.
ceremonyOwnedNullability(from feat(cli,runtime)!: the upgrade ceremony opens — v17 and 18.0 databases take it, and a server serves only a database it created or one the ceremony certified (18.x steps 5 and 6) #24681) already keeps the declared-nullable / physical-NOT-NULL difference out of drift. Pinned for a fresh table, a new shard and the remote face.Pins
driver-sql/src/sql-driver-tenant-column-not-null.test.ts, on SQLite and live PostgreSQL 16 (MySQL cell declared un-run):organization_idnullable; a tenant column added to an existing table stays nullable; a pre-existing shard keeps its nullability.driver-turso/src/turso-remote-tenant-column-not-null.test.ts: a real libSQLfile:client behind the remote face (no hosted remote reached).notnullflags, refuses a raw NULL tenant insert, and reports no drift on either face.|| tenantColumnterm: driver-sql 2 cases go red, Turso remote 4 cases go red; restored, green.Fixtures (no assertion weakened)
About 200 test files across 25 packages wrote organization-less rows through a fresh table. Each was fixed one of three ways, chosen by what the test is about:
options, or a raw insert).singleinstall has:installDefaultOrganization(amanagedBy: 'better-auth'sys_organizationand its row, seeded at rest). The engine's D9 derivation then stamps the writes. In cli, a fixture plugin is spliced in ahead of the app's seed.relax_not_nulldrift door (pre-18-tenant-column.testkit.ts). The Turso remote face has no such door, so there the testkit creates its table with the transport's own pre-18 CREATE. These are the readers 7a-1/7a-2/7a-3/7b-2 retire. Their tests keep measuring the shape they were written for.Size, for the approval
3352 changed lines (+3271 / -81) in 367 files, against main edcc8ce:
sql-driver.ts30,remote-transport.ts16,injected-system-column-provenance.ts9);default-organization.testkit.ts,pre-18-tenant-column.testkit.ts), because a package's tests do not import another package's test files;A database the published 18.0.0 created
ORGANIZATION_OWNERSHIP_SHAPE_GENERATIONis unchanged (1). NOT NULL on new tables is not a shape the guard certifies.upgrade-18x-harness.dogfood.test.ts, a real 18.0.0-born database: nullable tenant columns, birth marker generation 1) is green on this head, 4/4. HEAD plans no destructive change over it and boots it with no refusal.ceremonyOwnedNullabilitykeeps its nullable tenant columns out of drift.os migrate organization-ownership(plan, then apply, with auth composed) answeredcomplete: true, blocking: 0and tightened 62 of its 65 tenant tables to NOT NULL. The 3 it left nullable:sys_email_template(mirror-deletion fate);sys_sso_provider(tenancy disabled, with an authoredorganization_id).This is a measurement only; nothing here changes the ceremony.
Merge resolutions (main 7169612, then 688052e)
parent-derived-write-refusal-not-visiblepredicate-write-unreadable-not-matchedwrite-door-unreadable-is-not-foundsys-user-default-row-scopesso-register-platform-admin-gatedefault-organization-statements.goldensharing-rule-org-less-callersharing-rule-criteria-requiredtakes main's version verbatim. Both sides had added the organization key, which would have been a duplicate property.notification-display-title.test.ts(service-messaging): it keeps main's removal of the retiredNotificationSubscriptionimport (feat(service-messaging,spec,cli)!: retire sys_notification_subscription — no reader ever expanded a subscription (ADR-0030 L3, amended) #25024) and this branch's Default Organization testkit import.webhook-delivery-real-sql-drivercase (plugin-webhooks) gets the lean engine's Default Organization. Its assertion is unchanged.Measured (merged tree: main 7169612, with #24681, 7a-1, 7a-2, 7a-3, 7b-3, 7c, 7z and #24911)
On the latest merge (head 0b58ee3, main 688052e with fix(objectql, plugin-auth)!: organization delete refuses while records remain, removes its own auth structure as one unit, and never orphans a tenant column #25016):
turbo run typecheck: 151/151;dispatch-gates --commands: 103 commands, all exit 0;check-tenant-chokepoint: exit 0.On the merge before it (main 7169612):
turbo run typecheck: 151/151;check-tenant-chokepoint: exit 0.Every package's suite is green except the two organization-delete dogfood cases above. cli: 6046 passed.
dispatch-gates --commands: 103 commands, all exit 0. Typecheck of the 23 touched packages: clean.pnpm lint: exit 0.Main's own new fixtures were also brought to the NOT NULL column. Among them is 7b-3's "a lean embedding keeps
__global__" pin: it now runs on a pre-18 store, because a fresh table refuses the organization-less row at the database. That is a cross-part question for the seat; see the report.R1: fresh
pnpm dev -- --freshon random ports, for showcase, crm and todo. Each answered health 200 and seeded-admin sign-in 200, with 0 ERROR lines and 0 NOT NULL refusals. The fresh database's tenant columns are all NOT NULL.R2 + R3:
upgrade-18x-harness.dogfood(8, over the ceremonied fixture) andorganization-ownership-boot-refusal-order.dogfood(4) are green.R4: a production
os serveunderOS_TENANCY_POSTURE=isolated, with the real@objectstack/organizationslinked into a scratch app, answered health 200, owner sign-up 200 and sign-in 200, with 0 error lines. Step 4's seven walled e2e cases (serve-organizations-host-resolution3 +serve-organizations-mount-failure4, run withOS_TEST_TIERS=nightly) pass 7/7.Known NULL writer, made loud: a production
os servewith noOS_AUTH_SECRET(showcase) still answers health 200, and its inline seed is refused 172 times at the database. Before, it landed NULL rows. Part 7b-3 makes that boot refuse.Lean SQL embedding (
@objectstack/objectql/core+SqlDriver, no organization object): an author seesNOT NULL constraint failed: account.organization_idon the first insert. ADR-0131 D9 prescribes an engine refusal that names what is missing. Today it is the database's.Changeset
.changeset/24583-tenant-column-not-null-ddl.md:patchfor driver-sql, driver-turso and spec (Metadata-protocol: none, under main's level rule from #25114). It keeps the BREAKING banner, the ADR-0087 disposition and the remedy. Since review 6317931247 (B4), it also says that on a database that has not taken the ceremony, a table this release creates is NOT NULL beside nullable siblings.check-governed-merges --branch; no governed paths; see Size above), so this lands the Tier H way: an authorized approval, or a human merge.🤖 Generated with Claude Code
https://claude.ai/code/session_018EszgtA3V2KXBX5tf59Hi4