Skip to content

Commit e18b23f

Browse files
docs(qa): re-point six stale platform-checklist clauses across five items to the shapes their rulings shipped (#24000)
Fixes #23950 Clause-②: no Six platform-checklist clauses across five items pinned shapes that rulings and PRs have since moved. Each was red in a QA run against a product that is right. This PR re-points each clause to the shape that shipped. Each item's `revision` is bumped and a `history` row names #23950 and the ruling or PR that moved the shape. No clause is loosened past its ruling: each one still fails if the product broke the ruled behaviour. The diff is `docs/qa/platform-checklist/areas/{ai,cli,approvals,platform-core}.json` only (+110 / −64, four files). No product code changes. All five items were re-run against a real showcase boot built from this tree's product sources (`fb82f9b1`, whose product tree is `origin/main` `022c6e799`). One record per item: #23995, #23996, #23997, #23998, #23999. The six re-pointed clauses all pass. One item, `platform-core.shell-nav-personalization`, fails on a different clause (0, icon-mode navigation) because of a console defect this run found. That clause is not one this card touches (see Acceptance notes). ## Per clause **1. `ai.mcp-stdio-fail-closed` clause 6** (r4 → r5; from record #23915) - Old pin: the generic error text `MCP stdio identity is no longer valid (key revoked or expired)`, scored from a log line. - New clause: revocation is honored on the next call and fails closed. The post-revoke call serves no row and is a tool error whose envelope carries code `API_KEY_REVOKED` and status 403. A pre-revoke read on the same session is the control. The oracle is the tool result (api). The message's wording is not pinned: the code is what a client branches on, and #23944 is rewording that text. - Cites: `packages/mcp/src/plugin.ts` `resolvePrincipal` (#23367; it raises `ApiKeyLifecycleRefusalError`), `packages/core/src/security/api-key.ts#lifecycleRefusalOf`, and `packages/mcp/src/mcp-http-tools.ts#errorResultWithCode`. - Re-run (#23995): **pass**. Before the revoke, 1 row. After `PATCH /api/v1/data/sys_api_key/KEY_ID {"revoked": true}` (200), the next `query_records` returns isError with `{"error": {"code": "API_KEY_REVOKED", "status": 403, …}}` and no `records` key. **2. `cli.migrate-meta-codemod` clause 7** (r4 → r5; from record #23921) - Old pin: a restart condition, "`--help` lists exactly from/to/step/out/stored/database-url/apply/yes/force/type/json, with no in-place rewrite flag". - New clause: the item's own restart trigger fired (#9591 is done: `--write` landed in PR #22108, and PR #22142 named it in the house sentence). As revision 3 prescribed, the rewrite assertions come back rather than the clause being deleted. `--write` rewrites in place exactly the mechanical edits it can trace to one literal in one project file. It lists every other edit with a closed-set refusal kind and writes no semantic advisory. Every byte outside a written site is unchanged. The write is held to a re-run of the chain (`restored`, exit 1, on disagreement). A second `--write` writes nothing. - Cites: `packages/cli/src/commands/migrate/meta.ts#printWriteOutcome` and `MigrateMeta.writeSources`, `packages/cli/src/utils/authored-source-codemod.ts#CodemodRefusalKind`, and the `packages/cli/test/migrate-meta-write.test.ts` pin. - Re-run (#23996): **pass**. `Wrote 7 of 7 mechanical change(s) into 4 file(s)`. `Re-ran the chain over the written sources: no mechanical change remains.` `diff -r` touches only the 7 listed sites. The second `--write` prints the no-change line. `--write --json`: `write.status` `written`. A helper-built plant was refused as `[helper]`, which the item's knownGap names as the contract working. **3. `approvals.approver-resolution-matrix` clauses 4 and 5** (r2 → r3; from record #23926) - Old pin: under the default `admin_rescue`, the request opened on the literal `type:value` slot (`position:qa_nobody`, `queue:support`) and waited for the #3424 override. - New clauses: since the #22824 ruling (PR #22850), an empty slate opens on the request organization's administrators as ordinary approvers. The abandoned literal and its reason are recorded in `node_config_json.__resolvedFrom` (`admin_rescue:abandoned`, `admin_rescue:reason`). A routed-to-administrators system comment lands on the request, a WARN names the routing, and the request stays pending. The queue clause keeps its #3508 warn and its never-a-fake-approval property. The fail, auto_approve and fallback legs are unchanged. A new negative fails the retired nameless request. - Cites: `packages/plugins/plugin-approvals/src/approval-service.ts#organizationAdministrators` and `emptySlateReason`, and the `admin-rescue-named-slate.integration.test.ts` pin (11/11 green on this tree). - Re-run (#23997): **pass**. The administrators are the members whose resolved sets hold `organization_admin(_no_bypass)`: here, only the org owner. admin_rescue and queue requests open on [the owner], with the abandoned literal in `__resolvedFrom` and the comment `Routed to this organization's administrators because the approver slate named no one: …`. auto_approve's `review.autoApproved` reads `true` on the approve edge. **4. `platform-core.shell-nav-personalization` clause 5** (r2 → r3; from record #23939) - Old pin: app/section/record breadcrumbs on a record route, each crumb navigable. - New clause: the header's crumbs follow the route. A report draws one crumb, its label, with no 「Reports」 type crumb. A `system/*` route draws `System` then its section. An object route (list, view, record) draws no crumb after the PREVIEW chip. Crumbs are plain text and rebuild on reload. "Each crumb navigates" is withdrawn by that contract, not relaxed. The clause still fails if a crumb returns on an object route, if a report or system route loses its crumb, or if a type crumb comes back. - Cites: objectui `packages/app-shell/src/layout/AppHeader.tsx` (objectui#12461, objectui#12526, objectui#12273). It was read at the run's pin `daeb77fc` and again at `f8267df9`, the pin `main` moved to during this work (#23973). objectui#12605 there only adds the product mark before the pill. - Re-run (#23998): **pass**. Report: one crumb, `Hours by Status (Summary)`. `system/settings`: `System`, `Settings`. Record: none, with the page heading `Fabrikam`. 0 links after the chip. Identical after reload. **5. `platform-core.activation-ledger-row-contract` clause 4** (r2 → r3; from record #23939) - Old pin: the scope wall `{flow, action}`, and "a permission/position row is a FAIL" on the 2026-08-26 #12159 ruling. - New clause: the wall is the four values the shipped doors write: flow, action, position, permission. ADR-0131 D3 moved the position and permission-set switch into this ledger (regime C as ADR-0131 D6 amends ADR-0126 §3), superseding #12159. The clause gains a door-side leg: a `:type` outside the two (capability) and an audience anchor (`everyone`) answer 400 and leave no row. It still fails on a fifth type or an anchor row. The item's own knownGap asked for exactly this revision when a third writer shipped. - Cites: `packages/runtime/src/domains/catalog-activation.ts#handleCatalogActivationWrite` and `packages/core/src/security/resolve-authz-context.ts#readDisabledCatalogNames`. - Re-run (#23999): **pass**. Four OFF switches return 200 and leave distinct `metadata_type` {action, flow, permission, position}. The capability probe answers 400 (`:type one of position, permission`) and the `everyone` probe answers 400 (audience anchor); the row count is unchanged after both. ## Other edits inside the same revisions, each re-measured on the re-run - `ai.mcp-stdio-fail-closed` clause 1: same ruling, same item. The up-front admission refuses a lifecycle-refused key with `[MCP] OS_MCP_STDIO_API_KEY is refused (API_KEY_REVOKED): …`, not the did-not-resolve text. The clause now names both refusals, and a new step restarts with the just-revoked key. Measured: exit 1 with that line. Neither clause pins the lifecycle message's wording. - `cli.migrate-meta-codemod`: the title, fixtures, third knownGap, step 2 (the current house sentence), clause 1 (scoped to runs without `--write`) and the mutation negative follow #9591. Measured on the re-run and corrected in the same revision: - Step 8 called the stock tree canonical for `--from 17`, but it lists 3 `flow-decision-mode-inclusive-explicit` edits. The canonical source is now the tree the write leg wrote, and the identical-run diff normalizes the `(Nms)` stamp. - Step 8's parenthetical said the runtime was at PROTOCOL_MAJOR 17. It is 18 since #22215, which the run's `Chain:` line prints. - `approvals.approver-resolution-matrix` fixtures: the data API refuses `sys_team` writes (measured 405) and `sys_user.manager_id` (measured 403). The fixtures name the doors that work instead: `POST /api/v1/auth/organization/create-team` and `add-team-member`, and `POST /api/v1/auth/admin/set-user-manager`. - `platform-core.activation-ledger-row-contract`: steps 1 and 5 flip and re-enable all four switches (clause 3 follows). The variants, the knownGap's writer list, the title, a negative and the sources follow, and the #12159 citation is marked superseded. ## Verification (final head `c4bf2ba4e`) Gate list derived with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`. It reports 13 commands, and each exited 0 with its exit captured before any pipe: - `check-ci-filter-parity` - `check-closing-keyword-parity` and its `--self-test` - `check-comment-mask-corpus` - `@objectstack/lint check:doc-formula-expressions` - `check:cross-package-test-inputs` - `check:doc-authoring` - `check:driver-memory-census` - `check:gitlink-declared` - `check:nul-bytes` - `check:platform-checklist` - `check:refd-timer-probe` - `check:watch-hint-literal` `--ran` with exit codes: `13 derived, 13 run, 0 NOT-MEASURED, 0 UNRUN`. `check-governed-merges --branch`: NOT governed, 174 changed lines. Tier 1 pins on the run tree: mcp `plugin.test.ts` 17/17, objectql `action-activation.test.ts` 18/18, service-automation `flow-activation-ledger.test.ts` 47/47, dogfood `packaged-activation-ledger-reach` 11/11. Supporting suites: cli `migrate-meta-write.test.ts` 23/23 and plugin-approvals `admin-rescue-named-slate` 11/11. `docs/qa/**` ships in no package, so no changeset. ## Acceptance notes - The re-run found three product defects outside this card. They are handed to the dispatching seat for their own cards (the runner files none): - objectui sidebar, icon mode: the invisible group label (`SidebarGroupLabel`: `-mt-8` and `opacity-0`, with pointer events still on) covers the lower half of the item above it. On stock showcase the Page Authoring icon does not route on a centre click. This is `platform-core.shell-nav-personalization` clause 0's fail in #23998, with a full reproduction there. - `os start` used as an MCP stdio host: a client that writes `initialize` at spawn has that line read as `serve`'s positional `config` argument. The boot falls back to the artifact on the default host, and the `initialize` goes unanswered. - The showcase flow `showcase_approver_bindings` is `status: 'draft'` and its docblock says that keeps it out of the runtime, but draft flows fire their triggers. Every `showcase_field_zoo` update opens an admin_rescue approval request. - Harness observation, not filed: the nav scripts restore a saved browser state per script. Replaying a stale `objectui-favorites` cache brought an unpinned entry back. That is this harness replaying old state, noted in #23998. - The interrupted first run of this dispatch left `examples/app-showcase/objectstack.config.bundled_*.mjs` (gitignored) in the worktree. It was removed before the re-run. - The console bundle the browser item ran on was a `vite build` of an objectui checkout at the pin. It carries no `sdui.manifest.json`, which only the boot's page-source warning reads. --- _Generated by [Claude Code](https://claude.ai/code/session_016PVjjuVPte5XEJvaQx1E4t)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 4ffdebe commit e18b23f

4 files changed

Lines changed: 110 additions & 64 deletions

File tree

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

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@
202202
"title": "MCP stdio auto-start fails closed without OS_MCP_STDIO_API_KEY; with a member-bound key, reads AND aggregates honor RLS/FLS and revocation bites on the next call",
203203
"since": "v16",
204204
"status": "active",
205-
"revision": 4,
205+
"revision": 5,
206206
"priority": "P0",
207207
"surface": "cli",
208208
"personas": ["restricted member API key (for the RLS half — on showcase, a contributor-bound member)", "a second restricted member API key for the FLS half (on showcase, a client_liaison-bound member — see fixtures.requires)", "admin API key (for the full-row comparison)"],
@@ -226,7 +226,8 @@
226226
"run the same two reads over REST as the same member (Bearer the same key) and as the admin key; compare",
227227
"aggregate parity: over MCP stdio as the member, aggregate_records the OWD-private object with a count grouped by a readable field ({ aggregations: [{ function: 'count', alias: 'n' }], groupBy: ['<readable field>'] }); run the SAME aggregate over REST as the member and as the admin; compare the per-group counts",
228228
"FLS aggregate fail-closed: over MCP stdio as the member, aggregate_records with an aggregation (or a groupBy) naming the field the member cannot read (the masked field above); capture the refusal and confirm no numeric result is returned",
229-
"revoke the member key while the stdio session is up; issue one more read; capture the refusal",
229+
"revoke the member key while the stdio session is up (as the admin, PATCH /api/v1/data/sys_api_key/<key id> {\"revoked\": true}); issue one more read on the SAME session; capture the tool result and parse its error envelope (code and status, not only the prose)",
230+
"start once more with stdio enabled and the key revoked in the previous step; capture the refusal to start (the lifecycle arm of the up-front admission)",
230231
"start once more with stdio NOT enabled and no key set; confirm a clean boot (the key is only demanded when stdio is on)"
231232
],
232233
"acceptance": [
@@ -237,10 +238,10 @@
237238
"evidence": "the thrown message / startup log"
238239
},
239240
{
240-
"clause": "an unknown/revoked/expired key is rejected up front — 'OS_MCP_STDIO_API_KEY did not resolve to a valid identity (unknown / revoked / expired / owner-less). Refusing to start stdio (ADR-0101).' — never a fall-back to an anonymous-but-serving session",
241+
"clause": "an unknown/revoked/expired key is rejected up front — never a fall-back to an anonymous-but-serving session. A key that resolves to nobody (unknown, owner-less) is refused with 'OS_MCP_STDIO_API_KEY did not resolve to a valid identity (unknown / revoked / expired / owner-less). Refusing to start stdio (ADR-0101).'; a key its own lifecycle refuses (revoked, expired, inactive) is refused naming its code since #23367: '[MCP] OS_MCP_STDIO_API_KEY is refused (API_KEY_REVOKED): … Refusing to start stdio (ADR-0101).', the elided middle being the key's own lifecycle message, whose wording this item does not pin",
241242
"oracle": "log",
242-
"verify": "startup with osk_unknown throws /did not resolve to a valid identity/",
243-
"evidence": "the thrown message"
243+
"verify": "startup with osk_unknown throws /did not resolve to a valid identity/; startup with the key revoked in the revocation step throws /is refused \\(API_KEY_REVOKED\\)/ (packages/mcp/src/plugin.ts, the initial admission's isApiKeyLifecycleRefusalError arm); neither process serves",
244+
"evidence": "the two thrown messages"
244245
},
245246
{
246247
"clause": "a valid key binds the transport to a REAL principal and says so: '[MCP] stdio transport principal-bound to OS_MCP_STDIO_API_KEY identity <userId> (RLS/FLS/tenant applied)'",
@@ -267,10 +268,10 @@
267268
"evidence": "the refusal + a control aggregate over a readable field"
268269
},
269270
{
270-
"clause": "revocation is honored on the NEXT read (the identity is re-resolved per call), failing with 'MCP stdio identity is no longer valid (key revoked or expired)'",
271-
"oracle": "log",
272-
"verify": "the post-revocation read errors with that message instead of serving stale authority",
273-
"evidence": "the refusal"
271+
"clause": "revocation is honored on the NEXT read (the identity is re-resolved per call) and fails CLOSED with the key's own lifecycle refusal: the post-revocation call returns no row and is a tool ERROR whose envelope carries code API_KEY_REVOKED and status 403. The envelope's message is the key's own lifecycle refusal and its wording is NOT pinned here: the code is what a client branches on, and the lifecycle owner rewords that text (#23944, now that restore_api_key can bring a revoked key back). Since #23367 resolvePrincipal raises that branded ADR-0112 envelope (ApiKeyLifecycleRefusalError) instead of the generic 'MCP stdio identity is no longer valid (key revoked or expired)' error, which now answers only an identity that resolves to nobody at all",
272+
"oracle": "api",
273+
"verify": "on the SAME live stdio session: a query_records before the revoke returns rows (the control that the session was serving); after PATCH /api/v1/data/sys_api_key/<key id> {\"revoked\": true} the next tools/call query_records answers result.isError true, and its content text parses to {error: {code: 'API_KEY_REVOKED', status: 403, message}} with no record in it (packages/mcp/src/plugin.ts resolvePrincipal; the coded tool error is errorResultWithCode in packages/mcp/src/mcp-http-tools.ts). Score the code, the status and the absence of rows, never the message's wording",
274+
"evidence": "the pre-revocation rows, the revoke write, and the post-revocation tool result (isError, code, status, no rows)"
274275
},
275276
{
276277
"clause": "stdio disabled ⇒ no key demanded: the boot completes and logs '[MCP] Transport not auto-started …' (the HTTP surface is served per-request regardless)",
@@ -283,12 +284,15 @@
283284
"an invalid/revoked key falling back to an anonymous-but-serving session is THE fail this item exists for — any served read without a resolved principal is a P0 FAIL",
284285
"a member-keyed MCP read returning rows the same member's REST read hides is a FAIL (MCP must not be a side door around RLS)",
285286
"an MCP aggregate count that equals the ADMIN's total (i.e. counts rows the same member's REST read hides) is a P0 FAIL — an RLS-bypassing count is the sys_attachment-total-leak class (#2976): a bypass leaks row existence and statistics even when the individual rows stay hidden",
286-
"an aggregate over an FLS-masked field returning a number instead of failing closed is a FAIL — the input gate is the only place the leak is stopped, since output masking cannot see through an alias"
287+
"an aggregate over an FLS-masked field returning a number instead of failing closed is a FAIL — the input gate is the only place the leak is stopped, since output masking cannot see through an alias",
288+
"a post-revocation read that serves any row is a P0 FAIL (stale authority); a post-revocation refusal that carries no API_KEY_REVOKED code is a FAIL of the #23367 contract — a client branches on the code, never on prose"
287289
],
288290
"traps": ["wrong-persona"],
289291
"automated": { "kind": "unit", "ref": "packages/mcp/src/__tests__/plugin.test.ts" },
290292
"source": [
291293
"packages/mcp/src/plugin.ts (the three refusal strings + per-call re-resolution, ADR-0101)",
294+
"packages/core/src/security/api-key.ts#lifecycleRefusalOf (#23367 — the API_KEY_REVOKED / API_KEY_EXPIRED / API_KEY_INACTIVE refusals, 403, and the located message naming the key's prefix; raised as ApiKeyLifecycleRefusalError out of the stdio door's per-call resolvePrincipal and its up-front admission)",
295+
"packages/mcp/src/mcp-http-tools.ts#errorResultWithCode (the data tools render a thrown coded refusal as {error: {code, message, status}} in an isError tool result)",
292296
"packages/mcp/src/__tests__/plugin.test.ts ('stdio principal admission — fail-closed')",
293297
"packages/runtime/src/domains/mcp.ts#callData (the aggregate bridge routes through callData('aggregate') → the ObjectQL engine so RLS + the FLS aggregate gate always run; the raw driver is deliberately NOT passed)",
294298
"packages/mcp/src/mcp-http-tools.ts#registered (aggregate_records registration: 'Runs under the caller's permissions, row-level security and field-level security'; registered only when the bridge exposes aggregate)",
@@ -305,6 +309,12 @@
305309
"date": "2026-10-01",
306310
"change": "checklist-accuracy findings of QA run #21056: no single showcase persona carries both RLS-hidden rows and an FLS-masked field on one object, so the item now names two (contributor for RLS, client_liaison for FLS); acceptance[5] no longer expects details.forbiddenFields on the wire, which neither transport serializes; steps[0] names `os start` as the host and says why `os dev` corrupts the stdio channel (its banner goes to stdout — 15 lines measured, against 0 for `os start`)",
307311
"ref": "#21060"
312+
},
313+
{
314+
"revision": 5,
315+
"date": "2026-10-16",
316+
"change": "stale clause re-pointed (#23950, from QA run #23915): acceptance[6] pinned the generic text 'MCP stdio identity is no longer valid (key revoked or expired)', but since #23367 (API-key lifecycle) resolvePrincipal raises the key's branded ADR-0112 refusal, so a revoked key's next stdio call is an isError tool result carrying code API_KEY_REVOKED, status 403 and the key's own lifecycle message. The clause now scores that code (and pins none of the message's wording, which the lifecycle owner rewords under #23944), no row served after the revoke (the fail-closed property it exists for, unchanged) and a pre-revoke control read on the same session; its oracle is the tool result (api), not a log line. Same ruling, same item: acceptance[1] attributed one message to unknown, revoked and expired keys alike, while the up-front admission now refuses a lifecycle-refused key with '[MCP] OS_MCP_STDIO_API_KEY is refused (API_KEY_REVOKED): …', so it names both messages and gains a leg (a new step starts stdio with the just-revoked key). A negative pins both failure directions",
317+
"ref": "#23950"
308318
}
309319
]
310320
},

0 commit comments

Comments
 (0)