You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
check:durability-log-level cannot represent a seam that propagates a durability failure by RETURNING an outcome object — its propagation lists are keyed on callee names #16233
scripts/check-durability-degradation-log-level.mjscannot represent a durability seam whose catch propagates by returning an outcome object, and this is a limitation of the instrument's shape rather than a missing entry in its lists.
The seam that exposed it: recordNotificationEventReceipt in packages/metadata/src/migrations/migrate-sys-notification-to-event.ts (added by PR #16230).
It is a durability seam of exactly the persistSeedTenancyReceiptRow class — the data really was
rewritten, every other reading is clean, and the only durable record of it can be absent.
Why the gate cannot hold it:
DURABILITY_CRITICAL_CALLEES does not name it — that part is merely an entry.
⛔ But it cannot be added without a second change: this catch propagates by RETURNING an
outcome object, while both FAILURE_PROPAGATION_CALLEES and FAILURE_PROPAGATION_SITES are
keyed on callee NAMES. A returned object literal has none.
⇒ Any seam in this repo that reports a durability failure to its caller through a return value,
rather than by calling a named reporter, is invisible to this gate by construction.
Measured, and it is the control that makes the zero above a reading: the gate's read-side
limb moves 68 → 67 when the same file is swapped back to origin/main. ⇒ the file is in the
swept population; the write-side silence is the instrument's blindness, not the file's absence.
⚠️NOT a live defect. The seam is pinned per-file by that round's own failed receipt
case, so this is an instrument-expressiveness gap, ⛔ not an unprotected degradation. Hence finding, not bug.
⚠️NOT measured: how many other seams in the tree propagate this way. That census is the first
thing a round on this card should buy, because it decides whether the fix is worth its cost — a
gap with one member is a note, a gap with thirty is a hole.
Why the shape of the fix is not obvious
A name-keyed list cannot key on "returns an object with a failure-shaped field". The candidate
directions each cost something and none is free:
Key on the enclosing function's name instead of the callee's, for a declared set of seams —
cheap, but it moves the gate from "what does this call do" to "who is asking", which is a
different question and may weaken the existing 29.
Recognise a declared return shape (an outcome type carrying a failure member) — precise, but
it needs the gate to read types, which it currently does not.
Leave it name-keyed and accept the class as out of scope, recorded — honest, and it makes this
card's answer a documented boundary rather than a hole.
⛔ This card proposes none of them. It records the gap, its measurement, and its control.
⚠️ Context a maintainer may want more than this row
This is now the third open finding against this one script, alongside a decision and a blocked
triage card:
triage the parameterless-catch read seams, then decide the 2a criterion
⇒ Five open items on one instrument. Whether that is five small repairs or one rewrite is a
question this card does not answer and probably should not be decided one row at a time.
Provenance and dedup
Found by the #16100 execution round, which could not file it itself and said so rather than
guessing: filing without a duplicate check is a forbidden shape, and its dedup channel was gone in
both directions — REST /search/issues is not served on that channel (a same-session control query
that must have hit #16100 came back empty, so its zero was a channel artefact, not a reading),
and both search_issues MCP calls were refused by the user-level rate limit. It handed the row to
the PM seat instead. Its full reasoning: #16100 (comment)
Dedup performed by this seat, over a complete enumeration rather than a search: all 652 open
issues fetched with bodies (651 of 652 carry one) and grepped locally. FAILURE_PROPAGATION_CALLEES, FAILURE_PROPAGATION_SITES, persistSeedTenancyReceiptRow and recordNotificationEventReceipt return 0 hits each; DURABILITY_CRITICAL_CALLEES returns 2
(#15166, #8897) and check-durability-degradation-log-level returns 7 — all read, none covering
this shape. Firing controls on the same command and scope: sys_migration → 4 (#16194, #16185, #16100, #15207), NOTIFICATION_EVENT_MIGRATION_ID → 2, durability → 12. ⇒ the zeros are readings.
Filed unassigned and ungraded — domain:* and priority are triage's.
「a failure handed to the CALLER is not a degradation at all —— 第三个合法答案」,并明文把「a batch whose contract IS a per-item outcome report」算进去;
「Do not bolt a logger.error onto such a site」;
「declare how it delivers instead —— FAILURE_PROPAGATION_CALLEES 或 function-scoped 的 FAILURE_PROPAGATION_SITES」。
⚠️「function-scoped」这个词会让人以为第三条罩得住 return 式交付。本席去读了源码(scripts/check-durability-degradation-log-level.mjs:505、:513):SITES 的键确实是 FILE::FUNCTION,但每个条目还必须给出 callees: [[name, kind]],注释把话说死 ——「it supplies a name, and catchDeliversFailure()still has to prove every path out of the catch reaches it」;现存两个条目(migrateStoredMetadata 的 record()、duplicatePackage 的 failed.push())都是靠一次调用交付的。⇒ return { ok: false, error } 的 catch 里没有任何调用可供声明。
The gap
scripts/check-durability-degradation-log-level.mjscannot represent a durability seam whose catch propagates by returning an outcome object, and this is a limitation of the instrument's shape rather than a missing entry in its lists.The seam that exposed it:
recordNotificationEventReceiptinpackages/metadata/src/migrations/migrate-sys-notification-to-event.ts(added by PR #16230).It is a durability seam of exactly the
persistSeedTenancyReceiptRowclass — the data really wasrewritten, every other reading is clean, and the only durable record of it can be absent.
Why the gate cannot hold it:
DURABILITY_CRITICAL_CALLEESdoes not name it — that part is merely an entry.outcome object, while both
FAILURE_PROPAGATION_CALLEESandFAILURE_PROPAGATION_SITESarekeyed on callee NAMES. A returned object literal has none.
⇒ Any seam in this repo that reports a durability failure to its caller through a return value,
rather than by calling a named reporter, is invisible to this gate by construction.
What was measured, and what was not
adr-0030-notification-eventmigration writes itssys_migrationreceipt row per the ruled ledger-claim matrix —last_run_aton every completed run,applied_atonmigratedonly, neververified_at(#15710 follow-on) #16100 round, on its own diff): the gate's write-side limb reports 29seams both with and without the new file — i.e. adding a real return-propagating durability
seam moves that population by zero.
limb moves 68 → 67 when the same file is swapped back to
origin/main. ⇒ the file is in theswept population; the write-side silence is the instrument's blindness, not the file's absence.
failedreceiptcase, so this is an instrument-expressiveness gap, ⛔ not an unprotected degradation. Hence
finding, notbug.thing a round on this card should buy, because it decides whether the fix is worth its cost — a
gap with one member is a note, a gap with thirty is a hole.
Why the shape of the fix is not obvious
A name-keyed list cannot key on "returns an object with a failure-shaped field". The candidate
directions each cost something and none is free:
cheap, but it moves the gate from "what does this call do" to "who is asking", which is a
different question and may weaken the existing 29.
it needs the gate to read types, which it currently does not.
card's answer a documented boundary rather than a hole.
⛔ This card proposes none of them. It records the gap, its measurement, and its control.
This is now the third open finding against this one script, alongside a decision and a blocked
triage card:
pm:on-holdcollectLoggedLevelsonly records part of what it shouldpm:on-holdreadInventionKeyfalls back where it should notpm:on-holdpm:blocked⇒ Five open items on one instrument. Whether that is five small repairs or one rewrite is a
question this card does not answer and probably should not be decided one row at a time.
Provenance and dedup
Found by the #16100 execution round, which could not file it itself and said so rather than
guessing: filing without a duplicate check is a forbidden shape, and its dedup channel was gone in
both directions — REST
/search/issuesis not served on that channel (a same-session control querythat must have hit #16100 came back empty, so its zero was a channel artefact, not a reading),
and both
search_issuesMCP calls were refused by the user-level rate limit. It handed the row tothe PM seat instead. Its full reasoning:
#16100 (comment)
Dedup performed by this seat, over a complete enumeration rather than a search: all 652 open
issues fetched with bodies (651 of 652 carry one) and grepped locally.
FAILURE_PROPAGATION_CALLEES,FAILURE_PROPAGATION_SITES,persistSeedTenancyReceiptRowandrecordNotificationEventReceiptreturn 0 hits each;DURABILITY_CRITICAL_CALLEESreturns 2(#15166, #8897) and
check-durability-degradation-log-levelreturns 7 — all read, none coveringthis shape. Firing controls on the same command and scope:
sys_migration→ 4 (#16194, #16185,#16100, #15207),
NOTIFICATION_EVENT_MIGRATION_ID→ 2,durability→ 12. ⇒ the zeros are readings.Filed unassigned and ungraded —
domain:*and priority are triage's.os-decision-facets
决策箱 —— 普查买回来了,数是 12,⇒ 路线选择越过了本卡口径,交维护者
domain:devx执行席(座位贴 #6023,sessionsession_017ef78bLdybu3AffehKkhfk,round 12)。⛔ 本席不代裁。普查与边界记录已随 PR #18523 落地(Part of,⛔ 不关本卡)。先把「为什么现在必须有人裁」说清:⭐ 不是仪器缺一条腿,是治理文件自己写出了一条死路
AGENTS.md:938-943同时规定三件事(本席在origin/main上逐字读的,⛔ 不是转述报告):logger.erroronto such a site」;FAILURE_PROPAGATION_CALLEES或 function-scoped 的FAILURE_PROPAGATION_SITES」。scripts/check-durability-degradation-log-level.mjs:505、:513):SITES 的键确实是FILE::FUNCTION,但每个条目还必须给出callees: [[name, kind]],注释把话说死 ——「it supplies a name, andcatchDeliversFailure()still has to prove every path out of the catch reaches it」;现存两个条目(migrateStoredMetadata的record()、duplicatePackage的failed.push())都是靠一次调用交付的。⇒return { ok: false, error }的 catch 里没有任何调用可供声明。⇒ 在这 12 个成员中的任何一个上执行
AGENTS.md的第 1 条(「发现新缝就在同一个 PR 里把它加进DURABILITY_CRITICAL_CALLEES」),第 2、3 条同时不可用:正确的代码会把闸弄红,而唯二能弄绿的办法(挂logger.error/ 进 baseline)都是这份闸 header 自己拒绝过的。今天不触发,只因为 tier-1 是 0。读数时刻 2026-09-16T21:18Z。选项 × 真实代价
FAILURE_PROPAGATION_CALLEES表达的缝会被函数级许可覆盖,今天的绿会变松CREATE INDEX探测、claimSuspension消费)AGENTS.md(治理面,维护者独有),要么留一条写在书上却做不到的规则port.warn接收者是同一条轴的两半业务含义直译
四棱
Prior rulings read: failure_propagation_callees,failure_propagation_sites,return-propagating,outcome object,enclosing function name,durability → 0 hits; none
check-prior-rulings.mjs的口径写:docs/adr0/463,⇒ 无 ADR 裁定。⛔ 但AGENTS.md有 10 处命中,其中:938-943就是上面那条死路的出处 —— 它不是 ADR 裁定,是规则正文本身,本席按「不是裁定所以不写进 hits」的字面口径记 0,同时把它整段抄在上面,⛔ 不让它被这个 0 藏掉。推荐:B(读声明的返回类型),时序按 D 走(与 #8897 合并成一次设计再动手),回退项 C。⚠️ 另:dev 席的建议是「④ 其次 ②」,⛔ 本席不沿用 —— D 不是一个方向,它是 B 该怎么设计的范围;字母必须落在一个真会改变仪器的选项上。
自检行:「只看①选 B;②③④ 是否翻转:否 —— ② 零拉动只把它推后(合并进 D 那次设计),③④ 都指向同一个字母。」
置信缺口:⛔ 本席没有量 B 的实现成本(给这个闸引入 program 级类型解析要多久、会不会拖慢 CI),也没有读 #12576 —— 它可能在同一条轴上,若是,D 的设计面还要再大一圈。
裁定之后怎么执行(维护者只裁方向,⛔ 不背执行)
ACCEPTED_*台账。AGENTS.md:938-943的冲突作为治理面改动单列一张卡交还维护者(⛔ 本席不改治理面)。FAILURE_PROPAGATION_CALLEES缝不被函数级许可放松」,⛔ 证不出就退回。pm:on-hold+Restart-when:,并把 check-durability-degradation-log-level:collectLoggedLevelsonly recognises a logger named logger/log/console, so a catch that reports through an injected logger reads as silent to BOTH rules #8897 一并挂上。Generated by Claude Code