agents(os-dev): a declared narrowing that cites the affected set names the instrument's blind spot - #16656
Conversation
…s the instrument's blind spot `turbo ls --affected` derives its set from the package dependency graph, so it is structurally blind to a test that reads another package's source off disk, and the repo-root package is not in the workspace list at all — measured here: `pnpm turbo ls` prints 80 packages and `@objectstack/spec-monorepo` is absent. A narrowing sentence that cites the set therefore cannot assert "cannot move"; it can only assert "not on the graph". Two rule lines added in the local verification section, paid for by folding two cross-reference restatements whose every clause survives in the section each of them pointed at. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018dxq7YqsLDMeZDZ5AzsgJX
|
Body edited once after creation, to record a second merge-tree reading: Per Generated by Claude Code |
|
ACCEPT — flight L, PR #16656 at head Own readings (review worktree detached at
Review checklist:
Implemented-by: os-dev subagent 维护者速读改了什么: dev 契约文件 为什么改: 这句话今天在报告里是会静默说谎的:objectui 那边量到 73 个跨包读盘的测试在图上看不见,最近 30 个已合并 PR 里有 5 个拿受影响集当理由、5 个全都有集外的读盘测试。CI 跑全农场,所以没有线上缺陷;坏的是报告措辞本身。修法是一句规则,不加门禁(手工声明文件或映射门禁只会把同一个盲区搬进一个会漂移的镜像)。 风险与代价(含回滚): 两行文本,零 CI 成本,不动任何代码路径;最坏情况是 dev 报告多写半句。回滚 = revert 一个 commit。 席位意见: 复核通过,建议 A——只落在 你要做的: 认可 A 就直接手动合并本 PR(受管面,⛔ 不走队列、不挂 auto-merge);合并即关 #16631。A 还是 B? Generated by Claude Code |
Fixes #16631
turbo ls --affectedderives its set from the package dependency graph. A test that readsanother package's source off disk is not an edge in that graph, and the repo-root package
is not in the workspace list at all — so a dev report's narrowing sentence that cites the
affected set asserts a completeness the instrument does not have, and when it is wrong it is
wrong silently and green. This PR adds the standing rule that makes the sentence honest, in
the one file every dispatched dev reads in both repos.
The ruling this PR executes
No maintainer ruling exists on this card. The reporting seat's verdict, quoted from the card
body and untranslated:
D1/D3 (a declaration file, a mapping gate) are refused with the card's own reflexive evidence;
D2 stays opportunistic with no migration sweep; the 12 dependency-covered readers are not
recorded. The grading comment (5572475500) settles the landing:
.claude/agents/os-dev.md,fold-paid at 403/403, rules-only register, no issue numbers. The A/B placement question is the
maintainer's at the governed merge — proposed in the 速读 below, not decided here.
What was added
Two rule lines in the 「本地验证范围」 section, immediately after the block that defines what
makes a narrowing a measurement rather than a skip, and before the block on how to read a gate's
exit code:
117 and 116 bytes — both inside the ratchet's 120-byte per-line cap, measured with
printf '%s' LINE | wc -c, not estimated.Why this neighbourhood and not the other. The seat's grep named two candidates. L339–340
(「干净收尾」 rule 4) is about ending a turn: there, a narrowing is an escape hatch from the
lock queue, and the rule governs whether you may stop. L155–159 (「本地验证范围」) is where the
file already defines a narrowing's evidentiary shape — 「三件都在,收窄就是一次测量;缺一件,
它只是没跑」. The affected set is a second narrowing instrument of that same kind, so its required
disclosure belongs beside the first one's three evidences. Placing it after L163 rather than
after L159 keeps the eslint-union addendum (L160–163) unbroken instead of interleaving two
instruments.
Why it names no gate.
pnpm check:cross-package-test-inputsexists in this repo and, inAGENTS.md's own words, 「finds those tests by scanning source text, so it sees only thespellings it knows」. objectui has no such gate at all. Neither added line mentions a gate, so
nothing here implies the hole is shut.
The fold — two lines added, two deleted, 403/403
Two lines were added, so two were folded. Both deletions are pure cross-reference restatements:
each said in short what the section it pointed at says in full.
Fold 1 — deleted verbatim from
.claude/agents/os-dev.md:97(資源紀律 rule 6):origin/mainat.claude/agents/os-dev.md:262— 「硬线:消融/变异脚本一律自带trap '(restore)' EXIT INT TERM还原。」 — states the same rule and additionally pins the trap's exact spelling and its three signals.claude/agents/os-dev.md:263— 「前台上限的 SIGTERM 可以正落在变异中途;没有 trap 的树保持变异态,之后每次测量都错。」 — the surviving hard line names 前台上限 itself, so the tie back to rule 6 is stated inside the destination, not lost with the pointerFold 2 — deleted verbatim from
.claude/agents/os-dev.md:133(本地验证范围, item ②):origin/mainatpackages/cli只欠unit层」.claude/agents/os-dev.md:269— 「范围按本地验证范围节圈定;packages/cli的卡本地只欠unit层,integration层声明给 CI。」 — the same clause verbatim, plus what happens to the other tier.claude/agents/os-dev.md:270–272— the destination adds the run command (--project unit), the exceptions that pullintegrationback in locally, and the driver/kernel case; strictly more than the deleted line carriedNote on the quote above: the surviving line at
:262spells the trap's placeholder withangle brackets; it is rendered here with round brackets because a short angle-bracket fragment
is eaten by GitHub's body sanitizer. The deleted lines are quoted byte-verbatim.
No untouched line was re-wrapped: the diff is exactly
-2 / +2and the widest line in the fileis unchanged at 120 bytes.
The five dispatched premises, each measured against the tree
os-dev.mdis 403 lines onorigin/main, pinned at 403, last touchede692515a4, and no line names the blind spotgit show origin/main:.claude/agents/os-dev.md | wc -l= 403; ratchet map row['.claude/agents/os-dev.md', 403];git log origin/main -1 --format=%h -- .claude/agents/os-dev.md=e692515a4;grep -n -E 'affected|narrow|收窄|turbo ls'over the file returns 10 hits, all of them the 收窄 rules at L102–103 / L155–159 / L339–340 and none namingturbo ls,--affectedor the graphpnpm turbo lslist the root package here?pnpm turbo lsin this worktree prints80 packages (pnpm9);grep 'spec-monorepo'over that output exits 1 —@objectstack/spec-monorepois absent, the same shape the card measured in objectui (46 packages, root absent)scripts/__tests__git ls-tree -r --name-only origin/main -- scripts/= 352 files,grep -c '__tests__'= 0; objectui's same read lists a populatedscripts/__tests__/turbo ls⇒ a root-level suite is entirely off-graph), which is true here and has bite there; it does not namescripts/__tests__pnpm check:cross-package-test-inputsexists here and sees only the spellings it knows; objectui has no such gatepackage.json:check:cross-package-test-inputs = node scripts/check-cross-package-test-inputs.mjs --self-test && node scripts/check-cross-package-test-inputs.mjs; objectui'spackage.jsonscripts matching/cross-package|test-inputs/=[]check:skill-frame-syncorcheck:pm-skill-id-lintis not a candidate✓ check-skill-frame-sync: 2 copies of the decision frame are structurally isomorphic across 2 files;✓ check-skill-id-lint: 26 file(s) clean (pattern /#[0-9]{3,}/g).claude/agents/⇒os-dev.mdis the whole dev-contract surface for both reposgit -C /home/user/objectui fetch origin main:git ls-tree origin/main -- .claude/lists exactly.claude/hooks,.claude/launch.json,.claude/settings.json,.claude/skills— noagents/AGENTS.mdalready carries a paragraph on the same blind spotAGENTS.mdL82–89 onorigin/main, heading 「A test that reads outside its own package must be spelled so the gate can see it」; a grep forturbo ls|--affectedover all 1068 lines returns exactly one hit, L86, inside that paragraphGates — derived, never hand-listed
Derived in the worktree at final HEAD
727794b5dwithnode scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack. Its stderrnames the repo and commit it answered for:
Every command was run with its exit captured to a file before any pipe
(
cmd > FILE 2>&1; ex=$?).node scripts/check-closing-keyword-parity.mjsnode scripts/check-closing-keyword-parity.mjs --self-testnode scripts/check-comment-mask-corpus.mjsnode scripts/pm/check-governed-queue-guard.mjs --self-testpnpm --filter @objectstack/lint run check:doc-formula-expressions(first run exited 3 =PREREQUISITE NOT MET, not a measurement; prerequisite built under the lock, then re-run)pnpm check:agent-model-declaredpnpm check:agent-test-spellingpnpm check:doc-authoringpnpm check:driver-memory-censuspnpm check:nul-bytespnpm check:pm-governed-mergespnpm check:pm-skill-id-lintpnpm check:pm-skill-ratchetpnpm check:refd-timer-probepnpm check:skill-frame-syncpnpm check:watch-hint-literalnode scripts/pm/check-governed-merges.mjs --test .claude/agents/os-dev.md— expected: 3 = GOVERNEDReconciliation, quoted from the tool's own verdict line:
Gate verdict lines that judge this diff, quoted as each gate printed them:
check-governed-merges --test, exit 3:The prerequisite build ran through the shared lock
(
OS_VERIFY_LOCK_SLOT=issue-16631), which printed:os-verify-lock: VERDICT command-exit 0 · held the lock 2s · waited 0s.pnpm lint— a measured narrowing, with all three evidencespnpm lintiseslint . --no-inline-configover the whole repo, a CI-owned run. It wasnarrowed to the changed file, and the narrowing is a measurement, not a skip:
files:selector ineslint.config.mjsis a JS/TS extension glob — L971**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs},L1015, L1054, L1103, L1172, L1212
**/*.{ts,tsx,mts,cts}, and L1238'sCOMMENT_SWALLOW_FILES(defined at L785 as['**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}']). Agrep for
markdown|processor|**/*.mdover the config exits 1 — no markdown plugin, noprocessor, no
.mdselector exists.--format json.eslint .claude/agents/os-dev.md --no-inline-config --format jsonreturned 1 result, 0 errors, 1 warning, and the singlemessage is eslint's own
"File ignored because no matching configuration was supplied."⇒ 0 linted files in this diff, stated by the instrument rather than inferred.
config block's
parserOptionsis{ ecmaVersion: 'latest', sourceType: 'module' }andnothing sets
parserOptions.projectorprojectService.eslint.config.mjsL326–329 saysso in its own words — 「this repo runs one
eslint.config.mjs, which never enablestype-aware linting (no
parserOptions.project, no typed@typescript-eslintrules) for ANYfile, test or not」. With no cross-file type program, a markdown-only diff cannot move any
verdict on any untouched file.
Merge-tree reading
Two readings, because
mainadvanced between the first one and this PR's creation. Both arerecorded rather than the convenient one:
What the advance changed, measured rather than assumed.
bc0ac1d1f..4fe00b80eis twomerges (PR #16597 and PR #16604).
git diff --name-onlyover that range restricted to mysurface —
.claude/agents/os-dev.md,scripts/pm/check-skill-line-ratchet.mjs,scripts/pm/dispatch-gates.mjs,eslint.config.mjs— prints nothing, so every measurementabove still describes the tree it claims to, and the merge base of this branch and
mainisstill
bc0ac1d1f. The range does touchpackage.jsonand.github/workflows/lint.yml, whichis where a new gate family would appear, so that was checked too rather than waved past:
comparing the two
package.jsonscript maps, exactly one family is new —check:pm-widening-tells = node scripts/pm/check-widening-tells.mjs --self-test— and its ownworkflow comment says it "has no whole-tree production run at all — its input is a DIFF
supplied by its caller, never a file in the tree". It grades the checker's fixtures, never this
diff. So the 16-family derivation above is still the complete list for these paths.
Serial constraints re-verified myself rather than recalled:
git log origin/main -1 --format=%h -- .claude/agents/os-dev.md=e692515a4; and across thehead of every open PR fetched locally,
git diff --name-only origin/main...origin/BRANCH -- .claude/agents/os-dev.mdproduced no output for any of them — no open PR touches the path.AGENTS.md(PR #16573),.claude/skills/pm-dispatch/**(PR #16597, PR #16625),content/docs/releases/**anddocs/adr/**are untouched by this diff.验收备注
AGENTS.mdL82–89 states the blind spot only for theescaping-read half, and only as a gate's rationale addressed to a test author; it never says
the repo-root package is absent from the workspace list, so a root-level suite's total
invisibility to the instrument is recorded nowhere in
AGENTS.md. Not filed as a cardbecause an omission is not a reproducible defect and the file is held by PR docs(ci, AGENTS.md): the merge queue runs the affected set, not the full package list — two stale sentences corrected #16573 anyway.
承接者: the maintainer's A/B stroke on this very PR — if B is chosen, that paragraph is
exactly where the missing half lands.
turbo lsreports 80 workspace packages here against the card's46 in objectui, so the objectui census's absolute counts do not transfer to this repo.
No count from that census was reused in the added lines — only the structural facts, each
re-measured here. 承接者:无.
维护者速读(草稿)
改了什么
.claude/agents/os-dev.md加了两行规则、删了两行,文件仍是 403 行,棘轮 403/403 headroom 0。新规则说:一句收窄若拿
turbo ls --affected当理由,必须在同一句里写明这个工具的盲区 ——集是从包依赖图推出来的,一个从磁盘上读别的包源码的测试根本不在图上;仓根包压根不在
turbo ls的清单里,挂在根上的套件整个在图外。所以这句话最多只能声称「不在图上」,永远不能声称「动不了」。删掉的两行都是纯指路重复句(各自指向的那一节把同一条规则说得更全),
每条子句在哪儿存活,上面的表里逐条给了
file:line。为什么改
这不是补一个功能,是止住一句会静默说谎的话。CI 跑的是全农场,所以今天没有用户可见的缺陷 ——
坏的就是这句话本身:它断言了一个仪器没有的完整性,而它错的时候是绿着错的。
报告席在 objectui 上量到:73 个跨包读盘的测试是依赖图看不见的;最近 30 个已合并 PR 里有 5 个
拿受影响集当理由,5 个全都有落在集外的读盘测试。修法是一句话,不是一个门禁 ——
造一份手工声明文件或一个映射门禁(D1/D3)只会把同一个失效搬进一个会漂移的镜像里,
而卡片自身的反身证据(那次普查的机械查询迭代四次仍漏测三类)正说明那份镜像会同样不全、同样绿。
风险与代价(含回滚)
代价:两行规则文本,零仓库文件新增,零 CI 成本,棘轮不抬。风险很低但不是零 ——
新规则只约束报告的措辞,不改任何代码路径、不改任何门禁判定;它唯一能造成的伤害是让 dev
在报告里多写一句话。回滚就是 revert 这一个 commit:文件回到 403 行、两条被折的指路句原样回来,
没有生成物、没有基线、没有其它文件依赖它。折掉的两行是可独立还原的,因为它们的内容
在别处完整存活,revert 只会造成一次重复,不会造成缺失。
席位意见
(留空,待席位定稿)
你要做的
这张卡带着一个 A/B 放置问题,是您在这次受管合并时拍的:A = 只落在 agent 契约文件(本 PR 现在
的样子);B = 另外在
AGENTS.md也写一段。我这边量到的读数供您判断:本仓AGENTS.mdL82–89已经带了 B 的「事实」那一半 ——「a test whose reads escape its package is invisible to both
turbo ls --affectedand thetesttask's input hashing」,而且全文 1068 行里turbo ls只出现这一次;但它是写给测试作者看的门禁理由,B 还会补上三样它现在没有的:(一)这条报告措辞
规则本身(「不在图上」而非「动不了」);(二)仓根包不在工作区清单里这半个事实,
AGENTS.md一个字都没提;(三)覆盖面 ——
AGENTS.md约束每一个席位,os-dev.md只约束被派发的 dev。反过来说,普查是在 objectui 做的,那边既没有
check:cross-package-test-inputs这个门禁,AGENTS.md也是另一个文件,所以只在本仓加 B 并不能覆盖到出问题的那个仓,而os-dev.md是两仓 dev 都读的同一份契约(objectui 的
.claude/下确实没有agents/,我自己重读过)。请回一个词:A(照现在合)或 B(我再补
AGENTS.md那一段,另开一个 PR,因为该文件现在被PR #16573 占着)。