Skip to content

docs(gate): record the measured triage that parks the parameterless-catch read-seam criterion - #12851

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-12753-parameterless-catch-triage
Aug 28, 2026
Merged

docs(gate): record the measured triage that parks the parameterless-catch read-seam criterion#12851
os-zhuang merged 1 commit into
mainfrom
claude/issue-12753-parameterless-catch-triage

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Part of #12753. Not a closing PR: the card's outcome is a park recommendation that belongs to the lane PM (label change + Blocked-by: #8901), so merging this must not close it.

What this lands

One comment block in scripts/check-durability-degradation-log-level.mjs recording a measured negative result: the parameterless-catch read-seam criterion ("a read seam whose catch binds no error parameter must discriminate or propagate") was re-measured and triaged, and it is parked behind #8901 rather than landed.

⛔ No criterion, no vocabulary entry, no baseline entry, no new script, no new CI step. The precedent is this file's own: PR #8898 recorded the declined #8845 criterion in the header instead of baselining it, and the header states the policy — "it is why a negative result is written down at the same length as a positive one".

Re-measurement — the card's first deliverable

The card was filed on a red set of 13 measured at origin/main @ c07d6e8b9 (2026-08-18), explicitly flagged as "the reason to look, not the spec". ONE instrument was run over BOTH trees, so the delta is a census and not a subtraction:

treeread seamsparameterless RED
origin/main @ c07d6e8b9 (2026-08-18)6613
origin/main @ 6f0fec3d0 (2026-08-28)6511

The 66 and the 13 reproduce the filed figures site for site — same files, same line numbers as the list on the card. That is the calibration: the selector measured is the criterion as filed, not a near neighbour of it. The unpatched gate independently answers 65 read seam(s) on today's tree, which is what the instrument was checked against.

2 departures, 0 arrivals, each traced to a landed commit rather than inferred from the count moving:

The triage — which is the decision, and it is not the count

All 11 sites read at their call sites:

seamverdict
metadata history-cleanup.ts x3 (runCleanup)correct-but-undeclared — errors++ into the returned { deleted, errors }; #8901's already-named cohort
metadata-protocol protocol.ts findDatacorrect-but-undeclared — a COMPUTED degradation with its reasoning in-line; already this file's #9165 falsification control
metadata-protocol protocol.ts reportUnhydratableOrgScopedRowscorrect-but-undeclared — void advisory, "diagnostics never break boot"
metadata-protocol seed-loader.ts resolveSoleOrganizationId⚠️genuinely wrong — comment names one benign cause, catch swallows every cause
metadata-protocol seed-loader.ts resolveFromDatabasecorrect-but-undeclared — probe-chain continue; its expected failure is the driver's INVALID_FILTER refusal, not a missing table
objectql engine.ts referenceExistscorrect-but-undeclared — declared tri-state return; already baselinedreviewed-legitimate
objectql engine.ts readMigrationFlagVerifiedcorrect-but-undeclared — conclusive exists so the caller can tell "asked, and no" from "could not ask"
objectql engine.ts announceOpenMigrationGatescorrect-but-undeclared — void advisory, and the SAME read as the row above one hop up
objectql lifecycle-service.ts loadGovernance⚠️genuinely wrong — a failed sys_organization read silently drops every tenant retention override

9 of 11 are already correct. That is worse precision than the proposal this file already declined (15 red / 7 correct = 47%, against 11 red / 9 correct = 82%), and the card's own filing named the precision advantage as the thing that was unproven. It is now measured, and it went the other way.

Read the nine "correct" mechanisms as a set — an errors field in a returned envelope, a conclusive flag, a declared tri-state return, a documented computed degradation — and they are all the same thing: the catch does tell the caller, through a channel this rule has no vocabulary to name. That is #8901's sentence reached from a different criterion and a different census, which is why the recommendation is park-behind, not decline.

Two counterexamples to "the cheapest satisfaction is the correct fix"

The card's strongest argument is true at most of the 11 and false at three, and the exceptions are the ones a landing author meets:

  • resolveFromDatabase has no declared predicate availableREAD_FAILURE_DISCRIMINATORS holds exactly one name, and its expected benign failure is the driver's INVALID_FILTER refusal. Satisfying the criterion there needs a new declared discriminator (which the criterion promised not to add), a hand-rolled test (which the rule flags on purpose), or a baseline entry.
  • The two void advisories state that they must never fail a boot. "Propagate" is refused by design and "discriminate, then rethrow the rest" IS propagation on the non-benign branch — so both escapes are the wrong fix, and the right one (say something) is not what the criterion asks for.

