Skip to content

Commit 5e80009

Browse files
docs(qa): re-word three v18 checklist clauses the 18.0 QA sweep found mis-asserted (#24592)
Fixes #24543 Refs #24567 (rows 1–2) Clause-②: no ## What this changes Three platform-checklist clauses that the 18.0 QA sweep scored `fail` are **assertion defects**: the product is right and the clause is wrong. This PR re-words the three clauses, and nothing else: no product code, no test, no pin. Each item gets `revision` 1 → 2 and one `history` entry that names its QA source. The entry's `ref` is this PR, set by a one-token follow-up commit once the number existed. | item | fields edited | revision | QA source | |---|---|---|---| | `platform-core.v18-boot-refusals` | `acceptance[2]` (clause, verify, evidence), `steps[0]` | 1 → 2 | QA run #24556, acceptance[2], P0; card #24567 row 1 | | `access-security.security-catalog-one-holder-and-boot-report` | `acceptance[1]` (clause, verify, evidence), `title` (install-local parenthetical) | 1 → 2 | QA run #24558, acceptance[1]; card #24567 row 2 (folded from #24568) | | `approvals.act-door-and-admin-rescue-slate` | `acceptance[1]` (clause, verify, evidence), `title` (PUT leg) | 1 → 2 | QA run #24542, clause 1; card #24543 | Files: `docs/qa/platform-checklist/areas/platform-core.json`, `docs/qa/platform-checklist/areas/access-security.json`, `docs/qa/platform-checklist/areas/approvals.json`. ## 1 · `platform-core.v18-boot-refusals` acceptance[2] (rev 2) Before: ```text clause: the refused boot did not alter the database verify: checksum before == after evidence: the two checksums steps[0]: checksum the v17 DB; run `os serve` (v18) against it with -d file:COPY.db; read the output and exit code; checksum again ``` After: ```text clause: the refused boot wrote no row and ran no D10 step: every table the v17 copy held keeps every row and every value in the columns it held (no row added, deleted or changed — so no column dropped, no mirror row deleted, no organization_id attributed), every table the boot created is empty, and `sys_migration` holds no `adr-0131-organization-ownership` marker. The additive schema sync the engine runs before the guard judges (new tables, new columns, new indexes) is expected and is not a failure verify: a row-level diff of the two SQLite snapshots from steps[0], read through SQLite so the `-wal` is included: for each table present before, its pre-existing columns compared row by row (ordered by primary key) — no difference; for each table present only after, its row count — 0; `SELECT id FROM sys_migration WHERE id = 'adr-0131-organization-ownership'` — no row. ⛔ Not a file checksum: in WAL mode the boot's DDL can sit in COPY.db-wal with the main file's hash unchanged, and the additive sync changes the hash with no row written, so a checksum answers neither question evidence: per-table row counts before and after, the (empty) row diff over the pre-existing columns, the tables the boot created with their row counts, and the sys_migration read steps[0]: snapshot the v17 copy through SQLite — its schema (`sqlite_master`, `pragma_table_info` per table) and every table's rows, read with the sqlite3 CLI or node:sqlite so a `-wal` file is included (copy the v17 DB together with its `-wal`, or checkpoint the original before copying); run `os serve` (v18) against the copy with `OS_DATABASE_URL=file:COPY.db` (`os serve` has no `-d` flag); read the output and exit code; snapshot again and diff the two ``` Why the new clause is true of the code, re-measured against the source at this PR's base: - `packages/runtime/src/organization-ownership-boot-guard.ts` (module doc, "When it judges"): the guard judges in its own `start()`, ordered after the engine, whose `start()` connects the drivers and runs the additive schema sync, and ahead of every plugin that writes at start. The doc also says the ceremony's boot "must write nothing before it is refused". So schema additions on a refused boot are the design, and rows written would be the defect. The new clause asserts exactly that. - `docs/adr/0131-total-organization-ownership-no-null-organization-id.md` D10: the four fates (column dropped, mirror deleted, attributed, reported) are the ceremony's. D10 item 5 says the refusing runtime "never auto-migrates". Nothing in D10 forbids additive sync. Each fate is visible in the row diff as a lost column, a lost row or a changed `organization_id`. The ceremony's completion marker is `ORGANIZATION_OWNERSHIP_MIGRATION_ID = 'adr-0131-organization-ownership'` in `DATA_MIGRATION_FLAG_OBJECT = 'sys_migration'` (`packages/spec/src/system/migration.zod.ts`). - `steps[0]`: `os serve` declares only `port/dev/ui/console/server/prebuilt/preset/log-level/verbose/cert/key` (`packages/cli/src/commands/serve.ts`, `Serve.flags`). There is no `-d` flag, so the old step is an invocation error. The QA run booted with `OS_DATABASE_URL`. The step had to change anyway, because it was the checksum procedure the old verify read. The `-d` correction rides along; see Deviations. ## 2 · `access-security.security-catalog-one-holder-and-boot-report` acceptance[1] (rev 2) Before: ```text clause: install-local of a package redeclaring a catalog name → 422 NAMESPACE_CONFLICT (or PLUGIN_REGISTER_FAILED) with conflicts[], and nothing is recorded as installed verify: status + code + conflicts; package list unchanged evidence: refusal body + the list title: … two packages declaring one permission-set / position name refuse boot (422 NAMESPACE_CONFLICT on install-local), … ``` After: ```text clause: install-local of an inline manifest (a `{ manifest }` body) whose package redeclares a catalog name → 422 PLUGIN_REGISTER_FAILED whose message names each conflicting name and its holder, and nothing is recorded as installed verify: status + error.code + error.message: every redeclared catalog name (permission set, position or capability) appears in the message with the package, environment or built-in that already holds it; GET /api/v1/packages lists the same packages before and after. ⛔ No `conflicts[]` is expected on this door: it answers its own registered PLUGIN_REGISTER_FAILED and interpolates only the register error's message (that route's entry in packages/spec/src/api/error-code-ledger.zod.ts); the structured `conflicts` ride the thrown SecurityCatalogNameConflictError, which the boot-refusal pins in automated.ref assert evidence: the refusal body + the package list before and after title: … two packages declaring one permission-set / position name refuse boot (on install-local, 422 PLUGIN_REGISTER_FAILED naming each name and its holder), … ``` Re-measured against the source: - `packages/cloud-connection/src/marketplace-install-local-plugin.ts`, register step 3: when `manifestService.register` throws and the body carried an inline manifest (`body.manifest`), the door returns `422 { error: { code: 'PLUGIN_REGISTER_FAILED', message: 'Failed to register imported manifest: ' + the error's message } }` before step 4 persists anything. A **cloud-fetched** manifest takes the lenient path instead and IS installed (`200`, `hotLoaded: false`). That is why the clause now names the inline shape: without it, "nothing is recorded as installed" is false on the other source. - `packages/objectql/src/registry.ts`, `SecurityCatalogNameConflictError`: the message lists every conflict as `TYPE "name" is already held by HOLDER`, joined with `; `. So "names each conflicting name and its holder" holds for one conflict and for several. - `packages/spec/src/api/error-code-ledger.zod.ts` documents this door answering its own `PLUGIN_REGISTER_FAILED` at 422 and interpolating only the message. - None of the six `automated.ref` files mentions install-local. A `grep -l` for `install-local|installLocal|marketplace` over them exits 1, and all six paths exist. The `.conflicts` assertions in `packages/runtime/src/standalone-stack-security-catalog-one-holder.test.ts` are made on the boot-refusal throw. - Title: the same false `422 NAMESPACE_CONFLICT on install-local` sat in the title. Leaving it would have left the item contradicting its own clause; see Deviations. - ⛔ No `conflicts[]` enhancement here, as ruled. ## 3 · `approvals.act-door-and-admin-rescue-slate` acceptance[1] (rev 2) Before: ```text clause: GET renders the action page, POST redeems, PUT → 405 with Allow: GET, POST, a dead token → a 200 page (not an error envelope) verify: status + headers per verb evidence: four lines title: … (GET renders, POST redeems, PUT 405 Allow: GET, POST, a dead link a 200 page, …) ``` After: ```text clause: GET renders the action page, POST redeems, a dead token → a 200 page (not an error envelope), and PUT — a method neither wire door claims — is that door's ordinary miss: 404 with no Allow header, never reaching the member (ENDPOINT_NOT_FOUND on the self-hosted `os dev` boot, whose raw-app mount registers only GET and POST; ROUTE_NOT_FOUND on the hosted dispatcher door, whose `/approvals/act` domain claims only GET, HEAD and POST). 405 with Allow: GET, POST is the in-process ApprovalService.handleActionPage member's answer only, which no wire request reaches with a PUT verify: status + headers per verb; for the PUT also error.code and the absence of an Allow header. The member's 405 is not a wire leg: it is pinned in-process by packages/plugins/plugin-approvals/src/action-page-member.integration.test.ts, and the dispatcher's 404 miss by packages/runtime/src/domains/approvals-act.test.ts (both in automated.ref) evidence: four lines (the PUT's with its error.code and its headers) title: … (GET renders, POST redeems, a PUT neither door claims an ordinary 404 miss with no Allow header, a dead link a 200 page, …) ``` Re-measured against the source. The two wire doors miss with **different codes**, and the clause names both: - **Self-hosted** (`os dev` / `os serve`, the RUNNER's boot). `packages/plugins/plugin-approvals/src/approvals-plugin.ts` `mountActionPages` puts `rawApp.get` and `rawApp.post` straight on the raw Hono app, outside the adapter's `registeredRoutes`. A PUT therefore reaches the adapter's `notFound` seam. There `allowedMethodsForPath` finds no registered method for the path, and `unmatchedResponse` answers `404 { error: { code: 'ENDPOINT_NOT_FOUND' } }` with no `Allow` header (`packages/plugins/plugin-hono-server/src/adapter.ts`). That is exactly what the QA run measured, twice; DELETE and PATCH answered the same. - **Hosted** (`createHonoApp` catch-all, no `http.server`). `packages/runtime/src/domains/approvals.ts` declares `APPROVALS_ACT_METHODS = ['GET', 'HEAD', 'POST']`, and every other method "is not claimed and falls through". The item's own pin `packages/runtime/src/domains/approvals-act.test.ts` asserts `{ status: 404, code: 'ROUTE_NOT_FOUND' }` for PUT, with `handleActionPage` never called. - **Member**. `ApprovalService.handleActionPage` (`packages/plugins/plugin-approvals/src/approval-service.ts`) returns `405` with `Allow: GET, POST` for any other method. `action-page-member.integration.test.ts` pins that in-process for PUT, DELETE, PATCH, HEAD and OPTIONS. - The card's text named `ROUTE_NOT_FOUND`, the pin's code, while the QA run on `os dev` saw `ENDPOINT_NOT_FOUND`. Both are right for their door. ## Verification - **QA subject versus this base.** `git merge-base --is-ancestor 5b14b7f de43524` exits 0. `git diff --stat 5b14b7f de43524` over the eleven files these clauses rest on (the boot guard, the install-local plugin, the approvals domain, plugin and service, the hono-server adapter, the registry, the dispatcher plugin, `serve.ts`, both approvals pins) plus the three area files shows one changed file: `packages/objectql/src/registry.ts`, from #24526's bound-action reporting. None of its changed lines touch `SecurityCatalogNameConflictError`. The QA runs' live measurements therefore stand on this base unchanged. - **Gates.** Derived with `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` (change set from git: 3 paths, 48 changed lines; 13 families). All 13 ran at `ad55c68f84`, and every exit code was captured before any pipe: - `node scripts/check-ci-filter-parity.mjs` · exit 0 - `node scripts/check-closing-keyword-parity.mjs` · exit 0 - `node scripts/check-closing-keyword-parity.mjs --self-test` · exit 0 - `node scripts/check-comment-mask-corpus.mjs` · exit 0 - `pnpm --filter @objectstack/lint run check:doc-formula-expressions` · exit 0. The first attempt answered `PREREQUISITE NOT MET` (exit 3: `@objectstack/formula` / `@objectstack/lint` not built). That attempt measured nothing; it was re-run after `turbo run build --filter=@objectstack/formula --filter=@objectstack/lint`, taken under the shared verify lock. - `pnpm check:cross-package-test-inputs` · exit 0 - `pnpm check:doc-authoring` · exit 0 - `pnpm check:driver-memory-census` · exit 0 - `pnpm check:gitlink-declared` · exit 0 - `pnpm check:nul-bytes` · exit 0 - `pnpm check:platform-checklist` · exit 0. Its line: `OK — 16 areas, 327 items …; symbol anchors: 855/865 resolved …, 18 file floors held`. That is the same anchor count as on the untouched base, so no anchor was lost or added. - `pnpm check:refd-timer-probe` · exit 0 - `pnpm check:watch-hint-literal` · exit 0 - `--ran` reconciliation: `13 derived, 13 run, 0 NOT-MEASURED, 0 UNRUN`. - `check:platform-checklist` is not per-PR CI. It runs in the daily watchdog, so the local reading above is its evidence for this PR. - **Cross-package reader.** `packages/plugins/plugin-auth/src/checklist-refusal-envelope-consistency.test.ts` reads every area file for its retired-convention absence regex. Run over all 5715 strings of the three edited files, the regex matches 0 times; its positive control matches. CI's Test Core runs the file itself. - **NOT MEASURED: a live verification re-run** of the three re-worded clauses against a booted showcase. Reason: it needs a full workspace build, a v17 SQLite minted from npm `@objectstack/cli@17.7.0`, and two showcase boots, all in a container shared with another dev agent. The dispatch asked for a source re-measurement, which is above. Each clause's verify step is concrete and runnable: the SQLite row diff (row 1), the 422 body's `code` and `message` plus the package list (row 2), and the PUT's status, `code` and absent `Allow` header (#24543). - Docs-only (`docs/qa/**` is inside no package's published `files`), so `skip-changeset`. ## Deviations (each beyond the three clause texts, inside the three claimed files) - `platform-core.v18-boot-refusals` `steps[0]`: re-worded. It was the checksum procedure the old verify read, and it passed `-d` to `os serve`, which has no such flag. That `-d` was a checklist-accuracy finding of QA run #24556, and the fix is mechanical: the run booted with `OS_DATABASE_URL`. - `access-security.security-catalog-one-holder-and-boot-report` `title`: its install-local parenthetical carried the same false `NAMESPACE_CONFLICT`. - `approvals.act-door-and-admin-rescue-slate` `title`: its PUT leg, as the dispatch allowed. ## Acceptance notes - **Row 1's open question for the maintainer**, quoted unchanged from #24567: > Open question for the maintainer (not a defect on this evidence): the refusal tells the operator to keep this database on a 17.x runtime after the v18 boot has added v18 schema to it. This PR does not answer it. The new clause records the additive schema as expected only because the guard's own contract orders it. - **#24567 row 3 remains open**: the drop-gate refusal text in `packages/cli/src/commands/migrate/apply.ts` and the help example in `organization-ownership.ts` belong to the `domain:cli` lane. This PR leaves `packages/**` untouched. - **Checklist-accuracy findings that the same QA records list for these three items, not taken here.** They close out through the wave's anchor card, per RUNNER's extraction obligation; that card has no number yet. - `platform-core.v18-boot-refusals`: steps[1]'s `os dev` with `services:{auth:false}` is refused earlier on three counts; and clause 1 names the code `ORGANIZATION_OWNERSHIP_MIGRATION_REQUIRED`, which the CLI output does not print. - `access-security.security-catalog-one-holder-and-boot-report`: clause 4's clean-boot info line shows only with `OS_LOG_LEVEL=info`. - `approvals.act-door-and-admin-rescue-slate`: `fixtures.requires` asks for a dev mail transport capture, but the action links already sit in `sys_notification.payload.actions` after a remind. - **Observation, not filed.** `packages/cloud-connection/src/marketplace-install-local-hot-loaded.test.ts` and `marketplace-install-local-convert-forward.test.ts` assert `PLUGIN_REGISTER_FAILED` at this door. Whether either pins the catalog-conflict message, which would make it a candidate for the item's `automated.ref` (RUNNER rule 6, reverse direction), was not judged. ⛔ No pin was changed. --- _Generated by [Claude Code](https://claude.ai/code/session_01FM29ZqLTrnPNXxijioZwMp)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 5a871e4 commit 5e80009

3 files changed

Lines changed: 33 additions & 15 deletions

File tree

‎docs/qa/platform-checklist/areas/access-security.json‎

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3501,10 +3501,10 @@
35013501
},
35023502
{
35033503
"id": "access-security.security-catalog-one-holder-and-boot-report",
3504-
"title": "The security catalog has exactly one holder per name: two packages declaring one permission-set / position name refuse boot (422 NAMESPACE_CONFLICT on install-local), a body packageId that disagrees with its package is refused, `os migrate security-catalog-overlays` clears environment shadows, and boot reports grants naming undefined catalog entries",
3504+
"title": "The security catalog has exactly one holder per name: two packages declaring one permission-set / position name refuse boot (on install-local, 422 PLUGIN_REGISTER_FAILED naming each name and its holder), a body packageId that disagrees with its package is refused, `os migrate security-catalog-overlays` clears environment shadows, and boot reports grants naming undefined catalog entries",
35053505
"since": "v18",
35063506
"status": "active",
3507-
"revision": 1,
3507+
"revision": 2,
35083508
"priority": "P1",
35093509
"surface": "mixed",
35103510
"personas": [
@@ -3532,10 +3532,10 @@
35323532
"evidence": "the vitest summary line per pin file, with the package name and the subject sha"
35333533
},
35343534
{
3535-
"clause": "install-local of a package redeclaring a catalog name → 422 NAMESPACE_CONFLICT (or PLUGIN_REGISTER_FAILED) with conflicts[], and nothing is recorded as installed",
3535+
"clause": "install-local of an inline manifest (a `{ manifest }` body) whose package redeclares a catalog name → 422 PLUGIN_REGISTER_FAILED whose message names each conflicting name and its holder, and nothing is recorded as installed",
35363536
"oracle": "api",
3537-
"verify": "status + code + conflicts; package list unchanged",
3538-
"evidence": "refusal body + the list"
3537+
"verify": "status + error.code + error.message: every redeclared catalog name (permission set, position or capability) appears in the message with the package, environment or built-in that already holds it; GET /api/v1/packages lists the same packages before and after. ⛔ No `conflicts[]` is expected on this door: it answers its own registered PLUGIN_REGISTER_FAILED and interpolates only the register error's message (that route's entry in packages/spec/src/api/error-code-ledger.zod.ts); the structured `conflicts` ride the thrown SecurityCatalogNameConflictError, which the boot-refusal pins in automated.ref assert",
3538+
"evidence": "the refusal body + the package list before and after"
35393539
},
35403540
{
35413541
"clause": "a permission-set save whose body packageId disagrees with the target package → 400 VALIDATION_ERROR at packageId",
@@ -3576,6 +3576,12 @@
35763576
"date": "2026-10-16",
35773577
"change": "initial — authored from the v18 line (18.0.0-next.*) delta: the capability landed on the v18 pre-release line and the ledger carried no `since: \"v18\"` item for it",
35783578
"ref": "#23879"
3579+
},
3580+
{
3581+
"revision": 2,
3582+
"date": "2026-10-19",
3583+
"change": "acceptance[1] (clause, verify, evidence) and the title's install-local parenthetical re-pointed — an assertion defect found by the 18.0 QA sweep (QA run #24558, access-security.security-catalog-one-holder-and-boot-report acceptance[1]: FAIL, confirmed by an independent RUNNER rule 7 re-derivation; card #24567 row 2, folded from #24568). The product is right and the clause was wrong: on an inline manifest the install-local door (packages/cloud-connection/src/marketplace-install-local-plugin.ts) answers its OWN registered 422 PLUGIN_REGISTER_FAILED and interpolates only the register error's message — that route's documented contract in packages/spec/src/api/error-code-ledger.zod.ts — so it never carried NAMESPACE_CONFLICT or a `conflicts[]`, and no pin in automated.ref asserts either at this door. The run measured the refusal twice: the names and their holders inside the message, nothing installed (27 packages before and after). The clause now asserts that; a structured `conflicts[]` on this door would be a separate enhancement decision, not taken here. The other four clauses are unchanged",
3584+
"ref": "#24592"
35793585
}
35803586
]
35813587
},

