Skip to content

Commit 2496415

Browse files
measure the return-propagating durability seams the log-level gate cannot name (#18523)
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 #16233 for the per-command exit codes. ## Acceptance notes - `skip-changeset`: verified rather than assumed. Both touched paths are under `scripts/`, and no package `files[]` in this workspace ships `scripts/**` from the repo root. - Noted, not filed — `FAILURE_PROPAGATION_SITES`-style keys are `<file>::<function>`, and where the enclosing function is large (`storage-service-plugin.ts::start`) the key is coarser than the catch. The gate accepts that trade for its own map; this census inherits it. Carrier: whoever takes #16233's repair route. Generated with Claude Code in session https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk --- _Generated by [Claude Code](https://claude.ai/code)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 2882528 commit 2496415

2 files changed

Lines changed: 1008 additions & 1 deletion

File tree

‎scripts/check-durability-degradation-log-level.mjs‎

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
* failure is known to mean "the bytes did not land". Adding an entry is a
5959
* deliberate, reviewable act.
6060
*
61-
* Three honest limitations, stated up front rather than discovered later:
61+
* Four honest limitations, stated up front rather than discovered later:
6262
*
6363
* 1. It cannot FIND a durability seam whose operation is not in the
6464
* vocabulary. It guarantees the seams already paid for cannot regress to
@@ -76,6 +76,33 @@
7676
* This is a DECIDED narrowness, not an undiscovered one: see the decision
7777
* recorded at `LOGGER_RECEIVERS` for what was weighed and why widening it
7878
* was declined.
79+
* 4. The third answer can only be spelled as a NAME, so a catch that hands
80+
* the failure to the caller by RETURNING AN OUT-PARAM OBJECT is not
81+
* expressible here. `FAILURE_PROPAGATION_CALLEES` and
82+
* `FAILURE_PROPAGATION_SITES` both key on a callee the catch reaches;
83+
* `return { ok: false, error: err }` reaches none — the delivery IS the
84+
* constructed value. This is constructive, not a missing row: no entry in
85+
* either map can name that seam, and at such a site the only ways to green
86+
* the gate are the two this file's own header rejects, a baseline entry for
87+
* correct code or a bolted-on `logger.error`.
88+
*
89+
* MEASURED rather than asserted, because "a gap with one member is a note,
90+
* a gap with thirty is a hole". The census is
91+
* `scripts/measure-return-propagating-durability-seams.mjs`, and it prints
92+
* the population split by how the guarded operation is declared. Two of its readings are what
93+
* make this limitation latent rather than live — every seam THIS gate
94+
* currently sees answers through a log or a declared callee, so the shape
95+
* costs the present verdict nothing, while the seams that return their
96+
* failure sit one vocabulary entry away from becoming visible. ⛔ Read the
97+
* instrument's own output rather than quoting a count from here; and note
98+
* that a green over any site it lists means NOT MEASURED for that site,
99+
* never "level approved".
100+
*
101+
* What to DO about it is deliberately not decided here — re-keying on the
102+
* enclosing function, reading the declared return TYPE, and recording the
103+
* shape as out of scope are all open, and they are not equivalent: the
104+
* first moves the question from "what did this call do" to "who is asking"
105+
* and could weaken the seams the gate holds today.
79106
*
80107
* ## Why AST, not regex
81108
*

0 commit comments

Comments
 (0)