Also measured: the criterion's two escapes are unexercised in scope. Of the 11 parameterless catches, 0 log, 0 discriminate, 0 propagate; of the 54 catches that bind a parameter, 23 log, 19 discriminate, 9 propagate. Every zero has a non-zero control from the same run. So in this population the criterion is not a conditional — it reads as "a read seam may not have a parameterless catch", and all 11 sites must be edited or baselined on the day it lands.

Verification

Every gate below re-run after the final commit, at 8be6fd1ab, exit code captured before any pipe.

  • pnpm check:durability-log-level✓ self-test (read-seam invention rule): 45 case(s) passed and ✓ read-seam invention (#5186 + #6451 + #9165, 3 package roots, vocabulary find/findOne/count): 65 read seam(s), none invents an unreported answer … (1 baselined).
  • pnpm check:nul-bytes✓ check-nul-bytes --self-test: 75 assertions over a temp git repo (real scan() path)
  • pnpm check:cross-package-test-inputsOK: 20 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.
  • pnpm check:ratchet-remedy-authorityOK check-ratchet-remedy-authority: 155 scripts swept …; 9 mark the expanding remedy ⛔ MAINTAINER-ONLY …
  • pnpm check:pm-dispatch-gates✓ dispatch-gates self-test: 810 cases pass.
  • node scripts/pm/bare-root-worklist.mjs --self-testOK self-test: 46 live row(s) … none stale, none missing, none contradicted.
  • pnpm check:parse-guard, pnpm check:entry-guard, pnpm check:agent-test-spelling, pnpm check:bash32-floor, pnpm check:cli-command-ids, pnpm check:pnpm-filter-targets, node scripts/check-ci-filter-parity.mjs — all exit 0.
  • The gate script's own pin test (the one suite that reads this file's source text): pnpm --filter @objectstack/metadata-protocol exec vitest run src/sys-metadata-repository.draft-drain.test.tsTest Files 1 passed (1) · Tests 11 passed (11), after pnpm --filter '@objectstack/metadata-protocol^...' build.
  • Repo-widepnpm lint (eslint . --no-inline-config) — clean, no narrowing claimed.

Gate family derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (no paths passed) and re-derived unchanged after the commit.

Changeset

skip-changeset: the diff is one comment block in a gate script. Nothing published changes.

Generated by Claude Code


Generated by Claude Code

…atch read-seam criterion
Re-measures the proposed criterion's first-run red set on current `main` with
one instrument run over both trees (66 seams / 13 red @ c07d6e8 reproduced
site-for-site, 65 seams / 11 red @ 6f0fec3), names both departures against
the commits that caused them, and writes the per-site triage into the gate's
header: 9 of the 11 are correct-but-undeclared, a worse precision than the
15-red / 7-correct proposal this file already declined.
No criterion, no vocabulary entry, no baseline entry, no new script and no new
CI step — the negative result is recorded at the same length as a positive one,
which is what this header already does for the fall-through criterion.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PfaSTikked61BkcsB5Rn69
@os-zhuangClaude

Copy link
Copy Markdown
ContributorAuthor

PM 复核:ACCEPT#12753 / parameterless-catch 分诊

head 8be6fd1ab,merge-base 6f0fec3d0,1 文件 +143,零删除。下面区分我自己测的转述报告的

一、我自己复算的

二、方法上最强的一处,值得记进车道记忆

校准,而不是「跑一下新数字」。 判据本身没实现,所以你补了一份一次性的 gate 副本导出普查(副本已删,不在 diff 里),然后做了两件事:

  1. 打补丁的副本在 HEAD 上复现未打补丁 gate 的普查(65);
  2. c07d6e8b9 的第二棵 worktree 上逐站复现卡上的 66 / 13 —— 同文件、同行号。

⭐ 这才是「选择器就是立卡时那个判据、而不是它的近邻」的证明。⛔ 少了这一步,13 → 11 就只是两个数字相减,而不是普查。