‎docs/qa/platform-checklist/areas/approvals.json‎

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1556,10 +1556,10 @@
15561556
},
15571557
{
15581558
"id": "approvals.act-door-and-admin-rescue-slate",
1559-
"title": "Approvals on v18: the email action door /approvals/act works through the dispatcher exactly as the self-hosted mount (GET renders, POST redeems, PUT 405 Allow: GET, POST, a dead link a 200 page, a policy-held session not 403'd before the token), and an approval over an unstaffed position opens on the org administrators as ordinary approvers",
1559+
"title": "Approvals on v18: the email action door /approvals/act works through the dispatcher exactly as the self-hosted mount (GET renders, POST redeems, a PUT neither door claims an ordinary 404 miss with no Allow header, a dead link a 200 page, a policy-held session not 403'd before the token), and an approval over an unstaffed position opens on the org administrators as ordinary approvers",
15601560
"since": "v18",
15611561
"status": "active",
1562-
"revision": 1,
1562+
"revision": 2,
15631563
"priority": "P1",
15641564
"surface": "api",
15651565
"personas": [
@@ -1586,10 +1586,10 @@
15861586
"evidence": "the vitest summary line per pin file, with the package name and the subject sha"
15871587
},
15881588
{
1589-
"clause": "GET renders the action page, POST redeems, PUT → 405 with Allow: GET, POST, a dead token → a 200 page (not an error envelope)",
1589+
"clause": "GET renders the action page, POST redeems, a dead token → a 200 page (not an error envelope), and PUT — a method neither wire door claims — is that door's ordinary miss: 404 with no Allow header, never reaching the member (ENDPOINT_NOT_FOUND on the self-hosted `os dev` boot, whose raw-app mount registers only GET and POST; ROUTE_NOT_FOUND on the hosted dispatcher door, whose `/approvals/act` domain claims only GET, HEAD and POST). 405 with Allow: GET, POST is the in-process ApprovalService.handleActionPage member's answer only, which no wire request reaches with a PUT",
15901590
"oracle": "api",
1591-
"verify": "status + headers per verb",
1592-
"evidence": "four lines"
1591+
"verify": "status + headers per verb; for the PUT also error.code and the absence of an Allow header. The member's 405 is not a wire leg: it is pinned in-process by packages/plugins/plugin-approvals/src/action-page-member.integration.test.ts, and the dispatcher's 404 miss by packages/runtime/src/domains/approvals-act.test.ts (both in automated.ref)",
1592+
"evidence": "four lines (the PUT's with its error.code and its headers)"
15931593
},
15941594
{
15951595
"clause": "the unstaffed request appears in each org admin's pending list and the decision is recorded via_override:false",
@@ -1614,6 +1614,12 @@
16141614
"date": "2026-10-16",
16151615
"change": "initial — authored from the v18 line (18.0.0-next.*) delta: the capability landed on the v18 pre-release line and the ledger carried no `since: \"v18\"` item for it; approvals.approver-resolution-matrix still says admin_rescue is decidable only via the privileged override — stale against 22824 (reported, not edited here)",
16161616
"ref": "#23879"
1617+
},
1618+
{
1619+
"revision": 2,
1620+
"date": "2026-10-19",
1621+
"change": "acceptance[1] (clause, verify, evidence) and the title's PUT leg re-worded — an assertion defect found by the 18.0 QA sweep (QA run #24542, approvals.act-door-and-admin-rescue-slate clause 1: FAIL; card #24543). The product is right and the clause was wrong: on a stock `os dev` showcase boot `PUT /api/v1/approvals/act?token=…` answered 404 ENDPOINT_NOT_FOUND with no Allow header (twice; DELETE and PATCH the same), which is the design — the self-hosted mount registers only GET and POST (changeset 22578), the dispatcher domain claims only GET, HEAD and POST and lets every other method fall through (packages/runtime/src/domains/approvals.ts), and the item's own pin packages/runtime/src/domains/approvals-act.test.ts asserts that miss (404 ROUTE_NOT_FOUND) with the member never run. 405 with Allow: GET, POST belongs to the in-process member ApprovalService.handleActionPage alone (packages/plugins/plugin-approvals/src/action-page-member.integration.test.ts). The GET, POST and dead-token legs held and are unchanged, as are the other two clauses",
1622+
"ref": "#24592"
16171623
}
16181624
]
16191625
}

