Repository navigation
Description
Filing gate ③: a finding carried from the 18.x line, step 7 (#24583). It comes from the CONTRACT_REVIEW_TIER review of PR #24893 (7b-1+7b-2), record 6293077099, under "Observation (not this part's)".
- Reader who acts: the epic seat (
epic:#15194,session_01Y5583bs2RDd53MH6kM1ca3), after step 7 lands. The answer is likely a maintainer decision, because it touches the certificate's meaning. - Dedupe: none found (
birth marker nullable,shapeGeneration NOT NULL,D1 certificate). docs(adr): ADR-0131 dated notes for the 18.x line — release scope, the D9 lean-embedding exception, D1 at createTable, D2 deployment-wide declared rules (Tier H) #24897 (ADR-0131 dated notes) names "D1 at createTable" only.
Not an 18.0 gate, and not a defect today. No reader on HEAD treats a NULL organization as meaningful: 7a-1 retired the driver arm, 7a-2/7a-3 the authz readers, and 7z made check-tenant-chokepoint refuse the arm. What stays open is the shape ADR-0131 D1 promises ("no nullable tenant column anywhere").
What is measured
- 18.0.0-born stores. The first boot that creates a datastore writes the birth marker (
details.birth.shapeGeneration = 1, runtimeorganization-ownership-boot-guard.tswriteBirthMarker). 18.0.0 created every tenant column nullable; 7b-0 makes fresh tables NOT NULL at createTable only from the next release on. Such a store reads as certified (organizationOwnershipShapeGenerationOfreads birth ?? checkpoint, atORGANIZATION_OWNERSHIP_SHAPE_GENERATION = 1), so no step ever tightens its columns. The fixture in test(dogfood): fixture-18.0.0 — the upgrade harness boots a database the published 18.0.0 created #24911 (fixture-18.0.0/) is such a store. - Ceremonied stores. The ceremony's
not-nullstep sets NOT NULL only where a table will receive it and its NULL count is 0 (organization-ownership-apply.tsrunNotNull). The other tables (not-declared,unsupportedon the Turso remote face, andsys_email_templateper the 7b-12 measurement) stay nullable, and the store is still certified at generation 1. - The consequence that is live once feat(driver-sql,driver-turso,driver-memory,objectql,cli,metadata-protocol): plain organization key part in new unique indexes; the differ reads COALESCE(tenant) as tenant on a certified database (18.x step 7b-1 + 7b-2) #24893 lands. A new organization-scoped unique declared on an existing table of such a store gets the plain index, which exempts NULL-organization rows. feat(driver-sql,driver-turso,driver-memory,objectql,cli,metadata-protocol): plain organization key part in new unique indexes; the differ reads COALESCE(tenant) as tenant on a certified database (18.x step 7b-1 + 7b-2) #24893's changeset is being corrected to say so (B1).
The question to measure, then decide
How does D1 become true for stores that are already certified, without making an 18.0.0 install refuse its own next boot? Bumping ORGANIZATION_OWNERSHIP_SHAPE_GENERATION would refuse every 18.0.0-born store until it runs a ceremony; the 18.x line has kept the generation at 1 for that reason.
- Measure:
- how many tables per posture stay nullable on an 18.0.0-born store and on a ceremonied store;
- whether any of them can hold a NULL
organization_idrow today (7b-3 measured such rows on an 18.0.0-born store, record 6284717698); - what an attributing NOT NULL step would need for an already-certified store: a boot-time tighten (a narrowing DDL, never applied unattended today), an operator command, or a generation bump with its boot refusal.
- Then: a decision card with those options, a recommendation, and the ADR-0131 D1 text each one makes true.
Blocks on: step 7 (#24583) landing. 7b-0 and 7b-1+7b-2 change the baseline this measures.
Activity
objectstack-fleet commentedon Oct 20, 2026
Hold · epic PM
session_01Y5583bs2RDd53MH6kM1ca3(os-steve) · 2026-10-20T14:51Z. The epic seat (epic:#15194) holds this card. It is not for another seat to claim.Blocked-by: #24583
The seat sorted the 18.x line's follow-ups. The cards that decide whether ADR-0131 is actually complete stay with the epic: #24897, #24919, #24883 and #24902. #24899 (security) is dispatched now. #24898 and #24900 stay in the shared queue. The maintainer agreed, verbatim: 「同意」.
When step 7 (#24583) closes, the seat dispatches:
objectstack-fleet commentedon Oct 20, 2026
Measurement for this card · epic PM
session_01Y5583bs2RDd53MH6kM1ca3(os-steve) · 2026-10-20T17:34Z. The 7b-0 dev (#24940) took it on a copy offixture-18.0.0, the database the published 18.0.0 created (#24911), with HEAD of 7b-0's branch merged with main71696121a.os migrate organization-ownershipplan and apply, with auth composed, answeredcomplete: true,blocking: 0.sys_email_template(the mirror-deletion fate);sys_sso_provider(tenancy disabled).OS_AUTH_SECRET, the apply blocks at the rewrite step: the security catalog does not resolveadmin_full_access. That is the existing gate.What this changes here. There is already an operator path that brings an 18.0.0-born database to D1 for almost every table: run the ceremony, which the birth marker does not require. So the decision this card owes may be smaller than "a generation bump or a new command". It could be enough to document the voluntary ceremony and settle the 3 residual tables. The measurement this card asks for still stands, in particular whether any of those tables can hold a NULL row today.
objectstack-fleet commentedon Oct 21, 2026
Claim: epic seat round · 2026-10-21T18:22Z · releasing the epic's hold (6293749011) now that step 7 (#24583) is closed (6323222024). This is a MEASURE round, then a decision card.
Session:
session_01Y5583bs2RDd53MH6kM1ca3Account:
os-steve(the card's assignee)Branch: none this round
Worktree: the cloud session's own checkout (
mode:cloud)Domain:
domain:engineSeat:
epic:#15194File surface:
mainin this round.d5d8146012, which carries 7b-0 and 7b-1+7b-2, and builds on the measurement 6297109158.docs/adr/**(docs(adr): ADR-0131 dated notes for the 18.x line — release scope, the D9 lean-embedding exception, D1 at createTable, D2 deployment-wide declared rules (Tier H) #24897).ORGANIZATION_OWNERSHIP_SHAPE_GENERATIONchange. It stays 1, and any option that bumps it is a decision for the maintainer.Container & model:
M,mode:cloud,model: TIER_DEFAULTClause-②: no — a measurement round with no diff
Responsibility: a store 18.0.0 created, and a ceremonied store, are certified while some tenant columns stay nullable | none | ADR-0131 D1's promise. No reader treats a NULL organization as meaningful on HEAD.
Thread-read: 6297109158
Serial constraints cleared: step 7 is closed, and 7b-0 and 7b-1+7b-2 are on main.
objectstack-fleet commentedon Oct 21, 2026
os-dev-report · measure round for claim 6323364911 ·
session_01F4TVnTQLXTmLgduMbWUUV1· 2026-10-21T18:33ZTree:
origin/maind5d8146 (carries 7b-0 #24940 and 7b-1+7b-2 #24893). Nothing pushed, no branch, no PR.ORGANIZATION_OWNERSHIP_SHAPE_GENERATIONwas not touched (packages/spec/src/system/migration.zod.ts:215, still 1).Method:
bootFixture: showcase, the audit plugin and the boot guard). Each ceremony is the realos migrate organization-ownershipbinary: the plan, then--apply --plan-file … --backup-taken, through the harness'srunOwnershipCeremony.pragma_table_info(notnullonorganization_id) andCOUNT(*) WHERE organization_id IS NULL, after each boot and each ceremony.Two things about posture you need to read the tables right:
OS_TENANCY_POSTURE=group|isolatedand no org-scoping runtime resolves to effectivesingle(its log says "posture 'single' — Layer 0 is inert"). So the walled rows below come from a second run with@objectstack/verify'smultiTenant: 'posture-only'stand-in. That run's tenancy service answeredgroup/isolatedat every boot. The ceremony reads the posture from the environment and reportedposture: group|isolateditself.fixture-18.0(an 18.0 pre-release store with no ownership marker: the v17 refusal class, refused at first boot namingsys_flow_credential, sys_secret). Undergroupandisolatedits ceremony first blocked atdeclared-webhooks: one customized copy of the declaredshowcase_task_changedwebhook is an operator decision under a wall. I renamed the copy, as the blocker prescribes, and re-ran. Both runs then answeredcomplete: true, blocking: 0.1. Nullable tenant tables per store and posture
The counts are the same under all three postures, in every store. That is ADR-0131 D11, now measured.
organization_idfixture-18.0.0(before any HEAD boot)fixture-18.0), then the ceremonysys_activity__r20261021, the shard HEAD's boot rotated in. 7b-0'sensureShardTablecreated it NOT NULL. All 65 tables 18.0.0 created stay nullable. Reason: certified at birth. The guard boots a verified marker at generation 1 (organization-ownership-boot-guard.ts:545), and no step ever tightens such a store.not-nullstep reporteddue 63, applied 61, unsupported 2, notDeclared 0in all six runs. The 4 tables left nullable:sys_activity__r20261020in (b),sys_activity__r20261019in (c)organization-ownership-not-null.ts:41-48sys_email_templateorganization-ownership-plan.ts:1098-1101; inventory:428sys_sso_providernotNullExempt: better-auth's provider relation, not the tenancy anchor):297-305; objectsys-sso-provider.object.ts:59sys_notification_subscriptionnotNullExempt):479sys_sso_provider, which is an opt-out. That meets the expectation of 0 apart from opt-outs.Unsupported on the Turso remote face. Not measured at runtime: no hosted remote was reached. By code,
driftApplyLimit('tighten_not_null')answers the table-rebuild limit on the remote face (driver-turso/src/turso-driver.ts:4669-4672). So on a Turso-remote store, the ceremony leaves every pre-existing tenant table nullable, reportedunsupported.What changed since 6297109158. That measurement left 3 tables nullable; HEAD leaves 4. The fourth is
sys_notification_subscription. #25024 (63085c3) retired it after the tree that measurement used (7169612), so the plan now exempts it (git merge-base --is-ancestorsays 63085c3 is not in 7169612).The ceremony mis-reports the new shard. It lists the already-NOT-NULL
sys_activity__r20261021asunsupported … each new shard is created with sys_activity's nullable column. That text predates 7b-0 and is wrong since 7b-0 (organization-ownership-not-null.ts:31and:46). It is listed below as a finding.2. NULL rows today
fixture-18.0.0: 0 NULL rows on all 65 tables, before and after HEAD's boot and after the ceremony.fixture-18.0: 0 on all 67 tables, raw.os servewithoutOS_AUTH_SECRET. The published 18.0.0 already refuses that door:serve.tsat 06cab48 (the 18.0.0 tag's commit) carries theno-secretrefusal, and HEAD has it atserve.ts:4688. A store 18.0.0 served normally therefore holds none from that path.os serve --no-serverwith noOS_AUTH_SECRET.flags.server(serve.ts:4666,:4688).sys_organizationis registered.no-organization-objectand passes the row on with no organization (objectql/src/engine.ts:7785; decision atcore/src/security/system-write-organization.ts:115-125).showcase_categoryrow deleted, the run re-seeded it asEngineeringwithorganization_idNULL (1 NULL row afterwards).[SeedLoader]ERROR lines and 0 rows. The NOT NULL column refuses each insert.driver-sql/src/sql-driver.ts:12204-12230);objectql/src/lifecycle/lifecycle-service.ts:1502);metadata-protocol/src/seed-loader.ts:2096,:2109);os migrate *boot runs withskipSeedData: true(cli/src/utils/schema-migrate.ts:1312).sys_email_template: the organization door is closed (plugin-email/src/email-template-door.ts), and the plugin no longer writes the table.sys_notification_subscription: retired, no reader or writer.sys_sso_provider: a NULL there is a legitimate better-auth row by design.--no-serverNULL row.single:attribution {default-organization: 1},willReceive: true.isolated:1 row(s) whose owner cannot be derived stay NULL and are reported,willReceive: false. That table would stay nullable.3. Options for an already-certified store
tighten_not_nullis destructive, and the boot reconcile appliessafeonly (sql-driver.ts:14425,:14479). On SQLite it rebuilds each table (61 on this fixture). On PostgreSQL it isSET NOT NULL(sql-driver.ts:14705): a full scan under an exclusive lock. A NULL row blocks it. It has two choices: skip the table (residue nobody is told about) or refuse the boot (a generation bump under another name, hitting exactly the store that holds the--no-serverrow)plugin-audit/src/objects/sys-activity.object.ts:29-34);sys_email_template; retired tables; opt-outs; every table on the Turso remote face; tables holding unattributable NULL rows under a wall:545-552line fires only for retired columns), andos migrate planstays silent by design (ceremonyOwnedNullability,driver-sql/src/schema-drift.ts:949,:974). The cost is one advisory: a bootinfoline, or anos migrate plannote listing the nullable tenant columns, plus a release-note sentence. Running it rewrites the birth marker as a checkpoint at generation 1 (measured)not-nullstep does not change), unless the ceremony changes tooorganization-ownership-boot-guard.ts:284-291: "This database was certified by an organization-ownership ceremony of shape generation 1, and this release requires generation 2: the ceremony steps added since have not run on it. The server refuses to start rather than serve it in a shape it was never migrated to. Run 'os migrate organization-ownership --out PLAN' …" The 18.x line kept the generation at 1 to avoid exactly this--no-serverwriter is closedThe 4 residual tables: does any need more than documentation?
sys_activity__r*shards: documentation only. HEAD creates new shards NOT NULL, writes go only to the current shard, and the pre-7b-0 shards expire within the declared 14 days on SQLite. Rotation is SQLite-only; on other dialectssys_activityis the base table, and the ceremony tightens it.sys_email_template: documentation only. Its writer is closed, its mirrors are deleted by the ceremony (0 rows in these fixtures), and D13 retires the table.sys_notification_subscription: documentation only. The object is retired, and its table is left in place untouched by ruling. No reader, no writer.sys_sso_provider: documentation only, but D1's wording needs care. It is not a tenant column. It is better-auth's provider relation on atenancy.enabled: falseobject, nullable even on a fresh store. Today D1 says an object with no organization "has no column". This object keeps a column namedorganization_id. D1 should say the rule binds the tenant column, not a column of that name.Recommendation: (ii) + (iv), and close the
--no-serverwriter first.os serve --no-server, no secret) is what would make the documented residue untrue on an 18.0.0-born store. It is ADR-0131 C8's open question, and 7b-3's option A (6284717698) covers it if extended to--no-serverboots that seed.D1 wording option (iv) would make true, given (ii):
{ "issue": 24919, "status": "done", "part": "measure round (claim 6323364911)", "branch": null, "head": "d5d8146012 (origin/main), unmodified", "pr": null, "session": "session_01F4TVnTQLXTmLgduMbWUUV1", "premise_still_valid": true, "summary": "Tenant columns, all three postures alike: store (a) 18.0.0-born, booted on HEAD, has 65 of 66 nullable; (b) after the voluntary ceremony and (c) a v17-class store after the ceremony each have 4 of 66; (d) a fresh HEAD store has 1 of 64 (sys_sso_provider, an opt-out). Every store holds 0 NULL rows. One live NULL writer exists: production `os serve --no-server` without OS_AUTH_SECRET re-seeded a missing row with organization_id NULL on an 18.0.0-born store. Recommendation: (ii)+(iv), and close that writer.", "measurements": { "a_18.0.0_born_booted_on_HEAD": "66 tables carry organization_id, 65 nullable, 0 NULL rows; sys_activity__r20261021 (rotated in by HEAD) is NOT NULL; same in single/group/isolated", "b_a_plus_voluntary_ceremony": "complete true, blocking 0; not-null due 63 / applied 61 / unsupported 2; 4 nullable (old shard, sys_email_template, sys_sso_provider, sys_notification_subscription); 0 NULL rows; same in all postures", "c_v17_class_fixture_18.0_ceremonied": "the first boot is refused (absent marker); the ceremony completes in single; in group/isolated it blocks at declared-webhooks until the customized webhook copy is renamed, then completes; 4 nullable, 0 NULL rows", "d_fresh_HEAD": "64 tables, 1 nullable (sys_sso_provider), 0 NULL rows, all postures", "turso_remote_face": "not measured at runtime (no hosted remote); by code, every pre-existing tenant table is unsupported (turso-driver.ts:4669-4672)", "null_writer_os_serve_no_server_no_secret": "live: a seeded row deleted on a copy of (a) is re-seeded with organization_id NULL; on fresh (d), 240 seed inserts are refused at NOT NULL and 0 rows are written", "ceremony_on_a_null_row": "single: attributed to the Default Organization, then tightened; isolated: reported, the table stays nullable" }, "tests": "No code change, so no suite run. Build: pnpm install --frozen-lockfile, then turbo build of the @objectstack/dogfood and @objectstack/cli closures (66/66). The probes are scratchpad tsx scripts calling the harness's own bootFixture / runOwnershipCeremony and the real CLI binary. 3 production `os serve --no-server` runs, each stopped by its recorded PID.", "gates": "None. There is no diff.", "mcp_calls": "reads only", "api_writes": "1: this comment, through post-stamped", "open_questions": [ { "question": "How does ADR-0131 D1 become true for stores that are already certified?", "options": ["(i) boot-time tighten", "(ii) voluntary ceremony + an advisory telling the operator", "(iii) generation bump to 2", "(iv) document the residue (D1 wording above)"], "recommendation": "(ii)+(iv), after the `--no-server` NULL writer is closed (ADR-0131 C8; 7b-3 option A extended to --no-server boots that seed)" } ], "out_of_scope_findings": [ "class: b · the `os serve --no-server` production boot with no OS_AUTH_SECRET writes organization_id NULL on nullable tenant columns (18.0.0-born stores, ceremony residue, MongoDB) · serve.ts:4666/:4688 are gated on flags.server; engine.ts:7785 · dedupe words: no-server, no-organization-object, AuthPlugin skipped, seed NULL organization", "class: c · the ceremony's not-null note says each new shard is created nullable, which is untrue since 7b-0, and it reports an already NOT NULL shard as unsupported · organization-ownership-not-null.ts:31, :46 · dedupe words: rotation shard nullable note stale" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 21, 2026
Seat note · epic PM
session_01Y5583bs2RDd53MH6kM1ca3(os-steve) · 2026-10-21T18:39Z. The measurement round is done (6323585273), and it ends in two decisions for the maintainer:os serve --no-serverboot that composes no organization object still writes ownerless rows. This is the live NULL writer the report found. The seat recommends A: refuse that boot regardless of--server.#25134's option A holds only once #25133 is fixed. The ceremony's stale shard note (
organization-ownership-not-null.ts:31,:46) rides #25134's execution. This card stays open until both are ruled and executed.objectstack-fleet commentedon Oct 22, 2026
Closed · epic PM
session_01Y5583bs2RDd53MH6kM1ca3(os-steve) · 2026-10-22T07:30Z. The measurement is done (6297109158, 6323585273), and both decisions it raised (6323698198) are executed onmain:os serve/os startthat composes no organization object is refused, with or without--server. This closes the live NULL writer the measurement found. PR fix(cli)!: refuse a production os serve that composes no organization object, --server or not #25155, merged asab488d9b7d.os migrate plannow lists the tenant columns still nullable. Each column is marked as one the ceremony tightens or leaves, with the reason, and the plan names the ceremony as the remedy. The ceremony also reports a NOT NULL shard truthfully. PR feat(cli): os migrate plan lists the tenant columns still nullable, naming the voluntary ownership ceremony; the ceremony reports a NOT NULL shard truthfully #25179, merged as6251f59051.37ccd5f2cf). Its follow-up docs(adr): ADR-0131 18.x notes, review precision (B1–B3, B6) and the #25155 / #25179 citations (Tier H) #25154 (Tier H, the maintainer's approval) carries the last citations to both merges above.Nothing on this card stays open.