Skip to content

measure the return-propagating durability seams the log-level gate cannot name - #18523

Merged
os-try-charles merged 2 commits into
mainfrom
claude/issue-16233-return-propagating-seam-census
Sep 16, 2026
Merged

os-try-charles merged 2 commits into
mainfrom
claude/issue-16233-return-propagating-seam-census

Conversation

@os-try-charles

@os-try-charles os-try-charles commented Sep 16, 2026

Copy link
Copy Markdown
Collaborator

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.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 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 by findPropagationCall()
matchesPropagationName(child, d.name).

A catch that 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 a
gate (exit 0 on any membership count; deliberately not named check:*/gen:*, the
shape measure-durability-swallow-family.mjs established).

try/catch statements                                             2944
...guarding a declared durable call                               250
...whose catch returns a constructed failure object on EVERY path  21

MEMBERS — the gap's live population                                12
  [1] the gate SEES this seam today                                 0
  [2] write-shaped (#12981 vocabulary)                              4
  [3] durable, read on the merits here                              8
ADJACENT, not members                                               5
EXCLUDED by reading                                                 4

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 the
catch returning an object CONSTRUCTED IN THE CATCH, that object NAMING the failure,
and the catch not already being expressible (no rethrow, no error/fatal log, no
declared 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_CALLEES and
#12981's WRITE_SHAPED_CALLEES — so this census cannot drift from them, and a
vocabulary 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 claims
persistence. NOT_A_DURABILITY_CLAIM runs the other way: sites matching the shape over
a declared write name that claim no persistence, excluded WITH the reading instead of
filtered silently. Both registers fail --self-test when a row stops matching — which
already paid for itself: an appendEvent entry was dropped because the call sits inside
a nested arrow the same-tick walk correctly refuses to descend into.

The 12

site guards returns
core/src/utils/migration-journal.ts:660 unwind transaction status: 'failed', error
drivers/driver-sql/src/sql-driver.ts:6934 attemptWithoutPoisoning transaction ok: false, error
drivers/driver-turso/src/remote-canonical-backfill.ts:439 execute, runBatchedUpdate error
metadata-protocol/src/migrations/partial-index-probe.ts:390 exec (CREATE INDEX) status, detail
metadata-protocol/src/migrations/partial-index-probe.ts:410 exec (CREATE INDEX) status: 'failed', detail
metadata-protocol/src/protocol.ts:18272 publishPackageDrafts inTxn success: false, failed[]
plugins/plugin-auth/src/reconcile-membership.ts:230 insertMembership outcome: 'failed'
services/service-automation/src/builtin/crud-nodes.ts:320 create_record insert success: false, error
services/service-automation/src/builtin/crud-nodes.ts:460 update_record update success: false, error
services/service-automation/src/builtin/crud-nodes.ts:548 delete_record delete success: false, error
services/service-automation/src/engine.ts:2897 claimAdvance claimSuspension kind: 'unavailable', message
services/service-messaging/src/inbox-channel.ts:212 send insert ok: false, error

Two of them already state the contract in prose. sql-driver.ts's
attemptWithoutPoisoning documents "Returns a discriminated result rather than
rethrowing, so the caller keeps the ORIGINAL error"; engine.ts's claimAdvance
comments "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.ts is not on
main, and recordNotificationEventReceipt is 0 occurrences repo-wide (control word
persistSeedTenancyReceiptRow, same command shape: 1 file). #16194 retired the
ADR-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) and
    68 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.
  • Gate families derived from the diff with
    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

Generated with Claude Code in session https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk


Generated by Claude Code

…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>
@os-try-charles os-try-charles added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 16, 2026 — with Claude

Copy link
Copy Markdown
Collaborator Author

复核:ACCEPT —— 但下面每一条都是本席自己对着 GitHub 与 origin/main 取的,⛔ 不是照报告转录

domain:devx 执行席(座位贴 #6023,session session_017ef78bLdybu3AffehKkhfk,round 12)。

一、派发口径的三条硬线,逐条核

口径 核法 结果
第一交付物是普查,⛔ 不是修 /pulls/18523/files 两个文件:闸 +28/−1、新仪器 +980/−0
⛔ 不得在本轮动闸的判据/行为 读闸那一跳 diff 的每一行 −1Three honest limitationsFour;+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 ::sendorigin/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 catch that answers errorFromThrown(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 a logger.error onto such a site; declare how it delivers insteadFAILURE_PROPAGATION_CALLEES (repo-wide names) or the function-scoped FAILURE_PROPAGATION_SITES in the checker …

⚠️ 「function-scoped」这个词会让人以为 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.

且现存两个条目(migrateStoredMetadatarecord()duplicatePackagefailed.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

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

Labels

size/xl skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants