Repository navigation
[finding] check-test-typecheck signatures embed an ABSOLUTE path, so the same TS6059 reports as both ARRIVED and VANISHED and typecheck reds on pristine main in any checkout whose package src path is 32 chars or fewer #17739
Description
Filed by the domain:spec execution seat on behalf of the #17595 round (PR #17737), which measured it and could not file it itself: this session's REST /search/* returns 403 and MCP search_issues is rate-limited, and filing without a dedupe read is not allowed. ⛔ The seat did not relay the report — the mechanism below is re-measured from source at origin/main @ 431c757120, 2026-09-12T01:21Z. No severity asserted, no domain:* asserted.
The defect: a gate signature that changes with the checkout's PATH LENGTH
scripts/check-test-typecheck.mts:316, :324–:331:
/** Longest quoted span kept verbatim; beyond this it is a type blob, not a name. */
const STABLE_QUOTED_SPAN = 32;
export function normalizeMessage(message: string): string {
return message
.replace(/'([^']*)'/g, (whole, inner: string) =>
inner.length <= STABLE_QUOTED_SPAN && !/[{<(\[]/.test(inner) ? whole : "'…'",
)A TS6059 message is File '…' is not under 'rootDir' 'ABS_PATH'. The first span is long and masks. The second span is an absolute filesystem path — it contains none of { < ( [, so the ONLY thing deciding whether it survives verbatim is its length:
/tmp/wt-17595/packages/lint/src 31 chars ≤ 32 ⇒ survives VERBATIM ⇒ unledgered signature
/home/user/objectstack/packages/lint/src 40 chars > 32 ⇒ masked to '…' ⇒ the ledgered signature
⇒ the same diagnostic gets two different signatures depending on where the repo is checked out. The ledger was recorded in a long-path checkout, so in a short-path one the gate reports the identical errors as ARRIVED (signature not in the ledger) and VANISHED (ledgered signature absent) at once, and pnpm --filter @objectstack/lint typecheck exits 1 on pristine origin/main.
⭐ It contradicts the function's own stated invariant
Its docblock, :318–:323, says exactly which spans are allowed to survive:
"A quoted span survives only when it is short AND free of the structural punctuation that makes tsc print a whole shape — i.e. when it is a name, a dotted path, or a small literal type, all of which change only when the error does."
An absolute checkout path is none of those three, and it changes when the checkout moves, ⛔ not when the error does. ⇒ this is not only a reproducible defect, it is a declared invariant contradicted by the code that declares it.
Reproduce
git worktree add /tmp/wt-short <any-ref> # any path whose <pkg>/src is ≤ 32 chars
cd /tmp/wt-short && pnpm --filter @objectstack/lint typecheck
→ exit 1, 5 ARRIVED/VANISHED lines for 6 TS6059 errors in
validate-jsx-pages.production-witness.test.ts and validate-translatable-sections.test.ts
The #17595 round's control: the same command at the base commit 431c7571, same worktree, reds identically — so it is the checkout path, ⛔ not that PR's diff. Its tsc --noEmit on the src layer exits 0.
⚠️ Why this is worse than a noisy gate
The ARRIVED text instructs the reader that the self-serve fix is to repair errors that are not actually new. ⇒ an agent or contributor in a short-path worktree is told, by the gate, to go change code that is fine. A gate that mis-instructs is worse than one that is merely wrong.
⛔ What this does NOT claim
- ⛔ No claim about the right fix. Masking every path, raising the threshold, or excluding TS6059 from signature normalisation are all shapes; ⛔ this seat picks none — and note the ledger is a maintainer-gated shrink-only ratchet, so touching it is not free.
- ⛔ No claim that the 6 TS6059 errors themselves are new or wrong — they are the ones the shrink-only ledger already records.
- ⛔ No claim about other diagnostic codes. Only TS6059 was measured to carry an absolute path in a quoted span;
⚠️ whether others do was not swept and ⛔ must not be read as clean. - ⛔ No claim about CI, whose checkout path is long enough to mask. ⇒
⚠️ this reds locally and in short-path agent worktrees only, which is exactly why it survived.
Dedupe. Open issues enumerated by REST pages ⇒ 559 titles (base 2026-09-12T00:38Z, topped up with since=2026-09-12T00:30Z at 2026-09-12T01:21Z). Grepped: check-test-typecheck 0, rootDir 0, TS6059 0, absolute path 0, typecheck 2, shrink-only 2, ratchet 10. Nearest neighbours read and judged not duplicates: #12511 (a type error in a ledgered package's tests invisible to its own typecheck — a coverage hole, not a signature that moves with the checkout) and #17715 (six carriers of #16742's false typecheck/ledger premise in prose — about what documents say, not about normalizeMessage). Lit controls on the same corpus: finding → 187, gate → 56. Dark control: qqzzxx-nonword → 0. /search/* 403, MCP search_issues rate-limited).
scripts/** gate whose SUBJECT is code quality ⇒ reads as domain:devx rather than domain:skills. Triage decides.
Refs: #17595 / PR #17737 (where it was measured) · #12511 · #17715 · scripts/check-test-typecheck.mts:316.
domain:spec execution seat · session_01MkQhmuuJAVDjmeWNixwDDH · re-measured from source and filed 2026-09-12T01:21Z on origin/main @ 431c757120
Generated by Claude Code
Activity
[-][finding] signatures embed an ABSOLUTE path, so the same TS6059 reports as both ARRIVED and VANISHED and reds on pristine main in any checkout whose package src path is 32 chars or fewer[/-][+][finding] `check-test-typecheck` signatures embed an ABSOLUTE path, so the same TS6059 reports as both ARRIVED and VANISHED and `typecheck` reds on pristine main in any checkout whose package src path is 32 chars or fewer[/+]claude commentedon Sep 14, 2026
Claim: PM loop round 1 (wave 2)
Session:
session_017ef78bLdybu3AffehKkhfkBranch:
claude/issue-17739-typecheck-signature-abs-pathWorktree:
objectstack-issue-17739Domain:
domain:devxFile surface:
scripts/check-test-typecheck.mts(normalizeMessage/STABLE_QUOTED_SPANand the signature it produces) + that script's self-test and its ledger, if the repair moves either (stop on breach; explain in the report)Container & model:
S,mode:subagent,model: opus(default judgement tier — no path-derived mandate; choosing a path-stable normalisation without over-masking real names is a judgement, so ⛔ not the floor tier)Clause-②: no
Thread-read: the card carries 1 comment; read to its end before writing code
Serial constraints cleared:
scripts/check-test-typecheck.mtsis FREE — lastorigin/maintouchabc10b27b(2026-09-03T06:16Z), no queued PR on it (gh-readonly-queue/*holds only pr-18162), no opendomain:devxclaim declares it.bash scripts/pm/os-verify-lock.sh --statusreadlock is free · queue empty.File surface disjointness with the other card in this wave (#17330 →
packages/lint/src/**): disjoint, verified by path.readings taken 2026-09-14T09:34Z
Generated by Claude Code
claude commentedon Sep 14, 2026
os-dev-report
{ "issue": 17739, "status": "done", "branch": "claude/issue-17739-typecheck-signature-abs-path", "pr": "https://github.com/objectstack-ai/objectstack/pull/18173", "premise_still_valid": true, "summary": "The card's headline is CONFIRMED, and on today's main (7e58d177e), not only the 431c757120 it was filed against. Two worktrees at the same pristine commit, both installed and both with the same @objectstack/lint dependency closure built, differing only in checkout path: /home/user/objectstack-issue-17739 (packages/lint/src = 52 chars) exits 0, /tmp/w9 (25 chars) exits 1 with 4 problems, reporting the same six already-ledgered TS6059 errors as ARRIVED and VANISHED at once. Repair: in normalizeMessage, a quoted span that is a filesystem-absolute path (POSIX, Windows drive letter with either separator, UNC) now collapses at every length instead of only when longer than STABLE_QUOTED_SPAN. That is path-INDEPENDENT, not laxer: it collapses exactly what a long-path checkout already collapsed, so no discrimination any ledger or CI relied on is given up, and an over-masking control pins that short NON-path spans (relative specifier, named type) still survive verbatim. Relativising the path against the repo root was the alternative and is rejected in the PR body: it re-spells the signature every ledger already holds collapsed, churning a maintainer-gated shrink-only ratchet for discrimination no ledger records.", "ledger_effect": "ZERO CHURN, measured two ways. (1) By construction: across all 19 test-typecheck-debt.json ledgers, quoted spans beginning with a slash = 0 and drive-letter spans = 0, against 89 capital-initial spans as the firing control, so no recorded signature can be re-spelled. (2) Empirically at the final head with the fix in: pnpm -r run check:test-typecheck over every package that wires the gate = 36 packages, 36 OK lines, 0 problems, and git status --porcelain empty afterwards. The lint verdict is identical before and after on the long path: 2 file(s) / 6 error(s) / 2 pinned signature(s). No ledger file is in the diff.", "path_length_readings": { "before_long_52_chars": "/home/user/objectstack-issue-17739 :: pnpm --filter @objectstack/lint typecheck :: exit 0 :: 'check:test-typecheck: OK - 2 file(s) / 6 error(s) / 2 pinned signature(s)'", "before_short_25_chars": "/tmp/w9 :: pnpm --filter @objectstack/lint typecheck :: exit 1 :: 'check:test-typecheck: 4 problem(s)' - 2 ARRIVED naming rootDir '/tmp/w9/packages/lint/src' + 2 VANISHED naming the ledgered collapsed spelling", "after_long_52_chars": "exit 0 :: same OK line, byte-identical verdict", "after_short_25_chars": "exit 0 :: same OK line, byte-identical verdict", "controls": "both worktrees at origin/main 7e58d177e, both pnpm install clean, both with pnpm --workspace-concurrency=2 --filter '@objectstack/lint^...' build run; only the checkout path differs" }, "tests": "self-test: new battery 'Path-independence: a signature must not move with the CHECKOUT (#17739)', floor 7, registered in SELF_TEST_BATTERIES, SELF_TEST_BATTERY_FLOOR raised 5 to 6; pins the property, a control that the fixture straddles the 32-char threshold, a zero-churn pin against the ledgered spelling, the Windows spellings, an over-masking control and a detector-discrimination control. tsx scripts/check-test-typecheck.mts --self-test :: exit 0. ABLATION (one-shot, on the throwaway short-path worktree whose copy was proved byte-identical to this branch's blob 76c65e7e41f1230139d7cbdbdf48ce09a3f0b462 first, trap-restored, hash re-verified after): removing ' && !isAbsolutePathSpan(inner)' moved the on-disk marker count 1 to 0 and the blob hash 76c65e7e to d568fd90 (landing proved before reading any result), self-test exit 1 with 3 named #17739 failures (property, zero-churn, Windows) and pnpm --filter @objectstack/lint typecheck exit 1; restore returned hash 76c65e7e and exit 0. No dist is involved: the script runs from source through tsx, so there is no build leg to preflight. Repo-wide eslint . --no-inline-config: 6752 files, 0 errors, 0 warnings (counts read from --format json), so no narrowing was needed. Manual control-character sweep over the changed file: 0 hits, beside check:nul-bytes green.", "gates": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 27 families for the one-path diff; all 27 run at the final head f7c116ae2, all exit 0; reconciled with --ran carrying 'command :: exit N' lines: '27 derived, 27 run, 0 NOT-MEASURED (a DERIVED zero - all 27 recorded an exit code and none of them is 3)'. check:type-check-debt returned exit 3 PREREQUISITE NOT MET twice and is NOT recorded as a measurement either time: first for an unbuilt workspace closure (fixed with turbo run build over ./packages/* and ./packages/*/*, 72/72 successful), then for a V8 OOM under the caller's own --max-old-space-size=4096, which is tighter than the CI-shaped 6144 ceiling that gate pins; re-run at 8192 it exits 0 ('5 ledger entr(ies) re-measured in 74.9s, 55 raw tsc error(s) total, none above its recorded number'). The first derivation warned STALE TREE (2 commits behind origin/main); origin/main was merged into the branch, the derivation re-run clean, and the whole 27-command sweep re-run on the merged head. Plus the direct consumer sweep: pnpm -r run check:test-typecheck = 36 packages, 36 OK, 0 problems. CI convergence on PR #18173 is not waited on.", "line_budget": "n/a - no skills/** path in the diff; nothing published moves (see changeset).", "changeset": "skip-changeset, applied via scripts/pm/label-write.mjs and read back (labels now size/m + skip-changeset; size/m is the size-labeler's additive write, preserved). Measured on the publish surface, not asserted: all 70 non-private packages declare files[], the distinct entries are CHANGELOG.md README.md api-surface dist json-schema liveness llms.txt prompts spec-changes.json src/**/*.zod.ts, 0 of them escape their own package directory, and grepping the 69 built dist trees for the changed symbols gives isAbsolutePathSpan 0 and STABLE_QUOTED_SPAN 0 against a positive control of ObjectKernel 106.", "files_changed": ["scripts/check-test-typecheck.mts (+143/-5)"], "deviations": [ "Two extra git worktrees were created as the measurement rig: /tmp/w9 (detached at origin/main, the short-path leg) and the task worktree itself as the long-path leg. /tmp/w9 was restored to pristine main (git status --porcelain empty) and removed at cleanup.", "origin/main was merged into the branch (merge commit f7c116ae2) to clear dispatch-gates' STALE TREE warning; the two incoming commits touch scripts/check-empty-changeset.mjs and scripts/git-merge-regen.mjs only, no os-regen deferral was recorded, and the derived 27-command set is byte-identical before and after.", "One heavy command ran above the default heap with a stated reason: check:type-check-debt needs a process limit above the 6144 CI-shaped ceiling it pins for tsc, so it ran at --max-old-space-size=8192 (box has 16 GB, 12.9 GB free).", "The ablation's end-to-end leg reds through the SELF-TEST, which the package's check:test-typecheck script runs first, so the ablated package gate exits 1 before reaching the ledger comparison. The 4-problem ledger red is the pristine-main BEFORE reading, already recorded above." ], "mcp_calls": "2 - both READ-only GitHub MCP calls on the card before any edit (issue_read get, issue_read get_comments). 0 MCP GitHub writes.", "api_writes": "3 - POST /repos/objectstack-ai/objectstack/pulls (draft), POST /repos/objectstack-ai/objectstack/issues/18173/labels (via scripts/pm/label-write.mjs), POST /repos/objectstack-ai/objectstack/issues/17739/comments (this report). Plus 3 git pushes to the feature branch (empty branch probe, the fix commit, the merge commit). No PATCH of any body.", "open_questions": [], "out_of_scope_findings": [ "noted, not filed: the card's declared scope limit stands - only TS6059 was measured to carry an absolute path in a quoted span, and no sweep of other diagnostic codes was made. The repair does not need one because it keys on the span's shape rather than on the TS code, so this is an observation, not a defect. Carrier: whoever next reads normalizeMessage." ] }Generated by Claude Code
claude commentedon Sep 14, 2026
复核裁决:ACCEPT — PR #18173
domain:devx执行席session_017ef78bLdybu3AffehKkhfk· R1 · 复核读数 2026-09-14T10:31Z · 对 GitHub 与树核验,⛔ 不对报告自述核验清单结论
main✅ · 正文首行Fixes #17739✅ · 2 commits(含一次合 main)scripts/check-test-typecheck.mts(+143/−5),在认领的文件面内 ✅skip-changeset✅ —— dev 实测 70 个非私有包的files[]无一逃出自身目录,且 69 个 dist 树里isAbsolutePathSpan/STABLE_QUOTED_SPAN均 0,阳性对照ObjectKernel106no✅ 两肢一致f7c116ae2:34 个 check,RED 0,2 个仍 in_progress ⇒ ⛔ 本裁决不含放行抽查读数 —— 本席自己取,⛔ 不引用报告的数字
派发令给了一条硬约束:修法必须让签名路径无关,⛔ 不是更宽松。这是本次复核唯一真正要证的东西,逐条验:
① 结构上只能收紧,不可能放宽。 改动是在"原样保留"那一支上追加一个条件:
追加合取项只能把 span 从"保留"移向"折叠",⛔ 不可能反向。⇒ 不存在"本来能区分的现在区分不了"之外的第二种风险,而那一种由 ② 排除。
② 零台账变动 —— 本席独立重测。 这是"没有放弃任何区分力"的承重腿:
⇒ 没有任何已记录的签名会被重新拼写。 折叠的正是长路径检出本来就已经折叠的那一类。
③ 过度遮蔽对照 —— 本席直接跑正则,⛔ 不看自测。
⇒ 只吃真正的绝对路径;短的相对说明符与类型名一个都没受影响。硬约束满足。
⭐ 另记一笔:dev 把卡的头条断言在今天的 main(
7e58d177e)上重新证实了,而不是只信卡面记的431c757120—— 两个同 commit、同安装、同依赖闭包、只差检出路径长度的 worktree,52 字符 exit 0 / 25 字符 exit 1,同六个已登记的 TS6059 同时报成 ARRIVED 与 VANISHED。这是本卡最该先做的那次测量,做对了。偏差
dev 报 4 条,逐条核过,均为如实申报、无一越界:两个测量用 worktree(短路径那个已还原并删除)· 合 main 清 STALE TREE(两个进入的提交只碰
check-empty-changeset.mjs与git-merge-regen.mjs,即本席本班刚落地的那两条,⛔ 与本卡文件面不相交)·check:type-check-debt在 8192 堆上跑并说明了理由 · 两次 exit 3 都没有被记成测量(正确处置 —— exit 3 是 NOT MEASURED 不是 pass)。⭐ 它拒绝了"把路径相对化"的替代方案,理由是那会把台账已经折叠记录的签名重新拼写一遍,为一个台账根本没记录的区分力去动一个维护者把关的只减不增棘轮。⇒ 这个取舍方向正确,且理由是可核的。
⇒ ACCEPT。 放行前置:34 个 check 全绿且 RED 0 才转 ready + 挂 auto-merge;⛔ 入队资格是每一个 check 全绿,不是 required 子集。
out_of_scope_findings仅 1 条noted, not filed(只测了 TS6059 一个码,未扫其它诊断码)—— 本席判不属三类:修法按 span 形状生效而非按 TS 码,所以那不是缺口而是观察。⛔ 不立卡。Generated by Claude Code
claude commentedon Sep 14, 2026
✅ 已落地 ——
08a6c7a12onorigin/maindomain:devx执行席session_017ef78bLdybu3AffehKkhfk· 落地读数 2026-09-14T11:36ZPR #18173 已合入。落地判据按内容读(⛔ 不按
merged布尔、⛔ 不按 head sha 祖先性),重新 fetch 后带发火对照:⇒ 同一条 TS6059 不再因为检出路径的长度而产生两个签名。 卡的头条断言(在短路径检出上
typecheck会在 pristine main 上红)在交付前已由 dev 在今天的 main 上重新证实,而不是只信卡面记的431c757120。本卡就此收口。⚠️ 仍记着的一条(⛔ 不因落地而消失):该修法按 span 的形状生效而非按 TS 码,所以「只测了 TS6059、未扫其它诊断码」是观察不是缺口 —— 本席已判不属三类,⛔ 未立卡,记在这里以免下一个读
normalizeMessage的人误以为欠了一次普查。Generated by Claude Code
github-actions commentedon Sep 14, 2026
os-closed-card-sweep — machine-findable marker for this generated comment.
Removed the pm-loop state label(s) this closed card no longer claims:
pm:dispatched.08a6c7a124, merged intomain.bug,priority:p2,domain:devx— ownership, priority and outcome are not state claims.A state label claims work is in flight. This card is closed on a merged delivery, so the claim
is stale; every other label is left exactly as it was found. Nothing here is a judgement about
the card, and no verdict-bearing label is ever touched by this sweep.
posted by half-state-patrol run 34851083408 · trigger
scheduleGenerated by Claude Code
fix(scripts): make check-test-typecheck signatures path-independent (o…