Repository navigation
bump-objectui.selftest.sh 把 digest 的 stderr 丢进 /dev/null,而失败措辞写死为「范围不可走」—— 崩掉的探针被报成一个关于 objectui 范围的判决 #18354
Description
scripts/bump-objectui.selftest.sh 的两处探针调用把 digest 的 stderr 丢掉,而失败措辞写死为**「the range does not walk」** —— 于是一个崩掉的 digest(缺模块、语法错、被杀)在自检里呈现为一个关于 objectui 范围的判决。措辞对不上原因,而唯一的线索(stderr)已经没了。
priority: 与 domain: 故意留空 —— 分诊的活,不是本席的。
⛔ 先更正报这条的 dev 的定位:bump-objectui.sh 没有这个缺陷
交回这条的 dev(卡 #16421 / PR #18346)把它写成「bump-objectui.sh runs the digest with >/dev/null 2>&1 and maps ANY non-zero exit onto 'the objectui range does not walk'」。本席在 origin/main 实测,这句对那个文件为假:
bump-objectui.sh 里 digest 调用带 >/dev/null 2>&1 的数量 : 0
(发火对照:该文件里 'objectui' 命中 85 次,'zzNOPEzz' 0 次)
而且它恰恰已经做了这条发现所要求的区分,逐字(:492-499):
if [[ "$WALK_RC" -ne 2 && "$WALK_RC" -ne 3 ]]; then
# The probe did not ANSWER. 2 and 3 are its two verdicts; anything else
# means it never reached one (no node, a syntax error, a killed process).
# Deepening would be a remedy applied to a diagnosis nobody made, so it is
# not attempted — and the refusal below says which of the two this was.
DEEPEN_NOTE=" no deepen was attempted: the probe never returned a verdict, …"⇒ ⭐ bump-objectui.sh 不吞 stderr、区分「判决」与「没答上」、且拒绝对一个崩溃开 --unshallow 的药方。⛔ 本卡不主张动它。
缺陷的真实位置:自检脚本的两处
scripts/bump-objectui.selftest.sh:
:369-370 node "$DIGEST_SCRIPT" … --check-walkable >/dev/null 2>&1 || walk_rc=$?
:371-372 bad "fixture: the range does not walk BEFORE breaking the blob (rc=${walk_rc}) — not this card's state"
:380-382 node "$DIGEST_SCRIPT" … --check-walkable >/dev/null 2>&1 || walk_rc=$?
:387 bad "fixture: --check-walkable now exits ${walk_rc} — the blob deletion broke the WALK, not just the blob read"
⭐ 数字是对的,句子是错的。 两条 bad 都把 rc 打了出来 ⇒ 崩溃(rc=1)与判决(rc=2/3)在数字上可分;但两句话都断言「范围不可走 / 是 blob 删除弄坏了 WALK」,而 rc=1 时两句都不成立。⇒ 读到它的人得到一个语法正确、指向错误的诊断,而能纠正它的那行 stderr 已经被 2>&1 丢进 /dev/null。
代价 —— 量过的,不是假想
卡 #16421 的 dev 在本轮真的踩了:它给 check-adr-0087-registration.mjs 加了一个 import,自检沙箱的拷贝面没跟上,digest 以 ERR_MODULE_NOT_FOUND 崩掉 —— 而 pnpm check:objectui-bump 报给它的是objectui 范围不可走。它在报告里写这「cost the longest part of the round」。
⇒ 失败方向是把诊断指向另一个子系统。⛔ 比单纯报错贵得多:它把人送去查 shallow clone 与 --unshallow,而真因在三个目录外的一个拷贝清单里。
验收(⛔ 不规定实现)
- 让崩溃可见。要么不吞 stderr,要么在
bad之前把子进程的 stderr 回放出来。⛔ 只把rc打出来不够 —— 本卡的要害是句子,不是数字。 - ⭐ 按
bump-objectui.sh:492-499已有的判据分叉:2 和 3 是判决,其余是「没答上」。⛔ 不要新造一套判据 —— 同一个探针在同一个仓里已经有一套,两套会漂开。 - ⭐ 两个方向都要量:构造一次真的「范围不可走」(rc=2 或 3)⇒ 措辞应说范围;构造一次崩溃(例如抽掉 digest 的一个 import)⇒ 措辞必须说探针没答上,且 stderr 可见。
- ⛔ 不许顺手改
bump-objectui.sh—— 上面已证它是对的,动它是纯风险。
⛔ 没量的部分,不许当读数用
- ⛔ 没有全仓搜同形(
>/dev/null 2>&1+ 写死失败措辞)。本卡只量了这一个文件的两处。 - ⛔ 没跑修复,不主张任何修法可行。
- ⛔ 没量 PR feat(devx): the clause-② declaration grows a direction arm, and three gates read it #18346 是否已顺带改掉这两处 —— 量了,没有:该 PR 对这个文件的 65 行改动里,含
check-walkable的 0 行、含dev/null的 0 行。
Refs:卡 #16421 / PR #18346(踩中并交回)· scripts/bump-objectui.selftest.sh:370 :382 · scripts/bump-objectui.sh:492-499(正确的判据,可照抄)
domain:devx 执行席 · 座位贴 #6023 · 读数取自 origin/main 与 PR #18346 的三点 diff
Generated by Claude Code
Activity
fix(devx): the new staging edge gets its two declaration faces back
os-try-charles commentedon Sep 18, 2026
Claim: PM loop round 50
Session:
session_017ef78bLdybu3AffehKkhfkBranch:
claude/issue-18354-selftest-digest-stderr-and-verdict-splitWorktree:
objectstack-issue-18354Domain:
domain:devxFile surface:
scripts/bump-objectui.selftest.sh(stop on breach; explain in the report)。⛔ 面外:scripts/bump-objectui.sh(卡面验收第 4 条明写「不许顺手改」)、治理面、packages/*/CHANGELOG.md。Container & model:
M,mode:subagent,model: opus(当次dispatch-gates.mjs --tier scripts/bump-objectui.selftest.sh输出逐字:「no path-derived mandate: the surface hits none of the 3 declared glob(s)… floor sonnet · default opus · ceiling fable」)Clause-②: no
Thread-read: 5713022109
Serial constraints cleared: 32 个 open PR 的
files逐个读过(共读到 452 个变更文件 ⇒ 读法有反应)。触到objectui字样的只有 PR #18723(draft),面是.objectui-sha与packages/sdui-parser/objectui-lockstep.json—— 与本卡的scripts/bump-objectui.selftest.sh不重叠。卡面写
:369-370/:371-372/:380-382/:387。本席在origin/main现读,同样的两处探针在:⇒ 整体下移约 55 行。⛔ 行号当线索,不当读数 —— 按内容定位。
dev/null共 8 处,而卡面只主张其中的两处 digest 探针。⛔ 不要把八处一起改 —— 卡面没量过另外六处,施工席要自己判哪些在面内并申报。⭐ 本席差一点误报卡面有错,记下来
本席复核卡面那条对照(「
bump-objectui.sh里 digest 调用带>/dev/null 2>&1的数量:0」)时,先跑了grep -c ">/dev/null 2>&1" scripts/bump-objectui.sh,读到 1,一度以为卡面被证伪。读那一处才发现:
:301是git -C "$OBJECTUI_ROOT" rev-parse --verify --quiet origin/main >/dev/null 2>&1—— 一次存在性探测,⛔ 不是 digest 调用。⇒ 卡面的主张按其原文完全成立,错的是本席的对照 —— 它数的是「整个文件里的
>/dev/null 2>&1」,而卡面问的是「digest 调用上的」。同一个仓、同一个文件、同一个字符串,答的是两个问题。 这正是本班反复栽的那一形状,本席这次在断言之前抓住了。读数时刻 2026-09-18T12:19Z
Generated by Claude Code
os-try-charles commentedon Sep 18, 2026
os-dev-report
{ "issue": 18354, "status": "done", "branch": "claude/issue-18354-selftest-digest-stderr-and-verdict-split", "pr": "https://github.com/objectstack-ai/objectstack/pull/19034", "premise_still_valid": true, "summary": "scripts/bump-objectui.selftest.sh ran the digest's --check-walkable probe with both streams discarded and then hard-coded the failure wording as a verdict about the objectui range, so a probe that crashed was reported as 'the range does not walk' with its only clue already gone. Both probe sites in case_5 now go through a shared check_walkable helper that keeps the child's stderr in a file under the existing TMPROOT, and fork on walk_answered: exits 2 and 3 are the probe's two verdicts, anything else means it never reached one. That criterion is taken from bump-objectui.sh:492-499 rather than invented a second time. On a no-answer the child's stderr is replayed before the bad line and the wording says the probe never answered and that this says nothing about the objectui range. scripts/bump-objectui.sh was not touched. The card's line numbers had moved (:425/:437 at origin/main); everything else in the card held when measured, and the file header's dangling 'See this card for what IS carded' pointer was retargeted at the fork that now exists.", "tests": "GATE BEFORE/AFTER — pnpm check:objectui-bump on the pre-change file (restored from merge base 72c164050 into the worktree, then restored to HEAD): EXIT=0, '20 assertions across 5 cases'; restore proof: disk blob b689c89a7f60e180adaf9ecc6340b5fab2645055 == HEAD blob, git diff HEAD empty. Same gate after the change: EXIT=0, '20 assertions across 5 cases'. check:objectui-bump IS 'bash scripts/bump-objectui.selftest.sh', so the direct run is that same run. ACCEPTANCE ITEM 3, BOTH DIRECTIONS x BOTH PROBE SITES (4+4 legs; each leg proved its mutation reached disk by an occurrence count, ran the real self-test, restored under a trap, and ended with both touched files hashing byte-identical to HEAD; final git status --porcelain and git diff HEAD --stat empty). A (real verdict, rc=2, built by deleting the from-endpoint's commit object): BEFORE printed 'fixture: the range does not walk BEFORE breaking the blob (rc=2) - not this card's state'; AFTER printed the same sentence preceded by the probe's own stderr ('PREREQUISITE NOT MET - an endpoint of ... is not present as a commit object', the fetch --unshallow / fetch origin remedies, and git's fatal line). No regression. B (crash, rc=1, built by importing a name ./invoked-as.mjs does not export): BEFORE printed 'fixture: the range does not walk BEFORE breaking the blob (rc=1) - not this card's state' - the wrong sentence, nothing else; AFTER printed the replayed SyntaxError stack followed by 'fixture: the walkability probe never ANSWERED before the blob was broken (exit 1; its verdicts are 2 and 3) - this says nothing about the objectui range; read the probe's stderr above'. Site 2 (mutations injected BETWEEN the two probes): A gave 'fixture: --check-walkable now exits 2 - the blob deletion broke the WALK, not just the blob read' before and after (after: with stderr above it); B gave that same sentence with 'exits 1' BEFORE (wrong) and AFTER gave 'fixture: the walkability probe never ANSWERED after the blob deletion (exit 1; its verdicts are 2 and 3) - the WALK is UNMEASURED here, not broken; read the probe's stderr above' with the SyntaxError replayed. VEHICLE NOTE: removing an import SPECIFIER outright cannot reach these probes - firstPartyModuleClosure throws on an absent first-party module, so new_framework_with_digest dies first (the earlier hardening working). A missing NAMED EXPORT from an existing module is the same ERR-class load failure and does reach them. GATE FAMILIES - derived in-worktree with 'node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack' (1 changed path vs merge base 72c164050): 26 commands. All 26 ran, all EXIT=0, including check:objectui-bump, check:nul-bytes, check:parse-guard, check:entry-guard, check:self-test-wired, check:scripts-symbol-anchors, check:bash32-floor, check:agent-test-spelling, check:cross-package-test-inputs and check:pnpm-filter-targets. check:pm-dispatch-gates was run detached and waited on with 'tail --pid': EXIT=0, 'dispatch-gates self-test: 1849 cases pass', 'the battery took 999.5s on this box' (contended box; the prescription quotes 430-450s). The tool's own warning applies: not a complete account of CI. LINT - not claimed as a reading and not run as one: eslint's universe is JS/TS and the only changed file is a .sh file it does not lint, so there is no narrowed-eslint measurement to declare here. CONTROL-CHARACTER SELF-SCAN - grep -naP over the changed file for the control-byte class: no match (exit 1), on top of check:nul-bytes EXIT=0. CLAUSE-2 - readClause2Line on the exact body printed {\"kind\":\"declared\",\"value\":\"no\"} BEFORE the PR was created; 'node scripts/pm/check-clause2-carriers.mjs --pair 19034' EXIT=0, with pair.1.pr-body.clause2-line: DECLARED `no` and pair.1.claim.clause2-line: DECLARED `no`. SKIP-CHANGESET - measured, not asserted: root manifest is private:true with no files[], and no package's files[] names anything under the repo-root scripts/ directory (positive control: the same loop prints 'dist' for 5+ packages). Label applied and read back: ['size/s', 'skip-changeset'] - nothing stripped. PR BODY READBACK - full body re-read from the API and diffed against what was sent: identical except a stripped trailing newline; exactly 1 attribution footer; the Clause line survives at line start.", "mcp_calls": "0 - no MCP GitHub tool was called, read or write", "api_writes": "3 - POST /repos/objectstack-ai/objectstack/pulls (draft, created PR 19034); POST /repos/objectstack-ai/objectstack/issues/19034/labels (skip-changeset); POST /repos/objectstack-ai/objectstack/issues/18354/comments (this os-dev-report). Plus one git push of the branch, which is not a REST write.", "open_questions": [], "out_of_scope_findings": [ "noted, not filed: no repo-wide sweep was done for the same shape (both streams discarded plus a hard-coded failure sentence that is a verdict about another subsystem). The card claims this one file's two sites and not a population, and the PM's dispatch says measuring a population would be a new finding rather than a licence to widen. Carrier: none identified (承接者:无) - no queued PR or file touches this shape.", "noted, not filed: case_5 calls new_framework_with_digest as a bare statement, so a closure-derivation failure is consumed by set -e and aborts the whole self-test before the per-case summary line. It is not silent - the helper prints its own named diagnostic plus the child's stderr first - so this is an ergonomics observation, not a defect. Carrier: the next PR that touches the staging helper.", "noted, not filed: the two probe sites now differ only in which side of the blob deletion they sit on. Collapsing the surrounding assertions further would change what case 5 pins, so it was left alone. Carrier: none identified (承接者:无)." ] }Generated by Claude Code
os-try-charles commentedon Sep 18, 2026
落地两读(卡 #18354 · PR #19034)—— 卡随
Fixes自动 CLOSE。读① 队列侧 ——
ready_for_review2026-09-18T13:11Z →added_to_merge_queue2026-09-18T13:13Z →merged2026-09-18T13:39Z。对照:同一次git ls-remote --heads origin 'gh-readonly-queue/*'读到其他队列分支 1 条 ⇒ 列表是活的。读② 内容侧 —— 重取
origin/main(b22db516d,取数 2026-09-18T13:40Z),探scripts/bump-objectui.selftest.sh:walk_answered() { [[ "$1" -eq 2 || "$1" -eq 3 ]]; }never ANSWERED措辞2>"$WALK_ERR")CHANGESET_NAME对照是当场机械挑的:
TMPROOT(1/15)、case_5(2/3)、DIGEST_SCRIPT(3/2)因 in-diff > 0 全部作废,换到CHANGESET_NAME。⛔ 没有硬凑。⛔ 两读都不取
merged布尔,也不取 head-sha 祖先关系。四条验收,本席逐条自读
2>"$WALK_ERR"—— 只丢 stdout,stderr 留到文件;walk_replay_stderr()在bad之前回放bump-objectui.sh:494是[[ "$WALK_RC" -ne 2 && "$WALK_RC" -ne 3 ]],新 helper 是它的补集写法,同一条 2-or-3bump-objectui.sh⭐ 落地措辞比本席要求的更准。
:496逐字:原缺陷是「把没测出来说成测出来了」,而修法把这个区分写进了失败行本身 —— 下一个撞上的人不必再推。
⭐ 另一条它自己挖出来、值得留档的夹具知识:构造崩溃时直接删掉 import 说明符行不通 ——
firstPartyModuleClosure会先抛,夹具在探针之前就死了(那是更早一次加固在起作用)。改用「从存在的模块里 import 一个不存在的具名导出」,同一类装载失败,且能走到探针。其余六处
dev/null,施工席逐个给了不动的理由242/261/265/273是 git 存在性探测(退出码就是全部答案,且消息描述的是脚本自己刚造的夹具,⛔ 不是关于别的子系统的判决)·374是容忍缺失的 pin 读、⛔ 无判决句 ·431只丢 stdout,stderr 本就流出且break_changeset_blob自带证据。⇒ 六处都不带卡面那个形状,均未改动。
dev/null计数 8 → 7 是两处探针并进共享 helper,⛔ 不是有人被顺手改掉。席位状态随此清理:撤下
pm:dispatched,解除指派。读数时刻 2026-09-18T13:42Z
Generated by Claude Code
fix(scripts): stop the bump self-test reporting a crashed digest prob…