Skip to content

Commit 0b6a52e

Browse files
docs(qa): wave-3 checklist accuracy slice S9 — the 20 W3-C1R findings at eeec88d (#27377) (#28652)
<!-- claude-projects-attribution: {"github_login":"marchtian"} --> _Requested by **田騏** · [project thread](https://claude.ai/code/project/chan_01VmuUhXU9SSUzqJYyXSmYSi?thread=cmsg_01VmuUhXU9SSUzqJYyXSmYSi9paTx1Lu8ENYwwssthfK3h)_ Fixes #28641 Part of #27377 Clause-②: no Metadata-protocol: none ## 维护者速读 - 改了什么:按 W3-C1R(#28545,`eeec88d1`)留下的 20 条清单准确性发现,逐条对照当前 main 源码复核后改了平台测试清单:9 个区文件、13 个条目,每个条目 revision +1 并追加一行 history。19 条落地修改;第 16 条(Studio 发布提示文字)main 上已由 #28526 改好,记为「已在上游修好」,不重复改。 - 为什么改:这些步骤里的计数、错误码、路由和入口已经过时(例如 Setup 现有 50 个入口、设置命名空间有 11 个、成员打开 Setup 设置页现在显示「无权访问」而不是嵌在别的应用里),每轮 P0 冒烟都要多走一遍手工核对或报出假问题。不改,#27377 发版巡检锚点无法收口。 - 风险与回滚:只改 QA 清单 JSON,不动产品代码、不进任何发布包;没有任何判据被放宽,只补得更准或更严。回滚就是 revert 本 PR。 - 先看:`docs/qa/platform-checklist/areas/platform-core.json`(`builtin-apps-nav-render` 的 negative[3] 改写与 `nav-surfaces-render` 的常设手工项);`approvals.json` 的 `account-app-entry` acceptance[0](接受调度方构建,但新增「服务端入口包与 dist 一致」核对);`integration-system.json` 的 knownGaps[5](两条新凭据通道)。 - 两分钟验证:仓库根运行 `pnpm check:platform-checklist`,预期最后一行以 `check-platform-checklist: OK — 16 areas, 352 items` 开头。 - 需要你做的:无,席位落地。 ## What changes Twenty checklist-accuracy findings that QA run #28545 (W3-C1R, at `eeec88d1`) parked on anchor comment 6554042054 were each re-verified against source at `6b9c1ca991` before editing. 19 rows are edited across 13 items in the nine area files the card names. One row (16) had already landed upstream. Every edited item gets `revision` +1 and one `history` row citing #28545 and #28641. Main's history stays an exact prefix. No file outside the nine is touched. No clause is weakened. | # | item · clause | outcome | source confirmation | |---|---|---|---| | 1 | `access-security.crud-permission-matrix` steps[0], [6] | edited: the live grant door (admin POST to `sys_user_permission_set`) and the delegate's `sys_business_unit_member` row into `bu_field_ops` | object `apiMethods`, the pin's inserts, seed. 201 and "effective" are #28545's reading | | 2 | `access-security.owd-sharing-matrix` steps[3] | edited: the project body spelled with the required `account` | `project.object.ts` `required: true`. The 400 wording is #28545's | | 3 | `access-security.walled-environment-metadata-operator-only` acceptance[0].verify | edited: `--maxWorkers=1`, one package at a time | RUNNER memory fact | | 4 | same item, steps[1] / acceptance[1] | edited: `_migrate-stored` and `POST /automation` driven, 403 FORBIDDEN and 403 PERMISSION_DENIED named | both pins | | 5 | `api-backend.packaged-action-disabled-dispatch` acceptance[6] | edited: 409 ACTION_DISABLED with the turned-off sentence on the bulk-reassign route. The sentence names no action, and the target flow is untouched | `describeDisabled` ignores the name; the live pin does too | | 6 | `api-backend.query-contract-matrix` | edited: negative[1] for the #26644 contract | `projectionFieldsValue` / `countFlagValue` + both door pins | | 7 | `ai.mcp-stdio-fail-closed` steps[7] | edited: revoke from a signed-in session or Setup's API Keys page | `api-key-management-credential.ts` | | 8 | `cli.organization-ownership-ceremony` acceptance[1] | edited: `plan-file-required` is the `--json` reason; the text run prints the Refused line | `organization-ownership.ts` refuse helper | | 9 | same item acceptance[5] + `cli-18-prerelease-db` recipe | edited: recipe knownGaps[3] + a pointer in acceptance[5].verify | the paused rows, timers and null assignees are read from the committed dump. The id replacement is #28545's | | 10 | `platform-core.builtin-apps-nav-render` negative[3] | edited: Setup's access verdict, the shell-less denied screen, and the wire half driven directly | `AppContent.tsx` `appSurfaceAwaitsVerdict` | | 11 | same item steps[4], [5] | edited: 11 namespaces (sms, lifecycle); 50 Setup / 8 Account | the namespaces are read from source. 50 is #28545's reading | | 12 | `platform-core.nav-surfaces-render` steps[0], [2] | edited: `nav_report_index` named as the standing hand-walk; the id diff reads 51 | 50 leaves in `apps/index.ts`, 49 SURFACES | | 13 | `approvals.account-app-entry` steps[3] | edited: entry through the avatar menu's Profile item, not the switcher | `account.app.ts` `hidden: true`, `AppHeader.tsx` | | 14 | same item acceptance[0] | edited: the dispatcher's build is accepted, plus a required served-entry identity check | RUNNER one-build fact | | 15 | `records-forms.crud-roundtrip` steps[5], acceptance[4] | edited: the Activity tab on the tab row; read after a fresh read | `RecordPageColumns.tsx` tab row. The panel lag is #28545's | | 16 | `studio-authoring.first-run-loop` steps[3] | **already upstream**: revision 9 (#28526, commit `37184244b8`, after `eeec88d1`) quotes the current toast | i18n keys `publishedAllCountOne`, `publishedAccessHint`, `publishedOpenAccess` | | 17 | same item steps[2] | edited: opens on Records; Add field on the Form tab; option values derive from labels | `StudioDesignSurface.tsx`, `OptionsEditor` | | 18 | `integration-system.datasource-credential-refusal-matrix` acceptance[6].verify | edited: item route GET/PATCH/DELETE; Allow DELETE, GET, HEAD, PATCH | `admin-routes.ts` | | 19 | same item knownGaps / source | edited: the code-author env route and the `external.connector` channel, as the code and ADR-0015 state them | `datasource-env-credential.ts`, `common.zod.ts`, ADR-0015 D7 | | 20 | same item steps[0] | edited: which door names both refusals for a `mongo` probe | `assertValidConfig` runs before `assertValidRecord`; `resolveDriverId` | ## Tests Docs only: no package source changes, so no build closure and no package test suite is owed (os-dev local-verification scope ①/②). `skip-changeset`: the diff touches only `docs/qa/platform-checklist/areas/*.json`, which no package ships. ## Acceptance notes - Row 12 offers two remedies. Adding a Reports entry to SURFACES edits `examples/app-showcase/e2e/showcase-smoke.spec.ts`, which is outside this card's file surface. So the item names `/reports` as the standing hand-walk, and the SURFACES entry is left to the seat as an out-of-scope note. - Row 17: the Records grid's add-column header also carries "Add field" (`StudioDesignSurface.tsx`). The step names the Form tab, where #28545 found it, and mentions the grid header too. - Row 19 describes only what `datasource-env-credential.ts`, `common.zod.ts`, `datasource.zod.ts` and ADR-0015 D7 already state. It adds no repro detail. ## Verification <details><summary>Gates (head 6d1cadb)</summary> `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 16 families at `6d1cadb865` (9 paths, +116 / -44, under the 3000 human-merge line). Each ran with stdin from /dev/null; exit codes were written to disk before any output was read: - `node scripts/check-adr-links.mjs` :: exit 0 - `node scripts/check-adr-links.mjs --self-test` :: 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 - `node scripts/ci/plan-test-core-shards.mjs --self-test` :: exit 0 - `pnpm --filter @objectstack/lint run check:doc-formula-expressions` :: exit 3 on the first run, a prerequisite refusal that measured nothing (formula and lint dist absent). Exit 0 after `turbo run build --filter=@objectstack/formula --filter=@objectstack/lint` (lock VERDICT command-exit 0) - `pnpm check:ci-filter-parity` :: exit 0 - `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 - `pnpm check:refd-timer-probe` :: exit 0 - `pnpm check:watch-hint-literal` :: exit 0 `--ran` reconciliation: "16 derived, 16 run, 0 NOT-MEASURED, 0 UNRUN" (exit 0). `pnpm check:platform-checklist`: `check-platform-checklist: OK — 16 areas, 352 items (347 active, 1 planned)`, symbol anchors 1086/1096 resolved (10 on the named residual). `origin/main` (`f23e051232`) was merged once before opening. Its three commits touch none of the nine area files. Two cited sources moved (`StudioDesignSurface.tsx`, `packages/runtime/src/domains/automation.ts`) and were re-read on the merged tree: the Records default view and the walled `/automation` refusal still hold. </details> Session: https://claude.ai/code/session_017ihL2gLghEqeDKTbDTC7Zd 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <noreply@anthropic.com>
1 parent dd6349e commit 0b6a52e

9 files changed

Lines changed: 116 additions & 44 deletions

File tree

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

Lines changed: 27 additions & 9 deletions
Large diffs are not rendered by default.

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@
233233
"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",
234234
"since": "v16",
235235
"status": "active",
236-
"revision": 8,
236+
"revision": 9,
237237
"priority": "P0",
238238
"surface": "cli",
239239
"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)"],
@@ -257,7 +257,7 @@
257257
"run the same two reads over REST as the same member (Bearer the same key) and as the admin key; compare. On `os start` the stdio transport answers tool calls before the HTTP listener is up, so poll GET /api/v1/health until 200 before the first REST leg; a refused connection there is the boot still coming up, not a result",
258258
"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",
259259
"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",
260-
"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)",
260+
"revoke the member key while the stdio session is up (as the admin from a SIGNED-IN SESSION, PATCH /api/v1/data/sys_api_key/<key id> {\"revoked\": true}, or revoke it on Setup's API Keys page. Never with the admin's API key: a sys_api_key write from a principal an API key admitted is refused 403 PERMISSION_DENIED 'Changing an API key requires a signed-in session…' and nothing is written, packages/core/src/security/api-key-management-credential.ts); issue one more read on the SAME session; capture the tool result and parse its error envelope (code and status, not only the prose)",
261261
"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)",
262262
"start once more with stdio NOT enabled and no key set, again with `--log-level info` (the not-auto-started line is logged at INFO, like the principal-bound line of step 2); confirm a clean boot (the key is only demanded when stdio is on)"
263263
],
@@ -359,6 +359,12 @@
359359
"date": "2026-10-28",
360360
"change": "steps[4] waits for the HTTP listener, and automated.ref names two corroborating files. Checklist-accuracy findings of the 18.2.0 wave 2 (QA runs #26481, priority:P0, and #26468, Tier 1 half A, both at 756220c4; wave anchor #25807, slice B). (1) #26481: on `os start` stdio answered tool calls before the HTTP listener was up, so the REST comparison leg met a refused connection; the step now polls health 200 first. (2) #26468: plugin.test.ts's 'Transport not auto-started' cases and plugin-execution-context.test.ts (generic 'no longer valid' refusal) corroborate without scoring; the ref names both, and its statement that no packages/mcp test asserts API_KEY_REVOKED still holds. Re-read at base fd246c22 (grep API_KEY_REVOKED over packages/mcp/src: no hit; plugin-execution-context.test.ts, its /no longer valid/ case). No clause changed",
361361
"ref": "#25807"
362+
},
363+
{
364+
"revision": 9,
365+
"date": "2026-11-01",
366+
"change": "QA run #28545 (W3-C1R) finding: steps[7]'s revoke 'as the admin' failed when the admin used an API key, which answers 403 PERMISSION_DENIED 'Changing an API key requires a signed-in session' since #27692 (api-key-management-credential.ts: every non-read sys_api_key operation from an API-key credential is refused). The step now says to revoke from the admin's signed-in session, or Setup's API Keys page, and names that refusal. No clause changed. Read from source at base 6b9c1ca991. Wave anchor #27377, wave-3 accuracy slice S9 (#28641); finding parked in anchor comment 6554042054",
367+
"ref": "#28545"
362368
}
363369
]
364370
},

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

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@
396396
"title": "Data-API query contract: every filter operator gives known answers; $-params, select, sort, expand honored; malformed input 400s with the exact code",
397397
"since": "v15",
398398
"status": "active",
399-
"revision": 8,
399+
"revision": 9,
400400
"priority": "P0",
401401
"surface": "api",
402402
"personas": [
@@ -478,7 +478,8 @@
478478
}
479479
],
480480
"negative": [
481-
"silent WIDENING is the catastrophic failure mode (#3899): a malformed query body must never degrade into an unfiltered 200 full read. A mistyped clause (e.g. {\"limit\": \"ten\"}) answers 400 VALIDATION_FAILED at the request schema; an UNDECLARED top-level key (e.g. {\"not_a_query_key\": {…}}) is lowered as an implicit field filter and answers 400 INVALID_FIELD. ⛔ Do not probe with {\"filter\": {…}}: `filter` is a DECLARED transport spelling of `where` and is served. Verify by asserting each probe's status and code AND that no response carries the full table"
481+
"silent WIDENING is the catastrophic failure mode (#3899): a malformed query body must never degrade into an unfiltered 200 full read. A mistyped clause (e.g. {\"limit\": \"ten\"}) answers 400 VALIDATION_FAILED at the request schema; an UNDECLARED top-level key (e.g. {\"not_a_query_key\": {…}}) is lowered as an implicit field filter and answers 400 INVALID_FIELD. ⛔ Do not probe with {\"filter\": {…}}: `filter` is a DECLARED transport spelling of `where` and is served. Verify by asserting each probe's status and code AND that no response carries the full table",
482+
"#26644 — an empty projection or a count flag that is neither true nor false is refused, never read as the absent default (every column, or a reported total). GET /api/v1/data/showcase_account?$select= (and ?$select=%20, ?$select=, with a lone comma, ?select= and ?fields=) answers 400 VALIDATION_ERROR, \"Query parameter '$select' names no field\" naming the spelling sent; GET ?$top=2&$count=maybe (and ?$count=1, ?count=) answers 400 VALIDATION_ERROR, \"Query parameter '$count' must be true or false\"; POST /api/v1/data/showcase_account/query {\"fields\": []} (and {\"select\": \"\"}) answers 400 VALIDATION_FAILED at the body schema, with fields[0] on the key, and {\"limit\": 2, \"count\": \"maybe\"} answers 400 VALIDATION_FAILED; GET /api/v1/data/showcase_account/ID?select= answers 400 VALIDATION_ERROR naming select. No probe returns rows: a 200 carrying every column or a total is the widening this contract closed (packages/metadata-protocol/src/protocol.ts projectionFieldsValue and countFlagValue; door pins packages/rest/src/data-empty-projection-count-door.test.ts and packages/runtime/src/domains/data-empty-projection-count.test.ts)"
482483
],
483484
"variants": [
484485
"op:eq",
@@ -579,7 +580,8 @@
579580
"change": "automated.ref names the HTTP pin for acceptance[4] and says what two named pins do and do not reach; the variants gain the six AST_OPERATOR_MAP operators the list omitted; steps[1] names the comparand-less null spellings. Checklist-accuracy findings of the 18.2.0 wave 2 (QA run #26468, release-18.2.0-w2-tier1-a, at 756220c4; wave anchor #25807, slice A): showcase-expand-crud-gate.dogfood.test.ts drives acceptance[4] over HTTP and was missing (so 'no clause has a pin at its oracle's layer' was false); request-schema-gate.conformance.test.ts has no collection-operator case; engine-filter-array-lowering asserts the engine's INVALID_FILTER, not the POST door's code; and AST_OPERATOR_MAP serves icontains, ilike, is_empty/is_not_empty and before/after beyond the list. Re-read at base e4ba846f (packages/spec/src/data/filter.zod.ts AST_OPERATOR_MAP). acceptance[8] now asks a verdict of six more rows",
580581
"ref": "#25807"
581582
},
582-
{ "revision": 8, "date": "2026-10-31", "change": "steps[1] names the prefix-form compound AST and steps[2] the case contract of the substring operators. Checklist-accuracy findings of QA run #27409 (18.3.0 sweep, wave 3, Tier 1 half A, at c4c515ad; sweep anchor #27377): the infix compound [cond, 'and', cond] answered 400 while the step showed only the single-condition form, and the substring operators matched case-exactly on the SQL driver while the step gave no case note. Re-read at base 488b3ed0: packages/spec/src/data/filter.zod.ts lowerFilterAST and the CONTAINS / NOT_CONTAINS / STARTS_WITH / ENDS_WITH / ICONTAINS descriptions. No clause changed", "ref": "#27409" }
583+
{ "revision": 8, "date": "2026-10-31", "change": "steps[1] names the prefix-form compound AST and steps[2] the case contract of the substring operators. Checklist-accuracy findings of QA run #27409 (18.3.0 sweep, wave 3, Tier 1 half A, at c4c515ad; sweep anchor #27377): the infix compound [cond, 'and', cond] answered 400 while the step showed only the single-condition form, and the substring operators matched case-exactly on the SQL driver while the step gave no case note. Re-read at base 488b3ed0: packages/spec/src/data/filter.zod.ts lowerFilterAST and the CONTAINS / NOT_CONTAINS / STARTS_WITH / ENDS_WITH / ICONTAINS descriptions. No clause changed", "ref": "#27409" },
584+
{ "revision": 9, "date": "2026-11-01", "change": "QA run #28545 (W3-C1R) finding: the #26644 contract (an empty projection or an unrecognised $count answers 400) had no acceptance or negative row, so its recheck rested on a dispatcher note. negative[1] adds it, spelled from source: protocol.ts projectionFieldsValue / countFlagValue (400 VALIDATION_ERROR naming the spelling) and the body schema's 400 VALIDATION_FAILED, with the probe set of data-empty-projection-count-door.test.ts. #28545 observed the #26644 fix holding. A negative is added; nothing is removed. Read from source at base 6b9c1ca991. Wave anchor #27377, wave-3 accuracy slice S9 (#28641); finding parked in anchor comment 6554042054", "ref": "#28545" }
583585
]
584586
},
585587
{
@@ -1676,7 +1678,7 @@
16761678
"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",
16771679
"since": "v17",
16781680
"status": "active",
1679-
"revision": 10,
1681+
"revision": 11,
16801682
"priority": "P0",
16811683
"surface": "api",
16821684
"personas": [
@@ -1744,9 +1746,9 @@
17441746
"evidence": "post-restart traces + the row read"
17451747
},
17461748
{
1747-
"clause": "a disabled `type: flow` action is refused by its OWN switch, ahead of the type branch — the refusal is ACTION_DISABLED for the action's name, independent of anything the target flow's own ledger row says",
1749+
"clause": "a disabled `type: flow` action is refused by its OWN switch, ahead of the type branch — the refusal is 409 ACTION_DISABLED on the action's own route (POST /api/v1/actions/showcase_task/showcase_bulk_reassign) with the turned-off sentence of acceptance[0], which names no action (describeDisabled ignores the name, packages/objectql/src/action-activation.ts), independent of anything the target flow's own ledger row says, and the target flow is left untouched",
17481750
"oracle": "api",
1749-
"verify": "step-8: 409 ACTION_DISABLED naming showcase_bulk_reassign; the automation service is never consulted for the run",
1751+
"verify": "step-8 on /actions/showcase_task/showcase_bulk_reassign: status 409, body.error.code ACTION_DISABLED and body.error.message the turned-off sentence (first sentence \"This action is turned off for this deployment.\", the Setup › Packaged automation remedy). Do not look for the action's name in it: the sentence names none, and the live pin's expectDisabledSentence ignores the name too. The automation service is never consulted for the run, so showcase_reassign_wizard records no run from it",
17501752
"evidence": "the trace"
17511753
}
17521754
],
@@ -1841,6 +1843,12 @@
18411843
"date": "2026-10-30",
18421844
"change": "acceptance[0]'s clause and verify respelled for #27842: the disabled-action sentence is now written for the end user who clicked (what happened, that an administrator can turn the action on again under Setup › Packaged automation, or use another action) and names no system table and no ADR; the engine's describeDisabledAction writes the ledger table, ADR-0126 §8 and the ACTION_DISABLED code to its log line instead. The code and the 409 are unchanged, so every other clause stands; the clause's 'naming the ledger' was the only text that turned false. The dogfood pin's sentence helper and packaged-activation-ledger-reach.dogfood.test.ts's /ACTION_DISABLED|is disabled/ are flipped in the same change (the latter now reads the code alone)",
18431845
"ref": "#27842"
1846+
},
1847+
{
1848+
"revision": 11,
1849+
"date": "2026-11-01",
1850+
"change": "QA run #28545 (W3-C1R) finding: acceptance[6] (clause and verify) expected ACTION_DISABLED 'naming showcase_bulk_reassign', but since #27842 the sentence names no action (action-activation.ts describeDisabled returns describeTurnedOffPackagedItem('action'), and packaged-action-disabled-dispatch-live.dogfood.test.ts expectDisabledSentence ignores its action argument). The clause now asks 409 ACTION_DISABLED with the turned-off sentence on the /actions/showcase_task/showcase_bulk_reassign route, and the target flow untouched. The status, the code and the own-switch ordering are kept; only the expectation that could never hold is replaced. Read from source at base 6b9c1ca991. Wave anchor #27377, wave-3 accuracy slice S9 (#28641); finding parked in anchor comment 6554042054",
1851+
"ref": "#28545"
18441852
}
18451853
]
18461854
},

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

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1119,7 +1119,7 @@
11191119
"title": "A non-admin reaches the full Approvals Inbox from the account app's Approvals entry",
11201120
"since": "v17",
11211121
"status": "active",
1122-
"revision": 7,
1122+
"revision": 8,
11231123
"priority": "P0",
11241124
"surface": "browser",
11251125
"personas": ["a NON-ADMIN business user who is a pending approver and has no Setup access"],
@@ -1132,10 +1132,10 @@
11321132
]
11331133
},
11341134
"steps": [
1135-
"build the console from this checkout (`pnpm console:build`) and keep its ready line; boot the showcase on its own port and DB (dogfood §0)",
1135+
"build the console from this checkout (`pnpm console:build`), or take the build the dispatcher made of this checkout at the run SHA, and keep its ready line; boot the showcase on its own port and DB (dogfood §0); then compare the entry chunk the served /_console/ page loads with packages/console/dist's (acceptance[0])",
11361136
"grep the BUILT bundle for the registration key: `grep -o 'approvals:inbox' packages/console/dist/assets/index-*.js` — the entry chunk must carry the register call, because the unit pin (#7231) stubs the page at its module boundary and does not assert main.tsx performs the side-effect import",
11371137
"sign in as the non-admin persona; GET /api/v1/meta/app and record which apps the server returns for this session",
1138-
"open the account app and click the Approvals entry in the Inbox group; screenshot",
1138+
"open the account app from the avatar (user) menu's Profile item — it opens /apps/account/component/account/profile_card, which QA run #28545 read as /apps/com.objectstack.account/component/account/profile_card. Not from the app switcher: the account app declares hidden: true (packages/platform-objects/src/apps/account.app.ts), so for this persona the switcher offers only Showcase, Back to home and All apps. Then click the Approvals entry in the Inbox group; screenshot",
11391139
"read the resulting URL and the rendered surface (tabs, list rows, keyboard hint bar)",
11401140
"open a request from its inbox row — it navigates to the request's served record page, /apps/APP/sys_approval_request/record/ID — and read the rendered action set; execute one decision and re-read the request",
11411141
"cold-load the same request via ?request= on this component route and read the URL it lands on",
@@ -1145,8 +1145,8 @@
11451145
{
11461146
"clause": "the registration survives BUNDLING — the built dist, not just source, contains the approvals:inbox component registration",
11471147
"oracle": "build",
1148-
"verify": "`pnpm console:build` ran in this checkout at the run's framework SHA and printed its ready line (`✓ @objectstack/console dist ready (… KB) from ui/apps/console`) AND the built entry chunk contains the `approvals:inbox` register call; a source-only grep does not satisfy this clause",
1149-
"evidence": "the framework SHA + the console:build ready line + the grep hit with surrounding bundle context"
1148+
"verify": "`pnpm console:build` ran in this checkout at the run's framework SHA and printed its ready line (`✓ @objectstack/console dist ready (… KB) from ui/apps/console`) — run by the runner, or by the dispatcher that prepared the checkout (RUNNER.md: one container builds one tree at a time), whose ready line the run record then cites — AND the server serves that build: the entry chunk the served /_console/ page loads (index-HASH.js) is the one in packages/console/dist, same name and same bytes (QA run #28545 compared index-RFCp05oY.js) — AND that built entry chunk contains the `approvals:inbox` register call; a source-only grep does not satisfy this clause",
1149+
"evidence": "the framework SHA + the console:build ready line (and who ran it) + the served-entry comparison + the grep hit with surrounding bundle context"
11501150
},
11511151
{
11521152
"clause": "the entry renders the FULL inbox — tabs (en: My Pending / Submitted by me / All; zh-CN: 待我审批 / 我发起的 / 全部) and the request list — not a component-not-found empty state",
@@ -1228,6 +1228,12 @@
12281228
"date": "2026-10-29",
12291229
"change": "fixtures.requires[2] no longer says a persona-created invoice without `owner` lands and reads 404 to its creator: the showcase contributor set now carries the ownership check on insert as well as update (invoice_owner_on_create beside invoice_owner_immutable, examples/app-showcase/src/security/permission-sets.ts), so such a create is refused 403 PERMISSION_DENIED with nothing written. The requirement that the persona own the invoice is unchanged",
12301230
"ref": "claude/harden-invoice-owner-check-on-create"
1231+
},
1232+
{
1233+
"revision": 8,
1234+
"date": "2026-11-01",
1235+
"change": "Two QA run #28545 (W3-C1R) findings. steps[3] did not say how to reach the account app: it is hidden from the app switcher (account.app.ts hidden: true), which offered the persona only Showcase / Back to home / All apps, and is entered from the avatar menu's Profile item (AppHeader.tsx, /apps/account/component/account/profile_card, which #28545 read under the com.objectstack.account segment). acceptance[0] asked the runner to run pnpm console:build itself; it now also accepts the dispatcher's build of the same checkout at the run SHA, and adds a served-entry check (the chunk /_console/ loads is the dist's), so the clause still proves the bundle under test is this checkout's. steps[0] and the evidence follow. No clause weakened: the build, SHA and grep requirements stand, and one check is added. Read from source at base 6b9c1ca991. Wave anchor #27377, wave-3 accuracy slice S9 (#28641); finding parked in anchor comment 6554042054",
1236+
"ref": "#28545"
12311237
}
12321238
]
12331239
},

0 commit comments

Comments
 (0)