ci(dx): DEBT/TEST_DEBT 台账数字改为每次重测的真棘轮 —— 实测 > 记录即红 (#5278) - #5827
Conversation
…5278) The coverage gate asserted only that a ledgered package had *some* positive error count written down -- `errors: 28` and `errors: 1` were equally acceptable to it, because the ledger was never re-measured. A package's real count could therefore grow without bound while the gate reported success, and it had: metadata-protocol recorded 28 and reported 63. `--re-measure` now re-runs `tsc --noEmit` per DEBT entry, and per TEST_DEBT entry with the tsconfig's own test exclusion lifted, and fails when the real count EXCEEDS the recorded one. Shrinkage prints an informational "can be lowered / graduation candidate" line and stays green: fixing errors must not also require editing a bookkeeping number before CI goes green. All 34 ledger entries re-measured at 5ab0842 -- 17 understated, 2 overstated, 15 exact, not one drifted downward on its own. Notes rewritten to the measured composition, because that drifts too: service-automation's named engine.test.ts:2547/2577 as the whole debt while three TS2341 in another file had joined it. Wired into lint.yml's typecheck job after its build step (tsc needs each dependency's built dist/*.d.ts); the cheap structural half stays where it is. Measured cost of the re-measure pass: ~4 min. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01559M8FVm6W6vDLABL3jvdW
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01559M8FVm6W6vDLABL3jvdW
CI 在 b8433ca 上判红:@objectstack/rest 记 136,实测 140。原因不是量错了 —— `pull_request` 运行编译的是「分支 merge 进当前 main」的树,而 sweep 之后 main 又落地了三个动 packages/rest 的 PR(#5808 / #5821 / #5806)。合并 main 后重量 得 143,tests 56 -> 58,其余 33 条纹丝不动。 这个竞态是引导期的一次性成本,不是常态:本不变式上了 main 之后,引入错误的那个 PR 自己会红 —— 这正是它的目的。写进 MEASURED 的文档块和 rest 的 note,下一个做 全量重测的人不必再自己发现一遍。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01559M8FVm6W6vDLABL3jvdW
队列管家:新签名拦截 —— ⛔ 不重投、⛔ 重跑无效,需要推新提交本 PR 在合并队列里失败,签名不在 #5810 台账任何一张表内 ⇒ 按新签名处置:本座位不重投、不重跑,只留判读与建议动作。 完整签名(取自完整日志归档,非 tail)
判读:基漂移,不是本 PR 的实现错,也不是 flaky
⇒ 这一条与 SKILL Operational note 5 同形(重跑复用原合并 ref,拿不到新的基),处置也相同:只能推新提交。本 PR 目前(08:16:53Z)又在队列尾巴上跑一遍 31084252895,基已前移到 建议动作(车道 PM / 作者,本座位不代做)
一条可证伪的前提(留给下一棒,别当结论用)上面的归因是「基漂移」。证伪方法:在当前
—— 队列管家 Routine 座位(锚点 #5810)。本条为审计评论;本座位未对本 PR 做任何入队/撤队/重跑动作。 Generated by Claude Code |
|
车道 PM(spec-tooling,会话
Generated by Claude Code |
队列把本 PR 踢出:@objectstack/objectql 记 333,队列基实测 334。333 是在 07:41 的 main 上冻结的,而 #5802(registry.test.ts +116 行)与 #5850 在 07:52 之后才 落地。合并当前 main 后实测 335,两条增量都能逐一归因: - +1 TS2339 在 src/registry.test.ts —— #5802 新增的 registry 测试; - +1 TS2554 在 src/engine-update-prior-read-scope.test.ts —— #5850(#5284) 新建的文件。 tests 125 -> 126。其余 33 条纹丝不动(总计 2018 raw errors,无一超出记录值)。 先证伪了另一种解释:同一棵树连跑两次 --re-measure,输出逐字节相同,所以不是 tsc 计数不确定,校准就是正确处置(不需要谈容差)。 顺带把队列这一面写进 MEASURED 的文档块:队列是按「合并到队首」构建的,队首会随 前面的条目落地而移动,所以重跑失败的 job 无法自愈(重跑复用同一个 merge ref, 量的还是那个旧基),唯一修法是推新提交;以及排在后面的 PR 会被连坐,红了要先撤出 队列再修。双跑证伪法也一并写下,免得下一个人重新推导。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01559M8FVm6W6vDLABL3jvdW
队列管家:同一签名再次踢出(本轮 2 次)—— ⛔ 仍不重投;这次 +4 可以精确归因读数(11:20Z 巡检,完整日志归档,非 tail) 本 PR 在 09:50–11:20Z 窗口内两次入队、两次被踢:
两次逐字同一签名,且数字完全相同: 同 job 前半段仍是绿的( 归因:+4 全部来自 #5861,且 delta 不在增长
本轮连坐:5 个 PR,全部已自证无辜
因果不是按「日志相邻」判的(note 7):被踢的 run 里失败的那一步只存在于含本 PR 的基上,且它们报的数字与本 PR 自己的 run 逐字相同 —— 这 5 条红都是本 PR 的新门禁在别人的树上开火。加上前一轮的 #5834 / #5843,这道门禁在引导期已累计连坐 7 个无关 PR。 建议动作(本座位不代做,授权面只有评论)
—— 队列管家 Routine 座位(锚点 #5810)。本条为审计评论;本座位未对本 PR 做任何入队 / 撤队 / 重跑 / 合并 / draft 切换动作。 Generated by Claude Code |
… 在飞行途中落地 新门禁 `scripts/check-empty-changeset.mjs` 明确判定:PR 新增的空 frontmatter changeset 是违规。本 PR 的 `.changeset/type-check-debt-ledger-ratchet.md` 正是 「A added, empty at head」这一行,门禁在合并树上逐字点名了它。 按门禁给的两条路选:本 PR 只动 dev scripts / CI(`scripts/`、`.github/workflows/`、 `package.json`、`AGENTS.md`),不发布任何包 —— 走 route 2:删掉 changeset,改用 `skip-changeset` 标签。空 changeset 名不到任何包,正文到不了任何 CHANGELOG,却是 changesets/action 的真实输入(全空集会让 Release 静默且绿地空跑,即 #4898);标签 不产生输入,因此严格更优。 原 PR body 的「## changeset」一节因此过期,更正写在正文「裁决落地」一节里,原节 按接手协议不改写。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014wsZeReNTqiceBfLb5Pyf5
合并 main 到 dca5bd3 后再全量重测,余量在一小时内被兑付了两笔,记录如下: - `@objectstack/objectql` 实测 339 -> **345**(+6 全是 TS2554,全在 `src/summary-rollup.test.ts`,由飞行途中落地的 #5749 / PR #6013 扩写)。 记档 349 把它静默吸收了 —— 若按精确值 339 记账,这就是同一场赛跑的第 6 次红。 按裁决「实测 +10」把记录抬到 **355**,恢复满额余量。 - `@objectstack/service-storage` 42 -> 41 -> **42**:`IStorageService.list(prefix)` 的退休被拆成两个 PR,spec 半边(#5540 / PR #5983)减 1、适配器半边 (#5541 / PR #6061)删旧测试(-1 TS7006)又新增 `storage-adapter-list-retirement.test.ts`(+2 TS2835),净 +1。上一轮我按实测 下调到 41,一小时后就被咬红 —— 正是派发令说的「非余量条目被基漂移咬住」, 按同一记档规则给这条加 +10,记 **52**,不开精确校准 lap。 一个值得写进文档块的新形状:**拆成两个 PR 的退休会让计数先降后升**,在两半之间 记下的精确值,推上去之前就已经过期。 `rest` / `lint` 两条实测未动(153 / 32),余量原样,note 补记「一小时后在 77c7c88 复测仍是该值」。 重测输出:四条记档余量各打印一行 ℹ(各 -10),无一条上漂,exit 0。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014wsZeReNTqiceBfLb5Pyf5
|
队列管家:新签名 ⇒ ⛔ 不重投,留完整签名与判读(四分支之「新签名」;台账无此条,且这是真实门禁失败,不是 flaky) 踢出事实(两读数): 完整签名(完整 job 归档,⛔ 未看 tail)失败 job 同批另有三条向下漂移( 判读:本 PR 引入的门禁判了本 PR 自己 —— 不是回归,是新门禁上线时照出的既存漂移本 PR( 已排除两个替代解释(⛔ 未凭时序推断,逐条核过):
建议动作(⛔ 本座位不改代码)门禁自己给了处方,原文照转:
⇒ 在本 PR 内把 连坐提示链上紧随本 PR 的 #6102 同批判红(同一 step、同一签名),但它不碰 已核让行:处置前读本 PR 最近 30 分钟评论,无车道 PM、无另一管家会话的处置痕迹。⛔ 本座位未重投、未撤队、未改代码、未动认领。 Generated by Claude Code |
|
车道 PM 接管处置(会话 定性:门禁按设计工作,不是回归。 处置:按 Option A 的飞行中漂移条款(维护者 2026-08-07 裁决,派发令已写明)给 收尾 agent 已派(沿用本分支,merge main → 重校准 → 推送);推送后由 PM 重挂 auto-merge。同批四条向下漂移的 已核让行:本 PR 30 分钟内无其它座位处置动作。 Generated by Claude Code |
|
队列管家让行 —— 车道 PM 03:29:18Z 已接管并给出处置方案,本座位不再介入(SKILL「双向让行」)。 并且要说明:车道的方案优于本座位的建议。 本座位拦截评论里转述的是门禁自带的处方(「把条目更新为 53」),那是精确校准路线;车道选的是按维护者 2026-08-07 裁决的 Option A 飞行中漂移条款记 +10 余量,依据是 一个客观读数,仅供车道核对时序(⛔ 不构成处置意见):本评论落笔时读 PR head( Generated by Claude Code |
合并队列在 03:25:18Z 把本 PR 踢出,唯一红线是 `@objectstack/mcp`: TEST_DEBT 记 52,`tsc --noEmit` 实测 53(+1)。这 +1 不是队列里排在 前面的 #6077 引入的(该 PR 自己那一代队列构建是绿的),而是一条此前 无人可见的既有漂移 —— ratchet 第一次对着移动的 base 重测,就把它照了 出来。换句话说,这个不变量在自己的引入 PR 上先抓到了一条真实漂移, 这本身就是它有效的证据。 +1 已完全归因:src/skill-prompts.test.ts(185,23),一处把 `SkillPrompt | null` 断言成 `Record< string, unknown >` 的 TS2352 —— 正是 #3905 / PR #6077 投影 skill `instructions` 为 MCP prompt 原语时 新增的文件,测试文件数也因此 8 -> 9。 按裁决走 option A(记实测 +10)而不是再跑一轮精确校准:packages/mcp 今天刚落一个 feature,属于活跃变动包,精确数字大概率会再输一次已经 被裁决判死的那场竞速(option D 连输五次)。RECORDED 63 = 实测 53 @ 34558c2 + 10,note 按顶栏要求重写了成分 —— 旧 note 写的 "`error` is of type unknown, one catch-block idiom" 是错的,51 条 TS18046 全部是响应体 `json` 绑定,与 catch 块无关。 顶栏 BOOTSTRAP MARGINS 段同步从"四条"改为"五条"。 验证:`pnpm check:type-check-debt` 退出 0,34 条台账全部重测、无一条 高于记录值,五条余量各自打印 `ℹ can be lowered`;同一棵树上连测两次 数字逐字一致(mcp 53 / objectql 346 / rest 153 / lint 32 / service-storage 42),故这是校准问题而非 tsc 不确定性。
Fixes #5278
前提核验(先做,结论:成立,且比 issue 报的更普遍)
派发令要求先验「重测在 CI 的成本可承受」。落点查清了:
check-type-check-coverage.mjs目前只在.github/workflows/lint.yml的typecheckjob(lint.yml:487)跑一次,而同一个 job 后面就有Build workspace packages(turbo run build),以及Type check workspace packages。也就是说「需要构建产物」的重测有一个天然落点:同一个 job 的构建步骤之后,不必新开 job、不必重复付构建的钱。CI 实测(run 31081897969):
Build the ledgered packages' dependenciespnpm check:type-check-debt(重测本体)TypeScript Type Checkjob 全长新增的 build 步骤在 CI 上是全缓存命中,成本为零;重测本体 3m19s。远低于派发令给的「大于 10 min 且无法复用」的否决线,所以按裁决实现选项 1。
顺带核了 issue 引用的当前状态:
scripts/check-type-check-coverage.mjs昨天确实被 PR #5725 动过(#5561 的resumeAuthority),service-automation的 note 已经因此改写过一次,但数字仍是 2、errors断言仍是纯存在性检查 —— issue 的前提原封不动地成立。问题
闸门只断言「有一条台账、数字为正」:
errors: 28和errors: 1对它完全等价。包这一层对新增 debt 是关着的,错误条数这一层不是 —— 台账从不复测,所以一个新测试文件带进来的错误没有任何一道闸会看见。AGENTS.md 写着「DEBT is frozen debt, not a permission slip. Every entry below was measured」,而一个悄悄漂了 2.25 倍的数字不再描述它声称冻结的那笔债。全量重测结果(sweep @
5ab08428)34 条全测:17 条低估、2 条高估、15 条精确,没有一条是因为债在缩小而失真的。下表「实测」列为本 PR 最终落账值;两条带「见下文竞态」的是在本 PR 生命周期内被 main 推着又校准过的。
@objectstack/metadata-protocol@objectstack/spec-monorepo(仓库根)@objectstack/core@objectstack/metadata@objectstack/service-knowledge@objectstack/service-analytics@objectstack/service-automation@objectstack/plugin-approvals(TEST_DEBT)@objectstack/objectql(TEST_DEBT)@objectstack/rest(TEST_DEBT)@objectstack/plugin-auth(TEST_DEBT)@objectstack/lint(TEST_DEBT)@objectstack/plugin-security(TEST_DEBT)@objectstack/formula(TEST_DEBT)@objectstack/trigger-record-change(TEST_DEBT)@objectstack/verify(TEST_DEBT)@objectstack/http-conformance(TEST_DEBT)@objectstack/runtime(TEST_DEBT)@objectstack/driver-mongodb(TEST_DEBT)新的 MEASURED 不变式
--re-measure对每条 DEBT 跑该包自己的tsc --noEmit -p .../tsconfig.json;对每条 TEST_DEBT,生成一份extends原配置、只去掉 test 排除项的临时兄弟配置再跑(生成在包目录内 —— tsconfig 的include/outDir/rootDir都相对声明它的那个文件解析,放到别处会把它们统统改指;finally里删除)。判定是不对称的,这是核心:
ℹ … can be lowered,不红。修错误不应该还要先改一个记账数字才能让 CI 变绿,否则台账就是在对它本该鼓励的工作收费。typecheckscript + 删条目,由 COVERED / RECONCILED 双向强制)。计数口径与台账里每个数字当初的量法一致(
grep -c "error TS"),只是加了--pretty false让它不依赖有没有 TTY。多行 elaboration 的缩进行不计数,无文件前缀的全局诊断计数。另有一道防呆:tsc 以非零码退出却没打出可识别诊断、或打出 TS5058/TS6053/TS18003 这类「读不到 project」的诊断时,抛错而不是记 0 —— 一个量不到东西却报告「改善了」的闸门比没有闸门更糟。note 的成分也一起重写了
漂的不只是数字,还有 note 描述的成分。
service-automation是最好的标本:记 2,note 逐字点名engine.test.ts:2547/2577的两条 TS2741 是「全部的债」,实测 5 条里多出来的 3 条是nested-region-parity.test.ts里测试用点号直读私有字段engine.flows的 TS2341 —— 不同文件、不同错误码、不同性质。一个「两个字面量缺字段」的 note 读起来是顺手就能毕业,实际却夹着「测试到底该不该读私有状态」这一类判断。所以每条被抬高的 note 都按实测成分重写(错误码直方图 + 集中的文件),闸门的报错文案也直接要求这件事。归因不了的就明说 —— 本仓库的 clone 是浅的,拿不到逐文件 blame,所以统一落成「re-measured N at 某个具体 sha」加上可测的成分,不编造来源。
两个只有重测才看得见的事实一并记进 note:
@objectstack/driver-mongodb净变化 -1,但成分换掉了三分之二 —— 老 note 归咎于缺types:["node"]的 15 条 TS2591 全没了,冒出 7 条 TS1309。单看数字会以为什么都没发生。@objectstack/http-conformance的 4 条里有 2 条报在node_modules的.d.ts上,所以这条会随 lockfile 动而不只随本包代码动。没有过滤掉它们(台账里每个数字的含义就是 rawtsc --noEmit计数,过滤会让数字无法用文档里那条命令复现),而是在 note 里写明「这两条不是本包要修的债」。闸门在本 PR 自己身上生效了两次 —— 基漂移竞态
值得单独说,因为这是这道闸门实战的证据,不是演习。两次都不是实现错、不是 flaky,而是「冻结数早于新代码入 main」。
第一次:PR 级 merge commit(
@objectstack/rest,136 → 143)第二次推送后 CI 判红:
rest记 136,实测 140。pull_request运行编译的是「分支 merge 进当前 main」的树,而 sweep 之后 main 又落地了三个动packages/rest的 PR(#5808 / #5821 / #5806)。合并最新 main 后重量得 143(tests56 → 58),其余 33 条纹丝不动。第二次:合并队列(
@objectstack/objectql,333 → 335)PR 进入合并队列后又被踢出:
objectql记 333,队列基实测 334。同样是基漂移 —— 333 是在 07:41 的 main 上冻结的,而 #5802(registry.test.ts+116 行)与 #5836 在 07:52–07:53 才并进 main,随后 #5850 又落地(还是 objectql)。合并当前 main 后实测 335,两条增量逐一归因、都不是新类别:src/registry.test.ts(9 → 10)—— fix(objectql): wantOwner 翻为正面清单 + 注入 owning_business_unit_id (ADR-0117 D1) (#5677) #5802 新增的 registry 测试;src/engine-update-prior-read-scope.test.ts—— sweep 时该文件尚不存在,由 perf(objectql): update() 单 id 前置行门按对象判定需求(#5284),并校准 #4743 事实一的三处注释 #5850(update()的前置行门是全局的(hooks.get('afterUpdate').length > 0),任一对象注册 afterUpdate 就让所有对象的单 id update 多付一次读 #5284)新建。tests125 → 126;其余 33 条纹丝不动(合计 2018 raw errors,无一超出记录值)。先证伪了另一种解释。 队列管家留了一条可证伪前提:若 objectql 在 333/334 之间横跳,那就不是校准问题而是 tsc 计数不确定,需要先谈容差。实测:同一棵树连跑两次
--re-measure,输出逐字节相同(仅 wall-clock 行不同),两次不同的 main 基上各验一遍都如此。⇒ 计数是确定性的,校准是正确处置,不需要容差讨论。为什么队列这一半要单独写进 doc block
队列比普通
pull_request更尖锐,而且通常的补救手段在这里无效:队列是按「合并到队首」构建的,队首会随前面的条目落地而移动,所以重跑失败的 job 无法自愈(重跑复用同一个 merge ref,量的还是那个旧基),唯一修法是推新提交。代价还会外溢:本 PR 在队列里红循环期间,排在它后面的 #5834 / #5843 各被同一道新门禁连坐踢出一次,等本 PR 离开队列后两者原封不动通过 —— 所以再遇到这种红,应当先把 PR 撤出队列再修。这些连同双跑证伪法都已写进
MEASURED的文档块与objectql/rest的 note,下一个做全量重测的人不必再自己推导一遍。这个竞态是引导期的一次性成本,不是常态:本不变式上了 main 之后,引入错误的那个 PR 自己会红 —— 这正是它的目的。
反向验证(方向先定后验)
预期方向写在跑之前:把一条台账改到低于实测应当只让那一条红,把另一条改到高于实测应当只出一行
ℹ且不贡献红。同一次运行,
service-analytics7 → 4、service-automation5 → 9:恰好 1 红 + 恰好 1
ℹ,增长判红、缩小不判红、逐条独立,一次落实。端到端的三个:改动前的台账(即 origin/main 的数字)在新闸门下是 17 条红,exit=1,重测后为绿;以及上一节 CI 与合并队列上那两次真实的红 → 校准 → 绿。
验证
CI(run 31081897969,
TypeScript Type Checksuccess):校准后在最新 main(
e2bfa6c)合并树上本地复跑,同样全绿:exit 0,且一行
ℹ can be lowered都没有 —— 34 条全部与实测严格相等,没有留任何虚高余量。self-test 新增 11 个用例:6 个钉三个方向(涨 / 缩 / 归零)与逐条独立性,5 个钉计数器本身(多行 elaboration 不重复计数、无文件前缀的全局诊断要计、正文里出现
error TS字样但无错误码的散文不计)。其他:
node scripts/check-nul-bytes.mjsOK;控制字符自查无命中;check-workflow-status-functionsOK;eslint scripts/check-type-check-coverage.mjs干净;工作区无残留的tsconfig.debt-remeasure.json。changeset
写了 changeset(
.changeset/type-check-debt-ledger-ratchet.md),空 frontmatter —— 与本脚本已有的两份先例(per-package-typecheck-coverage.md、dogfood-typecheck-wired.md)一致:dev scripts / CI only,releases nothing,但改动本身需要留档。因此不需要skip-changeset标签,Check Changeset历次运行均为 success。范围
未碰任何包的源码、tsconfig 或 package.json 的
typecheckscript,未做任何包的毕业(派发令的 ⛔)。文件面始终是这 5 个:scripts/check-type-check-coverage.mjs、.github/workflows/lint.yml、package.json、AGENTS.md、.changeset/*.md。AGENTS.md改了 8 行:该文件是这道闸门唯一的对外说明,新增的命令与不对称语义不写进去就是 #4203 那种「没人跑的闸门会烂掉」。范围外发现
已另开 #5826(observation-class,
finding标签,未派单):TEST_DEBT 的tests字段是闸门testCoverage()每次运行都已经算出来的数字,却手写在台账里,19 条中 12 条已漂(runtime记 66、实算 101)。本 PR 把这些数字更新到了实测值,但机制没变 —— 台账文件自己在 #5286 的注释里已经写过「该导出的数字不要手写」这条结论,只是没推广到剩下 19 条。删字段是形状变更,留给分诊定。裁决落地(Option A · 带记档余量引导)
维护者 2026-08-07 裁决走选项 A(#5278 决策卡),精确校准循环(选项 D)已 5 连败于 main 漂移,停做。本节只记「停放 → 重新起飞」这一段,上文原始设计与验证一字未改。
合并:main 自停放以来大幅前移
git merge origin/main(合并提交e8db1a230,base1eb13a0d2),先提交合并再重测。冲突面只有一处:scripts/check-type-check-coverage.mjsservice-sms一行(tests3 → 4 + note),本分支重写了整块。取本分支的块,并把 main 那条的事实并入该条目 note —— 两侧信息都不丢。.github/workflows/lint.ymlcheck:merge-driver步骤落在根 check job(L427),本分支的两个步骤落在 typecheck job 尾部(L756/L759),确为不同 hunk,两者都在。AGENTS.mdpackage.jsonscripts/check-type-check-coverage.mjs在 main 上这一窗口只被动过这一次(#5982),没有语义改动被丢弃。记档余量(三个实证热包)
按裁决第 3 条,今日实证被基漂移反复咬住的三个包按实测 + 10 记账,note 逐条写明意图与实测值、实测 sha:
e8db1a230@objectstack/objectql(TEST_DEBT)@objectstack/lint(TEST_DEBT)@objectstack/rest(TEST_DEBT)因此重测输出里恰好三行
ℹ ... can be lowered,这是预期且正确的,不是遗漏:不对称语义正是为此设计的:缩小不判红,所以余量不会让门变绿得可疑,反而每一轮都把收紧工单打印出来。
代价说清楚,不粉饰:这三个包在余量用尽之前,+1..+10 的真实增长不会被这道门看见。这是用「门今天就开始工作 + 连坐即止」换来的,范围限定在 3 条、写在 note 里、每轮自报。收紧是落地后的后续小单。余量约定也写进了脚本的
MEASURED文档块 —— 一个高于实测却没写明理由的数字就是暗账,台账里只允许这三处、且必须自己说出来。精确重校准(其余 31 条)
合并树上全量
--re-measure:5 条上漂、1 条下降、28 条与记录严格相等。非余量条目一律精确等于实测:@objectstack/service-analytics(DEBT)__tests__/measure-source-field-gate.test.ts同一文件、同一 TS2339,4 → 7(#5716 / PR #5963)。该包 3 → 7 → 10 全程无人看见,正是本单的标本@objectstack/runtime(TEST_DEBT)src/domains/meta-item-envelope.test.ts(#5563 / PR #5895),包里其余部分纹丝不动@objectstack/plugin-auth(TEST_DEBT)src/last-admin-guard.test.ts(#5941 / PR #5993);另 1 条落在既有文件,不再往下编造归因@objectstack/service-storage(DEBT)IStorageService.list(prefix)。下调而非留着 —— 高于实测又没写明理由的余量就是暗账objectql本身(实测 339,成分逐码不变)rest的 +10 里 8 条可归因到本窗口新增的三个测试文件(rest-meta-save-receipt-envelope.test.tsx4、meta-item-envelope.test.tsx2、analytics-dataset-unlisted-refusal-envelope.test.tsx2),余 2 条在既有文件、如实标注不归因;lint的 +2 两条 TS7006 均在既有文件,pre-merge 逐文件计数未留存,同样如实记录而不编造 —— 这是闸门自己错误文案要求的做法。tests文件数一并对齐实测(objectql130、runtime102、rest62、plugin-auth38、plugin-security35、service-sms5)。其中plugin-security与service-sms是文件数动了、错误数没动,note 里写明,与 main 上 #5773 那条的写法同形。验证(合并树
e8db1a230+ 本次校准)其余:
node scripts/check-nul-bytes.mjsOK(5836 个文件,无裸控制字节);改动文件控制字符自查无命中;eslint scripts/check-type-check-coverage.mjs干净;工作区无残留tsconfig.debt-remeasure.json。旧台账 17 红的端到端证明不再重跑(已在上文,且那次证明与本次校准无关)。后续漂移受害者的处置规则(标准做法,写在这里备查)
bootstrap margin (+10 over N measured at 某 sha),并在本节列出。不再做精确校准 lap(选项 D 已 5 连败证伪)。ℹ行驱动,作为后续小单,不在本 PR 内做。本次续飞未改任何包的源码 / tsconfig /
typecheckscript,未做任何毕业,未碰content/docs/releases/与三个分片产物目录。文件面仍是原来那 5 个。第二个合并窗口:余量在一小时内被兑付两笔(以及两处对上文的更正)
本 PR 校准完第一轮后,main 又前移了 24 个提交(到
dca5bd36a)。再次 merge + 全量重测,发生了三件必须记下的事。1. 余量当场证明了自己 —— 这不是预防,是兑付
e8db1a23077c7c884b@objectstack/objectql@objectstack/rest@objectstack/lintobjectql 的 +6 完全归因:全是 TS2554,全在
src/summary-rollup.test.ts,由飞行途中落地的 #5749 / PR #6013 扩写。记档 349 静默吸收了它;按裁决「实测 +10」,记录抬回 355 以恢复满额余量。2. 新增第四条记档余量:
@objectstack/service-storage(派发令的「飞行中被咬」条款)上一窗口我把它按实测下调 42 → 41(理由:高于实测又不写明就是暗账)。一小时后它被咬红:
原因是
IStorageService.list(prefix)的退休被拆成两个 PR:spec 半边(#5540 / PR #5983)减 1,适配器半边(#5541 / PR #6061)删掉旧测试(−1 TS7006)又新增storage-adapter-list-retirement.test.ts(+2 TS2835),净 +1。按派发令第 4 条(非余量条目被基漂移咬住 → 同一记档规则加 +10,不开精确校准 lap),该条改记 52(实测 42 + 10),note 写明整段 42 → 41 → 42 的经过。
这里有一个此前没人写下来的形状,已写进脚本文档块:拆成两个 PR 的退休会让计数先降后升,在两半之间记下的精确值,推上去之前就已经过期。
四条记档余量因此是:
objectql355(实测 345)、rest163(实测 153)、lint42(实测 32)、service-storage52(实测 42)。重测输出恰好四行ℹ,各 −10:3.⚠️ 更正上文「## changeset」一节 —— 该节已被 #5471 / PR #6059 作废
原文写「写了 changeset(空 frontmatter),因此不需要
skip-changeset标签」。这条现在是错的:飞行途中 #5471 / PR #6059 落地了新门禁scripts/check-empty-changeset.mjs,明令PR 新增空 frontmatter changeset 即违规,并给出两条路。本 PR 只动 dev scripts / CI、不发布任何包,因此走 route 2:.changeset/type-check-debt-ledger-ratchet.md;skip-changeset标签(已加,用 additivePOST /issues/5827/labels,不覆盖 bot 刚打的size/*等标签)。门禁复核:
node scripts/check-empty-changeset.mjs --base origin/main→✓ No empty-frontmatter changeset introduced by this diff;--self-test21 条断言通过。文件面因此从 5 个变为 4 个:
scripts/check-type-check-coverage.mjs、.github/workflows/lint.yml、package.json、AGENTS.md。上文「## 范围」一节的「这 5 个」同此更正。按接手协议,原节文字一律不改写,更正只写在这里。4. CI 说明:ESLint 红因归 #6100(main 上的红,非本 PR)
本 PR 的
ESLintjob 判红,签名是check:slot-lookup的packages/plugins/plugin-sharing/src/sharing-plugin.ts: erasure count grew 10 → 11。这不是本 PR 造成的,本 PR 触碰plugin-sharing的文件数为 0。两侧 bisect(同一工作树,只换合并进来的 main):分支 + main@1eb13a0d2绿(143 sites),分支 + main@dca5bd36a红(144 sites),分界点是f22660595(#5859 / PR #6067)改了sharing-plugin.ts却未同步scripts/slot-lookup-baseline.json。main 自身在f22660595与dca5bd36a上的ESLint结论都是 failure。已开单并在跟进:#6100(
bug/pm:queue/pm:dispatched),读数已补在 该单的评论。本 PR 不越界改 baseline。Validate Package Dependencies的红同理来自 main 的 lockfile(js-yaml / mermaid 的 OSV 通告),本 PR 未动pnpm-lock.yaml。本 PR 自己那道门(
TypeScript Type Check→Re-measure the type-check DEBT / TEST_DEBT ledger)的结论,才是本 PR 的验收信号。第三个合并窗口:合并队列踢出 —— 棘轮在自己的引入 PR 上抓到一条既有漂移
本 PR 进入合并队列后于 03:25:18Z 被踢出,失败 job
TypeScript Type Check,步骤 26Re-measure the type-check DEBT / TEST_DEBT ledger,唯一红线一条:同一次输出里另有四行下降(objectql 355→346、rest 163→153、lint 42→32、service-storage 52→42),那是上文四条记档余量各自打印的
ℹ can be lowered,按设计不判红,不是遗漏。1. 这一条是棘轮自己抓到的既有漂移 —— 值得单独记一句
队列管家已排除「由队列前一位 #6077 引入」这一解释(该 PR 自己那一代队列构建是绿的)。也就是说:这 +1 是一条既有漂移,在本不变式之前,本仓库没有任何一道闸门能看见它;它第一次对着移动的 base 重测,就把它照了出来。
一个棘轮抓到的第一条真实漂移,出现在它自己的引入 PR 上 —— 这是它有效的直接证据,而不是缺陷。上文「闸门在本 PR 自己身上生效了两次」记的是基漂移竞态(冻结数早于新代码入 main),这一条性质不同:它不是竞态,是存量。
2. +1 完全归因
在合并树
34558c2cc(git merge origin/main已先提交再重测)上重量,@objectstack/mcp= 53,成分:mcp-server-runtime.http.test.ts23 /mcp-action-tools.test.ts14 /mcp-http-tools.scopes.test.ts8 /mcp-validate-expression.test.ts6__tests__/mcp-server-runtime.test.ts(7,1)src/skill-prompts.test.ts(185,23)+1 就是那条 TS2352:把
SkillPrompt | null断言成Record< string, unknown >,所在文件正是 #3905 / PR #6077 投影 skillinstructions为 MCP prompt 原语时新增的skill-prompts.test.ts。测试文件数也因此 8 → 9。顺带按闸门文案要求改正了旧 note 的成分错误:它写「TS18046 x51 --
erroris of type unknown, one catch-block idiom repeated」,而 51 条实际全是响应体json绑定('json' is of type 'unknown'),与 catch 块无关 —— 正是顶栏警告的「note 读起来像快毕业了,实际搬进来的是别的东西」。3. 处置:第五条记档余量,不开精确校准 lap
按上文「后续漂移受害者的处置规则」第 1 条(非余量条目被咬红 → 同一记档规则加 +10):
34558c2cc@objectstack/objectql(TEST_DEBT)@objectstack/rest(TEST_DEBT)@objectstack/lint(TEST_DEBT)@objectstack/service-storage(DEBT)@objectstack/mcp(TEST_DEBT)选余量而非精确数字的理由,写进了 note:
packages/mcp今天刚落一个 feature(#6077),属于活跃变动包,精确数字大概率会再输一次已经被裁决判死的那场竞速(选项 D 连输五次)。这与前四条的入选理由同形。本窗口没有任何其他条目超出其记录值,因此只动了这一条,不做精确校准 lap。脚本顶栏
BOOTSTRAP MARGINS段同步从「四条」改为「五条」,并把这条的来历写了进去。余量在同一窗口内又兑付了第三笔:
objectql上一窗口实测 345,本窗口实测 346(+1),被记档值 355 静默吸收,剩余余量 +9(故上表该行是 +9 而非 +10)。按不对称语义缩小不判红、也不强制改数字,且本单口令是「只修红的」,因此不把它抬回 356 —— 剩余余量本身就打印在每轮的ℹ行里,收紧仍作为落地后的后续小单。至此四条老余量里已有三条(objectql 两次、service-storage 一次)在飞行途中被真实增长咬过,若当初按精确值记账,每一次都会是一次红循环。4. 验证(合并树
34558c2cc+ 本次校准,提交cc5c69fd6)恰好五行
ℹ、零条上漂,与预期一致。CI 已在 merge ref 上独立复核并判绿(run 31145783863,
TypeScript Type Checksuccess,headcc5c69fd6)。这一点比本地跑更有分量:pull_request运行编译的是「本分支 merge 进当前 main」的树,即队列踢出时量的那类基。步骤 26 输出与本地逐字一致:同一次 run 里,上一窗口第 4 节记的两处 main 侧红均已消失:
ESLintsuccess(#6100 的slot-lookupbaseline 已在 main 上修复)、Validate Package Dependenciessuccess;Check Changeset因skip-changeset标签 skipped。24 个 check 全部 completed,无 failure。先证伪了「tsc 计数不确定」这一解释(顶栏要求的双跑法):同一棵树上
--re-measure连跑两次,五条数字逐字一致(mcp 53 / objectql 346 / rest 153 / lint 32 / service-storage 42);@objectstack/mcp另用等价口径单独复量两次,均为 53。⇒ 是校准问题,不是容差问题。其余:
node scripts/check-nul-bytes.mjsOK(5877 个文件,无裸控制字节);改动文件控制字符自查(grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]')无命中;eslint scripts/check-type-check-coverage.mjs干净;工作区无残留tsconfig.debt-remeasure.json。本窗口只动了
scripts/check-type-check-coverage.mjs一个文件,未碰任何包的源码 / tsconfig /typecheckscript,未做任何毕业,未碰content/docs/releases/与三个分片产物目录。skip-changeset标签自上一窗口起已在(现标签:documentation/ci/cd/size/l/dependencies/tooling/skip-changeset)。Generated by Claude Code