Repository navigation
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
…se announcements (objectstack-ai#19041) Fixes objectstack-ai#18926 Clause-②: no The near-miss ownership census (`ownershipMarkerNearMisses` over `OWNERSHIP_MARKER_NEAR_MISS_FORMS`, landed by PR objectstack-ai#18911 / objectstack-ai#18831) lists cards with a near-miss ownership line and no readable `Claim:` / `Release:`. Five of objectstack-ai#18914's sixteen rows have no ownership record to normalise: four `pm:seat` posts (objectstack-ai#6017 · objectstack-ai#6021 · objectstack-ai#6026 · objectstack-ai#6367), whose ownership is their BODY plus their audit comments and whose comments carry shift narration, and objectstack-ai#17536's `## Released — PR objectstack-ai#17517 merged. pm:blocked -> pm:queue`, a blocker release naming a PR on a card that was never claimed. Two exclusions, both COUNTED rather than dropped, and they are deliberately different KINDS: a seat post leaves the POPULATION (no record is owed on the thread), a release announcement leaves the near-miss CHANNEL (the line is not a refused record). - `ownershipCensusSpeaksAbout(issue)` — a `pm:seat` thread is out of the population, read off the label page the sweep already holds, so the census keeps its "buys NOTHING" contract. Gated at the call site like every other population predicate in this file (`h47SpeaksAbout`, `h58SpeaksAbout`, `h67SpeaksAbout`), which is also what makes it usable by the direct full-board caller that produced objectstack-ai#18914's rows. - `releaseAnnouncementHeading(formId, prefix, line)` — a heading whose word is the PARTICIPLE `Released`, whose remainder names the `PR #n` that landed or the `pm:*` -> `pm:*` transition it unblocked, and which names NO session. The line keeps its card, comment id and prefix and moves to a second channel, so the two arrays partition exactly what the single array used to carry. - `ownershipMarkerNearMissCensus` returns `{ misses, announcements }`; `ownershipMarkerNearMisses` stays the near-miss half and the name every caller and every fixture already uses. The vocabulary is untouched (no form added, no form removed) and so is the reader: no change to what a readable `Claim:` / `Release:` is. H8 / H46 territory is not entered. ## The three narrowings, each measured against the corpus line that forced it Every one of these leaves the line IN the census — the safe direction for a filter whose failure mode is silence. | narrowing | the line that forced it | verdict | |---|---|---| | HEADING forms only | objectstack-ai#18740 comment 5723834336 `Release-landed: … (PR objectstack-ai#18852)` | `separator`, not a heading — stays listed | | the PARTICIPLE only | objectstack-ai#16233 comment 5704218834 `### Release: PR objectstack-ai#18523 …` | bare noun opens the directive — stays listed | | NO session token (`session` or the Chinese spelling) | objectstack-ai#6023 comment 5552092492 `## Released tail — in flight under THIS session's process tree` | names a session — stays listed | ## The census over the live board — BEFORE / AFTER Two readings, both read-only through the session proxy, both with the `--use-env-proxy` re-exec the file performs itself. **(1) The instrument's own clause, one full sweep each.** BEFORE at the base (`784366372`), AFTER at this branch's head: ``` BEFORE Ownership-marker near misses: 17 line(s) on 15 of 222 thread(s) … AFTER Ownership-marker near misses: 16 line(s) on 14 of 226 thread(s) … 0 `pm:seat` thread(s) are OUT of that population (0 near-miss line(s) not listed above) … 1 further line(s) are release ANNOUNCEMENTS rather than refused records … — objectstack-ai#17536 comment 5625672905 「## Released」 (names a PR, names a `pm:*` transition) ```⚠️ MEASURED AND REPORTED AS MEASURED: the population clause prints `0` / `0` on the live sweep, and that is not the filter failing. The census reads only threads `commentCache` already holds, and a `pm:seat` post's page enters that cache in the H44 pass BELOW the census loop (`commentCache.set` at the seat-comments leg; `seatPostRowsFor` itself writes only `seatPageCache`). So on the sweep as currently ordered a seat thread is UNJUDGED by the census, never listed — which is why the clause renders both numbers unconditionally instead of staying silent: a run with nothing excluded and a run where the exclusion stopped being applied must not print alike. The ordering itself is left exactly as it is; moving the census would widen what it reads, which this card does not ask for. **(2) The full-board corpus — the population objectstack-ai#18914's sixteen rows were actually measured over**, and the reading where the population filter bites. Read 2026-09-18 13:0xZ, tree `784366372`: 539 open cards, 484 of them carrying a comment, the census run over every thread: ``` BEFORE (vocabulary alone): 36 line(s) on 30 card(s) pm:seat threads OUT of the population: 5 thread(s), 9 line(s) objectstack-ai#6017 · objectstack-ai#6021 · objectstack-ai#6023 (×4) · objectstack-ai#6026 · objectstack-ai#6367 release ANNOUNCEMENTS out of the near-miss channel: 1 line objectstack-ai#17536 comment 5625672905 「## Released」 AFTER (near-miss channel): 26 line(s) on 24 card(s) partition check: 26 + 9 + 1 === 36 -> true ``` The five rows the card names are gone, each with its printed reason. Of objectstack-ai#18914's other eleven, the ten that are still open are listed byte-identically — objectstack-ai#11633 · objectstack-ai#11663 · objectstack-ai#14512 · objectstack-ai#15638 · objectstack-ai#6736 · objectstack-ai#9659 · objectstack-ai#9707 · objectstack-ai#11286 · objectstack-ai#11453 · objectstack-ai#11925. The eleventh, objectstack-ai#18755, is CLOSED (`pm:done`) and so is absent from an open-board read altogether, not dropped by anything here.⚠️ objectstack-ai#6023 is a FIFTH `pm:seat` post the filing card's table does not name, carrying four narration lines. It is out of the population on the same grounds as the four, and the count above says so rather than folding it into the named five. ## Self-test — floors rise, and the controls are inside the floored battery ``` BEFORE 4978 cases pass. … H2/H47/H66 decorated ownership marker 140/136 … AFTER 5031 cases pass. … H2/H47/H66 decorated ownership marker 193/184 … ``` +53 cases, all in the floored battery, pin raised 136 -> 184 (the file's "just under the count" convention, ~95%). What they floor is a NARROWING — the one direction in which a suite can go green by asserting less — so the firing controls live inside the same battery: the five measured rows as fixtures (each line pasted from the REST read of its own comment), the three narrowings above each pinned through its corpus line, the positive control that objectstack-ai#14512's `Claiming` line is still listed on the near-miss channel and its card still in the population, and the ablation pin that the excluded line IS a near miss under the vocabulary alone (so the exclusion pin is capable of failing). One landed case was RE-POINTED, not deleted: `objectstack-ai#18831: the release side of the HEADING form is named too` carried objectstack-ai#17536's line as its fixture, which is the very line this card moves to the announcement channel. Its claim is untouched and still floored; its fixture is now objectstack-ai#16712 comment 5605768467 (`## Release + seat ruling — …`, a live release heading that names no PR and no transition), and a sibling case pins where the old fixture went. ## Verification - `pnpm check:pm-half-states` — exit 0, `✓ check-half-states self-test: 5031 cases pass.` - Gates derived from this worktree with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (1 path vs merge base `784366372`): 39 commands, 38 run in the foreground, all `exit 0`, reconciled with `--ran`. `pnpm check:pm-dispatch-gates` exceeds the foreground cap and was detached. - The derivation reports a STALE TREE against `origin/main` `dbd474431` (`.github/workflows/pr-automation.yml`). Read: that change is prose inside the `Check Changeset` job (objectstack-ai#18375's route-0 discriminator) and declares no family, so no command is missing from the list above. - No changeset: `scripts/pm/**` publishes nothing from any released package. Route 0 of that same discriminator does not apply — this PR edits no existing changeset. - Control-byte self-scan over the touched file: no hits. **Reader test** — one line, from the worktree root, prints `0 1 false`: the excluded line is no longer a near miss, it IS counted on the announcement channel, and a `pm:seat` thread is out of the population. ```bash node -e 'const L="## Released — **PR objectstack-ai#17517 merged.** `pm:blocked` → `pm:queue`";import("./scripts/pm/check-half-states.mjs").then(m=>console.log(m.ownershipMarkerNearMisses([{id:1,body:L}]).length,m.ownershipMarkerNearMissCensus([{id:1,body:L}]).announcements.length,m.ownershipCensusSpeaksAbout({labels:[{name:"pm:seat"}]})))' ``` ## Acceptance notes Noted, not filed — observations, no card: - The `separator` form reads `Release-landed:` (objectstack-ai#18740 ×3, objectstack-ai#16529 comment 5720102045) as a near miss. Those are report-line labels, not ownership records, and they are the largest remaining class of the same kind this card narrows. Deliberately left listed: the ruling scopes this card to the seat population and the release HEADING, and ⛔ no widening or further narrowing of the vocabulary. Whoever next touches `OWNERSHIP_MARKER_NEAR_MISS_FORMS` is the successor; today there is none. - The census loop sits ABOVE the pass that puts a `pm:seat` page into `commentCache`, so the census never judges a seat thread on a live sweep. Stated in the sweep reading above and in the code comment; not filed, because changing the ordering would widen the census's judged population, which is a decision and not a defect. - The dispatch's assumption that the five rows' FIRST lines are the fixtures was falsified by the paste: on three of the five (objectstack-ai#6017 · objectstack-ai#6021 · objectstack-ai#6367) the census names a later narration line and the heading the card's table quotes carries no near miss at all. The fixtures are the lines the census NAMES; the discrepancy is pinned as a case rather than left as a silence. Serial: `scripts/pm/check-half-states.mjs`. PR objectstack-ai#18980 (objectstack-ai#18939) is a parallel draft on this file with old-line hunks :19613–:19659 and :35268 — the proxy-rearm plan and its guard — disjoint from the census hunks here (:1362–:1440 and the BATTERY68 battery). Nothing there is touched. Whichever lands second merges `origin/main` first. Not governed (`scripts/pm/**`): draft, for the `domain:skills` seat's contract-tier review. --- _Generated by [Claude Code](https://claude.ai/code/session_01BTeBejoPUvRHN8WdAJC6oF)_ Co-authored-by: Claude <noreply@anthropic.com>
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