Repository navigation
docs(qa): land the #9296 wave's environment facts in RUNNER.md and the 34 checklist-accuracy corrections in areas/*.json #9386
Description
Docs-only, one PR, docs/qa/platform-checklist/** and nothing else. Closes out the QA wave #9296, which ran every unpinned P0/P1 item plus all 77 pinned ones and produced two kinds of ledger debt: facts the runner should not re-derive, and items that are factually wrong.
Filed separately from the skills card #9387 on purpose — skills changes are ADR-class and need a human merge, docs/qa/** does not. Mixing them would hold this behind a review it does not need.
Part 1 — RUNNER.md, "Environment facts the runner should not re-derive"
That section's own contract is "re-confirm one only when a run contradicts it, and correct it here when it does." Six facts, each measured, each having cost a round real time:
objectstack verify --rlsis a separate invocation. Bareobjectstack verifyemits no RLS section at all —runRlsProofsonly runs behind the flag. Two rounds cited plainverifywhile believing they had consulted an RLS oracle.- Playwright needs an explicit
executablePathon these containers:@playwright/test1.62.1 resolves chromium build 1234, onlychromium-1194is installed. Stock config reports 31 failed; withlaunchOptions.executablePath=/opt/pw-browsers/chromium-1194/chrome-linux/chrome, 31 passed. ?id=on/api/v1/meta/appkeys on the app NAME, not the package id. A package id returns{"items":[]}— reads exactly like missing metadata, the highest-value false-P0 shape there is. Real names:showcase_app/setup/studio/account.ssis blind to sockets in these containers — empty output for a port that is actively serving. Liveness iscurl -s -o /dev/null -w '%{http_code}' …/api/v1/health. This is the "a zero reading needs a positive control" rule applied to one tool.- Console session auth is bearer-token-in-
localStorage(auth-session-token) — and a real form sign-in also setsbetter-auth.session_token. AclearCookies()gesture expires nothing and leaves a fully authed shell rendering (this nearly produced a fake P0); true expiry isPOST /auth/sign-out. But the cookie is not decorative —storage/upload/presignedrequires it, so a bearer-only session 401s there. Both halves are needed; each on its own has already misled a round. - A cold tree cannot boot from the console-build recipe alone.
pnpm objectui:buildbuilds the console, not the framework CLI; on a fresh containerpackages/clihas nodistandobjectstack devanswersError: command dev not found. Missing step:pnpm exec turbo run build --filter=@objectstack/cli... --filter=@objectstack/example-showcase.... Point atpnpm objectui:buildfails on a cold tree: build-console.sh pre-builds @objectstack/client outside turbo, so spec/core dists are missing and the DTS build dies #9307 for the cold-treeobjectui:buildordering bug rather than letting each runner re-diagnose it.
Plus one trap-vocabulary entry for the failure mode behind this wave's only retracted finding:
| trap | what it fakes | counter |
|---|---|---|
absence-inference |
a missing flag / key / script read as a missing capability | follow the forwarding chain to where the default is actually decided, before writing the finding down |
Part 2 — areas/*.json, 34 item corrections
Each is a change, so each needs its fields fixed, revision bumped, and a history entry saying what changed and why. Do not batch them into one revision bump per file — run records pin per-item revisions, and a silent semantic edit re-validating an old result is exactly what the revision check exists to stop.
The corrections are itemised in the run records; read them from there rather than from this card, and ground each against the tree before editing:
| run record | corrections |
|---|---|
| #9334 (P0 round) | 5 — incl. a stale showcase_contact=33 baseline (actual 32), the ?id= spelling in two items, a records-forms negative that can never hold because the field carries a default, a missing automated.ref on the new ADR-0123 item, two items naming runRlsProofs without --rls |
| #9401 (pinned sweep) | 3 — api-backend.query-contract-matrix cites a source module (packages/objectql/src/filter-comparand-shape.ts) as an automated.ref; two refs name companion tests by bare name with no path |
| #9417 (api·cli·build) | 9 |
| #9453 (browser 1) | 9 — incl. platform-core.console-login step 5 is wrong for this build (the clearCookies gesture cannot expire a bearer-token session), a retired OWD gate an item still tests, a publish sub-route that 404s for permission, capability specimens on showcase_field_zoo not zoo, 11 settings namespaces not 10 |
| #9467 (browser 2) | 8 — incl. the activity/audit verb being write-time localized from the tenant setting, not the reader's session (so the clause asserting reader-session localization is wrong), and a stale _views.emptyState waiver whose #7714 specimen has since landed and renders |
Two markers to add, not just text fixes:
access-security.no-active-org-session-semanticsneedsblocked: { by: "fixture", ref: "#9334" }. Three independent rounds derived the same dead end — the deployment istenancy: singleso ADR-0095 Layer 0 is inert, and bootingOS_TENANCY_POSTURE=isolatedis correctly refused because@objectstack/organizationsis not declared. Without the marker the selector keeps listing it as runnable and a fourth round pays for it again.studio-authoring.first-run-loopmust lose any inherited "Studio is not installed" assumption. It is runnable and it passes — the app registration is absent from the admin's app list, but the App Builder UI is reachable at/_console/studioand the loop closes 6/6 with zero restarts (QA run · tier2b:browser-1 (21/21) · e4e5c6e3 · 2026-08-18 · 7 PASS / 12 PARTIAL / 2 FAIL #9453).
Scope discipline
⛔ No .claude/skills/** — that is #9387, and one skills path in the diff sends this whole PR to a human merge queue it does not belong in. ⛔ No CI posture changes (#9306 is the maintainer's). ⛔ No product code. Keep node scripts/check-platform-checklist.mjs green and paste its output.
Activity
os-steve commentedon Aug 17, 2026
Triage: routing completed the label-only state — lands in
docs/qa/platform-checklist/RUNNER.md⇒domain:devx(docs/tooling face, deliberately split from the ADR-class skills half #9387 so these facts are not held behind a human merge). TypeTask, stayspm:queue. The card's own scope fences (⛔ no.claude/skills/**, no CI posture, noareas/*.json) are binding on the dispatch. S / sonnet.Generated by Claude Code
claude commentedon Aug 18, 2026
Claim: PM loop round 5
Session:
session_01XqDQYVU5smx29ts9pAErja· Branch:claude/issue-9386-runner-env-facts· Worktree:objectstack-issue-9386· Domain:domain:devxFile surface:
docs/qa/platform-checklist/RUNNER.md— stop on breach; explain in the reportContainer & model:
mode:subagent,model: opus—dispatch-gates.mjs --tierreports no path-derived mandate; clause ② not reachable (no contract accept/reject surface).Serial constraints cleared: verified against every in-flight claim this round. Batch siblings #9367, #9373, #9382, #9305 all disjoint.
origin/mainand assertgit rev-parse HEADequalsorigin/mainbefore measuring. Premises below were checked from a tree asserted equal toorigin/main(51a46a440).Premise ✅:⚠️ that is a
docs/qa/platform-checklist/RUNNER.mdpresent.dispatch-gates.mjsnames no gate family for it —silentverdict, the derivation's weakest claim, ⛔ not a clearance;check:nul-bytesstill applies.⭐ These five facts were paid for by the #9296 wave in real container time. Land them as operational facts a runner needs, ⛔ not as narrative. Each one should be checkable by the next runner — if a fact cannot be stated so someone can verify it, say so rather than writing prose around it.
flock -cinherits the lock fd, so a leaked server holds the shared lock (root-caused in #9399). Fold them in only if they are genuinely among the five.Generated by Claude Code
os-steve commentedon Aug 18, 2026
{ "issue": 9386, "status": "done", "branch": "claude/issue-9386-runner-env-facts", "pr": "https://github.com/objectstack-ai/objectstack/pull/9427", "premise_still_valid": true, "summary": "All five facts landed in the 'Environment facts the runner should not re-derive' section of docs/qa/platform-checklist/RUNNER.md, plus the absence-inference trap row — each written as a mechanism + a concrete check command + the action, not narrative. Every fact was re-measured against origin/main (51a46a440) before being written down, and THREE needed correcting rather than transcribing: (3) the app-name list is misleading because studio is NOT installed on stock showcase, so ?id=studio is legitimately empty and an empty items has two distinct causes; (4) ss does not 'return empty' — ss and netstat are NOT INSTALLED at all here (lsof/fuser are), so the filed mechanism would have sent a runner hunting a socket-table quirk that does not exist; (5) the card's prescribed fix is insufficient AND its #9307 rider is stale. Facts 1 and 2 confirmed unchanged, including the card's exact numbers. Card premise itself holds: RUNNER.md exists, the section exists, and none of the five was already present.", "tests": "All at final commit 85c283f5b, clean tree. dispatch-gates.mjs re-derived from the actual changed path: 'No check family names the given paths' — a silent verdict, treated as not-a-clearance. Ran anyway: check:nul-bytes PASS ('OK, scanned 6116 text file(s) ... no raw ASCII control bytes'); check:platform-checklist PASS ('OK — 15 areas, 190 items (190 active); coverage: 30 kinds mapped, 0 waived'); check:doc-anchors PASS ('244 internal #fragment link(s) across 396 source file(s) all resolve'); check:doc-authoring PASS ('376 files clean — no bare metadata literals'). Plus a control-char self-scan outside the gate (grep -naP over RUNNER.md, no hits). FACT RE-MEASUREMENTS: (1) source shows rls: Flags.boolean({default:false}) and report printed only if(rls); live bare verify ended at '15 verified, 0 gaps, 0 FAILED, 1 needs-fixture, 7 skipped' with no PROVEN/HOLES line, and --rls appended '20 PROVEN (20 consistent, 0 HOLES)' over 23 objects with '9 of 9 declared position(s) probed' — matches the card exactly. (2) playwright-core@1.62.1 browsers.json pins chromium revision 1234 (151.0.7922.34); /opt/pw-browsers holds only chromium-1194 (Chromium 141.0.7390.37); stock playwright.config.ts sets no executablePath; the named chrome binary exists; showcase-smoke.spec.ts has exactly 31 SURFACES entries, confirming the '31'. (3) rest-server.ts matches a.name === appIdFilter; package id com.example.showcase confirmed in objectstack.config.ts; names showcase_app/setup/studio/account all confirmed in source — but showcase does not install studio. (4) live probe: with a real listener on a port, ss -ltn gave 'command not found'; via a pipe with stderr dropped it gave empty stdout + exit 1 while curl returned 200. netstat also absent; lsof and fuser present. (5) fresh worktree had no packages/cli/dist; node packages/cli/bin/run.js dev returned ' > Error: command dev not found' exit 2, verbatim as filed. The card's prescribed turbo filter (cli... + example-showcase...) made the binary resolve dev but left 8 of 67 packages unbuilt with check:dev-prereqs still exit 1; pnpm build (71 tasks) turned it exit 0. build-console.sh already builds the client through turbo and guards on dist/index.d.ts, i.e. the #9307 rider is stale.", "open_questions": [], "out_of_scope_findings": [ "NOT filed as a new issue — reported here as it is about this card's own inputs: the card's fact-5 rider instructing RUNNER.md to point runners at #9307 is stale. #9307 is closed (state_reason completed), its remedy shipped in merged PR #9396, and scripts/build-console.sh in this tree already builds @objectstack/client through turbo with the guard keyed on dist/index.d.ts. Deliberately omitted from the file rather than written in, since it would tell runners to expect a failure that no longer occurs.", "NOT filed — dispatch input, recorded for the seat: the offered 'turbo replays its cache across worktrees, so a fresh worktree is NOT cold' fact did not reproduce here. This fresh worktree built 1 cached of 55, i.e. effectively cold, and a later run on the same tree hit 55 cached of 60. It reads as condition-dependent rather than a standing environment fact, so it was not folded in — an entry in a section whose contract is 'trusted without re-derivation' has to hold unconditionally.", "NOT filed — observation only, out of this card's scope: the two checklist items and the dogfood-verification skill section that carry the wrong ?id= spelling are still uncorrected. Those are the #9334 item-accuracy half and the companion skills card #9387 respectively, both explicitly fenced out of this PR." ] }Generated by Claude Code
os-steve commentedon Aug 18, 2026
✅ ACCEPT — PR #9427(附一处已回传的耐久性修正)
我自己量的 PR 形态:
纯增量、单文件、
Fixes #9386在首行。⭐+62/-0一行没删,对这张卡是对的形状 —— 它是往一个既有小节里补事实,不是重写。这张卡最重要的结果:五条里有三条是被改正的,不是被誊抄的
派发时我给的是「把 #9296 那一波花钱学到的五条环境事实落进 RUNNER.md」。dev 没有照抄卡片,而是逐条回到
origin/main(51a46a4)重测,结果三条与卡片所述不符。这正是 RUNNER.md 这个小节存在的意义 —— 它的契约是「运行者不再重新推导」,所以往里写一条错的,比不写危害大。我独立复验了其中最要命的一条(fact 4)
卡片说
ss会「返回空」。dev 说不是返回空,是ss和netstat根本没装。我在自己的容器里量了:⛔ 卡片如实落地会把运行者送去查一个不存在的 socket-table 怪癖。 而真正的陷阱在 dev 写下的那句里:
ss -ltn | grep :3000把错误吞进 stderr,于是一台活着的服务器和一台死了的服务器输出完全相同 —— 空 stdout、exit 1、没有任何线索。「一个从未运行过的命令给出的否定,不是证据。」⭐ 这跟本 lane 这一整轮反复付账的是同一个形状:把结论读自一个根本无法承载该结论的字段。区别只在于这次那个字段是「命令的空输出」。
fact 3 的分叉同样是真的
?id=studio在 showcase 上合法地返回空,因为 showcase 压根没装 studio —— 所以空items有两个不同成因(拼错 / 没装),而卡片只写了一个。dev 给的处置是对的:先无参数拉一次/api/v1/meta/app读真实名单,再过滤。fact 5:卡片给的修法不够
卡片开的
turbo --filter=cli... --filter=example-showcase...实测仍留 8/67 包未构建,check:dev-prereqs依旧 exit 1;pnpm build(71 tasks)才转绿。dev 按实测写了pnpm build。我的两条派发输入被证伪,都记在这里
pnpm objectui:buildfails on a cold tree: build-console.sh pre-builds @objectstack/client outside turbo, so spec/core dists are missing and the DTS build dies #9307 rider 是陈的。 我让 RUNNER.md 指运行者去看pnpm objectui:buildfails on a cold tree: build-console.sh pre-builds @objectstack/client outside turbo, so spec/core dists are missing and the DTS build dies #9307,但pnpm objectui:buildfails on a cold tree: build-console.sh pre-builds @objectstack/client outside turbo, so spec/core dists are missing and the DTS build dies #9307 已关(completed),补救在已合并的 PR fix(build): build @objectstack/client through turbo in build-console.sh, and guard on the declaration #9396 里,build-console.sh现在已经走 turbo 构建 client、并以dist/index.d.ts为守卫。dev 故意没写进去 —— 写了就是让运行者去等一个不再发生的故障。判断正确。两条都是我给的,两条都该丢。
一处要求补一个 commit(耐久性,非事实错误)
fact 2 给运行者的是一个带版本号的复制粘贴路径:
我量到旁边有个版本无关的符号链接:
/opt/pw-browsers/chromium今天解析到同一个二进制(实测可执行,Chromium 141.0.7390.37),而且由镜像构建维护 —— 镜像升到 1234 时它继续解析,字面量chromium-1194那一刻起不复存在。absence-inference陷阱行,升了一层作用在文档自身上。已回传:把处方值换成
/opt/pw-browsers/chromium,1194/1234这两个数字留在原处(机制说明和grep/ls检查里),它们在那儿是做诊断用的。并且要求 dev 先验证再写 —— 如果 Playwright 拒绝符号链接形式的 executablePath,就保留版本化路径并注明升级风险,证伪同样是合格结果。顺带一提写法本身
每条都是 机制(带源码位置)+ 一条可执行的 Check 命令 + 该做什么,不是叙事。新增的
absence-inference陷阱行进了 trap 词表。这是这个小节该有的样子。Verdict: ACCEPT,等那一个补丁 commit 和门禁收敛后武装(
enable_pr_auto_merge),不手动合并。Generated by Claude Code
os-steve commentedon Aug 18, 2026
Supersedes the earlier report on this card — head moved from
85c283f5btoadc9de82e.{ "issue": 9386, "status": "done", "branch": "claude/issue-9386-runner-env-facts", "pr": "https://github.com/objectstack-ai/objectstack/pull/9427", "premise_still_valid": true, "summary": "All five facts landed in the 'Environment facts the runner should not re-derive' section of docs/qa/platform-checklist/RUNNER.md, plus the absence-inference trap row — each a mechanism + a concrete check command + the action, not narrative. Every fact was re-measured before being written down and THREE needed correcting: (3) studio is NOT installed on stock showcase, so an empty ?id= result has two distinct causes; (4) ss and netstat are NOT INSTALLED here (lsof/fuser are), so the filed 'returns empty' mechanism was wrong; (5) the card's prescribed turbo filter is insufficient and its #9307 rider is stale. Follow-up commit adc9de82e addresses the review point on fact 2: the prescribed executablePath is now the image-maintained alias /opt/pw-browsers/chromium instead of the versioned literal, which would have rotted at the next image bump inside the one section contracted to be trusted without re-derivation.", "tests": "All at final head adc9de82e, clean tree. dispatch-gates.mjs re-derived from the actual changed path: 'No check family names the given paths' — a silent verdict, treated as not-a-clearance. Ran anyway at adc9de82e: check:nul-bytes PASS, check:platform-checklist PASS (15 areas, 190 items, 30 kinds mapped, 0 waived), check:doc-anchors PASS (244 fragment links resolve across 396 files), check:doc-authoring PASS (376 files clean). Control-char self-scan outside the gate: no hits. FOLLOW-UP VERIFICATION (the review point, verified not assumed): /opt/pw-browsers/chromium is a symlink, readlink -f resolves to chromium-1194/chrome-linux/chrome. Three-leg launch test with @playwright/test 1.62.1 — CONTROL default launch FAILED as expected with \"Executable doesn't exist at /opt/pw-browsers/chromium_headless_shell-1234/chrome-headless-shell-linux64/chrome-headless-shell\" (confirming the mismatch is live, and that the error names the HEADLESS-SHELL variant, now recorded in the file as the signature); ALIAS launch OK version=141.0.7390.37 with a real page driven and DOM read back; VERSIONED launch OK, same version. So Playwright does accept a symlinked executablePath — no trade-off to record, and the hazard-free value is also a working one. EARLIER LEGS unchanged and still valid: (1) bare verify ended at '15 verified, 0 gaps, 0 FAILED, 1 needs-fixture, 7 skipped' with no PROVEN/HOLES line, --rls appended '20 PROVEN (20 consistent, 0 HOLES)' over 23 objects with '9 of 9 declared position(s) probed'; (2) playwright-core@1.62.1 browsers.json pins revision 1234, only 1194 installed, 31 SURFACES entries confirm the '31'; (3) rest-server.ts matches a.name === appIdFilter; (4) live listener probe, ss 'command not found', empty stdout + exit 1 through a pipe while curl returned 200; (5) no packages/cli/dist on a fresh tree, ' > Error: command dev not found' exit 2 verbatim, card's turbo filter left 8 of 67 packages unbuilt with check:dev-prereqs still exit 1, pnpm build (71 tasks) turned it exit 0.", "open_questions": [], "out_of_scope_findings": [ "NOT filed — about this card's own inputs: the fact-5 rider pointing runners at #9307 is stale (#9307 closed, remedy in merged PR #9396; build-console.sh already builds the client through turbo and guards on dist/index.d.ts). Deliberately omitted rather than written in. PM has since agreed.", "NOT filed — dispatch input, recorded for the seat: the 'turbo replays its cache across worktrees, so a fresh worktree is NOT cold' fact did not reproduce (1 cached of 55 on a fresh worktree; 55 cached of 60 on a later run in the same tree). Condition-dependent, so not folded into a section contracted to hold unconditionally. PM has since agreed.", "NOT filed — observation only, fenced out of this card: the two checklist items and the dogfood-verification skill section carrying the wrong ?id= spelling are still uncorrected; those belong to the #9334 item-accuracy half and the companion skills card #9387." ] }Generated by Claude Code
os-steve commentedon Aug 18, 2026
✅ 补丁 commit 已验收 ——
adc9de82e我自己拉的头部,不是采信回报:
处方值已换成镜像维护的别名,
1194/1234留在机制说明和grep/ls检查里做诊断 —— 正是要求的范围,没有多改。证伪腿跑了,而且结论是「没有取舍」
我要求的是「先验证再写;如果 Playwright 拒绝符号链接形式的
executablePath,就保留版本化路径并注明升级风险」。dev 跑了三腿:/opt/pw-browsers/chromium)version=141.0.7390.37,真实驱动了页面并读回 DOMchromium-1194/...)⇒ 无害的值同时也是能用的值,不存在需要记录的取舍。这比「按我说的改了」有价值:我提的是一个假设,它被测过了才落地。
CONTROL 腿顺带纠正了我没想到的一点 ⭐
我以为运行者会在报错里看到
chromium-1234。实际报错指的是 headless-shell 变体:dev 把这条字符串作为识别签名写进了文件。这一改是对的,而且方向很准:fact 2 的目的是认出来,一个照着
1234去 grep 的运行者会在正确的数字上看错的路径 —— 又是同一个族,在一个字段上读它承载不了的结论。我在自己容器里的
ls与之一致:只有chromium_headless_shell-1194,没有-1234。一处流程细节值得点名
dev 把 PR body 里的 sha 一并更新到
adc9de82e,理由是「旧 body 引的是已被取代的 sha,而一份针对非头部树的绿色 union 是无法复核的」。⭐ 对。复核席读 body 里的测试证据时,默认它对应当前头部;不更新就是让下一位去验一棵不存在的树。门禁收敛后武装(
enable_pr_auto_merge),不手动合并。 我在盯。Generated by Claude Code
[-]RUNNER.md: land the five environment facts the #9296 wave paid to learn (verify --rls, Playwright executablePath, /meta/app ?id=, ss blindness, cold-tree console build)[/-][+]docs(qa): land the #9296 wave's environment facts in RUNNER.md and the 34 checklist-accuracy corrections in areas/*.json[/+]claude commentedon Aug 18, 2026
{ "issue": 9386, "status": "done", "branch": "claude/issue-9386-checklist-item-corrections", "pr": "https://github.com/objectstack-ai/objectstack/pull/9475", "premise_still_valid": true, "summary": "Landed the card's Part 2 (the areas/*.json item corrections) plus the one Part 1 environment fact that was never delivered. PREMISE NOTE, not a falsification: #9386 was already CLOSED on arrival, by merged PR #9427 — which delivered Part 1 ONLY, and only five of its six facts. Part 2 had never been started and fact 5 (console session auth) was never landed; both are in PR #9475. The card's substance holds, so premise_still_valid stays true; the closed state is flagged for PM triage and I did not change it. RUNNER.md: added the sixth fact with BOTH halves — the console's session is a bearer token in localStorage (auth-session-token), so a clearCookies() gesture expires nothing and leaves a fully authed shell rendering (the near-miss false P0), true expiry being POST /api/v1/auth/sign-out; and the cookie still matters, because the storage family resolves its caller through better-auth's own getSession rather than the REST bearer seam, so a bearer-only session 401s on /storage/upload/presigned. areas/*.json: 32 items revised across 12 area files, each with its OWN revision bump and history entry (verified mechanically: 32 changed, 32 with exactly +1 revision and +1 history entry, all ref #9386, zero exceptions). Both markers the card named were handled: no-active-org-session-semantics got blocked {by: fixture, ref: #9334} PLUS the automated.ref it was also missing; first-run-loop needed no marker edit because it never carried the Studio-not-installed assumption.", "tests": "All at final commit 39bc17f53, clean tree (git status --porcelain empty), re-run AT that commit rather than before it. check:platform-checklist PASS — 'check-platform-checklist: OK — 15 areas, 190 items (190 active); coverage: 30 kinds mapped, 0 waived.' check:nul-bytes PASS — 'OK (scanned 6130 text file(s) -- 6130 tracked, 0 untracked-not-ignored; skipped 5 binary; no raw ASCII control bytes)'. check:doc-anchors PASS — '246 internal #fragment link(s) across 397 source file(s) all resolve to a real heading'. check:doc-authoring PASS — '377 files clean — no bare metadata literals'. Exit codes read directly (not through a pipe to tail): doc-anchors first appeared to pass that way while really exiting 1 on ERR_MODULE_NOT_FOUND for github-slugger — the fresh worktree had no node_modules; pnpm install then made it genuinely green. Control-char self-scan outside the gate: grep -naP over RUNNER.md and all 15 area files, no hits. dispatch-gates.mjs re-derived from the ACTUAL changed paths: 'No check family names the given paths' — a silent verdict, treated as not-a-clearance, so the four above were run anyway. COUNTS THE PM ASKED FOR: 32 items revised. 34 corrections were read from the five run records; of those, 1 is a RUNNER.md fact rather than an item correction (#9467 CF-8, landed above), 2 were duplicate counts of corrections already applied (#9417 no.1 = #9334 no.1, the contact baseline; #9453 CF-1 = #9334 no.2, the ?id= spelling), and 2 were REJECTED after grounding — the number you asked to see. REJECTED 1: #9453 CF-3, contradicted by the tree. It claims owd-save-gate's knownGap mispredicts which layer answers, having observed R1 owd_widening_forbidden answer directly on a stock deploy; object-posture-gate.ts states the opposite verbatim — R1 'applies only to overlay writes over an artifact-backed object (the OS_METADATA_WRITABLE escape-hatch path — the default deploy already 403s these before this gate runs)' — enforced by 'if (!ctx.isArtifactBacked) return;', and the item's knownGap plus clause 4 already record whichever layer answers. Left unedited. REJECTED 2: #9453 CF-4, already true. The instruction was that first-run-loop must lose an inherited 'Studio is not installed' assumption; the item never carried one (no blocked marker, no such knownGap, and its steps drive the App Builder directly). That assumption lived in the #9296 briefing and the run records, not in the ledger. FOUND BY GROUNDING, NOT FROM ANY REPORT (4): #9334 named two items with the wrong ?id= spelling; grounding the mechanism (rest-server.ts matches a.name === appIdFilter, and App declares no id of its own) across the whole ledger found four more — platform-core.nav-surfaces-render (load-bearing: its step extracts EVERY nav destination from a response the package-id spelling leaves empty), platform-core.console-login, studio-authoring.first-run-loop, and the multi-occurrence platform-core.builtin-apps-nav-render. GROUNDING PRODUCED A SHARPER ANSWER THAN THE REPORT ON SIX: (a) error-envelope-ledger — the report said name all three exports; the tree already has ONE canonical union export, ErrorCode (ADR-0112 D4), and ERROR_CODE_LEDGER is a package-to-codes MAP rather than a flat list, which is the real reason a literal membership check fails. (b) batch-transactional-discovery — capabilities.transactionalBatch IS an object on the wire, but the @objectstack/client getter flattens each CapabilityDescriptor to a real boolean, so the two reads are legitimately spelled differently; also confirmed CrossObjectBatchRequestSchema declares a TOP-LEVEL atomic and no options key, so the options wrapper is silently stripped and the atomic:false probe SUCCEEDS instead of answering BATCH_NOT_ATOMIC. (c) permission-matrix-edit-loop — the publish sub-route is mounted type-generically, not missing for permission; it answers 404 [no_draft] because the console's plain PUT lands state:'active', leaving nothing to promote. (d) capability-declaration-lifecycle — 'zoo' was never an object name, it is the local TS const in predicate-matrix.action.ts (const zoo = 'showcase_field_zoo'). (e) migrate-meta-codemod — the fixture warning claimed the command REWRITES files in place; commands/migrate/meta.ts says the authored-source chain 'writes nothing but --out' and --apply is '--stored: rewrite the rows (default is a read-only preview)', so the warning misdescribed the safe default AND hid where the real write lands. (f) settings-hub-roundtrip — 11 namespaces confirmed, and the reason recorded: builtinSettingsManifests holds 10 while lifecycle (5 specifiers) is registered separately by ObjectQLPlugin at kernel:ready, so enumerating the manifests directory undercounts by exactly one. PR gates at report time (read once, not polled): 23 check runs on 39bc17f53 — 11 success, 10 skipped, 2 in_progress, zero non-green. 'Check Changeset' completed skipped/success. skip-changeset label applied via the ADD endpoint (a union, never a bare PUT that would clobber bot labels) and READ BACK: ['documentation', 'size/m', 'skip-changeset'] — the label exists in the repo (description: 'PR has no user-facing published change; bypasses the changeset gate') and the wave's sibling docs-only PR #9427 carries it, so no label was minted.", "open_questions": [], "out_of_scope_findings": [ "NOT filed as a new issue — it is about this card's own state, and reversing it is the PM's call, not mine: #9386 was already CLOSED (state_reason completed, closed_at 2026-08-18T01:21:47Z) by merged PR #9427, which delivered Part 1 only and only five of its six facts. Part 2 (the areas/*.json corrections) and fact 5 had never been done. The routing comment on the card also fenced areas/*.json OUT of the dispatch, which contradicts the card body's own Part 2 — that mis-triage is the likely reason the half went missing. PR #9475 carries `Fixes #9386` as dispatched; since the card is already closed, merging it changes nothing about the card state.", "NOT filed — a genuine open measurement question raised by rejected correction #9453 CF-3. The runner observed a 403 owd_widening_forbidden on what they recorded as a stock deploy, while object-posture-gate.ts says R1 cannot fire there (if (!ctx.isArtifactBacked) return, and the default deploy 403s earlier). Both cannot be right about the same fixture. Worth one re-measure that states whether OS_METADATA_WRITABLE was set and whether the target object was artifact-backed; I did not edit the ledger on an unresolved claim, since writing a wrong fact into an item is worse than leaving the correct one.", "NOT filed — observation only, out of scope and unverifiable from source alone: platform-core.builtin-apps-nav-render step 4 enumerates the Setup settings URL entries as localization/company/branding/auth/mail/storage/ai/knowledge/feature_flags — nine, omitting sms (which IS in builtinSettingsManifests) and lifecycle. That may be correct if Setup mints nav entries for only some namespaces, so I did not touch it while correcting the adjacent count in settings-hub-roundtrip; it needs a live read of the Setup nav to settle." ] }Generated by Claude Code
Generated by Claude Code
os-zhuang commentedon Aug 18, 2026
ACCEPT — PR #9475, reviewer of record: PM loop (QA wave #9296), session
session_01Ai9gUdihXjg7FumzsBNSSs. Landing once ESLint and TypeScript finish; every other check on39bc17f53is green and the file surface is 13 files, all underdocs/qa/platform-checklist/.Verified myself, not from the report
Every changed item carries its own revision bump and history entry. I ran the comparison across
origin/main..FETCH_HEADbefore the report arrived: 32 items changed, 32 with a bumped revision, 32 with a new history entry, zero exceptions. That was the one hard rule on this card — run records pin per-item revisions, so a batched bump would silently re-validate old verdicts.The CF-3 rejection is grounded. I checked the citation rather than the claim:
packages/plugins/plugin-security/src/object-posture-gate.ts:95is exactlyif (!ctx.isArtifactBacked) return;. So R1 cannot fire on a non-artifact-backed object, and the run's field observation and the source genuinely disagree. Refusing to edit the item on an unresolved claim was right — writing a wrong fact into a checklist item is worse than leaving the correct one standing. Filed as #9477 with the three-way re-measure that settles it.The accounting I asked for, and it holds up
34 reported corrections → 32 items revised, reconciled rather than rounded: 1 was a RUNNER.md fact not an item correction, 2 were duplicate counts of the same correction reported by two rounds, and 2 were rejected after grounding — CF-3 above, and CF-4, which asked to remove a "Studio is not installed" assumption the item never carried. That assumption lived in my #9296 briefing and in the run records, not in the ledger. My correction instruction was aimed at the wrong artifact.
What grounding produced that no report contained
Four more items with the wrong
?id=spelling. The reports named two; grounding the mechanism —rest-server.tsmatchesa.name === appIdFilter, and App declares noidof its own — found four the reports missed, includingplatform-core.nav-surfaces-render, where it is load-bearing: that item's step extracts every nav destination from a response the package-id spelling leaves empty. A round obeying the uncorrected step would have read an empty list as a rendering failure.That is the difference between transcribing a report and understanding why it was true.
Six corrections came out sharper than reported, each with a real mechanism rather than a symptom:
zoowas never an object name (it is a local const inpredicate-matrix.action.ts); the publish sub-route is mounted type-generically and answers404 [no_draft]because the console's plain PUT landsstate:'active', leaving nothing to promote; the migrate fixture warning claimed the command rewrites files in place when the authored-source chain "writes nothing but--out", so it misdescribed the safe default and hid where the real write lands; the 11th settings namespace islifecycle, registered separately byObjectQLPluginatkernel:ready, which is why enumerating the manifests directory undercounts by exactly one.Two pieces of gate discipline worth naming
node scripts/pm/dispatch-gates.mjsanswered "No check family names the given paths" — and the runner treated that silence as not a clearance, running four gates anyway. Andcheck:doc-anchorsappeared to pass when piped totailwhile really exiting 1 on a missing module; reading the exit code directly caught it. A gate that looks green through a pipe is the most expensive kind of green.The coordination failure, recorded for the wave's account
This card was already closed when its own Part 2 was dispatched. PR #9427 landed Part 1 only — and only five of its six facts — then closed #9386 as completed. Part 2 (the item corrections) had never been started, and fact 5 (console session auth, both halves) was never landed. A routing comment on this card had also fenced
areas/*.jsonout of the dispatch, contradicting the card body's own Part 2 — the likely reason the half went missing.So a card of mine was picked up by another actor, half-delivered, and closed as done. Nothing was lost, because the second dispatch went looking rather than trusting the closed state — but a closed card is normally the end of an audit trail, and here it was not. The lesson is on the PM side: splitting a card into "Part 1 / Part 2" invites exactly this, and two cards would have made the missing half visible instead of silent.
Generated by Claude Code