measure the return-propagating durability seams the log-level gate cannot name - #18523
Conversation
…g-level gate cannot name `check-durability-degradation-log-level.mjs` accepts three answers from a catch guarding a durability-critical operation: rethrow, log at `error`, or hand the failure to the caller through its declared propagation vocabulary. Both maps that spell the third answer — `FAILURE_PROPAGATION_CALLEES` repo-wide and `FAILURE_PROPAGATION_SITES` scoped per function — are keyed on the NAME of a callee the catch reaches. A catch that hands the failure back by RETURNING an out-param object calls nothing, so it has no name to declare: the delivery IS the constructed value. That is an expressiveness statement, true whether the population is one seam or fifty, and on its own not a reason to change anything. This census exists so the routing decision is taken against a number instead of an impression, and so the number can be re-taken rather than quoted from a report that has gone stale. It is a MEASUREMENT, not a gate: it exits 0 on any membership count and is deliberately not named `check:*` or `gen:*`, the shape `measure-durability-swallow-family.mjs` established. The durability axis is three declared vocabularies, never a spelling: the gate's own `DURABILITY_CRITICAL_CALLEES` and #12981's `WRITE_SHAPED_CALLEES`, both read out of their source files at run time so this census cannot drift from them, plus a `READ_ON_THE_MERITS` register carrying the reading for each operation admitted here. `NOT_A_DURABILITY_CLAIM` is the opposite direction — sites that match the shape over a declared write name but claim no persistence, excluded with the reading rather than filtered silently. Both registers fail `--self-test` when a row stops matching. Claude-Session: https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk Co-authored-by: Claude <noreply@anthropic.com>
… durability gate's fourth limitation The gate enumerates three honest limitations up front rather than letting the next reader discover them. This adds the fourth, which is constructive rather than a missing row: both maps that spell the gate's third answer key on the NAME of a callee the catch reaches, so a catch that hands the failure to the caller by RETURNING an out-param object reaches none and cannot be declared at all. The note points at the census for the count instead of quoting one, records that a green over any site the census lists means NOT MEASURED for that site, and deliberately does NOT pick a repair: re-keying on the enclosing function, reading the declared return type, and recording the shape as out of scope are all open, and the first would move the question from "what did this call do" to "who is asking". Verdict lines unchanged by this edit: 36 durability-critical catch seams and 68 read seams, both before and after. Claude-Session: https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk Co-authored-by: Claude <noreply@anthropic.com>
复核:ACCEPT —— 但下面每一条都是本席自己对着 GitHub 与
|
| 口径 | 核法 | 结果 |
|---|---|---|
| 第一交付物是普查,⛔ 不是修 | 读 /pulls/18523/files |
两个文件:闸 +28/−1、新仪器 +980/−0 ✅ |
| ⛔ 不得在本轮动闸的判据/行为 | 读闸那一跳 diff 的每一行 | −1 是 Three honest limitations → Four;+28 全在同一个 docblock 内。⇒ 判定逻辑一个字符没动 ✅ |
| ⛔ 拿到数之前不许选三条修法 | 读 header 新增段与 PR 正文 | 三条路线并列写着「都还开着」,且点名了第 ① 条的代价。⇒ 没选 ✅ |
⭐ 它还遵了一条我没在派发令里写、但 AGENTS.md 第 9 条要求的东西:新段落指向仪器,而不是把数字抄进注释(「⛔ Read the instrument's own output rather than quoting a count from here」)。
二、普查我抽验了一个成员,⛔ 不是抽验它的叙述
取报告里最短的一条 —— packages/services/service-messaging/src/inbox-channel.ts:212 ::send。origin/main 上逐字:
try {
const created = await data.insert(objectName, row); // :213 ← 被守的持久化写
…
} catch (err) {
return { ok: false, error: `inbox insert failed: …` }; // :217 ← 唯一出路,catch 内构造的对象
}引信对照(同一文件、同一命令):grep -c "" → 248 行。⇒ 四条判据全中:持久化写、每条出路都 return 一个 catch 内构造的对象、对象用 ok: false 明示失败、catch 里没有任何调用可供声明。这是一个真成员。
三、⭐ 一条本席在复核中读到、比报告更进一步的东西:AGENTS.md 自己就把这个缺口写成了死路
AGENTS.md:938-943(读数时刻 2026-09-16T20:06Z,工作树 8ca7aafc454a4cf1b15aad7bfa8094bee4ee3971):
And a failure handed to the CALLER is not a degradation at all — the third legal answer: a
catchthat answerserrorFromThrown(e, 400), or a batch whose contract IS a per-item outcome report, does not look normal from the outside — the requester was told. Do not bolt alogger.erroronto such a site; declare how it delivers instead —FAILURE_PROPAGATION_CALLEES(repo-wide names) or the function-scopedFAILURE_PROPAGATION_SITESin the checker …
FAILURE_PROPAGATION_SITES 按函数授权、因而能罩住 return 式交付。本席去读了源码,不是读这句话: :513 起,键确实是 FILE::FUNCTION,但每个条目还要给出 callees: [[name, kind]],而 :505 的注释把话说死了 ——
it supplies a name, and
catchDeliversFailure()still has to prove every path out of the catch reaches it.
且现存两个条目(migrateStoredMetadata 的 record()、duplicatePackage 的 failed.push())恰恰都是靠一次调用交付的。⇒ AGENTS.md 明文合法化的那个「per-item outcome report」,只在报告由一次调用构造时可声明;return { ok: false, error } 里没有任何调用,两张表都罩不住它。卡的核心论断成立,而且比卡自己写的更硬。
⇒ 这就给出了一个今天尚未触发、但按书面规则必然发生的死路:AGENTS.md 同时要求 ①「发现新缝就在同一个 PR 里把它加进 DURABILITY_CRITICAL_CALLEES」、②「⛔ 不要在这种站点上挂 logger.error」、③「改为声明它如何交付」。在这 12 个成员的任何一个上执行 ①,②③ 就同时不可用 —— 正确的代码会把闸弄红,而唯二能弄绿的办法都是这份 header 自己拒绝过的。 今天不触发,只因为 tier-1 是 0。
四、放行判据
skip-changeset:✅ 两个路径都在仓根scripts/,不在任何包的files[]里;它给了阳性对照(81 个包里 70 个把CHANGELOG.md写进files[])⇒ 零是读数。- clause-②:
node scripts/pm/check-clause2-carriers.mjs --pair 18523→ ✓,两个载体一致、diff 无 widening tell。 ⚠️ 一条要你(承接 dev)补的:PR 正文没有行首Clause-②:行。本 PR 的Check Changeset没因此变红(diff 不动packages/**/src/**),但同轮另一张 PR fix(lint): enter the publicPicker object reader by schema position, not by key name #18524 正因为缺这一行而红。⇒ 请补上Clause-②: no独占一行,闸订阅edited,⛔ 不用推、不用重跑。- CI:31 个去重检查名里 30 个已完成且绿/跳过,
Lint & Repo Gates在跑。⇒ 转 ready 之后本席会重读再武装(转 ready 会补跑检查,绿判据必须在转之后取)。
五、⛔ 本席不裁的那件事
普查落在 12(不是 0)⇒ 派发令里那条「落零就记边界关卡」不适用,三条候选修法的选择越过了本卡口径,而且它会动闸的判据。⇒ 本卡落地后进决策箱,由维护者裁。本席会在卡上写四象限块并给建议,⛔ 不代裁。
Generated by Claude Code
Part of #16233 — this PR delivers the CENSUS the triage ordered first. It does not
pick among the three candidate repairs; #16233 stays open for that decision.
Clause-②: no
The gap, verified structurally on this tree
check-durability-degradation-log-level.mjsaccepts three answers from acatchguarding a durability-critical operation: rethrow, log at
error, or hand the failureto the caller through its declared propagation vocabulary. Both maps that spell the
third answer are keyed on the NAME of a callee the catch reaches —
FAILURE_PROPAGATION_CALLEES(:444) repo-wide,FAILURE_PROPAGATION_SITES(:513)scoped to one
<file>::<function>, whose values are themselves[name, via]pairs.catchDeliversFailure()proves delivery byfindPropagationCall()→matchesPropagationName(child, d.name).A
catchthat hands the failure back by RETURNING an out-param object calls nothing.return { ok: false, error: err }reaches no name, so the seam is not missing a row —it is inexpressible as the vocabulary is shaped. At such a site the only ways to green
the gate are the two the file's own header rejects: a baseline entry for correct code,
or a bolted-on
logger.error("the mirror-image failure AGENTS.md warns about").This is NOT a live defect. Every seam the gate sees today answers through a log or a
declared callee — see the tier [1] reading below.
The measurement
scripts/measure-return-propagating-durability-seams.mjs, a MEASUREMENT and not agate (exit 0 on any membership count; deliberately not named
check:*/gen:*, theshape
measure-durability-swallow-family.mjsestablished).Tier [1] at zero is the load-bearing reading: the shape costs the present verdict
nothing, and the 12 sit one vocabulary entry away from becoming visible. Membership is
four parts — a declared durable call in the
try(same-tick), EVERY path out of thecatchreturning an object CONSTRUCTED IN THE CATCH, that object NAMING the failure,and the catch not already being expressible (no rethrow, no
error/fatallog, nodeclared propagation callee, not a declared site). The path analysis mirrors the
gate's own
catchDeliversFailure()and reports "cannot prove" as "does not deliver",so an unmodelled shape is DROPPED: the instrument's declared direction of error is to
UNDER-count.
The durability axis is three declared vocabularies, never a spelling. Two are READ OUT
OF THEIR OWN SOURCE FILES at run time — the gate's
DURABILITY_CRITICAL_CALLEESand#12981's
WRITE_SHAPED_CALLEES— so this census cannot drift from them, and avocabulary it cannot PARSE is a refusal (exit 2) rather than a shorter list. The third
is
READ_ON_THE_MERITS, each entry carrying the reading for why that operation claimspersistence.
NOT_A_DURABILITY_CLAIMruns the other way: sites matching the shape overa declared write name that claim no persistence, excluded WITH the reading instead of
filtered silently. Both registers fail
--self-testwhen a row stops matching — whichalready paid for itself: an
appendEvententry was dropped because the call sits insidea nested arrow the same-tick walk correctly refuses to descend into.
The 12
core/src/utils/migration-journal.ts:660unwindtransactionstatus: 'failed', errordrivers/driver-sql/src/sql-driver.ts:6934attemptWithoutPoisoningtransactionok: false, errordrivers/driver-turso/src/remote-canonical-backfill.ts:439execute,runBatchedUpdateerrormetadata-protocol/src/migrations/partial-index-probe.ts:390exec(CREATE INDEX)status,detailmetadata-protocol/src/migrations/partial-index-probe.ts:410exec(CREATE INDEX)status: 'failed', detailmetadata-protocol/src/protocol.ts:18272publishPackageDraftsinTxnsuccess: false, failed[]plugins/plugin-auth/src/reconcile-membership.ts:230insertMembershipoutcome: 'failed'services/service-automation/src/builtin/crud-nodes.ts:320create_recordinsertsuccess: false, errorservices/service-automation/src/builtin/crud-nodes.ts:460update_recordupdatesuccess: false, errorservices/service-automation/src/builtin/crud-nodes.ts:548delete_recorddeletesuccess: false, errorservices/service-automation/src/engine.ts:2897claimAdvanceclaimSuspensionkind: 'unavailable', messageservices/service-messaging/src/inbox-channel.ts:212sendinsertok: false, errorTwo of them already state the contract in prose.
sql-driver.ts'sattemptWithoutPoisoningdocuments "Returns a discriminated result rather thanrethrowing, so the caller keeps the ORIGINAL error";
engine.ts'sclaimAdvancecomments "Handed to the CALLER, so not a degradation and deliberately NOT a log site
(AGENTS.md 'Degradation log levels': a failure the requester was told about does not
look normal from the outside)". Those are the gate's own third answer, written by hand
because there is no way to declare it.
The card's sample seam is gone — read separately from the structural claim
packages/metadata/src/migrations/migrate-sys-notification-to-event.tsis not onmain, andrecordNotificationEventReceiptis 0 occurrences repo-wide (control wordpersistSeedTenancyReceiptRow, same command shape: 1 file). #16194 retired theADR-0030 cut-over. So the card's own before/after numbers are not reproducible and were
not re-derived; the STRUCTURAL claim was re-verified independently and holds.
Also here: limitation 4 in the gate's header
The gate enumerates three honest limitations. This adds the fourth — the boundary above
— pointing at the instrument for the count rather than quoting one, and recording that
a green over any listed site means NOT MEASURED for that site. It explicitly does NOT
decide what to do about it: re-keying on the enclosing function, reading the declared
return TYPE, and recording the shape as out of scope are all open, and the first moves
the question from "what did this call do" to "who is asking".
Verification
pnpm check:durability-log-level→ exit 0,36 durability-critical catch seam(s), all loud, rethrowing or propagating to the caller (4 propagating, declared)and68 read seam(s)— both unchanged by the doc-block edit.node scripts/check-durability-degradation-log-level.mjs --self-test→ exit 0,57 case(s) passed.node scripts/measure-return-propagating-durability-seams.mjs --self-test→ exit 0,7 membership fixture(s), 7 READ_ON_THE_MERITS entr(ies) and 4 NOT_A_DURABILITY_CLAIM row(s) all reached.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(32 commands) and all of them run — see the report on 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 for the per-command exit
codes.
Acceptance notes
skip-changeset: verified rather than assumed. Both touched paths are underscripts/, and no packagefiles[]in this workspace shipsscripts/**from therepo root.
FAILURE_PROPAGATION_SITES-style keys are<file>::<function>,and where the enclosing function is large (
storage-service-plugin.ts::start) the keyis coarser than the catch. The gate accepts that trade for its own map; this census
inherits it. Carrier: whoever takes 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's repair route.
Generated with Claude Code in session https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk
Generated by Claude Code