‎docs/qa/platform-checklist/areas/platform-core.json‎

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2977,7 +2977,7 @@
29772977
"title": "A v18 server refuses to boot rather than serve wrongly: against a 17.x-shaped database (a physical organization_id on a non-tenant table, no verified ADR-0131 ceremony marker) it stops before listening with ORGANIZATION_OWNERSHIP_MIGRATION_REQUIRED naming the tables and the command, and with security on but no identity objects it stops with AuthzIdentityObjectsMissingError",
29782978
"since": "v18",
29792979
"status": "active",
2980-
"revision": 1,
2980+
"revision": 2,
29812981
"priority": "P0",
29822982
"surface": "cli",
29832983
"personas": [
@@ -2991,7 +2991,7 @@
29912991
]
29922992
},
29932993
"steps": [
2994-
"checksum the v17 DB; run `os serve` (v18) against it with -d file:COPY.db; read the output and exit code; checksum again",
2994+
"snapshot the v17 copy through SQLite — its schema (`sqlite_master`, `pragma_table_info` per table) and every table's rows, read with the sqlite3 CLI or node:sqlite so a `-wal` file is included (copy the v17 DB together with its `-wal`, or checkpoint the original before copying); run `os serve` (v18) against the copy with `OS_DATABASE_URL=file:COPY.db` (`os serve` has no `-d` flag); read the output and exit code; snapshot again and diff the two",
29952995
"run `os dev` on a scratch config with services:{auth:false} and security on"
29962996
],
29972997
"acceptance": [
@@ -3008,10 +3008,10 @@
30083008
"evidence": "the refusal excerpt"
30093009
},
30103010
{
3011-
"clause": "the refused boot did not alter the database",
3011+
"clause": "the refused boot wrote no row and ran no D10 step: every table the v17 copy held keeps every row and every value in the columns it held (no row added, deleted or changed — so no column dropped, no mirror row deleted, no organization_id attributed), every table the boot created is empty, and `sys_migration` holds no `adr-0131-organization-ownership` marker. The additive schema sync the engine runs before the guard judges (new tables, new columns, new indexes) is expected and is not a failure",
30123012
"oracle": "build",
3013-
"verify": "checksum before == after",
3014-
"evidence": "the two checksums"
3013+
"verify": "a row-level diff of the two SQLite snapshots from steps[0], read through SQLite so the `-wal` is included: for each table present before, its pre-existing columns compared row by row (ordered by primary key) — no difference; for each table present only after, its row count — 0; `SELECT id FROM sys_migration WHERE id = 'adr-0131-organization-ownership'` — no row. ⛔ Not a file checksum: in WAL mode the boot's DDL can sit in COPY.db-wal with the main file's hash unchanged, and the additive sync changes the hash with no row written, so a checksum answers neither question",
3014+
"evidence": "per-table row counts before and after, the (empty) row diff over the pre-existing columns, the tables the boot created with their row counts, and the sys_migration read"
30153015
},
30163016
{
30173017
"clause": "the auth-less security boot is refused naming createIdentityObjectsPlugin(), not served with 503s",
@@ -3043,6 +3043,12 @@
30433043
"date": "2026-10-16",
30443044
"change": "initial — authored from the v18 line (18.0.0-next.*) delta: the capability landed on the v18 pre-release line and the ledger carried no `since: \"v18\"` item for it",
30453045
"ref": "#23879"
3046+
},
3047+
{
3048+
"revision": 2,
3049+
"date": "2026-10-19",
3050+
"change": "acceptance[2] (clause, verify, evidence) and steps[0] re-worded — an assertion defect found by the 18.0 QA sweep (QA run #24556, platform-core.v18-boot-refusals acceptance[2]: FAIL, confirmed by an independent RUNNER rule 7 re-derivation; card #24567 row 1). The product is right and the clause was wrong: the guard judges in its own start(), after the engine (whose start() runs the additive schema sync the ceremony waits for) and ahead of every plugin that writes a row (packages/runtime/src/organization-ownership-boot-guard.ts), and ADR-0131 D10 item 5 says the refusing runtime never auto-migrates (the ceremony's fates); nothing in it forbids additive schema sync. So a refused boot legitimately adds tables, columns and indexes, and 'did not alter the database' (checksum before == after) failed on a correct product. The clause now asserts what the guard promises — no row written, no D10 step run — through a row-level SQLite diff that includes the `-wal` (the run's second boot left the main file's hash unchanged with its DDL in the `-wal`). steps[0] also stops passing `-d` to `os serve`, which declares no such flag (the same run's checklist-accuracy finding); `OS_DATABASE_URL` is what the run booted with. The other three clauses are unchanged",
3051+
"ref": "#24592"
30463052
}
30473053
]
30483054
},

0 commit comments

Comments
 (0)