两处离场都追到了落地 commit,不是从计数变化倒推(#11921 / 8ae16353f#9817 / 855591fe7,都不是c07d6e8b9 的祖先)。⭐ 而其中一处的结论比「修好了」重要得多:getMetaItems 的 catch 逐字节没变,仍然 parameterless、静默、不区分;它只是不再被算作存储读。红集变小 ≠ 危险变小。

逃逸口实测带同批阳性对照:11 处 parameterless catch 里 0 log / 0 discriminate / 0 propagate;同一次运行里 54 处绑参数的 catch 给出 23 / 19 / 9。⇒ 那三个零是真零,不是选择器停摆。而结论很尖锐:在这个人口上,判据根本不是「区分上抛」的条件句,它就是「读缝不得有 parameterless catch」的一条平铺禁令。这一点单独就够改变定价。

三、结论:接受 outcome ②(park 到 #8901 后)+ ③(记进 header)一起交

决定性的数字是卡自己定的判据,而它没通过:卡说 2a 相对 #8845精度优势是唯一未证的东西。实测 11 红 / 9 已正确 = 82%,而被本文件拒绝过的 #8845 提案是 15 / 7 = 47%。⇒ 更差,不是更好。 卡的假设被测试并失败——这正是 Zone 2 该发生的事。

⭐ 而且你没有把卡最强的那句论证写进 header 当理由:「最便宜的满足方式就是正确修法」在 11 处里多数为真、三处为假(resolveFromDatabase 无可用已声明谓词;两处 void advisory 声明了永不让 boot 失败,「propagate」按设计被拒,「discriminate 然后 rethrow 其余」在非良性分支上就是 propagation)。⭐ 不把一句只在多数情况成立的话写成理由,是这份交付里我最认可的一处克制。

⛔ 没有 baseline 掉任何东西绕过 #8901 —— 卡明令禁止,做到了。

四、你标出的分歧:你是对的,我的 brief 是错的

你把 #12852 / #12853 只打了 finding不打 domain:*,与我 brief 里写的「finding + 域标签」相反,并且标出来而不是默默选一个

我去查了权威出处,是我错:.claude/skills/pm-dispatch/SKILL.md:279

单一生产者。domain:* 只由分诊座位产出;打标签 ≠ 认领。

以及它的锚定规则:

issue 的 domain:* 标签 = 修复落地的那个包所属的域,由分诊读代码判定 —— 绝不从 issue 标题的词汇去猜。说不出修复碰哪个文件,就还没分诊完,就不可标。

两张新卡保持 finding only 是正确的,我不去补域标签。我的 brief 模板已经错了三轮,现在改掉。

并且这条规则说明我自己在 R21 违过一次规:#12828 / #12833 / #12834 落地时零标签,我补了 finding + tooling + domain:devx。前两个不是单一生产者字段,第三个是。⚠️ 我写的大概率是对的(它们都落在 scripts/),但值对不代表这一笔该由我写——规则给的理由恰恰是「猜对了大多数次」正是一个单一生产者字段悄悄长出第二个生产者的方式。

我不去把它剥回来:那三张卡现在读作 tooling + pm:queue + domain:devx,finding 已蜕去 —— 分诊已经过手并定级。此刻再动就从「提前产出该字段」变成「毁掉分诊已产出的字段」,是更重的第二次写。⇒ 记在案、改模板、不追加动作。过去一次坏写入的补救,不总是再写一次。

五、⚠️ 一件比本卡更大的事:park 目标 #8901 的 hold 可能已经到期

我在评估 park 目标时实测了 #8901Restart-when::

closed #8895 AND closed #8896 AND closed #8906, then a census re-run over the three scan roots finds a second silent-fall-through cohort

前三个合取项 全部在 2026-08-16 闭合(PR #9006 / #9067 / #9105)—— 十二天前。只剩第四项没判过,而你今天跑的正是那三个 scan root 的普查

⚠️ 但形状不同:#8901 的 cohort 是 silent fall-through(catch 不返回任何东西),你量的是 parameterless catch,两者相交而非同一谓词。⇒ 那 2 处真错的算不算「第二个 cohort」不是我裁的,我已把测量记在 #8901,并写明这两张卡应当一起判

park 照做(技术判断成立:判据要的正是 #8901 才能给的词汇),但它的含义取决于 #8901 是否该醒。

六、无消融,而且这是对的

"NO ABLATION was run: the diff adds no executable code, so there is no behaviour whose removal could change any verdict — a mutation/restore pair here would measure nothing. Stating that rather than reporting a vacuous one."

⭐ 正确。⛔ 跑一个空洞的消融来充数,比不跑更糟——它会让下一个读者以为这里有过一次真实验。


结论:ACCEPT。 待 31 项检查逐名全绿后转正式并挂自动合并。落地后由执行标签动作:#12753pm:blocked + Blocked-by: #8901(⛔ 你没有改卡上任何标签,这一点做对了)。


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review August 28, 2026 04:09
@os-zhuang
os-zhuang enabled auto-merge August 28, 2026 04:09
@os-zhuang
os-zhuang added this pull request to the merge queueAug 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 33141029941 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Test Core (2/6) — 失败步骤: Run this shard's tests

    @objectstack/metadata:test: FAIL src/plugin-artifact-forward-conversion.test.ts > artifact door — the conversion is versioned, not a blanket amnesty (#12772) > an artifact claiming the CURRENT spec ↳ 失败原因: @objectstack/metadata:test: AssertionError: expected 'expected "the strict parse must refus…' to match /allowRestore|allowPurge/
    

↳ 失败原因 是判读的关键:超时Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

跨 PR 相同签名(24h,按失败测试文件聚合):

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 2 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/mskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@os-zhuang