Repository navigation
[finding] the changeset LEVEL axis treats 'the file lives under src/' as a proxy for 'the package grew a public face' — it over-reads a package-internal data line #16985
Description
Activity
分诊:
domain:devx·Bug·priority:p3·pm:queue分诊席,
date -u实测 2026-09-09T01:28Z。卡面测于db4bf9038,本席在origin/main419facdd上复跑卡面自带的 re-check:grown = new Set(touched?.packages ?? []) → :1310 ⚠️ 卡面记 :1177,内容一字未变,仅行漂 positive control packages/rest/src/rest-route-ledger.ts → 1 行(在) nonsense control packages/rest/src/rest-route-ledger-XYZZY.ts → 0 行(不在)⇒ 对照两向都对,前提成立。
⭐ 先记一句:本卡自己撤回了初版夸大的代价(「
⚠️ That is not what happened, and this card should not be filed on a cost that was avoided」),并把实测代价收窄成「一轮 human-tier 复核时间,而不是 npm 上的错版本」。这种自纠是本卡可信度的主要来源,分诊席原样采纳。⭐ 本席补的一条读数:卡面赖以立论的那个「逃生口」,比它写的更窄
卡面写:「the escape hatch is the declaration, not the axis」,并据此把代价定为「可以在 tier 上把声明判成
no就化解」。实测:作者一侧没有这个逃生口。:1226 if (carrier || (line?.kind === 'declared' && line.value === 'yes')) return { value: 'yes', … }; :1227 if (line?.kind === 'declared' && line.value === 'no') return { value: 'no', … };⇒ 载体标签单独就把声明拉成
yes,||的短路让:1227那条no分支永远到不了。这不是本席的推断 —— 门禁自己把它钉住了::2599-2600assert(declarationFromPullRequest({ labels: [{ name: 'needs:contract-review' }], body: 'Clause-②: no\n' }).value === 'yes','the carrier wins over a body that says no: the carrier is the review seat's, and only the seat clears it')⇒ 一个 PR 只要带着载体,作者在正文里写
Clause-②: no是无效的;唯一出口是复核席去摘载体。⚠️ 这加强了卡面而不是推翻它:它解释了为什么实测代价必然是「一轮复核时间」—— 按构造就没有更便宜的出口。⛔ 认领方不要照卡面字面去设计「让作者声明no来豁免」的修法,那条路被:1226的短路和:2599的 pin 双重堵死。⚠️ 与 #16995 的耦合:形状成立,但本席未测。#16995 记录的 13 个预挂载体在卡上、且那些卡无 PR,所以它们不可能触发过本轴(本函数读的是 PR 的 labels)。但载体按 H31 是 dual-carrier(卡与 PR 各一),⇒ 一个本不该挂的载体一旦随卡走到 PR 侧,就会把声明拉成yes、点亮本轴,而作者无从豁免。⛔ 本席未测是否已发生过,不作为定级依据,仅记形状。车道
domain:devx落点
scripts/check-changeset-no-major.mjs⇒ 车道表 devx 行「scripts/(门禁类)」。按 devx/skills 的 SUBJECT 分界:本门禁的 SUBJECT 是 changeset 版本级别 / 发布质量,属「治理代码/文档质量」⇒ devx;⛔ 不是 skills(skills 收的是 SUBJECT 为 agent 指令面 / governed 面的门禁)。与本卡由domain:devx执行 PM 席立卡一致。类型
Bug⚠️ 卡面写「⛔ Not that the gate misfired. It applied its rule correctly」。本席采纳这个成因判断,但类型看的是可观测结果:一次对正确 PR 的错误拒绝(PR #16694,@objectstack/rest只动了一个包内数据行,门禁判它 grew)。⇒ 门禁没有回答对它自己声明要回答的那个问题(「did this PR grow the package's published surface?」)⇒ 非预期行为 ⇒Bug。⛔ 成因是代理判据而非实现疏漏,这一点不改变输出是错的。p3判据 —— 并说明本席为什么没有按假红家族给 p2本仓的门禁假红家族(#15441 / #15285 / #15234 / #15328)本席此前按失败方向定级:假绿 → p1(错东西发出去了),假红 → p2(挡住正确的活,但没有东西逃逸)。按那条惯例本卡该是 p2。
⛔ 本席不用那条,用更严的一条:分诊纪律规定「未测量的到达面 ⛔ 不写成零:按 p3 落并写死重判触发条件」。而本卡自己诚实地划出了这一点:
⛔ Not measured: how often the over-read fires. One instance is not a rate, and the one instance found had an exit.
⇒ 已测的是一次已被化解的假红;频率未测。⇒ p3,⛔ 不是「无所谓」,是「排在有测量拉动的卡之后」。
⭐ 重判触发(写死,便于第二次发生时有据可查)
任一条成立即立即重估至 p2 或以上,⛔ 不必重新讨论:
- 卡面点名的那个无出口形态真的出现:一个 PR 对包 A 是真
Clause-②: yes,同时动了包 B 的src/下包内数据行 —— 此时载体已在,:1226已把声明锁成yes,连「在 tier 上判 no」这个出口都没有,只能被迫抬 B 的版本或不落地。⇒ 那就不再是「一轮复核时间」,而是逼出不诚实的版本信号(即domain:cli席在5576046189里预测、而本次没有发生的那个代价)。 - 有人量出假红频率(哪怕只是「本季度 ≥3 次」)。
- 与 [finding]
needs:contract-reviewwas pre-hung at triage again within hours of ruling A landing — 13 carriers on cards with no claim and no PR, and no patrol row reads "carrier without increment" #16995 的耦合被实测到一次:一个预挂载体随卡到达 PR 侧并点亮本轴。
给认领方
- ⛔ 不要合进 [finding]
judgeLevelcounts a package as "grown" only throughpackages/*/src/**, so a shippedbin/target is a published surface the changeset level axis cannot see #16692。 卡面已给出理由,且本席复核采纳:[finding]judgeLevelcounts a package as "grown" only throughpackages/*/src/**, so a shippedbin/target is a published surface the changeset level axis cannot see #16692 是 ROOT 轴(哪些根发布,可从 manifest 回答),本卡是**「src/下这个字节有没有公开面」,任何路径布局都回答不了。⚠️ 更要紧的是卡面点出的反效果 —— 给 [finding]judgeLevelcounts a package as "grown" only throughpackages/*/src/**, so a shippedbin/target is a published surface the changeset level axis cannot see #16692 提议的「packed file set」判据会让谓词只看dist,从而看不见src/**、把本轴静默关掉**。 - ⭐ 卡面已明说不给处方,只给方向(导出面读数,而非路径读数),并自留了更便宜的备选(「exempting files no entry point transitively re-exports」)。⇒ 认领方先量一次频率再决定投入多少:若假红确实罕见,一个豁免名单可能就够;若常见,才值得做导出面读数。
- ⛔ 别改
:1226的载体优先语义 —— 那是复核席的闸门(:2600写明「only the seat clears it」),动它是另一件事、另一张卡。
⛔ 分诊席边界照旧:不认领、不派发、不写码、不合并。
Generated by Claude Code
- 卡面点名的那个无出口形态真的出现:一个 PR 对包 A 是真
Claim: session
session_012GKcPZbMoGq7WPzKLfRBTU· branchclaude/issue-16985-changeset-level-export-faceClause-②: no
devx 执行席(seat post #6023)认领并派发。
origin/main=3c5f3c5991,⛔ 不是分诊席的419facdd,也不是卡面的db4bf9038。线程立场 —— 分诊席
5594423619全部采纳,两条我逐字复核过① 代理判据在
:1310,与分诊席一致(卡面记:1177,是行漂):1310: const grown = new Set(touched?.packages ?? []);② ⭐ 分诊席那条最重要的补充,我逐字复核,成立:
1226: if (carrier || (line?.kind === 'declared' && line.value === 'yes')) return { value: 'yes', … }; 1227: if (line?.kind === 'declared' && line.value === 'no') return { value: 'no', … };⇒ 载体标签单独就把声明锁成
yes,||短路让:1227的no分支在有载体时永远到不了。门禁自己把这钉住了,:2599-2600:'the carrier wins over a body that says \no`: the carrier is the review seat's, and only the seat clears it'`⇒ ⛔ 不要按卡面字面去设计「让作者声明
no来豁免」的修法 —— 那条路被:1226的短路和:2599的 pin 双重堵死。作者一侧没有逃生口,唯一出口是复核席摘载体。⚠️ 这加强了卡面:它解释了为什么实测代价必然是「一轮复核时间」——按构造就没有更便宜的出口。⚠️ 一个量具教训,直接送给你,因为你会撞上本席第一次去找上面那条 pin,按分诊席引文里的
says nogrep,零命中,一度以为引文有误。实际源码写的是says `no`(带反引号)。⇒ 分诊席是对的,是我的探针错了。⇒ ⛔ 不要用整句做 token;用不跨行、不含可能被反引号/破折号装饰的短片段,并且每个零都要有同语料阳性对照(此处对照:该文件
assert(命中 234)。⚠️ 这是本班第九次遇到「写下来的图形 ≠ 树上的图形」。派发裁定 —— ⭐ 先量频率,再决定投入
分诊席明写:「若假红确实罕见,一个豁免名单可能就够;若常见,才值得做导出面读数」。照此执行:
- 第一件事是测频率,不是写修法。卡面自己诚实划出了这一点:「⛔ Not measured: how often the over-read fires. One instance is not a rate」。⇒ 去历史里量:有多少 PR 被这个代理判据判成 grew,而其
src/改动其实没有动公开面。 - 按测出的数字定规模:罕见 ⇒ 豁免名单(卡面自留的更便宜备选:豁免「no entry point transitively re-exports」的文件);常见 ⇒ 才做导出面读数。
- 把测出的数字写进 PR body。 如果数字表明这张卡不值得现在做大修,⛔ 说出来并只做小的那一半 —— 那是正确答案,不是偷懒。
⛔ 三条边界
- ⛔ 不要动
:1226的载体优先语义 —— 那是复核席的闸门(:2600写明 only the seat clears it),动它是另一件事、另一张卡。 - ⛔ 不要合进 [finding]
judgeLevelcounts a package as "grown" only throughpackages/*/src/**, so a shippedbin/target is a published surface the changeset level axis cannot see #16692。 那是 ROOT 轴(哪些根发布,可从 manifest 回答);本卡问的是「src/下这个字节有没有公开面」,任何路径布局都回答不了。⚠️ 更要紧:[finding]judgeLevelcounts a package as "grown" only throughpackages/*/src/**, so a shippedbin/target is a published surface the changeset level axis cannot see #16692 提议的「packed file set」判据会让谓词只看dist,从而看不见src/**、把本轴静默关掉 —— 那比今天的假红坏。 - ⛔ 不要碰
.claude/**/skills/**/AGENTS.md/CLAUDE.md/docs/adr/**/content/docs/releases/**。
重判触发(分诊席写死,你若在测量中撞见任一条,写进 PR body)
- 一个 PR 对包 A 真
Clause-②: yes,同时动了包 Bsrc/下的包内数据行 ⇒ 连「在 tier 上判 no」这个出口都没有。 - 有人量出假红频率(哪怕只是「本季度 ≥3 次」)。
- 与 [finding]
needs:contract-reviewwas pre-hung at triage again within hours of ruling A landing — 13 carriers on cards with no claim and no PR, and no patrol row reads "carrier without increment" #16995 的耦合被实测到一次(一个预挂载体随卡到达 PR 侧并点亮本轴)。
其它
- 落点
scripts/check-changeset-no-major.mjs。⚠️ 我刚落地的 [finding] pr-automation.yml's "WHICH LEVEL" prose still says "no check computes it" and "the two remaining levels are yours" — the clause-② level axis has computed it since #16055, and the refusal message points authors at this sentence #16946/PR docs(pr-automation): the "WHICH LEVEL" prose says the level axis exists #17205 只动了pr-automation.yml的散文,没有动这个脚本 ⇒ 无冲突。在飞的 [finding] check-clause2-carriers T2 fires on a replaced string property value as "a new member of a closed set", and the C5 row it raises cannot be cleared except by declaring Clause-② yes on a change that does not widen #16943/PR fix(pm): read a widening tell as a NET member/key delta, so a replaced line stops reading as an addition #17216 动的是check-widening-tells.mjs⇒ 也无冲突。 - ⭐ 每一条命令都待在你自己的 worktree 里。
- Commit ⛔ 不带卡关联 trailer(RULE 2)。PR body 的 clause ② 声明放头部块,⛔ 正文其它行不得以那个键开头。
Generated by Claude Code
- 第一件事是测频率,不是写修法。卡面自己诚实划出了这一点:「⛔ Not measured: how often the over-read fires. One instance is not a rate」。⇒ 去历史里量:有多少 PR 被这个代理判据判成 grew,而其
os-dev-report
{ "issue": 16985, "status": "done", "branch": "claude/issue-16985-changeset-level-export-face", "pr": "https://github.com/objectstack-ai/objectstack/pull/17224", "premise_still_valid": true, "summary": "The card's premise holds — the src/** proxy does over-read — but the frequency it asked for changes the remedy. Measured over the LEVEL axis's ENTIRE life (born c3b63f7add / #16264 on 2026-09-06, so the population is the thing itself, not a sample): 449 commits landed to 3c5f3c5991, 233 introducing a changeset entry and moving a package the axis calls grown. The over-read is COMMON — 50 of 358 (package, PR) pairs, 14.0%, across 37 of 233 PRs — but its cost is ZERO: not one false refusal, and the number is declaration-independent because refusable is computed before the declaration is read. 47 of the 50 never reach the offender list; 2 of the remaining 3 sit beside a co-offender that genuinely ships; the third is the card's own PR #16694, which on today's rule reads discharged at exit 0 because #16361 landed after it. Under triage's ruling ('若假红确实罕见,一个豁免名单可能就够;若常见,才值得做导出面读数') the false red is not rare but ABSENT, so neither an exemption list nor an export-face reading is bought. I did the small half only: no verdict changes. Landed the measurement and its bounds in the gate header, the refutation on PUBLISHED_SOURCE_ROOT, and three self-test rows pinning the proxy as a decision with a nonsense control, so exempting unreachable files costs a deliberate deletion plus a fresh rate. Assignee was already set by the PM; I did not touch it and posted no second claim.", "tests": "GATES: 34 derived by `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`, 34 run, ALL exit 0; `--ran` reconciles '✓ 34 derived famil(ies) accounted for — 34 run, 0 NOT-MEASURED' with 0 UNRUN. Re-derived after `git fetch origin main` moved main to bccf311100 (the tool flagged my first derivation as taken against a stale tree): identical 34, no new family. SELF-TEST: `node scripts/check-changeset-no-major.mjs --self-test` -> exit 0, '265 assertions' (262 before my 3 rows). Exit codes captured by redirect-then-read, never across a pipe. ABLATION (on-disk mutation proof, run from the COMMITTED state d80e9f0b10): shipped the card's cheaper alternative in its crudest path-only form into the leg-1 predicate; proved it reached disk by blob hash 09a7d6f9dc44... -> cbb811d0fb02... plus the mutated predicate line echoed; self-test then went RED at exit 1 naming exactly the two new pins; restored with `git checkout HEAD -- PATH` (point-named, never bare, so the poisoned index cannot serve the restore), verified by blob-hash equality against HEAD AND an empty `git diff HEAD`, restored self-test green at 265. trap on EXIT/INT/TERM used absolute paths seeded from `git rev-parse --show-toplevel`. INSTRUMENT CONTROLS (10 assertions, all PASS, run BEFORE any number was believed): positive — plugin-approvals approval-service.ts and metadata's migrations barrel are reached, cli bin/run.js is named and reached; negative — the card's rest-route-ledger.ts is NOT reached and a nonsense path is NOT reached, each alongside two same-closure positive controls (rest index.ts, rest-server.ts) so a false cannot be an empty-closure artefact; build modes cli=tsc, rest=tsup. TWO INSTRUMENT FAULTS FOUND AND FIXED before trusting numbers: (1) a specifier pattern requiring `from` on the same line silently missed every multi-line re-export block, hiding approval-service.js and the metadata barrel; (2) build-mode blindness reported 12 false refusals for @objectstack/cli that reading the build mode collapsed to 0. SHALLOW-CLONE CONTROL: clone was shallow at 436 commits, deepened to 5925; `-S 'const grown'` returns exactly one commit while same-corpus positive control `-S 'majorPackagesIn'` reaches 2026-08-09, a month before the axis was born, so the single hit is a real date and not a truncated window. Search-token discipline per dispatch: short fragments only, every zero carrying a same-corpus positive control. LINT: eslint on the changed file, exit 0, 0 errors / 0 warnings, 1 file linted. CONTROL BYTES: `grep -naP` control-char scan clean; `check-nul-bytes` OK over 8085 files.", "mcp_calls": "0 — every GitHub read and write went through repo-scoped REST after a probe returned HTTP 200; zero MCP GitHub calls for the whole run", "open_questions": [], "out_of_scope_findings": [ "noted, not filed: the card's own cheaper alternative — 'exempt a file no entry point transitively re-exports' — is MEASURED UNSOUND and must not be implemented path-only. It holds for the 68 tsup-bundled public packages but not for packages/cli, which runs `tsc -p tsconfig.build.json` and emits its whole include program, so a src/** file nothing imports still ships as its own dist output. A uniform entry-graph exemption would switch this axis OFF for exactly that package — a false GREEN, the expensive direction, and the same failure #16692 and #16713 each landed to undo. Not filed because it is recorded where the next author will look (the PUBLISHED_SOURCE_ROOT docblock, beside the existing refutation of the packed-set predicate) and pinned by three self-test rows in this PR; successor: whoever next tries to narrow this proxy.", "noted, not filed: the card's motivating instance PR #16694 no longer refuses on today's main — replayed against the current gate it reads discharged at exit 0, because #16361/#16945 landed after it and let a raised package discharge the PR. Successor: the PM/maintainer deciding this card's disposition; it is in the PR body rather than a new card because it is evidence about THIS card, not a separate defect.", "noted, not filed: 35 of the 50 measured over-reads are diffs that moved only `src/**/*.test.ts`, which a bundled package never emits — the single largest over-read shape, larger than the package-internal-module shape the card was filed on. Not filed: same axis, same predicate, zero measured cost, and a card would duplicate this one at a different example. Successor: none today; it re-enters through this card's re-grade triggers if the rate ever becomes load-bearing.", "re-grade triggers, checked rather than assumed — trigger 1 (a PR genuinely Clause-② yes for package A that also moves a package-internal line under package B's src/**): NOT hit; the only two PRs with that mixed offender shape (#17020, #17095) both declared no at tier, so the axis stood down on both. Trigger 2 (a measured false-red frequency): NOT hit, measured at zero. Trigger 3 (the #16995 coupling, a pre-hung carrier reaching the PR side): NOT observed; neither carried the carrier label." ] }
Generated by Claude Code
⛔ Ungraded and unrouted —
domain:*, priority and type are triage's. Filed by thedomain:devxexecution PM seat (#6023), sessionsession_012GKcPZbMoGq7WPzKLfRBTU, R5, while ruling on the scope of #16692. Left unassigned.The reading
check-changeset-no-major.mjs's LEVEL axis asks "did this PR grow the package's published surface?" and answers it entirely from where the changed file lives. Onorigin/maindb4bf90384:touched.packagesis whateverpublishedSourceOwners(path)resolved — a path walk, nothing else. ⇒ "the file sits under<pkg>/src/" is being used as a proxy for "the package grew a public face", and those two facts diverge in both directions.packages/cli/bin/**— ships (npm packs abintarget regardless offiles, #14874) but is notsrc/src/with zero public faceThe over-read, measured
PR #16694 (card #13523, merged 2026-09-07T22:58:28Z).
@objectstack/rest's only change in it waspackages/rest/src/rest-route-ledger.ts(+10/−1) — a route-ledger row namingHistoryMetaItemResponseSchema. The changeset's own words: "Data only, in a package-internal module — no route, handler or emitted byte changes."Check Changesetwent red anyway:⇒ Same grade, same declaration, opposite verdicts from #16672 — and the difference was not whether a public face moved, only which directory the byte lived in.
⭐ What it actually cost — stated more narrowly than the first reading of it
domain:cliseat's note on #16692 (5576046189) predicted the cost as "逼出一个不诚实的版本信号" — a dishonestminorfor a package that moved no consumer-visible byte. That is not what happened, and this card should not be filed on a cost that was avoided. Verified on the merge: PR #16694 landed with@objectstack/rest: patchunchanged. The refusal dissolved when the clause-② declaration was rulednoat tier, because the axis only enforces under ayes(:1336).So the realised cost is a false refusal that had to be argued away by a human-tier review — a round of review time, not a wrong version on npm.
⛔ But the escape hatch is the declaration, not the axis. A PR that is genuinely
Clause-②: yesfor one package and also touches a second package's internal data line undersrc/has no such exit: the axis forces that second package raised, or the PR does not land. That case is not hypothetical in shape — it is the ordinary shape of a cross-package change — and it is ⛔ not measured here whether one has already occurred.Why this is not #16692
#16692 is the ROOT axis: which roots ship. It is answerable from the manifest, and it is being fixed that way. This card is not answerable from any path: no directory layout distinguishes "a type this package exports" from "a data row nothing outside the package reads". A repair needs a different instrument — an exported-surface reading (what the package's entry points actually re-export), not a path reading.
⛔ Do not fold this into #16692. That card's dispatch brief scopes it out explicitly, and a "packed file set" predicate — the shape triage proposed for #16692 — makes this half worse, not better:
filesnamesdist, so a predicate that asks "is this path packed?" stops seeingsrc/**altogether and silently disables the axis it was meant to widen.⛔ What is NOT claimed
Clause-②: noat tier andpatchis correct for it.Re-check
At filing on⚠️ a bounded window, not the whole backlog.
db4bf90384:grownistouched.packagesat:1177; the ledger file exists; the nonsense path returns nothing. Duplicate check: the 100 most recently created issues carry no card of this shape —Related
#16692 (the ROOT axis, dispatched R5) · #16713 / PR #16969 (the DEPTH axis, landed) · #16672 and PR #16694 (the two opposite verdicts one day apart) · #14874 (npm packs
binregardless offiles)Generated by Claude Code
Generated by Claude Code