Skip to content

Commit 693352e

Browse files
docs(qa): 18.0 sweep anchor round 16 — QA round 4's remaining checklist findings and round 15's carried rule-6 texts (#24963)
Refs #24579 Clause-②: no Round 16 of the 18.0 sweep's wave anchor (claim `6297142170`, PM loop round 27). It applies three sets of checklist text. First, QA round 4's remaining checklist-accuracy findings (summary `6288485402`, records #24838 and #24841, framework `307ce4c4`). Second, round 15's carried rule-6 texts (PR #24891's acceptance notes). Third, round 14's two text fixes (report `6289612504` on PR #24857). Only checklist text changes. 27 items in 11 area files each take one `revision` bump and one `history` row, and each row names its record and finding. `RUNNER.md` takes one environment fact: two lines, the entry and its blank separator. There is no product code, test, pin or changeset text. - Base `feb09db14a`. `origin/main` `223ae939bb` is merged in `79a13e85b7`. It touches no `docs/qa/**` path and none of the sources these edits cite. - Every item, pin and table named below was re-read at the base before it was written. The findings that no longer held are listed under "Items left alone". - A scripted span editor made every edit. It is round 15's editor, extended with an array-element removal and an object append, and it never re-serialises a file. For each area file it re-parses the result and asserts four things: - A1: the new document equals the old one with the declared ops applied. - A2: every other item and top-level key is value-equal. - A3: each edited item's changed paths lie inside its declared fields (array edits by exact index), plus `revision` and the one new history row. - A4: the text outside the edited items is byte-identical. - A whole-branch check against the base agrees: 27 items changed, each with revision +1 and exactly one appended row, and no other path changed. - The new history rows carry the placeholder ref `#PENDING-24579R16`, because a row's `ref` names this PR and the PR had no number yet. The next commit on this branch swaps in this PR's number and changes nothing else. ## What a negative is, and the 14 procedural negatives A `negative` names a product outcome whose occurrence is the FAIL. The format documentation already defines it so: the README's item anatomy calls `negative` "the other side of every gate (deny/absence cases)". So `RUNNER.md` takes no line for it (ruling 3). Each of #24841's 14 procedural negatives was handled one of two ways: - **(a)** Where the procedure guards a product outcome that can be stated and measured, and that is not already a negative, the negative is rewritten into that outcome. - **(b)** Otherwise its text moves, unchanged in substance, into `steps`. That is the schema's runner-procedure field ("how to exercise it"). There is no notes field, and `traps` is a closed vocabulary of trap names. The text is appended, so no step index shifts. Where the item already carries the same text in a procedure or notes field, the negative is removed and that carrier is named instead. | # | item | negative | outcome | where it lives now | |:--|:--|:--|:--|:--| | 1 | `automation.packaged-flow-disable-durable` | `negative[3]` | (a) | FAIL: a ledger-disabled flow answered 409 FLOW_DISABLED with the status dimension's bare "Flow NAME is disabled" (engine.ts `describeDisabledFlow`) | | 2 | `access-security.packaged-flow-write-door-parity` | `negative[1]` | (a) | FAIL: PUT or DELETE `/api/v1/automation/...` answers 2xx on the artifact the `/meta` door refused | | 3 | `access-security.packaged-flow-write-door-parity` | `negative[2]` | (b) | `steps[6]` | | 4 | `automation.packaged-flow-subflow-disable-refusal` | `negative[2]` | (b) | `steps[7]` | | 5 | `access-security.owd-save-gate` | `negative[2]` | (b) | already in `fixtures.knownGaps[0]` | | 6 | `search.rls-both-personas` | `negative[1]` | (b) | `steps[6]`; the outcome it guards is `negative[0]` | | 7 | `platform-core.runtime-config-boot-read` | `negative[3]` | (b) | `steps[6]` | | 8 | `cli.build-own-contract` | `negative[2]` | (b) | `steps[9]` | | 9 | `cli.hook-body-extraction-gates` | `negative[4]` | (b) | `steps[6]` | | 10 | `cli.migrate-duplicates-inventory` | `negative[2]` | (b) | `steps[9]`; `negative[3]` is now `negative[2]`, and nothing in the item cites either by index | | 11 | `cli.qa-suite-execution` | `negative[4]` | (b) | `steps[12]` | | 12 | `platform-core.seed-mode-matrix` | `negative[2]` | (b) | already in `steps[5]` and `acceptance[4]` | | 13 | `platform-core.seed-mode-matrix` | `negative[3]` | (b) | already in `fixtures.requires[1]`, which now carries its reason too | | 14 | `platform-core.marketplace-install-local-lifecycle` | `negative[5]` | (b) | already in `fixtures.knownGaps[0]` | Two are (a) and twelve are (b). ## Changes | finding | item | field | old → new | evidence read at `feb09db14a` | |:--|:--|:--|:--|:--| | #24838: no clause for a real 18.0 pre-release database | `cli.organization-ownership-ceremony` | `acceptance[5]`, `steps[5]`, `fixtures.requires[2]` added | none → an `18.0.0-next.*` database is refused at boot, takes the ceremony, and boots with its identity mail, global setting audit, pending mail jobs and declared webhook carried over. Each leg is read at its own layer: the boot output, a row read, the door's answer. The fixture is a database the pre-release's own `os dev` created, plus HTTP writes; without one the clause is `blocked(fixture)`. | The item's title already names "an 18.0 pre-release (18.0.0-next.*)" database. `organization-ownership-apply.ts` `SIBLING_MOVES`: identity mail is `related_object` sys_user plus the magic link, organization invitations stay, global audit moves. The mail-job rewrite covers `email.send.async` and `email.attachment.reclaim`. The oracle is #24838's measured matrix. | | the same | `platform-core.v18-boot-refusals` | `fixtures.knownGaps[0]` added | none → a cross-reference to the ceremony item's `acceptance[5]` | its title scopes a 17.x-shaped database | | #24841: the expiry leg has no executable step | `identity-auth.self-service-password-reset` | `steps[5]` | "let the token expire" → write the token's `sys_verification.expires_at` into the past with node:sqlite, then expect 400 INVALID_TOKEN and an unchanged password | `auth-schema-config.ts` `AUTH_VERIFICATION_CONFIG` maps expiresAt to `expires_at`. No `storeIdentifier` is set, so the row is keyed `reset-password:TOKEN` in plain text. better-auth 1.7.3's `consumeVerificationValue` treats a row past expiresAt as invalid. The TTL is `resetPasswordTokenExpiresIn` or 3600 s. | | #24841 n3 | `cli.migrate-meta-codemod` | `negative[3]`, `steps[11]`, `acceptance[7].verify` | "the files the Wrote block names" → those files plus the declared range's one site. That site is named by its own "Rewrote the declared protocol range, so the load admits protocol N:" block, and `--json` reports it as `write.range`. | `meta.ts` `printWriteOutcome`, `printRangeOutcome` and `writeOutcomeJson` (since #22252). The block also exists at QA's `307ce4c4`. | | #24841: plan log line | `cli.migrate-duplicates-inventory` | `acceptance[3].verify` | "Registered view" only → also the "Overwriting view" line that the seeded same-name pair prints | `registry.ts` `registerItem` prints "Overwriting TYPE: KEY" for a held key, then "Registered TYPE: KEY" on every registration | | #24841: scratch object needs the namespace prefix | `platform-core.seed-mode-matrix` | `steps[0]` | no prefix → `NS_SHORT`. NS is the manifest's namespace, or one derived from the id's last segment. An unprefixed publish answers 422 NAMESPACE_PREFIX. | `namespace-prefix.ts` `validateObjectNamespacePrefix` and `deriveNamespaceFromPackageId`. The package create in `protocol.ts` derives a missing namespace, and the publish doors judge it (ADR-0129 D2). | | #24841: the #4409 fixture needs `requires` | `cli.build-own-contract` | `steps[3]` | no note → keep `requires: ['automation', 'triggers']`, or `defineStack` refuses an auto-launched flow at load | `stack.zod.ts` `validateTriggerCapability`. The blank scaffold and the showcase both declare the pair, so the note covers a trimmed config. | | #24838: console bundled with server probes | `cli.dev-boot-contract` | `acceptance[0]` (clause, verify, evidence), `acceptance[7]` added, `fixtures.requires[2]` re-pointed | health + ready + console → health + ready. The console is its own clause (screenshot), `blocked(environment)` on an unbuilt tree. | `console.ts` `createConsoleNotBuiltPlugin` answers 503 with "The ObjectStack Console is not built …" | | #24838: config-declared default tier unstaged | `cli.dev-boot-contract` | `steps[9]` added, `acceptance[2].verify` | the tier was named and no step staged it → a staging step, marked as derived from source and not replayed live | `resolve-project-database.ts` `readConfigDeclaredDefault` reads the compiled artifact's `{ default: true, datasource: NAME }` rule and that datasource's `config.filename`, resolved against the project root. `dev.ts` compiles only when no artifact exists. | | #24838: console bundled with server probes | `platform-core.boot-health` | `acceptance[3]` (clause, verify, evidence), `acceptance[5]` added | console + app metadata → app metadata; the console is its own clause | `console.ts` | | #24841: procedural negatives | the 12 items above | `negative` (and `steps`, `fixtures.requires[1]` where named) | see the table above | README item anatomy | | #24841: environment | `RUNNER.md` | environment facts | + `rsync` is absent; copy with `tar` or `cp -a` | `command -v rsync` exits 1 here, while `tar` and `cp` exit 0 | | PR #24891: MCP clause's unit fallback | `api-backend.packaged-action-disabled-dispatch` | `fixtures.knownGaps[0]` | "fall back to the unit pin" → `blocked(environment)`, with the pin beside it | `action-activation-dispatch.test.ts` "door 2" calls `invokeBusinessAction` in-process over stubs, with no MCP transport and no HTTP | | PR #24891: engine-pinned arms of api clauses | `automation.packaged-flow-subflow-disable-refusal` | `fixtures.knownGaps[0]`, `negative[1]` | "scored from the unit pins" / "rides the pin" → engine evidence only; the door legs are `blocked(fixture)` without a scratch caller | `flow-activation-ledger.test.ts` runs over a fake store | | PR #24891: a log clause's half | `cli.datasource-introspect-codegen` | `acceptance[0].verify`, `fixtures.knownGaps[2]`, `steps[1]` | "scored from the pins" → `blocked(fixture)`, with the pins beside it | `remote-tables-twin.equivalence.test.ts` (real Hono adapter), the service test, `envelope-unwrap.test.ts` (the CLI over a stubbed fetch) | | PR #24891: wire-shape clauses | `integration-system.connector-auth-kind-application` | `acceptance[0].verify`, `fixtures.knownGaps[0]` | "unit fallback" → `blocked(fixture)`, with the pin beside it | `rest-connector.test.ts` injects a fetch stub (`fetchImpl`) | | PR #24891: legs scored from pins, mixed oracles | `approvals.email-action-token-door` | `fixtures.knownGaps[0]`, `fixtures.knownGaps[2]` | every live-token leg "scored from the pins" → each leg at its own clause's layer | `action-page-member.integration.test.ts` runs a started kernel (driver-sql, in-memory SQLite) on a real Hono app and mints `ttlMs: 1` for the expired page. `approval-service.test.ts` pins the reasons. | | PR #24891: the same | `approvals.act-door-and-admin-rescue-slate` | `fixtures.requires[1]`, `acceptance[1]` (verify, evidence), `automated.ref` | scored from the dispatcher and member pins → each at its layer, and the ref gains `packages/qa/http-conformance/src/hono-approvals-act.conformance.test.ts` | That pin drives `createHonoApp` → `HttpDispatcher` → the real `ApprovalService` member with a live token: "GET renders the confirm page for a live token; a form POST redeems it". `approvals-act.test.ts` uses a stub member. | | PR #24891: ref scope | `integration-system.datasource-admin-lifecycle` | `automated.ref`; `acceptance[3]` (verify, evidence); `steps[8]` | "pins route behavior + envelope" → per clause: `acceptance[7]`, `negative[4]` and `negative[5]` whole, and route-layer halves of `acceptance[0]`, `[2]`, `[4]`, `[6]` and `[8]`. The bind arm, once "scored from the unit pin", is now `blocked(fixture)` with the pin beside it. | Both ref pins mount `registerDatasourceAdminRoutes` on the real `HonoHttpServer` over stubbed services and fake auth. `datasource-credential-migration.test.ts` drives the planner over row objects. | | PR #24891: the REST wording under a `test` oracle | `records-forms.field-unique-enforcement` | `acceptance[4].clause`, `automated.ref` | "answering the same 409 UNIQUE_VIOLATION over REST" → the refusal satisfies `isUniqueViolationError`, measured at SqlDriver through `createAsApi`, never over a REST door | The SQL pins' `createAsApi` and `@objectstack/types` `isUniqueViolationError`; objectql `envelopeUniqueViolation`. `acceptance[0]` to `[3]` drive the REST door live, so no knownGaps line is owed. | | PR #24857's report | `records-forms.crud-roundtrip` | `acceptance[4].verify` | "showcase Project" → `showcase_account`, the showcase's `enable.trackHistory` object | objectui `e2e/live/record-history-display.spec.ts`, blob `7ebc45106cff` at the console pin `616df6598b42` | | PR #24857's report | `studio-authoring.first-run-loop` | `acceptance[0].verify` | F1 "targets testids no source declares" → F1 targets `studio-pkg-chip`, `package-form-id-input` and `package-form-submit`, all declared at the pin, so it is citable when run there | objectui `e2e/live/studio-object-designer.spec.ts`, blob `58a7f1cbf024`; a `git grep` at `616df6598b42` finds them in app-shell's `StudioDesignSurface.tsx` and `PackageFormDialog.tsx` | ## Items left alone, with reasons - `automation.trigger-status-contract` `steps[5]` (#24841): no longer holds. Revision 4 (#24824) already spells `{"inputs": {"new_assignee": "…"}, "output": {"$x": 1}}`. Re-read at the base: the wizard's screen requires `new_assignee`, and `refuseInvalidScreenInput` runs before `applyResumeSignal` (engine.ts). - `identity-auth.self-service-password-reset`, the `sys_email` half (#24841): no longer holds. Revision 4 (#24824) moved `requires`, `steps[1]`, `knownGaps[0]` and `acceptance[0]` to `sys_identity_email`, and the remaining `sys_email` mentions say "never sys_email". `IDENTITY_EMAIL_OBJECT` is `'sys_identity_email'` (email-log-objects.ts). - `cli.migrate-duplicates-inventory`, the index-drop half (#24841): no longer holds. Revision 9 (#24850) already says to drop `uniq_OBJ_organization_id_FIELD` first (schema-drift.ts `buildIndexName`). - `RUNNER.md`, a line defining a negative: not added, because the README's item anatomy already defines it. - A fresh-install clause for #24827's order (`os migrate apply` first, then `os serve`): not added. `platform-core.v18-boot-refusals` scopes the refusal of a 17.x-shaped database and has no fresh-install clause, so it is a candidate for the anchor. The change ships in **18.1.0**: `@objectstack/cli` is in the changesets `fixed` group at 18.0.0, `.changeset/24827-migrate-apply-birth-marker.md` is `minor`, and no pending changeset is `major`. It is unpublished at the base (npm `latest` is 18.0.0). - `records-forms.crud-roundtrip` `acceptance[6]`: its readonly half says "Score that half from the pins in `automated`, never as blocked(fixture)", under an `api` oracle, from a protocol pin over a stub engine and an engine pin. That is the rule-6 class, but it is not in this round's list, and changing a P0 clause's verdict rule is the anchor's call. Carried. - `cli.datasource-introspect-codegen` `automated.ref` still names "acceptance[0]'s NARROWING half ONLY". Under round 15's reading no pin holds a full clause, so `automated` would go and a knownGap would replace it. The dispatch named the spelling only. Carried. - `records-forms.record-discussion-mentions` `knownGaps[0]` / `personas[1]` (anchor comment `6290946648`, "a second signed-up user"): still present at the base, and not in this dispatch. Carried. - Not this round, carried by the PM: the fixture gaps of #24838 and #24841, the `studio-editor` / `form-view-subforms` widening candidates, and round 12's 80 "showcase isolated" spellings. ## PM hypotheses, measured 1. "Most of the 14 are (b), fewer than five (a)." This holds: 12 are (b) and 2 are (a). 2. "`platform-core.v18-boot-refusals` is the home, and the ceremony item takes a cross-reference." This does not hold. `cli.organization-ownership-ceremony`'s title names "an 18.0 pre-release (18.0.0-next.*)" database, the moves, the webhook settlement and the boot that refuses or serves. `platform-core.v18-boot-refusals`' title names a 17.x-shaped database. So by the ruling ("the item whose scope it is") the clause sits in the ceremony item, and the boot-refusals item takes the cross-reference. ## Acceptance notes - Some edits were located beyond the dispatch's field list, inside items it names. They were applied because they are the same rule-6 class and the item would otherwise contradict its own edit: - `automation.packaged-flow-subflow-disable-refusal` `negative[1]`; - `cli.datasource-introspect-codegen` `steps[1]` and `fixtures.knownGaps[2]`; - `approvals.email-action-token-door` `fixtures.knownGaps[2]`; - `approvals.act-door-and-admin-rescue-slate` `acceptance[1]` and `automated.ref`; - `integration-system.datasource-admin-lifecycle` `acceptance[3]` and `steps[8]`; - `records-forms.field-unique-enforcement` `automated.ref`, whose sentence quoted the old clause wording; - `cli.dev-boot-contract` `fixtures.requires[2]` (re-pointed to `acceptance[7]`) and `acceptance[2].verify`. - `integration-system.datasource-admin-lifecycle` took its edit in two commits on this branch. The net diff against the base is one revision bump and one history row. - The ceremony's `acceptance[5]` rests on #24838's measurements and on the source. Its `steps[5]`, and `cli.dev-boot-contract` `steps[9]`, were not replayed live here. `steps[9]` says so. - A configured TTL exists: the spec declares `auth.emailAndPassword.resetPasswordTokenExpiresIn`, and auth-manager.ts forwards it to better-auth. It was not used for the expiry step. Its plumbing from a stack config was not measured, it has no liveness row, and it would still need a wait. - `hono-approvals-act.conformance.test.ts` was read, not run, here. The objectui specs were read at the console pin `616df6598b42` with a read-only `git show`. Their verdicts at the pin are NOT MEASURED. - `integration-system.datasource-admin-lifecycle`: two pins are not in the ref, `admin-routes-auth-guard.test.ts` (the floor) and `datasource-route-ledger.conformance.test.ts` (cited by `acceptance[8].verify`). Whether either holds a whole clause at its layer was not measured. ## Gates The derived list is `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`, computed on the actual change set: 12 paths against merge base `223ae939b`, at head `e208b693b5`. It gives 13 commands. Each one ran at that head with its exit code captured before any pipe, and each exited 0: `node scripts/check-ci-filter-parity.mjs` · `node scripts/check-closing-keyword-parity.mjs` · `node scripts/check-closing-keyword-parity.mjs --self-test` · `node scripts/check-comment-mask-corpus.mjs` · `pnpm --filter @objectstack/lint run check:doc-formula-expressions` · `pnpm check:cross-package-test-inputs` · `pnpm check:doc-authoring` · `pnpm check:driver-memory-census` · `pnpm check:gitlink-declared` · `pnpm check:nul-bytes` · `pnpm check:platform-checklist` · `pnpm check:refd-timer-probe` · `pnpm check:watch-hint-literal` The first run of `check:doc-formula-expressions` (at `79a13e85b7`) refused with exit 3, a prerequisite, because `@objectstack/formula` and `@objectstack/lint` were not built. Both were built under the shared verify lock (`VERDICT command-exit 0`, held 116 s, waited 0 s), and the rerun exited 0. The `--ran` reconciliation reads "13 derived, 13 run, 0 NOT-MEASURED, 0 UNRUN". No per-PR CI job runs `pnpm check:platform-checklist`, so this local reading is its evidence: | reading | base `feb09db14a` | head `e208b693b5` | |:--|:--|:--| | items | 16 areas, 328 items (323 active, 1 planned) | 16 areas, 328 items (323 active, 1 planned) | | line citations | 0 survive across 21 swept documents | 0 survive across 21 swept documents | | symbol anchors | 965/975 against 430 cited sources, 10 on the named residual (#16898), 18 file floors held | 975/985 against 433 cited sources, 10 on the named residual (#16898), 18 file floors held | The ten new anchors all resolve, and the residual is unchanged. `git merge-tree --write-tree origin/main HEAD` exited 0 against `223ae939bb`, and again against `19e5338cc8`, which touches no `docs/qa/**` path. No open PR touches `docs/qa/platform-checklist/`: all 16 open PRs' file lists were read in full, every page. So there was no second merge-tree to run. There is no ablation, because no tested behaviour changed. `skip-changeset` was re-measured: `docs/qa/**` appears in none of the 73 non-private manifests' `files[]` (86 tracked `package.json` files), and the repo root is private. --- _Generated by [Claude Code](https://claude.ai/code/session_01FM29ZqLTrnPNXxijioZwMp)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 5f11cec commit 693352e

12 files changed

Lines changed: 250 additions & 106 deletions

‎docs/qa/platform-checklist/RUNNER.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,8 @@ contradicts it, and correct it here when it does.
341341
the real port). This is "zero hits needs a positive control" applied to one tool: a
342342
negative from a command that never ran is not evidence.
343343

