You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feat(spec,drivers,objectql,plugin-security): organization_id NOT NULL per cleared table; one predicate for Layer 0 and every driver; both orWhereNull arms, the __global__ sentinel and the #13491 ledger retire (ADR-0131 D1/D8/D9) — protocol 18 #15212
Stage plan: 6251466182 (epic seat epic:#15194, session_01Y5583bs2RDd53MH6kM1ca3, os-steve) · card-level Blocked-by: lines re-derived 2026-10-18T15:08Z: #15193, #15205, #15206 and #15207 are closed; #15196's and #15204's remaining items touch no C8 file, and #15204's Choice-4A remainder moves into this card as stage 8b-0a; stage 9 (#23952) and decision #24352 gate single stages, which the plan records.
History: until this edit the body opened with the #15193 gate and six Blocked-by: lines (#15193, #15196, #15204, #15205, #15206, #15207). The previous revision holds them.
In one sentence. Once the data is clean, the database itself forbids an ownerless row; both walls compute the same predicate; and the driver rule that let an unlabelled row through retires, taking the #13491 classification ledger with it.
⚠️Blocked-by C7 (the migration) in addition to the gate — the NOT NULL constraint is applied per table, only on tables C7's report shows clear. A table still reporting NULL rows does not receive the constraint and stays red.
Scope. DDL emits NOT NULL for organization_id on every cleared table (per-table gate). The engine computes one tenant scope — = :tenant / IN accessible_org_ids / none under single — and threads it to computeTenantLayer0Filter and to DriverOptions; applyTenantScope drops bothorWhereNull arms. driver-memory / driver-mongodb / driver-turso / driver-sqlite-wasm are audited to the same predicate. check-tenant-chokepoint.mjs refuses an IS NULL arm. GLOBAL_TENANT and ADR-0120 D3's COALESCE are removed through the D4 ceremony. platform-object-tenancy.ts, isPlatformObjectOutOfTenantAuditScope, the tenant-audit unclassified branch, reportLegacyPlatformAdminGrant, and the NULL readings in resolve-authz-context.ts / sharing-rule-service.ts / settings-service.ts / meta-write-org-scope.ts / bootstrap-system-capabilities.ts all retire. The id columns C2 left beside the name columns are dropped here. Tests pinning NULL semantics are rewritten (deal_p1, sql-driver-tenant-scope, memory-tenancy-guard, rule-criteria-org-scope).
Acceptance. An unstamped write is refused in every posture; a tenant read returns own rows only (isolated) or the membership union (group); single is unchanged end to end (showcase dogfood: Setup lists a non-empty declared catalog through the registry, organization-created items work). Ablation: re-adding one arm reddens exactly the predicate pin. A table still reporting NULL rows does not receive the constraint and stays red.
Protocol 18. Clause-②: yes — this changes the accept set.
Carrier hygiene — director seat (objectstack #12708, session_01LsEjuNMPitCHwEfYftZ1um), 2026-09-04. needs:contract-review removed from this card. Per the maintainer's 2026-08-28 ruling the carrier is never pre-hung: it marks a real reviewable increment (an open PR), and none exists — the card is pm:blocked behind #15193 and its C-row predecessors with no PR (closed_by_pull_requests 0). The Clause-② declaration in the body (yes, accept-set change, protocol 18) is the durable record; the carrier goes on the PR and the card the moment a draft PR opens. Labels rewritten read-modify-write, every other label untouched.
v18 pre-opening re-verification (C8): DRIFTED and partly done. Its Blocked-by lines missed C7
Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-06T14:41Z. ⛔ Not a claim, ⛔ not a dispatch. Read-only re-verification on objectstack main6befe19c6e. The cut base was 2026-09-04, 3,298 commits earlier. The maintainer asked for this pre-opening preparation in the triage seat's chat: 「现在就可以处理吧」. Classes, positions and functions only. The claiming seat applies these corrections before building. The body is not rewritten.
The arms: both orWhereNull arms remain (driver-sql/src/sql-driver.ts:15525, :15538). driver-turso now compiles the same method into remote statements (turso-driver.ts:2295-2345), so it needs its own pins. sqlite-wasm and turso inherit from the SQL driver.
Moot now:
the "audit driver-memory / driver-mongodb" step: the memory driver refuses tenant-scoped calls (memory-tenancy-guard.ts:38-42) and is no longer a boot store; mongo refuses multi-tenant use;
"retire reportLegacyPlatformAdminGrant": that was done on 17.x (74832b68f2).
Half done: the Layer 0 half of "one scope" landed (computeTenantLayer0Verdict, plugin-security/src/tenant-layer.ts:205). What remains is the driver threading (objectql/src/engine.ts:5502-5560 still derives tenantId on its own).
Flip, explicitly:scripts/check-tenant-chokepoint.mjsenforces the NULL-organization rule today (:684-694). The card's "refuses an IS NULL arm" is a reversal, and the PR says so.
Grown:GLOBAL_TENANT users went from 30 to 33 non-test files. Files with NULL-organization semantics went from 199 to 275 tests and from 58 to 67 non-test.
Recorded for C8, from C1's retriage (#151956039987580, triage's answer on #15195). Triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-07T14:56Z. ⛔ Not a claim, and no change to this card's state.
The open point: "every-posture refusal" (§8's C8 row) and D9's "refused otherwise".
The C1 seat's reading, A: no organization object means no tenancy to enforce, stated and pinned.
D9's literal reading: refuse.
C1 leaves the branch as it is today. At C8 the claimant measures the lean-composition reach first. If the measurement argues for A, it is a ruling of its own (it would narrow an accepted decision) and goes to the decision box. Otherwise D9 applies as written.
Remainder recorded for C8 from #14570 (domain:engine seat 1, seat post #6367) · session_01EUBvqtauTDmHi2ZgY759p2 (os-litant) · 2026-10-08T19:10Z. ⛔ Not a claim, and no change to this card's scope or labels. This records a case for whoever builds C8.
#14570's stage 0 (os-dev-report 6067123924) re-measured sys_business_unit_member's four write paths on maine36ee5351b, after C1 (34dba5ae1e). The measurements were taken on real showcase kernels through bootStack, under single with the Default Organization and under isolated (posture-only):
NULL, not refused (an application-object control in the same boot is refused walled-posture; the same insert carrying tenantId lands stamped)
The cause is the exemption C1 kept until C8 by ruling.packages/objectql/src/engine.ts near :5856, inside resolveSystemInsertOrganization: if (isPlatformObjectOutOfTenantAuditScope(object)) return undefined;. The predicate is in tenancy/platform-object-tenancy.ts near :255. The object is unclassified by absence.
Of the census's 49 in-reach platform objects, 8 are admitted tenant-scoped. The early return fires for the other 41, which include sys_business_unit_member, sys_business_unit (the identical path, measured: NULL under single) and sys_permission_set.
The same predicate sets isTenantAuditInScope false (near :5628), so these NULL writes are also quiet (read, not measured).
Fixture to re-measure when the early return is lifted.packages/qa/dogfood/test/showcase-bu-hierarchy-sharing.dogfood.test.ts:66 calls findOne('sys_organization', { where: {} }). The engine refuses that, .catch(() => null) swallows the refusal, and :68 then mints a second organization org_bu. Once C8 lifts the early return, the fixture's bare isSystem membership inserts (:73–:74) meet a two-organization single install, so they would be refused ambiguous-organization rather than derived. That is inferred from the C1 rule table, not measured. 5536484221 names C7 (feat(objectql,cli): inventory + migration — four fates per object, mirrors deleted only after the id→name rewrite is verified, per-table boot report (ADR-0131 D10) #15211) as owning that fixture case.
Pointer from domain:engine seat 2 · session_01ADzJtzYTLUfgrRZHxkagkX · 2026-10-13T04:59Z. ⛔ Not a claim; no reply is owed.
The maintainer ruled Q1 on #22937 (record 6138591940): 「随 C8 落地」. The v18 boot refusal of a database that has not run the organization-ownership ceremony (ADR-0131 D10 item 5) lands with this card, C8 (protocol 18). It reads the sys_migration marker that #22937's PR #23034 (os migrate organization-ownership --apply) writes. PR #23034 adds no refusal: a refusal on main before the ceremony can run would refuse every existing development database. This closes the gap the repo:cloud#1 seat named on #15211 (6138240272, item 3: "the v18 boot refusal (after Q1)").
Ruling-pointer · #24435 ruled Q1 A · Q2 A at 6259141885 (batch #368 item 2, the maintainer's 「同意」) · director seat (summon #43 continuation 5, session_01QJC7i981TSs9L1CGJK59Ax) · 2026-10-19T03:41Z
For the epic seat (session_01Y5583bs2RDd53MH6kM1ca3), two more walled fates before 8b: (Q1) sys_flow_credential becomes deployment-level — systemFields: { tenant: false }, the organisation column goes, ownership follows the flow; one D7 line rides PR #24422 while it is open; built inside 8b-0de. (Q2) the boot seeder bootstrapDeclaredWebhooks retires; declared webhooks are read from the registry as stage 9 reads declared sharing rules; the ceremony deletes the mirror rows and re-sources their sys_http_delivery tails; customising a declared webhook follows D6 regime C (a sys_metadata_activation row of type webhook; a clone under a new name); new stage 8b-0g, serial after 8b-0de, before 8b. sys_share_link is stamped before 8b, no letter. Measured first.
Plan amendment · epic PM session_01Y5583bs2RDd53MH6kM1ca3 (os-steve) · 2026-10-19T04:24Z. It amends the plan 6251466182 and amendments 9–12, per the ruling record 6259608309 on #15194: 18.0 ships for fresh installs only (the maintainer's 「同意」).
An object that becomes deployment-level (systemFields: { tenant: false }), or a new sys_* sibling, still needs its organization-ownership-inventory.ts row. inventory.test.ts about :113–:118 enforces that.
Each such stage edits only its own rows, placed next to the object's existing row. Never append at the list's end, because two stages appending there would conflict.
That edit is the inventory's consistency, not a ceremony behaviour.
Stages, re-cut. Each is measured first, and each stays under 3,000 lines.
Files: plugin-email; plugin-auth's mail send sites (about :2006–:2041, :3862–:3882, :4113–:4123, :5649, :5827); sys-email.object.ts and the new sibling; their inventory rows.
It goes after 8b-0f merges, because both touch auth-manager.ts.
Files: service-settings; sys-secret.object.ts; sys-setting-audit.object.ts and its sibling; service-automation/src/sys-flow-credential.object.ts; the engine's encryptSecretFields path if it needs it; their inventory rows.
18.0 ceremony refusal.os migrate organization-ownership (plan and apply) refuses in 18.0, with an ADR-0112 code naming the 18.x release that will carry the upgrade path. Nothing is read or written. The boot guard stays as is.
Files: packages/cli/src/commands/migrate/organization-ownership.ts and its tests.
Parallel with the others.
8b. After 8b-0d′, 8b-0e′, 8b-0g′, 8b-0h and 8c-T-α.
8e. After 8b. It now carries the NULL-semantics pins that 8c would have rewritten, but only those that NOT NULL breaks.
Plan amendment · epic PM session_01Y5583bs2RDd53MH6kM1ca3 (os-steve) · 2026-10-19T04:31Z. It amends amendment 13 (6259616282), per ruling record 6259698357 on #15194: 18.0 is single posture only (the maintainer's 「同意」 on tier ③).
Moves to 18.x: 8b-0d′, 8b-0e′, 8b-0g′ and 8e. Under single, 8b stamps those writers with the Default Organization. Their shape changes, and NOT NULL, ride the 18.x upgrade path. That path already has the ceremony's NOT NULL step.
os migrate organization-ownership refuses in 18.0, with an ADR-0112 code. It reads nothing and writes nothing.
A walled posture (group / isolated) is refused loudly at the operator's boot path in 18.0, naming the 18.x release that will carry it.
Measure first. Find the narrowest operator-facing entry: the CLI os start / os dev / serve, or the runtime host. The goal is that kernel-level test fixtures which boot a walled posture keep working.
⛔ No environment-variable escape hatch.
If the only refusal point would break the walled test fixtures, stop and report. The seat then takes the shape to the maintainer.
Claim: PM loop round 8 · 2026-10-19T04:41Z
Session: session_01Y5583bs2RDd53MH6kM1ca3
Account: os-steve (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-15212-180-guards (the "18.0 guards" stage, plan amendment 14, 6259710449). Other stages keep their own claims, and each PR carries Part of #15212.
Worktree: the cloud session's own checkout (mode:cloud)
Domain: domain:cli
Seat: epic:#15194 (the epic lane)
File surface, read on origin/mainfa26308bb; stop on breach and explain in the report:
packages/cli/src/commands/migrate/organization-ownership.ts and its tests. In 18.0 the command (plan and apply) refuses with an ADR-0112 code, reads nothing and writes nothing, and names the 18.x release that carries the upgrade path.
The operator's boot path for posture. Measure it first, among packages/cli/src/commands/serve.ts / dev / start and the runtime host. A walled posture (group / isolated) is refused loudly at that entry in 18.0, again naming 18.x. Kernel-level test fixtures that boot a walled posture must keep working.
⛔ No environment-variable escape hatch.
⛔ Not the ceremony utils (organization-ownership-*.ts).
⛔ Not plugin-sharing, plugin-auth or plugin-audit.
Container & model: S, mode:cloud, model: TIER_DEFAULT
Clause-②: no (narrowing) — the dev re-measures; refusing a command or a boot that used to run is a narrowing
Responsibility: n/a — a ruled scope stage (ruling records 6259608309 and 6259698357 on [epic] ADR-0131 — total organization ownership: no NULL organization_id (v18 line) #15194)
Thread-read: 6259710449
Serial constraints cleared:
Claim: PM loop round 9 · 2026-10-19T04:49Z
Session: session_01Y5583bs2RDd53MH6kM1ca3
Account: os-steve (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-15212-8b-stamp-or-refuse. This is C8 stage 8b, with 8c-T-α folded in: it starts from claude/issue-15212-8ct-alpha-fixtures-stamped at 5d1b91e9b (1 commit, 4 test files, ±5 lines). Ruling record 6259899486 on #15194.
Worktree: the cloud session's own checkout (mode:cloud)
Domain: domain:engine
Seat: epic:#15194 (the epic lane)
File surface, read on origin/main725a5b131. Stop on breach and explain it in the report:
The exemption 8b lifts under single.
packages/objectql/src/engine.ts: resolveSystemInsertOrganization at about :7165–:7195, called at about :15952. The exit at about :7183 (isPlatformObjectOutOfTenantAuditScope) is the exemption by absence. Also the WARN carrier at about :6896–:6943.
packages/objectql/src/platform-object-tenancy.ts: the 9-entry admit list at about :124–:233, and the exemption at :261–:264.
packages/objectql/src/index.ts: about :553–:568.
The docblocks in packages/core/src/security/system-write-organization.ts.
The census tooling.scripts/platform-object-tenancy-census.mjs (HEADER at about :126) and its JSON, where retiring the admit list requires it. ⛔ Not the census step in .github/workflows/lint.yml, which is governed. Size is not capped (ruling record 6259899486, item 2).
{
"issue": 15212,
"status": "done",
"branch": "claude/issue-15212-8b0h-share-link-stamped",
"pr": "https://github.com/objectstack-ai/objectstack/pull/24461",
"session": "session_01DhtBNRSSLKsbimJ37KcfZy (dispatched by epic seat session_01Y5583bs2RDd53MH6kM1ca3; claim 6259036738 verified as the newest Claim: and naming this branch)",
"premise_still_valid": true,
"summary": "C8 stage 8b-0h. ShareLinkService.createLink now stamps sys_share_link.organization_id. The order is: the linked record's organization, from a narrow system-context read of ['id', TENANT_FIELD]; then the acting context's tenantId; then ADR-0131 D9's one predicate (resolveSystemWriteOrganization). That predicate derives the Default Organization under single with exactly one organization, and otherwise throws the engine's own SystemWriteOrganizationRequiredError (ERR_SYSTEM_WRITE_ORGANIZATION_REQUIRED, 500) before any row is written. The stamp rides both the row and the write context ({...SYSTEM_CTX, tenantId}); the mint response keeps the ShareLink shape. Posture is the REQUESTED one (resolveTenancyPosture()), the source resolveAuthzContext and tenancy.defaultOrgId() read, so sharing-plugin.ts needs no wiring and is untouched. Nothing is stamped when sys_share_link has no tenant column or the composition registers no sys_organization. The engine and the #13491 ledger are untouched (8b's). Measured: A1 holds (one insert site, share-link-service.ts:665). A2 is falsified: the existing read is id-only and runs under the caller's context, so the stage adds the narrowest system read. A3: cross-organization is possible under group, and the record's organization is stamped. A5: no in-repo caller reaches the new refusal. A6: +281/-1 over 3 files.",
"tests": "New share-link-organization-stamp.test.ts: real ObjectQL + SqlDriver (better-sqlite3 :memory:), the real SysShareLink object, rows read back off the driver, 5/5 pass. Ablations through scripts/ablation-replace.mjs on 36706e5e (each anchor 1 to 0, each restore blob==HEAD with empty git diff HEAD; the test imports src, so no dist leg): dropping the record-organization return reds only pin 1; dropping the session fallback reds only pin 2; dropping the derived return reds only pin 3; turning the refusal into return undefined reds only pin 4 (each run '1 failed | 4 passed'). On final head a117f81c: pnpm --filter @objectstack/plugin-sharing test gives 49 files / 1144 tests passed, and typecheck exits 0 (os-verify-lock VERDICT command-exit 0). pnpm --filter @objectstack/runtime exec vitest run src/domains/share-links gives 6 files / 202 tests passed (these suites construct ShareLinkService). The first full plugin-sharing run on an earlier commit had 47 failures (real-ObjectQL suites without sys_organization: the core predicate probes before it checks registration). Fixed in 434345d9: the probe answers [] when no organization object is registered, like the engine's probe.",
"gates": "Derived on a117f81c: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack gives 67 commands, plus 53 artifact-roster families on STDERR (118 unique). Re-derivation after the last commit gives an identical list and roster. All 118 were run with exit codes written to a TSV before any pipe. --ran over the 67 (record format 'cmd :: exit N'): 'dispatch-gates --ran: 67 derived famil(ies) accounted for, 66 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)'. The NOT-MEASURED one is check-plugin-teardown-shape --self-test, which needs an unshallowed clone. The first battery (on 3d89674f) found two real reds, both fixed in a117f81c and green on the rerun: check-tenant-audit-census (a conditional insert context counted as elevation-undecidable; now '{ ...SYSTEM_CTX, tenantId }') and check:query-options-erasure (share-link-service.ts 5 to 7; the two 'as any' casts dropped). The rerun on a117f81c also passed check:i18n, check:dual-build-cjs-loads and check:published-readme-exports (exit 3 on the first run, which predated the runtime-closure build) and check:query-options-erasure ('65 unswept non-test site(s) ... none new'). Roster families that cannot measure without PR context: check-closing-target-claim exit 2, check-single-claim-paths exit 2, governed-queue-guard exit 1 (no GITHUB_EVENT_PATH); all NOT MEASURED locally, left to CI. check-partof-closing-keyword exit 2 bare, then exit 0 with PR_BODY set to the PR body. CI on a117f81c at report time: 'No other open PR may claim the same issue' success, 'Packed-tarball smoke' skipped, every other check-run in_progress or queued, including Lint & Repo Gates lanes, Type Check lanes, Check Changeset, Governed Surface Queue Guard, 'Part-of PR must not also close its card' and 'The card this PR closes must claim this branch'. Test Core and Build Core had not appeared yet.",
"mcp_calls": "0",
"api_writes": "2 relay strokes (scripts/pm/with-fleet.sh --via dispatch): (1) pr_create, i.e. POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos/objectstack-ai/objectstack/issues/24461/assignees, read back byte-identical (7686 bytes) with assignee os-steve; (2) this comment, POST /repos/objectstack-ai/objectstack/issues/15212/comments. git push is not counted. Reads used gh api.",
"open_questions": [
{
"question": "Which posture source should the mint read: the REQUESTED posture (resolveTenancyPosture(), env) or the engine's in-force posture (the injected tenancy.posture)?",
"options": [
"A: requested (taken). It is what resolveAuthzContext and tenancy.defaultOrgId() read, and fails stricter under a degraded wall. It needs no sharing-plugin.ts wiring, which is off-limits while #24428 is in flight.",
"B: in force. It matches the engine's system-insert rule exactly, but needs a new ShareLinkServiceOptions member wired in sharing-plugin.ts."
],
"recommendation": "A. Axis 3 (prevent AI mistakes): requested-walled refuses rather than derives, so the contract fails loud. Axis 2 (long-term soundness): it is the documented reading for exactly this degraded case (tenancy-service defaultOrgId docblock). Axis 4 (no scope creep): no new option surface. The two sources differ only on a degraded walled deployment."
},
{
"question": "Should the single-with-several-organizations neither case be refused (the core predicate, taken) rather than stamped with tenancy.defaultOrgId()'s choice?",
"options": [
"A: refuse 'ambiguous-organization' (taken). It is ADR-0131 D9 ('nothing defaults to any owner but the derivable one') and the engine's rule for the same write after 8b.",
"B: stamp defaultOrgId()'s answer, as the 8b-0c activity mirror does."
],
"recommendation": "A. Axis 2: one predicate, so the service and the engine cannot disagree after 8b. Axis 1 (real business need): the case needs a system caller or an organization-less session on an organization-less record in a topology the boot already reports at error. No in-repo producer reaches it."
}
],
"out_of_scope_findings": [
"carrier: stage 8b (epic seat) · noted, not filed. The sys_share_link row in packages/objectql/src/tenancy/platform-object-tenancy.ts is still 'unclassified'; the writer now stamps, so it is ready for 8b's engine change. The sys_share_link row in organization-ownership-inventory.ts (fate 'attribution', subject anchor object_name/record_id) already matches, no change needed.",
"class: none (observation) · carrier: none · Under single with 0 organizations and a registered sys_organization, the core predicate refuses 'no-organization'; that is the ADR-0131 D3 boot invariant broken, not a reachable state. A composition with no sys_organization still writes links as before."
]
}
{
"issue": 15212,
"status": "done",
"branch": "claude/issue-15212-180-guards",
"pr": "https://github.com/objectstack-ai/objectstack/pull/24480",
"session": "session_01Q4U6vXvHNpcDZr24aXeJo2",
"premise_still_valid": true,
"summary": "The 18.0 guards stage. (1) `os migrate organization-ownership` refuses in 18.0, plan and apply alike, with exit 1 and the standard ADR-0112 code NOT_IMPLEMENTED (--json: error plan_refused|apply_refused, reason upgrade-unavailable, code, detail). It reads and writes nothing, and the message names the 18.x release and says 18.0 is fresh-installs-only. Shape: the ceremony class is kept whole as the named export OrganizationOwnershipCeremony, and the registered default command is a subclass whose run() refuses. 18.x deletes the subclass. No ceremony or utils change and no re-enable flag. (2) `os serve` refuses a walled posture (group/isolated, including OS_MULTI_ORG_ENABLED=true) at its existing tenancy-posture gate. It is a FATAL plus exit 1 naming NOT_IMPLEMENTED and the 18.x release, before any config read, and no env hatch lifts it (OS_ALLOW_DEGRADED_TENANCY included). `os dev` and `os start` spawn serve; single is unchanged. Kernel-level walled boots (verify bootStack, the dogfood wall-* suites, unit tests) never pass through serve and keep working. A1 measured: os dev's automatic ceremony runs only when the boot guard refuses, which never happens on a fresh database; when it does run, it spawns this command and surfaces its refusal, so no os dev change was needed. One sentence each in cli.mdx (3 spots), tenancy-modes, environment-variables and self-hosting. Changeset 15212-180-guards-fresh-single-only.md, cli minor, Clause-②: yes (narrowing), ADR-0087 not-required marker. Size: 799 changed lines over 28 files (+565/-234), above the 0.1–0.4k estimate: 16 one-line test import changes, two rewritten e2e files and two new pin files.",
"tests": "All on head 9141352b (pre-merge; the merge of main cca3540481 brought only unrelated cli test changes). `pnpm --filter @objectstack/cli typecheck`: VERDICT command-exit 0. cli unit project (`vitest run --project unit --maxWorkers=2`): Test Files 312 passed (312), Tests 4615 passed (4615). The 16 changed ceremony test files (unit and integration), the 2 new pins and serve-tenancy-posture-gate: Test Files 18 passed | 1 skipped (19) — the skip is remote-face, which needs sqld — Tests 199 passed | 1 skipped. Nightly e2e (OS_TEST_TIERS=nightly) serve-organizations-host-resolution and serve-organizations-mount-failure: 2 passed, Tests 7 passed. Dogfood organization-ownership-boot-refusal-order: Tests 5 passed (5); the first run red at step 2 (oclif auto-transpile loaded src under NODE_ENV=test), fixed by turning auto-transpile off as bin/run.js does. Ablations through scripts/ablation-replace.mjs; each mutation landed and was restored with blob == HEAD and git diff HEAD empty. The test imports resolve src, so no dist preflight applies. A1: registered run replaced by the ceremony's → 4 failed | 1 passed, the ceremony answering reason posture-unresolved instead of upgrade-unavailable (the message-constant case stays green, as expected). A2: serve's walledPostureRefusal(tenancyPosture) call replaced by null → both in-process boot cases failed. A3: the single early return deleted → 'answers null for single' failed.",
"gates": "Derived with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (no paths; 28 paths vs merge base). 101 stdout commands plus the 50 artifact-roster families on STDERR = 150 unique, each run with its exit code written to a file before any pipe. 142 exit 0 on the first sweep. 4 prerequisite exit-3 results were re-run after remedy and exit 0: check:skill-examples (built client-react), check-plugin-teardown-shape --self-test (fetched pinned commit 621a4876), check:dual-build-cjs-loads and check:published-readme-exports (built the 7 named packages). 4 roster families are NOT MEASURED, NOT WIRED locally because they need PR or workflow event context and CI runs them: check-closing-target-claim, check-partof-closing-keyword, check-single-claim-paths, check-governed-queue-guard. `--ran` reconciliation: '101 derived famil(ies) accounted for — 101 run, 0 NOT-MEASURED (a DERIVED zero — all 101 recorded an exit code and none of them is 3)'. After the merge commit cca3540481 the gates were re-derived with no paths: 0 families added, 0 removed. The ratchet and changeset families were re-run on cca3540481: check:type-check-coverage 0, check:type-check-debt 0, check-changeset-no-major 0, check-adr-0087-registration 0 (exemption no-migration-prescription noticed). PR check-runs at report time: 'No other open PR may claim the same issue' success; 'Packed-tarball smoke (opt-in)' skipped; every other check in_progress (Lint ×9, Type Check ×4, Check Changeset, Governed Surface Queue Guard, Spec property liveness, the claim guards and others). The local subset is not a substitute for those.",
"mcp_calls": "0 — ToolSearch only loaded the issue_read/get_file_contents schemas; no MCP GitHub tool was called (reads went through gh api over the proxy)",
"api_writes": "2 relay dispatches as objectstack-fleet[bot]: (1) pr_create → POST /repos/objectstack-ai/objectstack/pulls (draft, #24480) + POST /repos/objectstack-ai/objectstack/issues/24480/assignees [os-steve], body read back byte-identical (7470/7470); (2) comment → POST /repos/objectstack-ai/objectstack/issues/15212/comments (this report). Plus git pushes (not REST).",
"open_questions": [
{
"question": "Where should the 18.0 walled-posture refusal sit? (measured)",
"options": [
"A os serve's existing tenancy-posture gate, the operator boot that os dev and os start spawn; it breaks only the two nightly CLI e2e files that spawn serve under isolated, now rewritten to pin that the refusal precedes resolution",
"B os dev / os start only: misses production os serve",
"C runtime or kernel (resolveTenancyPosture / createStandaloneStack / bootStack): breaks every kernel-level walled suite, which the dispatch named as the hard-blocker case"
],
"recommendation": "A (taken). Business: it refuses the real operator path before the operator does work, rather than after a D5 package install. Long-term: it uses the existing gate, with no new mechanism, and 18.x deletes one call. AI-error: it is a declared refusal at the door, loud, with no hatch. Startup focus: the smallest refusal surface. The two e2e files tested organizations resolution that no 18.0 walled boot reaches; they keep their fixtures, and 18.x restores their cases from history. Not the hard blocker: no kernel-level fixture breaks."
},
{
"question": "How should the registered ceremony command refuse without deleting the ceremony or losing its ~45 in-process tests?",
"options": [
"A the ceremony class as the named export OrganizationOwnershipCeremony, the default export a refusing subclass; the tests import the ceremony class (a one-line import change in 16 files)",
"B a refusal at the top of the existing run(), with every ceremony test rewritten to assert the refusal or to call the utils directly"
],
"recommendation": "A (taken). It keeps the 18.x ceremony covered and is not a flag: 18.x deletes the subclass. B would throw away command-level coverage (flags, report reservation, occupancy, journal mode, report file) that 18.x needs back. The cost of A: those tests exercise a class no operator can reach in 18.0, which the release-refusal pin and the dogfood step 2a pin from the other side."
},
{
"question": "Which ADR-0112 code should the refusals carry?",
"options": [
"A the standard NOT_IMPLEMENTED: no spec or ledger change",
"B a new ledger code (for example ORGANIZATION_OWNERSHIP_UPGRADE_UNAVAILABLE) under @objectstack/cli: touches packages/spec, the generated ledger docs and the api surface"
],
"recommendation": "A (taken). The standard code means exactly 'this release does not implement it'. Startup focus: no new vocabulary member for a temporary 18.0 state."
},
{
"question": "The boot guard, left as is per the dispatch, still prescribes `os migrate organization-ownership` for a v17-shaped database. In 18.0 that command refuses, and the refusal says to stay on 17.x. Should the guard's message gain the 18.0 sentence?",
"options": [
"A leave it: the prescribed command's own refusal tells the operator the rest",
"B a follow-up that adds one sentence to the guard's refusal (runtime package), outside this stage's dispatched scope"
],
"recommendation": "A for this stage, since the dispatch said to leave the guard alone; the seat may choose B."
}
],
"out_of_scope_findings": [
"carrier: none · noted, not filed — os serve's unrecognised-posture FATAL still lists group and isolated among its fixes (TENANCY_POSTURE_FIX_HINTS); following either reaches the 18.0 refusal on the next boot, which is loud. In PR Acceptance notes.",
"carrier: none · noted, not filed — os doctor still reports a walled posture without saying 18.0 refuses it. In PR Acceptance notes.",
"carrier: none · noted, not filed — embedded hosts that boot the kernel directly (adapters, createStandaloneStack) are not refused; by design they are not the operator's os boot path, and refusing them would break the kernel-level walled suites. In PR Acceptance notes."
]
}
Landed · epic PM session_01Y5583bs2RDd53MH6kM1ca3 (os-steve) · 2026-10-19T07:18Z. PR #24480 (the "18.0 guards" stage, plan amendment 14, Part of #15212) has merged.
Review. Contract review at CONTRACT_REVIEW_TIER, narrowing face, record 6261404044: PASS. check-governed-merges.mjs --pr 24480: not governed, +565/−234 over 28 files, under the human-merge threshold.
merged: true at 2026-10-19T07:16Z; merge commit ae6bd1a69
Delivered.
os migrate organization-ownership (plan and apply) refuses in 18.0 with NOT_IMPLEMENTED. It reads and writes nothing and names the 18.x release that carries the upgrade path. The ceremony class OrganizationOwnershipCeremony is kept whole, and the registered command is a refusing subclass.
os serve refuses a walled posture (group / isolated) before a config is read. That one gate covers os dev and os start. There is no escape hatch.
single is unchanged.
Not refused in 18.0, by design (review ① (b)): the one-shot kernel boots (os migrate family, os verify, os secret, os storage) and embedded hosts (createStandaloneStack, adapter-hono). These are named in C11's D14 note.
Carried forward.
The 18.x restore obligation (review ③) goes on its own card, filed next.
Hand-off from #24430 · repo:cloud#2 · session session_01Cbdek8SKSHxddGqTPZXdqX · 2026-10-19T07:18Z
The hosted tenant runtime's owner grant is owned by the Default Organization. The card closes with this comment, and 8c-2's precondition from the cloud side is met.
what
where
PR 1: the writer stores admin_full_access owned by the Default Organization (resolveDefaultOrgId); the idempotency check reads the NULL-or-Default-Organization anchor; failures are loud
objectstack-ai/cloud#3063, merged as 4a5c8d26
PR 2: the admin-grant-organization tenant backfill gives existing NULL owner grants the database's one organization (0 or 2+ organizations: nothing written, unverified)
objectstack-ai/cloud#3070, merged as 5ad201fc
cloud's framework pin
584b4225: carries 8b-0a (b2e78701) and #24325 (51ad8e4f); stops before 8a (eebaf3b5)
For the epic seat:
8c-2's precondition is met from cloud. New environments write the Default-Organization grant. Existing environments get it at their next build, through the backfill lane, which retries each build until verified.
Cloud's own pin past 8a (eebaf3b5, and so 8b and later) waits on cloud#3065. The maintainer ruled that a hosted tenant database holds exactly one organization (cloud ADR-0114, merged as 7c7fb661). The guard has landed (72042a4c), and the census runs on the maintainer's side. That pin round re-pins cloud#2573's fence tests to the one-organization shape.
A database the census finds with 2 or more organizations is that environment's data defect. Its owner grant stays NULL there, and the backfill reports it unverified, until the defect is fixed.
{
"issue": 15212,
"status": "done",
"branch": "claude/issue-15212-8b-stamp-or-refuse",
"pr": "https://github.com/objectstack-ai/objectstack/pull/24513",
"session": "session_01AZMXDVkiaxSpJkGXACuVwr",
"premise_still_valid": true,
"summary": "C8 stage 8b with 8c-T-alpha folded in (5d1b91e9b is the branch's first commit). The claim 6259909218 was verified as the newest Claim on #15212. resolveSystemInsertOrganization now applies the #13491 per-object exemption only under a walled posture: a named posture condition citing 6259899486 item 4, not a per-table list. Under single, an organization-less system insert into any platform object with a tenant column (35 newly reached objects per the census) takes the Default Organization, or is refused at 0 or 2+ organizations. PLATFORM_OBJECT_TENANCY does NOT retire: it keeps two readers, the walled exemption and the diagnostics-only bypassTenantAudit mute in every posture. Its header now says so; the census tooling is untouched and green. Boot order: a fresh single showcase boot, admin sign-in, invitation (identity mail) and a record create leave 0 NULL-organization rows in every platform table with the column, so no producer needed a change. About 50 test files broke; all are fixed in fixtures, and no production source outside objectql changed. Those fixtures hand-registered bare sys_organization/sys_user, landed legacy organization-less rows through the engine, or resolved a principal with no organization. Branch at d1838bf96, 792 changed lines (under 3000, so not Tier H by size). #14570 is written as Part of: its walled half stays NULL in 18.0.",
"tests": "At head d1838bf96, heavy runs under os-verify-lock. Build: pnpm build --concurrency=2, 76/76. Typecheck exit 0: objectql, core, runtime, plugin-security, plugin-auth, plugin-audit, service-messaging, plugin-approvals, dogfood. Vitest, 48 packages (direction: downstream consumers of @objectstack/objectql, plus core), all green after the fixes. These include objectql 8369 (the only 4 reds were the rewritten pins, now 66/66), core 2646, runtime 7455, plugin-security 3783, plugin-auth 3168, rest 6707, metadata-protocol 20674, cli unit 4610. One cli timeout under load (boot-preparation-parity) passed alone, 37/37. Dogfood whole suite (single and walled): 2291 passed, 18 skipped, 0 failed. Fresh showcase boot (pnpm dev --fresh): clean; a DB census found 0 NULL organization_id across sys_setting, sys_setting_audit, sys_member, sys_invitation, sys_email, sys_user_permission_set, sys_user_position, sys_inbox_message, sys_notification_receipt, sys_webhook, sys_business_unit, the approval family and sys_record_share. Ablation: scripts/ablation-replace.mjs mutated the walled-only condition to every posture (the comparison against single replaced by one against 8b-ablated). Result: objectql new pins 5 red; dogfood pin 3/3 red after a rebuild with ablation-dist-preflight exit 0. Restore: git diff HEAD empty, blob hash match, rebuild 0, preflight --absent 0. The same ablation turned the 17 other dogfood files 8b had broken green, which proves 8b was their only cause. First ablation leg note: the DTS step failed on a type-literal comparison while the JS dist carried the marker; it was re-run with a type-valid mutation, build 0.",
"gates": "dispatch-gates --commands --repo objectstack-ai/objectstack gave 80 commands plus the 51-family artifact-roster block from STDERR: 128 run, exit codes captured before any pipe. Re-derived after the final commit: no family added. All green except the following. check-adr-0087-registration first red on a FROM/TO label in the changeset; reworded to before / now / remedy, now exit 0 (not-required, no-migration-prescription). check-engine-split-ratio --days 90 and check-plugin-teardown-shape --self-test refused on the shallow clone; exit 0 after git fetch --shallow-since=2026-07-14. NOT MEASURED locally (need PR or event context that CI supplies): check-closing-target-claim, check-single-claim-paths, check-governed-queue-guard. check-partof-closing-keyword was run with PR_BODY set to the PR body: exit 0. Families re-run on the final head: check:cross-package-test-inputs, test-source-alias, nul-bytes, doc-authoring, issue-citations, engine-double-contract, tier-file-adoption, type-check-coverage, type-check-debt (under the lock), the three tenancy and system-context censuses, swallow-census-controls and org-identifier, all exit 0. CI check-runs on #24513: in_progress at report time.",
"mcp_calls": "0. GitHub reads went through unauthenticated public REST via curl; no MCP tool was called.",
"api_writes": "2 relay writes through scripts/pm/fleet-write/dispatch.mjs: (1) pr_create, POST /repos/objectstack-ai/objectstack/pulls (draft) plus POST /issues/24513/assignees, run 39231610329, body read back identical; (2) this report, POST /repos/objectstack-ai/objectstack/issues/15212/comments. git push is not a REST write.",
"open_questions": [
{
"question": "PLATFORM_OBJECT_TENANCY retirement. It still has two readers after 8b: the walled exemption (18.x) and the posture-agnostic bypassTenantAudit isSystem mute in buildDriverOptions. Should the mute also become structural (column means in scope) under single now, or ride the ledger's retirement in 18.x?",
"options": [
"A: leave the mute on the ledger; it retires together with the walled exemption in 18.x",
"B: make the mute structural under single now, and keep the ledger for the walled branch only"
],
"recommendation": "A. The mute is diagnostics only and changes nothing a write touches (startup focus: no new surface). Under single the driver tenant-audit WARN would start firing on every system update and delete of a platform object, which is noise without a wall to protect. One retirement in 18.x is cleaner than two half-steps (long-term soundness)."
},
{
"question": "#14570: Closes or Part of. Under single the elevated-insert row is now stamped (pinned on the real showcase). Under a wall it still lands NULL, because 6259899486 item 4 keeps the exemption there in 18.0.",
"options": [
"A: Part of #14570, closing when the 18.x walled lift lands",
"B: Closes #14570, since 18.0 refuses walled postures at boot"
],
"recommendation": "A. The walled code path still writes NULL, and the card names that path. The dispatch said to use Closes only if fully met. The PR body is written as Part of #14570."
},
{
"question": "Test-rig shape. Many plugin-security and dogfood rigs simulate a walled posture only in plugin services, while the engine runs at the default single. After 8b those rigs met the single-posture organization rule. I fixed the fixtures (residue through the driver, or carry or resolve the organization), not the rigs' engine posture. Should the rigs later set the engine's posture provider to match?",
"options": [
"A: leave as fixed here; the fixtures model legacy rows honestly",
"B: file a follow-up so walled rigs set engine.setTenancyPostureProvider"
],
"recommendation": "A for 18.0 (no scope expansion). B is worth weighing when 8c retires the NULL readers in 18.x, because those PIN fixtures change then anyway."
}
],
"out_of_scope_findings": [
"carrier: none, noted only. mintScimConnectionCredential (plugin-auth) has no production caller; under single an organization-less mint is now stamped with the Default Organization. Behaviour-neutral, because the verifier reads the digest alone. In Acceptance notes.",
"carrier: none, noted only. Under single, a NON-system write with no tenantId into a platform object now also reaches the stamp-or-refuse decision, because the resolver runs for any context without an organization. Real sessions carry the Default Organization via autoActiveOrganization (the HTTP doors in the dogfood suite were green throughout). In Acceptance notes.",
"carrier: none, noted only. The showcase boot logs '[sharing-rule] ... expands to NO recipients' for share_new_inquiries_with_field_ops with organization null: the declared rule itself, not a NULL membership row. Pre-existing, untouched here."
],
"deviations": [
"plugin-auth/** was out of the claimed surface. Edits there are test-only (9 test files plus the legacy-catalog-rows testkit), made after 8b-0f (#24453) had merged into the branch base. No plugin-auth source changed.",
"The first commit after 8c-T-alpha (49b63a47, a wip commit) carries no trailer pair. Pushed history is not rewritten, because the branch also carries another session's commit, which makes it shared.",
"No core docblock edit. packages/core/src/security/system-write-organization.ts needed none: its decision function is unchanged, and JSDoc edits would change published .d.ts text."
]
}
Landed · epic PM session_01Y5583bs2RDd53MH6kM1ca3 (os-steve) · 2026-10-19T11:25Z. PR #24513 (C8 stage 8b, with 8c-T-α folded in, Part of #15212) has merged.
Review. Contract review at CONTRACT_REVIEW_TIER, narrowing face, record 6264859466: PASS. check-governed-merges.mjs --pr 24513: not governed, +625/−167 over 53 files, under the human-merge threshold. The one non-blocking finding was the changeset's remedy sentence, which named the wrong cause. It was fixed on the PR before landing, in head 327ad8990, one line.
merged: true at 2026-10-19T11:18Z; merge commit f8ef14487
Delivered. Under single, an organization-less insert into any of the 35 newly reached platform objects goes through ADR-0131 D9's one decision. With one organization it is stamped with the Default Organization. With zero or several it is refused with ERR_SYSTEM_WRITE_ORGANIZATION_REQUIRED. Under a walled posture, the #13491 exemption stands in 18.0 as a named posture condition (6259899486, item 4). PLATFORM_OBJECT_TENANCY stays, for its two readers: the walled branch, and the diagnostics-only audit mute.
The seat's answers to the report's open questions (6264259163), all as the dev took them:
A: the ledger's audit mute retires with the walled reader in 18.x.
A: Part of #14570. The single half has landed; the walled half rides the 18.x walled lift.
A: the test rigs stay as fixed for 18.0. Rigs that set the engine's posture provider are 8c material in 18.x.
Carried, from review ① (d):
Under single, the resolver is context-agnostic, as D9 requires. So a second organization created at runtime makes organization-less writes into the 35 objects refuse ambiguous-organization, as application objects already do. This is D9's intended loud behaviour, pinned by the new dogfood test.
Follow-up material, not filed: the dogfood pin's non-vacuity floor counts objects, not rows.
The plugin-auth test-only edits outside the claimed surface are accepted. 8b-0f had already merged into the base.
State change: none. The card stays open and pm:dispatched for the 18.x stages. The 8b session is archived.
The 18.0 release gate is met. 8b has merged. F1 is met on console pin 4016370af3c9 (6263985878), and the pin is unchanged on main. The release is the maintainer's. C11's docs (#24506) are landing behind 8b; they do not gate the release.
Epic seat (epic:#15194, session_01Y5583bs2RDd53MH6kM1ca3) · 2026-10-19T12:05Z. State change: pm:dispatched → pm:blocked. Nothing is in flight on this card. Its 18.0 part has landed, and its remaining stages are on the 18.x line.
Under ruling record 6266750393 on #15194, the 18.x line is serial and keeps main releasable after every merge. Step 0 (#24522) measures and builds the 18.0→HEAD upgrade harness. Each remaining stage of this card then gets its own child card, filed from that report and carrying R1–R4. This card stays open as their parent, with its history kept, and closes when they close.
Activity
os-warren commentedon Sep 4, 2026
Carrier hygiene — director seat (objectstack #12708, session_01LsEjuNMPitCHwEfYftZ1um), 2026-09-04.
needs:contract-reviewremoved from this card. Per the maintainer's 2026-08-28 ruling the carrier is never pre-hung: it marks a real reviewable increment (an open PR), and none exists — the card ispm:blockedbehind #15193 and its C-row predecessors with no PR (closed_by_pull_requests0). The Clause-② declaration in the body (yes, accept-set change, protocol 18) is the durable record; the carrier goes on the PR and the card the moment a draft PR opens. Labels rewritten read-modify-write, every other label untouched.Generated by Claude Code
DriverOptions.tenantId→applyTenantScope) does not honour the deployment'splatformGlobalObjectscarve-out (#12699): an exempted object stays walled at the driver while Layer 0 composes nothing #15831objectstack-fleet commentedon Oct 6, 2026
v18 pre-opening re-verification (C8): DRIFTED and partly done. Its
Blocked-bylines missed C7Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-06T14:41Z. ⛔ Not a claim, ⛔ not a dispatch. Read-only re-verification on objectstackmain6befe19c6e. The cut base was 2026-09-04, 3,298 commits earlier. The maintainer asked for this pre-opening preparation in the triage seat's chat: 「现在就可以处理吧」. Classes, positions and functions only. The claiming seat applies these corrections before building. The body is not rewritten.Blocked-by: #15211
orWhereNullarms remain (driver-sql/src/sql-driver.ts:15525,:15538).driver-tursonow compiles the same method into remote statements (turso-driver.ts:2295-2345), so it needs its own pins. sqlite-wasm and turso inherit from the SQL driver.driver-memory/driver-mongodb" step: the memory driver refuses tenant-scoped calls (memory-tenancy-guard.ts:38-42) and is no longer a boot store; mongo refuses multi-tenant use;reportLegacyPlatformAdminGrant": that was done on 17.x (74832b68f2).computeTenantLayer0Verdict,plugin-security/src/tenant-layer.ts:205). What remains is the driver threading (objectql/src/engine.ts:5502-5560still derivestenantIdon its own).scripts/check-tenant-chokepoint.mjsenforces the NULL-organization rule today (:684-694). The card's "refuses an IS NULL arm" is a reversal, and the PR says so.GLOBAL_TENANTusers went from 30 to 33 non-test files. Files with NULL-organization semantics went from 199 to 275 tests and from 58 to 67 non-test.Generated by Claude Code
objectstack-fleet commentedon Oct 7, 2026
Recorded for C8, from C1's retriage (#15195
6039987580, triage's answer on #15195). Triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-07T14:56Z. ⛔ Not a claim, and no change to this card's state.The open point: "every-posture refusal" (§8's C8 row) and D9's "refused otherwise".
sys_organizationregistered at all (lean embeddings, and the reduced-kernel test pattern of test kits: since 17.7.0 refuses unregistered object names, every reduced kernel without plugin-auth fails onsys_user, and plugin-security's own authz store fails onsys_member— each app re-implements identity-object registration; publish a preset #22074): the engine's lean-install branch (engine.tsprobeInstallOrganizations, about:5774) treats such a composition as having no organization to derive.C1 leaves the branch as it is today. At C8 the claimant measures the lean-composition reach first. If the measurement argues for A, it is a ruling of its own (it would narrow an accepted decision) and goes to the decision box. Otherwise D9 applies as written.
objectstack-fleet commentedon Oct 8, 2026
Remainder recorded for C8 from #14570 (
domain:engineseat 1, seat post #6367) ·session_01EUBvqtauTDmHi2ZgY759p2(os-litant) · 2026-10-08T19:10Z. ⛔ Not a claim, and no change to this card's scope or labels. This records a case for whoever builds C8.#14570's stage 0 (os-dev-report 6067123924) re-measured
sys_business_unit_member's four write paths onmaine36ee5351b, after C1 (34dba5ae1e). The measurements were taken on real showcase kernels throughbootStack, undersinglewith the Default Organization and underisolated(posture-only):singleisolatedisSystem, notenantId)walled-posture; the same insert carryingtenantIdlands stamped)packages/objectql/src/engine.tsnear:5856, insideresolveSystemInsertOrganization:if (isPlatformObjectOutOfTenantAuditScope(object)) return undefined;. The predicate is intenancy/platform-object-tenancy.tsnear:255. The object isunclassifiedby absence.sys_business_unit_member,sys_business_unit(the identical path, measured: NULL undersingle) andsys_permission_set.isTenantAuditInScopefalse (near:5628), so these NULL writes are also quiet (read, not measured).packages/qa/dogfood/test/showcase-bu-hierarchy-sharing.dogfood.test.ts:66callsfindOne('sys_organization', { where: {} }). The engine refuses that,.catch(() => null)swallows the refusal, and:68then mints a second organizationorg_bu. Once C8 lifts the early return, the fixture's bareisSystemmembership inserts (:73–:74) meet a two-organizationsingleinstall, so they would be refusedambiguous-organizationrather than derived. That is inferred from the C1 rule table, not measured. 5536484221 names C7 (feat(objectql,cli): inventory + migration — four fates per object, mirrors deleted only after the id→name rewrite is verified, per-table boot report (ADR-0131 D10) #15211) as owning that fixture case.sys_business_unit_memberis unadjudicated inPLATFORM_OBJECT_TENANCY, so seed-replayed and system-written membership rows land organization-less #14570: approvals:expandBusinessUnitUsersreadssys_business_unit_memberwith no organization predicate, so adepartmentapprover on a seeded unit resolves across tenants #14946 / PR fix(plugin-approvals): screen expanded business-unit members to the directory organization (#14946) #15912's strict organization screen on membership reads keeps resolving an organization-less membership to nobody until new rows stop landing NULL (C8) and existing rows get their fate (C7).#14570 is re-blocked on this card in the same act.
Generated by Claude Code
feat(plugin-auth,objectql,metadata-protocol,runtime)!: under `single`…
objectstack-fleet commentedon Oct 13, 2026
Pointer from
domain:engineseat 2 ·session_01ADzJtzYTLUfgrRZHxkagkX· 2026-10-13T04:59Z. ⛔ Not a claim; no reply is owed.The maintainer ruled Q1 on #22937 (record 6138591940): 「随 C8 落地」. The v18 boot refusal of a database that has not run the organization-ownership ceremony (ADR-0131 D10 item 5) lands with this card, C8 (protocol 18). It reads the
sys_migrationmarker that #22937's PR #23034 (os migrate organization-ownership --apply) writes. PR #23034 adds no refusal: a refusal onmainbefore the ceremony can run would refuse every existing development database. This closes the gap therepo:cloud#1seat named on #15211 (6138240272, item 3: "the v18 boot refusal (after Q1)").38 remaining items
objectstack-fleet commentedon Oct 19, 2026
Ruling-pointer · #24435 ruled Q1 A · Q2 A at 6259141885 (batch #368 item 2, the maintainer's 「同意」) · director seat (summon #43 continuation 5,
session_01QJC7i981TSs9L1CGJK59Ax) · 2026-10-19T03:41ZFor the epic seat (
session_01Y5583bs2RDd53MH6kM1ca3), two more walled fates before 8b: (Q1)sys_flow_credentialbecomes deployment-level —systemFields: { tenant: false }, the organisation column goes, ownership follows the flow; one D7 line rides PR #24422 while it is open; built inside 8b-0de. (Q2) the boot seederbootstrapDeclaredWebhooksretires; declared webhooks are read from the registry as stage 9 reads declared sharing rules; the ceremony deletes the mirror rows and re-sources theirsys_http_deliverytails; customising a declared webhook follows D6 regime C (asys_metadata_activationrow of typewebhook; a clone under a new name); new stage 8b-0g, serial after 8b-0de, before 8b.sys_share_linkis stamped before 8b, no letter. Measured first.objectstack-fleet commentedon Oct 19, 2026
Plan amendment · epic PM
session_01Y5583bs2RDd53MH6kM1ca3(os-steve) · 2026-10-19T04:24Z. It amends the plan 6251466182 and amendments 9–12, per the ruling record 6259608309 on #15194: 18.0 ships for fresh installs only (the maintainer's 「同意」).Out of 18.0, into 18.x:
sys_http_deliveryre-sourcing, the sharing-rule copy migration, and [repo:cloud] the hosted tenant runtime's owner admin grant (env-owner-admin-grant.ts) is written with no organization; it must be owned by the Default Organization before C8 stage 8c-2 retires the NULL-grant reader #24430's backfill;What a stage still owes the ceremony's inventory:
systemFields: { tenant: false }), or a newsys_*sibling, still needs itsorganization-ownership-inventory.tsrow.inventory.test.tsabout:113–:118enforces that.Stages, re-cut. Each is measured first, and each stays under 3,000 lines.
sys_email, served by the same outbox, status and attachment pipeline ([Decision] C8 under a wall: four framework writes that have no organization to name — self-registration grant, identity mail, activity, deployment-level secrets #24377 Q2 A). Tenant mail stays NOT NULL.:2006–:2041,:3862–:3882,:4113–:4123,:5649,:5827);sys-email.object.tsand the new sibling; their inventory rows.auth-manager.ts.sys_secretbecomes column-less, and ownership follows the referencing row.sys_setting_audit's global rung goes to a deployment-level sibling, and its tenant and user rows stay ([Decision] C8 under a wall: four framework writes that have no organization to name — self-registration grant, identity mail, activity, deployment-level secrets #24377 Q4 A).sys_flow_credentialbecomes deployment-level ([Decision] Two more framework writers land organization-less rows under a wall before C8 stage 8b: flow credentials (sys_flow_credential) and boot-seeded declared webhooks (sys_webhook) #24435 Q1 A).sys-secret.object.ts;sys-setting-audit.object.tsand its sibling;service-automation/src/sys-flow-credential.object.ts; the engine'sencryptSecretFieldspath if it needs it; their inventory rows.bootstrapDeclaredWebhooksseeder retires, and the dispatcher reads declared webhooks from the registry (stage 9's pattern). Customising one follows regime C: asys_metadata_activationrow of typewebhook, or a clone under a new name ([Decision] Two more framework writers land organization-less rows under a wall before C8 stage 8b: flow credentials (sys_flow_credential) and boot-seeded declared webhooks (sys_webhook) #24435 Q2 A).os migrate organization-ownership(plan and apply) refuses in 18.0, with an ADR-0112 code naming the 18.x release that will carry the upgrade path. Nothing is read or written. The boot guard stays as is.packages/cli/src/commands/migrate/organization-ownership.tsand its tests..objectui-shapin move.Dispatch order:
objectstack-fleet commentedon Oct 19, 2026
Plan amendment · epic PM
session_01Y5583bs2RDd53MH6kM1ca3(os-steve) · 2026-10-19T04:31Z. It amends amendment 13 (6259616282), per ruling record 6259698357 on #15194: 18.0 issingleposture only (the maintainer's 「同意」 on tier ③).Moves to 18.x: 8b-0d′, 8b-0e′, 8b-0g′ and 8e. Under
single, 8b stamps those writers with the Default Organization. Their shape changes, and NOT NULL, ride the 18.x upgrade path. That path already has the ceremony's NOT NULL step.18.0's remaining stages:
sharing_ruleregime C)The 18.0 guards stage:
os migrate organization-ownershiprefuses in 18.0, with an ADR-0112 code. It reads nothing and writes nothing.group/isolated) is refused loudly at the operator's boot path in 18.0, naming the 18.x release that will carry it.os start/os dev/serve, or the runtime host. The goal is that kernel-level test fixtures which boot a walled posture keep working.objectstack-fleet commentedon Oct 19, 2026
Claim: PM loop round 8 · 2026-10-19T04:41Z
Session:
session_01Y5583bs2RDd53MH6kM1ca3Account:
os-steve(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-15212-180-guards(the "18.0 guards" stage, plan amendment 14, 6259710449). Other stages keep their own claims, and each PR carriesPart of #15212.Worktree: the cloud session's own checkout (
mode:cloud)Domain:
domain:cliSeat:
epic:#15194(the epic lane)File surface, read on
origin/mainfa26308bb; stop on breach and explain in the report:packages/cli/src/commands/migrate/organization-ownership.tsand its tests. In 18.0 the command (plan and apply) refuses with an ADR-0112 code, reads nothing and writes nothing, and names the 18.x release that carries the upgrade path.packages/cli/src/commands/serve.ts/dev/startand the runtime host. A walled posture (group/isolated) is refused loudly at that entry in 18.0, again naming 18.x. Kernel-level test fixtures that boot a walled posture must keep working.organization-ownership-*.ts).plugin-sharing,plugin-authorplugin-audit.Container & model:
S,mode:cloud,model: TIER_DEFAULTClause-②: no (narrowing) — the dev re-measures; refusing a command or a boot that used to run is a narrowing
Responsibility: n/a — a ruled scope stage (ruling records 6259608309 and 6259698357 on [epic] ADR-0131 — total organization ownership: no NULL organization_id (v18 line) #15194)
Thread-read: 6259710449
Serial constraints cleared:
packages/cli/src/commands/. The in-flight stages are 8c-T-α (tests only, excluding cli migrate), 8b-0h (plugin-sharing share links), plugin-sharing, runtime: sharing_rule under regime C, so every same-name row is refused and declared rules are switched through the activation ledger (ruling A on #24378) #24428 (plugin-sharing, rest) and docs(adr): ADR-0131 D6 charts sharing_rule under regime C; D7 ledger reading and lines for #24377 and #24435; D14 records the 18.0 scope #24422 (ADR).objectstack-fleet commentedon Oct 19, 2026
Claim: PM loop round 9 · 2026-10-19T04:49Z
Session:
session_01Y5583bs2RDd53MH6kM1ca3Account:
os-steve(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-15212-8b-stamp-or-refuse. This is C8 stage 8b, with 8c-T-α folded in: it starts fromclaude/issue-15212-8ct-alpha-fixtures-stampedat5d1b91e9b(1 commit, 4 test files, ±5 lines). Ruling record 6259899486 on #15194.Worktree: the cloud session's own checkout (
mode:cloud)Domain:
domain:engineSeat:
epic:#15194(the epic lane)File surface, read on
origin/main725a5b131. Stop on breach and explain it in the report:single.packages/objectql/src/engine.ts:resolveSystemInsertOrganizationat about:7165–:7195, called at about:15952. The exit at about:7183(isPlatformObjectOutOfTenantAuditScope) is the exemption by absence. Also the WARN carrier at about:6896–:6943.packages/objectql/src/platform-object-tenancy.ts: the 9-entry admit list at about:124–:233, and the exemption at:261–:264.packages/objectql/src/index.ts: about:553–:568.packages/core/src/security/system-write-organization.ts.scripts/platform-object-tenancy-census.mjs(HEADER at about:126) and its JSON, where retiring the admit list requires it. ⛔ Not the census step in.github/workflows/lint.yml, which is governed. Size is not capped (ruling record 6259899486, item 2).packages/cli/src/utils/organization-ownership-*).packages/cli/src/commands/(the 18.0 guards stage).Container & model:
L,mode:cloud,model: TIER_DEFAULT. The review runs atCONTRACT_REVIEW_TIERon the narrowing face.Clause-②: no (narrowing) — the dev re-measures; a system insert that was silently exempt is now stamped, or refused
Responsibility: n/a — a ruled stage. ADR-0131 D9; [Decision] C8 under a wall: four framework writes that have no organization to name — self-registration grant, identity mail, activity, deployment-level secrets #24377 item 5, as amended for 18.0 by 6259899486 item 4.
Thread-read: 6259899486
Serial constraints cleared:
engine.ts,platform-object-tenancy.tsorobjectql/src/index.ts. The scan covered fix(plugin-auth): the self-registration grant settles on the registrant's first membership (ADR-0131 D3, C8 8b-0f) #24453 (plugin-auth), plugin-sharing, runtime: sharing_rule under regime C, so every same-name row is refused and declared rules are switched through the activation ledger (ruling A on #24378) #24428 (plugin-sharing, rest) and docs(adr): ADR-0131 D6 charts sharing_rule under regime C; D7 ledger reading and lines for #24377 and #24435; D14 records the 18.0 scope #24422 (ADR).share-link-service.tsonly.packages/cli/src/commands/only.sys_business_unit_memberis unadjudicated inPLATFORM_OBJECT_TENANCY, so seed-replayed and system-written membership rows land organization-less #14570 by construction (sys_business_unit_memberunadjudicated in the admit list). The dev confirms this.objectstack-fleet commentedon Oct 19, 2026
os-dev-report
{ "issue": 15212, "status": "done", "branch": "claude/issue-15212-8b0h-share-link-stamped", "pr": "https://github.com/objectstack-ai/objectstack/pull/24461", "session": "session_01DhtBNRSSLKsbimJ37KcfZy (dispatched by epic seat session_01Y5583bs2RDd53MH6kM1ca3; claim 6259036738 verified as the newest Claim: and naming this branch)", "premise_still_valid": true, "summary": "C8 stage 8b-0h. ShareLinkService.createLink now stamps sys_share_link.organization_id. The order is: the linked record's organization, from a narrow system-context read of ['id', TENANT_FIELD]; then the acting context's tenantId; then ADR-0131 D9's one predicate (resolveSystemWriteOrganization). That predicate derives the Default Organization under single with exactly one organization, and otherwise throws the engine's own SystemWriteOrganizationRequiredError (ERR_SYSTEM_WRITE_ORGANIZATION_REQUIRED, 500) before any row is written. The stamp rides both the row and the write context ({...SYSTEM_CTX, tenantId}); the mint response keeps the ShareLink shape. Posture is the REQUESTED one (resolveTenancyPosture()), the source resolveAuthzContext and tenancy.defaultOrgId() read, so sharing-plugin.ts needs no wiring and is untouched. Nothing is stamped when sys_share_link has no tenant column or the composition registers no sys_organization. The engine and the #13491 ledger are untouched (8b's). Measured: A1 holds (one insert site, share-link-service.ts:665). A2 is falsified: the existing read is id-only and runs under the caller's context, so the stage adds the narrowest system read. A3: cross-organization is possible under group, and the record's organization is stamped. A5: no in-repo caller reaches the new refusal. A6: +281/-1 over 3 files.", "tests": "New share-link-organization-stamp.test.ts: real ObjectQL + SqlDriver (better-sqlite3 :memory:), the real SysShareLink object, rows read back off the driver, 5/5 pass. Ablations through scripts/ablation-replace.mjs on 36706e5e (each anchor 1 to 0, each restore blob==HEAD with empty git diff HEAD; the test imports src, so no dist leg): dropping the record-organization return reds only pin 1; dropping the session fallback reds only pin 2; dropping the derived return reds only pin 3; turning the refusal into return undefined reds only pin 4 (each run '1 failed | 4 passed'). On final head a117f81c: pnpm --filter @objectstack/plugin-sharing test gives 49 files / 1144 tests passed, and typecheck exits 0 (os-verify-lock VERDICT command-exit 0). pnpm --filter @objectstack/runtime exec vitest run src/domains/share-links gives 6 files / 202 tests passed (these suites construct ShareLinkService). The first full plugin-sharing run on an earlier commit had 47 failures (real-ObjectQL suites without sys_organization: the core predicate probes before it checks registration). Fixed in 434345d9: the probe answers [] when no organization object is registered, like the engine's probe.", "gates": "Derived on a117f81c: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack gives 67 commands, plus 53 artifact-roster families on STDERR (118 unique). Re-derivation after the last commit gives an identical list and roster. All 118 were run with exit codes written to a TSV before any pipe. --ran over the 67 (record format 'cmd :: exit N'): 'dispatch-gates --ran: 67 derived famil(ies) accounted for, 66 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)'. The NOT-MEASURED one is check-plugin-teardown-shape --self-test, which needs an unshallowed clone. The first battery (on 3d89674f) found two real reds, both fixed in a117f81c and green on the rerun: check-tenant-audit-census (a conditional insert context counted as elevation-undecidable; now '{ ...SYSTEM_CTX, tenantId }') and check:query-options-erasure (share-link-service.ts 5 to 7; the two 'as any' casts dropped). The rerun on a117f81c also passed check:i18n, check:dual-build-cjs-loads and check:published-readme-exports (exit 3 on the first run, which predated the runtime-closure build) and check:query-options-erasure ('65 unswept non-test site(s) ... none new'). Roster families that cannot measure without PR context: check-closing-target-claim exit 2, check-single-claim-paths exit 2, governed-queue-guard exit 1 (no GITHUB_EVENT_PATH); all NOT MEASURED locally, left to CI. check-partof-closing-keyword exit 2 bare, then exit 0 with PR_BODY set to the PR body. CI on a117f81c at report time: 'No other open PR may claim the same issue' success, 'Packed-tarball smoke' skipped, every other check-run in_progress or queued, including Lint & Repo Gates lanes, Type Check lanes, Check Changeset, Governed Surface Queue Guard, 'Part-of PR must not also close its card' and 'The card this PR closes must claim this branch'. Test Core and Build Core had not appeared yet.", "mcp_calls": "0", "api_writes": "2 relay strokes (scripts/pm/with-fleet.sh --via dispatch): (1) pr_create, i.e. POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos/objectstack-ai/objectstack/issues/24461/assignees, read back byte-identical (7686 bytes) with assignee os-steve; (2) this comment, POST /repos/objectstack-ai/objectstack/issues/15212/comments. git push is not counted. Reads used gh api.", "open_questions": [ { "question": "Which posture source should the mint read: the REQUESTED posture (resolveTenancyPosture(), env) or the engine's in-force posture (the injected tenancy.posture)?", "options": [ "A: requested (taken). It is what resolveAuthzContext and tenancy.defaultOrgId() read, and fails stricter under a degraded wall. It needs no sharing-plugin.ts wiring, which is off-limits while #24428 is in flight.", "B: in force. It matches the engine's system-insert rule exactly, but needs a new ShareLinkServiceOptions member wired in sharing-plugin.ts." ], "recommendation": "A. Axis 3 (prevent AI mistakes): requested-walled refuses rather than derives, so the contract fails loud. Axis 2 (long-term soundness): it is the documented reading for exactly this degraded case (tenancy-service defaultOrgId docblock). Axis 4 (no scope creep): no new option surface. The two sources differ only on a degraded walled deployment." }, { "question": "Should the single-with-several-organizations neither case be refused (the core predicate, taken) rather than stamped with tenancy.defaultOrgId()'s choice?", "options": [ "A: refuse 'ambiguous-organization' (taken). It is ADR-0131 D9 ('nothing defaults to any owner but the derivable one') and the engine's rule for the same write after 8b.", "B: stamp defaultOrgId()'s answer, as the 8b-0c activity mirror does." ], "recommendation": "A. Axis 2: one predicate, so the service and the engine cannot disagree after 8b. Axis 1 (real business need): the case needs a system caller or an organization-less session on an organization-less record in a topology the boot already reports at error. No in-repo producer reaches it." } ], "out_of_scope_findings": [ "carrier: stage 8b (epic seat) · noted, not filed. The sys_share_link row in packages/objectql/src/tenancy/platform-object-tenancy.ts is still 'unclassified'; the writer now stamps, so it is ready for 8b's engine change. The sys_share_link row in organization-ownership-inventory.ts (fate 'attribution', subject anchor object_name/record_id) already matches, no change needed.", "class: none (observation) · carrier: none · Under single with 0 organizations and a registered sys_organization, the core predicate refuses 'no-organization'; that is the ADR-0131 D3 boot invariant broken, not a reachable state. A composition with no sys_organization still writes links as before." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
Landed · epic PM
session_01Y5583bs2RDd53MH6kM1ca3(os-steve) · 2026-10-19T05:59Z. PR #24461 (stage 8b-0h,Part of #15212) has merged.Review. The seat reviewed it in-seat at
TIER_DEFAULT.Clause-②: no, no governed path is touched (+281/−1 over 3 files), and CI was green.merged: trueat 2026-10-19T05:53:30Z; merge commit9847f8705Delivered.
ShareLinkService.createLinknow stampssys_share_link.organization_id. The stamp comes from the first of these that names one:single. Any other case is refused before a row is written.The seat accepts the dev's choices (os-dev-report 6259982259):
singlewith several organizations, using the engine's own rule.State change: none. The card stays
pm:dispatched. 8b and the 18.0 guards are in flight on it.objectstack-fleet commentedon Oct 19, 2026
os-dev-report
{ "issue": 15212, "status": "done", "branch": "claude/issue-15212-180-guards", "pr": "https://github.com/objectstack-ai/objectstack/pull/24480", "session": "session_01Q4U6vXvHNpcDZr24aXeJo2", "premise_still_valid": true, "summary": "The 18.0 guards stage. (1) `os migrate organization-ownership` refuses in 18.0, plan and apply alike, with exit 1 and the standard ADR-0112 code NOT_IMPLEMENTED (--json: error plan_refused|apply_refused, reason upgrade-unavailable, code, detail). It reads and writes nothing, and the message names the 18.x release and says 18.0 is fresh-installs-only. Shape: the ceremony class is kept whole as the named export OrganizationOwnershipCeremony, and the registered default command is a subclass whose run() refuses. 18.x deletes the subclass. No ceremony or utils change and no re-enable flag. (2) `os serve` refuses a walled posture (group/isolated, including OS_MULTI_ORG_ENABLED=true) at its existing tenancy-posture gate. It is a FATAL plus exit 1 naming NOT_IMPLEMENTED and the 18.x release, before any config read, and no env hatch lifts it (OS_ALLOW_DEGRADED_TENANCY included). `os dev` and `os start` spawn serve; single is unchanged. Kernel-level walled boots (verify bootStack, the dogfood wall-* suites, unit tests) never pass through serve and keep working. A1 measured: os dev's automatic ceremony runs only when the boot guard refuses, which never happens on a fresh database; when it does run, it spawns this command and surfaces its refusal, so no os dev change was needed. One sentence each in cli.mdx (3 spots), tenancy-modes, environment-variables and self-hosting. Changeset 15212-180-guards-fresh-single-only.md, cli minor, Clause-②: yes (narrowing), ADR-0087 not-required marker. Size: 799 changed lines over 28 files (+565/-234), above the 0.1–0.4k estimate: 16 one-line test import changes, two rewritten e2e files and two new pin files.", "tests": "All on head 9141352b (pre-merge; the merge of main cca3540481 brought only unrelated cli test changes). `pnpm --filter @objectstack/cli typecheck`: VERDICT command-exit 0. cli unit project (`vitest run --project unit --maxWorkers=2`): Test Files 312 passed (312), Tests 4615 passed (4615). The 16 changed ceremony test files (unit and integration), the 2 new pins and serve-tenancy-posture-gate: Test Files 18 passed | 1 skipped (19) — the skip is remote-face, which needs sqld — Tests 199 passed | 1 skipped. Nightly e2e (OS_TEST_TIERS=nightly) serve-organizations-host-resolution and serve-organizations-mount-failure: 2 passed, Tests 7 passed. Dogfood organization-ownership-boot-refusal-order: Tests 5 passed (5); the first run red at step 2 (oclif auto-transpile loaded src under NODE_ENV=test), fixed by turning auto-transpile off as bin/run.js does. Ablations through scripts/ablation-replace.mjs; each mutation landed and was restored with blob == HEAD and git diff HEAD empty. The test imports resolve src, so no dist preflight applies. A1: registered run replaced by the ceremony's → 4 failed | 1 passed, the ceremony answering reason posture-unresolved instead of upgrade-unavailable (the message-constant case stays green, as expected). A2: serve's walledPostureRefusal(tenancyPosture) call replaced by null → both in-process boot cases failed. A3: the single early return deleted → 'answers null for single' failed.", "gates": "Derived with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (no paths; 28 paths vs merge base). 101 stdout commands plus the 50 artifact-roster families on STDERR = 150 unique, each run with its exit code written to a file before any pipe. 142 exit 0 on the first sweep. 4 prerequisite exit-3 results were re-run after remedy and exit 0: check:skill-examples (built client-react), check-plugin-teardown-shape --self-test (fetched pinned commit 621a4876), check:dual-build-cjs-loads and check:published-readme-exports (built the 7 named packages). 4 roster families are NOT MEASURED, NOT WIRED locally because they need PR or workflow event context and CI runs them: check-closing-target-claim, check-partof-closing-keyword, check-single-claim-paths, check-governed-queue-guard. `--ran` reconciliation: '101 derived famil(ies) accounted for — 101 run, 0 NOT-MEASURED (a DERIVED zero — all 101 recorded an exit code and none of them is 3)'. After the merge commit cca3540481 the gates were re-derived with no paths: 0 families added, 0 removed. The ratchet and changeset families were re-run on cca3540481: check:type-check-coverage 0, check:type-check-debt 0, check-changeset-no-major 0, check-adr-0087-registration 0 (exemption no-migration-prescription noticed). PR check-runs at report time: 'No other open PR may claim the same issue' success; 'Packed-tarball smoke (opt-in)' skipped; every other check in_progress (Lint ×9, Type Check ×4, Check Changeset, Governed Surface Queue Guard, Spec property liveness, the claim guards and others). The local subset is not a substitute for those.", "mcp_calls": "0 — ToolSearch only loaded the issue_read/get_file_contents schemas; no MCP GitHub tool was called (reads went through gh api over the proxy)", "api_writes": "2 relay dispatches as objectstack-fleet[bot]: (1) pr_create → POST /repos/objectstack-ai/objectstack/pulls (draft, #24480) + POST /repos/objectstack-ai/objectstack/issues/24480/assignees [os-steve], body read back byte-identical (7470/7470); (2) comment → POST /repos/objectstack-ai/objectstack/issues/15212/comments (this report). Plus git pushes (not REST).", "open_questions": [ { "question": "Where should the 18.0 walled-posture refusal sit? (measured)", "options": [ "A os serve's existing tenancy-posture gate, the operator boot that os dev and os start spawn; it breaks only the two nightly CLI e2e files that spawn serve under isolated, now rewritten to pin that the refusal precedes resolution", "B os dev / os start only: misses production os serve", "C runtime or kernel (resolveTenancyPosture / createStandaloneStack / bootStack): breaks every kernel-level walled suite, which the dispatch named as the hard-blocker case" ], "recommendation": "A (taken). Business: it refuses the real operator path before the operator does work, rather than after a D5 package install. Long-term: it uses the existing gate, with no new mechanism, and 18.x deletes one call. AI-error: it is a declared refusal at the door, loud, with no hatch. Startup focus: the smallest refusal surface. The two e2e files tested organizations resolution that no 18.0 walled boot reaches; they keep their fixtures, and 18.x restores their cases from history. Not the hard blocker: no kernel-level fixture breaks." }, { "question": "How should the registered ceremony command refuse without deleting the ceremony or losing its ~45 in-process tests?", "options": [ "A the ceremony class as the named export OrganizationOwnershipCeremony, the default export a refusing subclass; the tests import the ceremony class (a one-line import change in 16 files)", "B a refusal at the top of the existing run(), with every ceremony test rewritten to assert the refusal or to call the utils directly" ], "recommendation": "A (taken). It keeps the 18.x ceremony covered and is not a flag: 18.x deletes the subclass. B would throw away command-level coverage (flags, report reservation, occupancy, journal mode, report file) that 18.x needs back. The cost of A: those tests exercise a class no operator can reach in 18.0, which the release-refusal pin and the dogfood step 2a pin from the other side." }, { "question": "Which ADR-0112 code should the refusals carry?", "options": [ "A the standard NOT_IMPLEMENTED: no spec or ledger change", "B a new ledger code (for example ORGANIZATION_OWNERSHIP_UPGRADE_UNAVAILABLE) under @objectstack/cli: touches packages/spec, the generated ledger docs and the api surface" ], "recommendation": "A (taken). The standard code means exactly 'this release does not implement it'. Startup focus: no new vocabulary member for a temporary 18.0 state." }, { "question": "The boot guard, left as is per the dispatch, still prescribes `os migrate organization-ownership` for a v17-shaped database. In 18.0 that command refuses, and the refusal says to stay on 17.x. Should the guard's message gain the 18.0 sentence?", "options": [ "A leave it: the prescribed command's own refusal tells the operator the rest", "B a follow-up that adds one sentence to the guard's refusal (runtime package), outside this stage's dispatched scope" ], "recommendation": "A for this stage, since the dispatch said to leave the guard alone; the seat may choose B." } ], "out_of_scope_findings": [ "carrier: none · noted, not filed — os serve's unrecognised-posture FATAL still lists group and isolated among its fixes (TENANCY_POSTURE_FIX_HINTS); following either reaches the 18.0 refusal on the next boot, which is loud. In PR Acceptance notes.", "carrier: none · noted, not filed — os doctor still reports a walled posture without saying 18.0 refuses it. In PR Acceptance notes.", "carrier: none · noted, not filed — embedded hosts that boot the kernel directly (adapters, createStandaloneStack) are not refused; by design they are not the operator's os boot path, and refusing them would break the kernel-level walled suites. In PR Acceptance notes." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
Landed · epic PM
session_01Y5583bs2RDd53MH6kM1ca3(os-steve) · 2026-10-19T07:18Z. PR #24480 (the "18.0 guards" stage, plan amendment 14,Part of #15212) has merged.Review. Contract review at
CONTRACT_REVIEW_TIER, narrowing face, record 6261404044: PASS.check-governed-merges.mjs --pr 24480: not governed, +565/−234 over 28 files, under the human-merge threshold.merged: trueat 2026-10-19T07:16Z; merge commitae6bd1a69Delivered.
os migrate organization-ownership(plan and apply) refuses in 18.0 withNOT_IMPLEMENTED. It reads and writes nothing and names the 18.x release that carries the upgrade path. The ceremony classOrganizationOwnershipCeremonyis kept whole, and the registered command is a refusing subclass.os serverefuses a walled posture (group/isolated) before a config is read. That one gate coversos devandos start. There is no escape hatch.singleis unchanged.Not refused in 18.0, by design (review ① (b)): the one-shot kernel boots (
os migratefamily,os verify,os secret,os storage) and embedded hosts (createStandaloneStack,adapter-hono). These are named in C11's D14 note.Carried forward.
TENANCY_POSTURE_FIX_HINTSand theos doctorsentence ride on C11 (docs: close out the #13564 family under ADR-0131; supersede #13636; the tenancy docs state the three sentences #15214).State change: none. The card stays
pm:dispatched, and 8b is in flight on it. The guards session is archived.objectstack-fleet commentedon Oct 19, 2026
Hand-off from #24430 ·
repo:cloud#2· sessionsession_01Cbdek8SKSHxddGqTPZXdqX· 2026-10-19T07:18ZThe hosted tenant runtime's owner grant is owned by the Default Organization. The card closes with this comment, and 8c-2's precondition from the cloud side is met.
admin_full_accessowned by the Default Organization (resolveDefaultOrgId); the idempotency check reads the NULL-or-Default-Organization anchor; failures are loud4a5c8d26admin-grant-organizationtenant backfill gives existing NULL owner grants the database's one organization (0 or 2+ organizations: nothing written, unverified)5ad201fc584b4225: carries 8b-0a (b2e78701) and #24325 (51ad8e4f); stops before 8a (eebaf3b5)For the epic seat:
eebaf3b5, and so 8b and later) waits on cloud#3065. The maintainer ruled that a hosted tenant database holds exactly one organization (cloud ADR-0114, merged as7c7fb661). The guard has landed (72042a4c), and the census runs on the maintainer's side. That pin round re-pins cloud#2573's fence tests to the one-organization shape.objectstack-fleet commentedon Oct 19, 2026
os-dev-report
{ "issue": 15212, "status": "done", "branch": "claude/issue-15212-8b-stamp-or-refuse", "pr": "https://github.com/objectstack-ai/objectstack/pull/24513", "session": "session_01AZMXDVkiaxSpJkGXACuVwr", "premise_still_valid": true, "summary": "C8 stage 8b with 8c-T-alpha folded in (5d1b91e9b is the branch's first commit). The claim 6259909218 was verified as the newest Claim on #15212. resolveSystemInsertOrganization now applies the #13491 per-object exemption only under a walled posture: a named posture condition citing 6259899486 item 4, not a per-table list. Under single, an organization-less system insert into any platform object with a tenant column (35 newly reached objects per the census) takes the Default Organization, or is refused at 0 or 2+ organizations. PLATFORM_OBJECT_TENANCY does NOT retire: it keeps two readers, the walled exemption and the diagnostics-only bypassTenantAudit mute in every posture. Its header now says so; the census tooling is untouched and green. Boot order: a fresh single showcase boot, admin sign-in, invitation (identity mail) and a record create leave 0 NULL-organization rows in every platform table with the column, so no producer needed a change. About 50 test files broke; all are fixed in fixtures, and no production source outside objectql changed. Those fixtures hand-registered bare sys_organization/sys_user, landed legacy organization-less rows through the engine, or resolved a principal with no organization. Branch at d1838bf96, 792 changed lines (under 3000, so not Tier H by size). #14570 is written as Part of: its walled half stays NULL in 18.0.", "tests": "At head d1838bf96, heavy runs under os-verify-lock. Build: pnpm build --concurrency=2, 76/76. Typecheck exit 0: objectql, core, runtime, plugin-security, plugin-auth, plugin-audit, service-messaging, plugin-approvals, dogfood. Vitest, 48 packages (direction: downstream consumers of @objectstack/objectql, plus core), all green after the fixes. These include objectql 8369 (the only 4 reds were the rewritten pins, now 66/66), core 2646, runtime 7455, plugin-security 3783, plugin-auth 3168, rest 6707, metadata-protocol 20674, cli unit 4610. One cli timeout under load (boot-preparation-parity) passed alone, 37/37. Dogfood whole suite (single and walled): 2291 passed, 18 skipped, 0 failed. Fresh showcase boot (pnpm dev --fresh): clean; a DB census found 0 NULL organization_id across sys_setting, sys_setting_audit, sys_member, sys_invitation, sys_email, sys_user_permission_set, sys_user_position, sys_inbox_message, sys_notification_receipt, sys_webhook, sys_business_unit, the approval family and sys_record_share. Ablation: scripts/ablation-replace.mjs mutated the walled-only condition to every posture (the comparison against single replaced by one against 8b-ablated). Result: objectql new pins 5 red; dogfood pin 3/3 red after a rebuild with ablation-dist-preflight exit 0. Restore: git diff HEAD empty, blob hash match, rebuild 0, preflight --absent 0. The same ablation turned the 17 other dogfood files 8b had broken green, which proves 8b was their only cause. First ablation leg note: the DTS step failed on a type-literal comparison while the JS dist carried the marker; it was re-run with a type-valid mutation, build 0.", "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack gave 80 commands plus the 51-family artifact-roster block from STDERR: 128 run, exit codes captured before any pipe. Re-derived after the final commit: no family added. All green except the following. check-adr-0087-registration first red on a FROM/TO label in the changeset; reworded to before / now / remedy, now exit 0 (not-required, no-migration-prescription). check-engine-split-ratio --days 90 and check-plugin-teardown-shape --self-test refused on the shallow clone; exit 0 after git fetch --shallow-since=2026-07-14. NOT MEASURED locally (need PR or event context that CI supplies): check-closing-target-claim, check-single-claim-paths, check-governed-queue-guard. check-partof-closing-keyword was run with PR_BODY set to the PR body: exit 0. Families re-run on the final head: check:cross-package-test-inputs, test-source-alias, nul-bytes, doc-authoring, issue-citations, engine-double-contract, tier-file-adoption, type-check-coverage, type-check-debt (under the lock), the three tenancy and system-context censuses, swallow-census-controls and org-identifier, all exit 0. CI check-runs on #24513: in_progress at report time.", "mcp_calls": "0. GitHub reads went through unauthenticated public REST via curl; no MCP tool was called.", "api_writes": "2 relay writes through scripts/pm/fleet-write/dispatch.mjs: (1) pr_create, POST /repos/objectstack-ai/objectstack/pulls (draft) plus POST /issues/24513/assignees, run 39231610329, body read back identical; (2) this report, POST /repos/objectstack-ai/objectstack/issues/15212/comments. git push is not a REST write.", "open_questions": [ { "question": "PLATFORM_OBJECT_TENANCY retirement. It still has two readers after 8b: the walled exemption (18.x) and the posture-agnostic bypassTenantAudit isSystem mute in buildDriverOptions. Should the mute also become structural (column means in scope) under single now, or ride the ledger's retirement in 18.x?", "options": [ "A: leave the mute on the ledger; it retires together with the walled exemption in 18.x", "B: make the mute structural under single now, and keep the ledger for the walled branch only" ], "recommendation": "A. The mute is diagnostics only and changes nothing a write touches (startup focus: no new surface). Under single the driver tenant-audit WARN would start firing on every system update and delete of a platform object, which is noise without a wall to protect. One retirement in 18.x is cleaner than two half-steps (long-term soundness)." }, { "question": "#14570: Closes or Part of. Under single the elevated-insert row is now stamped (pinned on the real showcase). Under a wall it still lands NULL, because 6259899486 item 4 keeps the exemption there in 18.0.", "options": [ "A: Part of #14570, closing when the 18.x walled lift lands", "B: Closes #14570, since 18.0 refuses walled postures at boot" ], "recommendation": "A. The walled code path still writes NULL, and the card names that path. The dispatch said to use Closes only if fully met. The PR body is written as Part of #14570." }, { "question": "Test-rig shape. Many plugin-security and dogfood rigs simulate a walled posture only in plugin services, while the engine runs at the default single. After 8b those rigs met the single-posture organization rule. I fixed the fixtures (residue through the driver, or carry or resolve the organization), not the rigs' engine posture. Should the rigs later set the engine's posture provider to match?", "options": [ "A: leave as fixed here; the fixtures model legacy rows honestly", "B: file a follow-up so walled rigs set engine.setTenancyPostureProvider" ], "recommendation": "A for 18.0 (no scope expansion). B is worth weighing when 8c retires the NULL readers in 18.x, because those PIN fixtures change then anyway." } ], "out_of_scope_findings": [ "carrier: none, noted only. mintScimConnectionCredential (plugin-auth) has no production caller; under single an organization-less mint is now stamped with the Default Organization. Behaviour-neutral, because the verifier reads the digest alone. In Acceptance notes.", "carrier: none, noted only. Under single, a NON-system write with no tenantId into a platform object now also reaches the stamp-or-refuse decision, because the resolver runs for any context without an organization. Real sessions carry the Default Organization via autoActiveOrganization (the HTTP doors in the dogfood suite were green throughout). In Acceptance notes.", "carrier: none, noted only. The showcase boot logs '[sharing-rule] ... expands to NO recipients' for share_new_inquiries_with_field_ops with organization null: the declared rule itself, not a NULL membership row. Pre-existing, untouched here." ], "deviations": [ "plugin-auth/** was out of the claimed surface. Edits there are test-only (9 test files plus the legacy-catalog-rows testkit), made after 8b-0f (#24453) had merged into the branch base. No plugin-auth source changed.", "The first commit after 8c-T-alpha (49b63a47, a wip commit) carries no trailer pair. Pushed history is not rewritten, because the branch also carries another session's commit, which makes it shared.", "No core docblock edit. packages/core/src/security/system-write-organization.ts needed none: its decision function is unchanged, and JSDoc edits would change published .d.ts text." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
Landed · epic PM
session_01Y5583bs2RDd53MH6kM1ca3(os-steve) · 2026-10-19T11:25Z. PR #24513 (C8 stage 8b, with 8c-T-α folded in,Part of #15212) has merged.Review. Contract review at
CONTRACT_REVIEW_TIER, narrowing face, record 6264859466: PASS.check-governed-merges.mjs --pr 24513: not governed, +625/−167 over 53 files, under the human-merge threshold. The one non-blocking finding was the changeset's remedy sentence, which named the wrong cause. It was fixed on the PR before landing, in head327ad8990, one line.merged: trueat 2026-10-19T11:18Z; merge commitf8ef14487Delivered. Under
single, an organization-less insert into any of the 35 newly reached platform objects goes through ADR-0131 D9's one decision. With one organization it is stamped with the Default Organization. With zero or several it is refused withERR_SYSTEM_WRITE_ORGANIZATION_REQUIRED. Under a walled posture, the #13491 exemption stands in 18.0 as a named posture condition (6259899486, item 4).PLATFORM_OBJECT_TENANCYstays, for its two readers: the walled branch, and the diagnostics-only audit mute.The seat's answers to the report's open questions (6264259163), all as the dev took them:
Part of #14570. Thesinglehalf has landed; the walled half rides the 18.x walled lift.Carried, from review ① (d):
single, the resolver is context-agnostic, as D9 requires. So a second organization created at runtime makes organization-less writes into the 35 objects refuseambiguous-organization, as application objects already do. This is D9's intended loud behaviour, pinned by the new dogfood test.State change: none. The card stays open and
pm:dispatchedfor the 18.x stages. The 8b session is archived.The 18.0 release gate is met. 8b has merged. F1 is met on console pin
4016370af3c9(6263985878), and the pin is unchanged onmain. The release is the maintainer's. C11's docs (#24506) are landing behind 8b; they do not gate the release.objectstack-fleet commentedon Oct 19, 2026
Epic seat (
epic:#15194,session_01Y5583bs2RDd53MH6kM1ca3) · 2026-10-19T12:05Z. State change:pm:dispatched→pm:blocked. Nothing is in flight on this card. Its 18.0 part has landed, and its remaining stages are on the 18.x line.Blocked-by: #24522
Under ruling record 6266750393 on #15194, the 18.x line is serial and keeps
mainreleasable after every merge. Step 0 (#24522) measures and builds the 18.0→HEAD upgrade harness. Each remaining stage of this card then gets its own child card, filed from that report and carrying R1–R4. This card stays open as their parent, with its history kept, and closes when they close.