344+
- **`rsync` is not installed in these containers — copy a scratch app with `tar` or `cp -a`.** QA run #24841 met it.
345+
344346
- **A SIGTERM to the `pnpm` wrapper does not stop the server under it — start a
345347
backgrounded boot with `setsid` and stop its process group.** The canonical boot (in the
346348
`--no-ui` entry below) is `pnpm -C … exec objectstack dev …`: pnpm spawns `os dev`, which

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

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1406,7 +1406,7 @@
14061406
"title": "OWD save gate: the retired external dial is refused on every object write, and a packaged object's OWD cannot be changed at runtime — the managed-content seal refuses the write before any OWD rule judges it — server-side",
14071407
"since": "v16",
14081408
"status": "active",
1409-
"revision": 6,
1409+
"revision": 7,
14101410
"priority": "P1",
14111411
"surface": "api",
14121412
"personas": [
@@ -1465,8 +1465,7 @@
14651465
],
14661466
"negative": [
14671467
"a Studio panel that greys the save while a direct meta PUT widens is a FAIL — the server gate is the authority (ADR-0124 D1 both-sides)",
1468-
"a stock-deploy packaged-object widening PUT that answers 2xx (or leaves the object widened) is a FAIL",
1469-
"do NOT tick external-principal read/write enforcement: an external principal reaches explicitly shared rows only (ADR-0095) — this item is the save/authoring gate only"
1468+
"a stock-deploy packaged-object widening PUT that answers 2xx (or leaves the object widened) is a FAIL"
14701469
],
14711470
"variants": [
14721471
"the retired externalSharingModel (the object schema's tombstone, all object writes, draft included)",
@@ -1494,7 +1493,8 @@
14941493
{ "revision": 3, "date": "2026-08-19", "change": "the R1 knownGap, step 5 and clause 4 named the WRONG answering layer, and #9453 CF-3 was RIGHT — re-measured on a genuinely stock showcase boot and OVERTURNING the revision-2 note above (#9477). Proven stock from the server process's own /proc/<pid>/environ: zero METADATA_WRITABLE occurrences among 147 vars. Target artifact-backed, proven by GET ?layers=true returning a populated `code` layer (_packageId com.example.showcase) with `overlay: null`. A widening PUT then answered 403 declaredCode owd_widening_forbidden — reproduced twice, object byte-identical after. The mechanism the old text missed: saveMetaItem runs runAuthoringGate BEFORE the overlay/package doors on the host-config kernel the showcase boots (environmentId undefined, so protocol.ts's env-partitioned NOT_OVERRIDABLE branch never arms — the #7674 note in that file says the showcase is exactly that shape). So the deploy posture does not select the layer; the body's DIRECTION does. Control leg (same boot, same object, tightened body) passed R1 and answered 403 NOT_OVERRIDABLE, i.e. the door the knownGap called 'earlier' is in fact LATER. Six legs measured (widen/tighten x stock/hatch x ?package= present/absent) and WRITABLE_PACKAGE_REQUIRED appeared on none. ⛔ Not a product defect: every widening leg refused and the object never changed — object-posture-gate.ts behaved exactly as its executable guard (if (!ctx.isArtifactBacked) return) specifies; only the prose parenthetical in that file's header, and this item, mispredicted the door order", "ref": "#9477" },
14951494
{ "revision": 4, "date": "2026-10-13", "change": "clause 4, acceptance[3], re-pointed to the managed-content seal, with step 5, the R1 knownGap, the first knownGap's packaged tighten-only parenthetical, the R1 variant, the title and two new source notes (stale spec, #23104 A3). Revision 3 measured, at #9477, that a widening PUT on showcase_announcement met R1 first and answered 403 PERMISSION_DENIED with declaredCode owd_widening_forbidden. Two later changes reorder that. #22338 made saveMetaItem ask the package door on every kernel topology, ahead of the checks that judge the body. ADR-0131 D6 (#22401, e02833c24) made the OS_METADATA_WRITABLE hatch stop opening an item a managed package ships. A packaged object, whose type opens no overlay channel, is now refused 403 NOT_OVERRIDABLE (ITEM_LOCKED with ?package=) whatever the body says, so R1 is unreachable on this door on stock. The protection holds: nothing widens. Per the #23121 triage, no fixture package is built to reach R1. Its oracle is its unit pins, named in the clause: plugin-security object-posture-gate.test.ts for the verdict, and rest meta-object-owd-gate.test.ts, whose R1 block now pins the package door answering first, hatch open", "ref": "#23121" },
14961495
{ "revision": 5, "date": "2026-10-14", "change": "the external clauses retired with the key (#23268, ruling of record 6159300530, letter A): externalSharingModel was removed from the object schema (ADR-0049), so the Studio external-wider clause goes and the external-wider refusal, admit and shipped-declarations clauses and the D11 knownGap are rewritten: clause 1 now asserts the tombstone's 422 refusal on every object write, draft included, clause 2 the admit without the key, and the last clause that the showcase ships no retired key. The packaged-object seal clauses are unchanged (the seal is now clause 3, and step 4 names it so). surface mixed → api: no browser step remains", "ref": "#23268" },
1497-
{ "revision": 6, "date": "2026-10-20", "change": "acceptance[0] (verify, evidence) adds the draft re-read — carried finding 3 of round 12's report on PR #24824 (wave anchor #24579 round 13), from QA run #24779: 'a follow-up GET finds no qa_owd_probe' named no read for the ?mode=draft PUT leg, and a GET with ?mode=draft answers 400. A GET takes state (META_ITEM_READ_PARAMS, meta-item-read-gate.ts at base 7697ddcf94), so the verify now re-reads the probe with ?state=draft beside the plain GET, and 'nothing is stored' holds for both legs. No clause changed", "ref": "#24850" }
1496+
{ "revision": 6, "date": "2026-10-20", "change": "acceptance[0] (verify, evidence) adds the draft re-read — carried finding 3 of round 12's report on PR #24824 (wave anchor #24579 round 13), from QA run #24779: 'a follow-up GET finds no qa_owd_probe' named no read for the ?mode=draft PUT leg, and a GET with ?mode=draft answers 400. A GET takes state (META_ITEM_READ_PARAMS, meta-item-read-gate.ts at base 7697ddcf94), so the verify now re-reads the probe with ?state=draft beside the plain GET, and 'nothing is stored' holds for both legs. No clause changed", "ref": "#24850" },
1497+
{ "revision": 7, "date": "2026-10-20", "change": "negative[2] removed. Checklist-accuracy finding of QA run #24841 (18.0 sweep round 4 at 307ce4c4): it stated a scope rule ('do NOT tick external-principal read/write enforcement … this item is the save/authoring gate only') with no product expectation, so it could only score not-run and held the item at partial. A negative names a product outcome whose occurrence is the FAIL (the README's item anatomy: the other side of every gate, its deny/absence cases). Its text already stands, unchanged in substance, in fixtures.knownGaps[0] (this item tests the save gate, NOT external-principal enforcement; do not tick an external-principal read/write result; an external principal reaches explicitly shared rows only, ADR-0095), so nothing was added. No acceptance clause changed", "ref": "#24963" }
14981498
]
14991499
},
15001500
{
@@ -2677,7 +2677,7 @@
26772677
"title": "Write-door parity on a packaged flow: PUT/DELETE /automation/:name must refuse the same packaged artifact that PUT /meta/flow/:name refuses (ADR-0126 §2 locked base)",
26782678
"since": "v17",
26792679
"status": "active",
2680-
"revision": 2,
2680+
"revision": 3,
26812681
"priority": "P1",
26822682
"surface": "api",
26832683
"personas": [
@@ -2699,7 +2699,8 @@
26992699
"probe leg 1: PUT /api/v1/automation/showcase_urgent_task_alert with the same trivially modified definition — capture status and, if 2xx, GET the flow back to prove the live registration mutated",
27002700
"probe leg 2: DELETE /api/v1/automation/showcase_urgent_task_alert — capture status and, if 2xx, confirm GET /api/v1/automation/showcase_urgent_task_alert now 404s (the shipped flow is gone from the live engine)",
27012701
"RESTORE, only if a probe mutated anything: diff GET /api/v1/automation/showcase_urgent_task_alert against the stored capture; if it differs (or 404s), restore with a cold restart over the same database (the boot flow pull re-registers the packaged body — a restore PUT is itself refused 403 once the door is locked) and diff again — byte-identical",
2702-
"verify the flow still fires: trigger its record-change mutation once and confirm a run appears (the restore must revive the trigger binding, not just the definition read)"
2702+
"verify the flow still fires: trigger its record-change mutation once and confirm a run appears (the restore must revive the trigger binding, not just the definition read)",
2703+
"scope, not a probe: the toggle door is NOT this item. POST /:name/toggle is the sanctioned activation switch (access-security.activation-write-operator-gate); this item is about DEFINITION writes and deletion of the locked base (ADR-0126 §2/§6: sole definition ledger, never silent override), so the toggle is neither driven nor scored here"
27032704
],
27042705
"acceptance": [
27052706
{
@@ -2729,8 +2730,7 @@
27292730
],
27302731
"negative": [
27312732
"a 403 on the /automation probes coming from a MISSING manage_metadata capability is the wrong tier and proves nothing about the lock — this item's persona holds the capability precisely so any refusal observed is the package lock; quote the refusal message to prove which gate answered",
2732-
"do NOT read clause 1's /meta refusal as covering the /automation door — two doors onto one artifact are the item; scoring one door twice is the mistake",
2733-
"the toggle door is NOT this item: POST /:name/toggle is the sanctioned activation switch (access-security.activation-write-operator-gate); this item is about DEFINITION writes and deletion of the locked base (ADR-0126 §2/§6 — sole definition ledger, never silent override)"
2733+
"a 2xx from PUT or DELETE /api/v1/automation/showcase_urgent_task_alert on the artifact the /meta door refused (acceptance[0]) is a FAIL of door parity, whatever the /meta trace shows: each /automation door is scored from its own trace (steps[2] and steps[3]), because two doors onto one artifact are the item"
27342734
],
27352735
"traps": [
27362736
"destructive-in-place",
@@ -2751,7 +2751,8 @@
27512751
"change": "new — #12438 measured that PUT/DELETE /automation/:name re-register/unregister a PACKAGED flow on manage_metadata alone while PUT /meta/flow/:name refuses the same artifact: the ADR-0126 §2 lock is unimplemented at the /automation door. Authored as a door-parity item with the parity promise as the acceptance and the present-day 200 recorded as an expected fail (tracked centrally in FOLLOW-UPS), plus a mandatory restore step because the probe mutates a live engine registration",
27522752
"ref": "#12438"
27532753
},
2754-
{ "revision": 2, "date": "2026-10-02", "change": "retired the expected-fail framing: 4b45afae (#20817) locked the /automation write doors against the packaged base, and the 17.6.0 release-verification run measured PUT and DELETE /automation/showcase_urgent_task_alert answering 403 NOT_OVERRIDABLE with the /meta door's lock message, the flow unchanged and still firing. Title, knownGaps and clauses 2-3 now state the parity as holding; the restore step is conditional (a restore PUT is itself refused), and the knownGap names the permanent pin packaged-flow-write-door-parity.dogfood.test.ts", "ref": "#21330" }
2754+
{ "revision": 2, "date": "2026-10-02", "change": "retired the expected-fail framing: 4b45afae (#20817) locked the /automation write doors against the packaged base, and the 17.6.0 release-verification run measured PUT and DELETE /automation/showcase_urgent_task_alert answering 403 NOT_OVERRIDABLE with the /meta door's lock message, the flow unchanged and still firing. Title, knownGaps and clauses 2-3 now state the parity as holding; the restore step is conditional (a restore PUT is itself refused), and the knownGap names the permanent pin packaged-flow-write-door-parity.dogfood.test.ts", "ref": "#21330" },
2755+
{ "revision": 3, "date": "2026-10-20", "change": "negative[1] rewritten as the product outcome it guarded, and negative[2] moved to steps[6]. Checklist-accuracy finding of QA run #24841 (18.0 sweep round 4 at 307ce4c4): both stated a runner rule with no product expectation, so they could only score not-run and held the item at partial. A negative names a product outcome whose occurrence is the FAIL (the README's item anatomy: the other side of every gate, its deny/absence cases). negative[1]'s 'do not read the /meta refusal as covering the /automation door' guarded one: an /automation write door answering 2xx on the artifact /meta refused, which is now the FAIL. negative[2] (the toggle door is not this item) guards none, so its text moved, unchanged in substance, to steps[6]. No acceptance clause changed", "ref": "#24963" }
27552756
]
27562757
},
27572758
{

‎docs/qa/platform-checklist/areas/api-backend.json‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1565,7 +1565,7 @@
15651565
"title": "A disabled packaged action is refused 409 ACTION_DISABLED at BOTH dispatch doors — after the capability gate (a 403 is never an oracle for the switch), before the param contract and the record load, durable across restart",
15661566
"since": "v17",
15671567
"status": "active",
1568-
"revision": 4,
1568+
"revision": 5,
15691569
"priority": "P0",
15701570
"surface": "api",
15711571
"personas": [
@@ -1581,7 +1581,7 @@
15811581
"showcase_portfolio_snapshot (ui/actions/index.ts — object-less/global, and the ONLY stock action with ai.exposed: true): the live MCP-door probe; its activation flip spells the object segment `global` (/actions/_activation/global/showcase_portfolio_snapshot)"
15821582
],
15831583
"knownGaps": [
1584-
"The MCP door cannot be driven live with showcase_mark_done: the bridge's AI-exposure gate (actionAiExposureError, fail-closed on ai.exposed !== true) sits AHEAD of the activation consult, so a non-exposed action answers the exposure refusal regardless of its switch. showcase_portfolio_snapshot is the only stock ai.exposed action. The MCP transport IS drivable live on a stock boot: the boot banner prints `MCP: http://localhost:<port>/api/v1/mcp`, and QA run #21056 drove POST /api/v1/mcp tools/call run_action against showcase_portfolio_snapshot (disabled → isError carrying code ACTION_DISABLED, status 409; re-enabled → ok). Score the MCP clause live; fall back to the unit pin (action-activation-dispatch.test.ts 'door 2') only where the endpoint is not mounted, and record which oracle the verdict rests on.",
1584+
"The MCP door cannot be driven live with showcase_mark_done: the bridge's AI-exposure gate (actionAiExposureError, fail-closed on ai.exposed !== true) sits AHEAD of the activation consult, so a non-exposed action answers the exposure refusal regardless of its switch. showcase_portfolio_snapshot is the only stock ai.exposed action. The MCP transport IS drivable live on a stock boot: the boot banner prints `MCP: http://localhost:<port>/api/v1/mcp`, and QA run #21056 drove POST /api/v1/mcp tools/call run_action against showcase_portfolio_snapshot (disabled → isError carrying code ACTION_DISABLED, status 409; re-enabled → ok). Score the MCP clause live. Where the endpoint is not mounted, record acceptance[4] blocked(environment), with the unit pin cited beside it: action-activation-dispatch.test.ts 'door 2' calls invokeBusinessAction in-process over a stubbed engine and metadata service, with no MCP transport and no HTTP, so it is evidence of that layer only, never the clause's pass (RUNNER rule 6, a pin scores only the layer it drives).",
15851585
"The record-load half of the ordering proof has NO contrasting refusal: the REST door's record load is best-effort (try/catch in domains/actions.ts — a missing record passes an empty record for new-record actions), so a nonexistent recordId never 404s on its own, enabled or disabled. The record leg is proven by code order plus the absence of any read/side effect, never by expecting a 404 from the enabled control."
15861586
]
15871587
},
@@ -1694,6 +1694,12 @@
16941694
"date": "2026-10-19",
16951695
"change": "steps[5] names the MCP argument — checklist-accuracy finding of the 18.0 QA sweep (QA run #24556, api-backend.packaged-action-disabled-dispatch step 6; wave anchor #24579 §3). Re-derived at base 9a7838edf5 (packages/mcp/src/mcp-http-tools.ts run_action input: actionName, objectName, recordId). No clause changed",
16961696
"ref": "#24649"
1697+
},
1698+
{
1699+
"revision": 5,
1700+
"date": "2026-10-20",
1701+
"change": "fixtures.knownGaps[0] no longer falls back to the unit pin for the MCP clause. PR #24891's carried finding (round 15's acceptance notes: the 'score from the pin' spelling, the MCP clause's unit fallback), read under RUNNER rule 6. acceptance[4]'s oracle is the MCP tool's answer. The 'door 2' case of packages/runtime/src/domains/action-activation-dispatch.test.ts calls invokeBusinessAction in-process over a stubbed engine and metadata service (read at base feb09db14a), so where the endpoint is not mounted the clause is blocked(environment), with the pin beside it. No clause changed",
1702+
"ref": "#24963"
16971703
}
16981704
]
16991705
},

0 commit comments

Comments
 (0)