Skip to content

Commit 3ece130

Browse files
os-zhuangclaude
andauthored
docs(gate): record the measured triage that parks the parameterless-catch read-seam criterion (#12851)
Re-measures the proposed criterion's first-run red set on current `main` with one instrument run over both trees (66 seams / 13 red @ c07d6e8 reproduced site-for-site, 65 seams / 11 red @ 6f0fec3), names both departures against the commits that caused them, and writes the per-site triage into the gate's header: 9 of the 11 are correct-but-undeclared, a worse precision than the 15-red / 7-correct proposal this file already declined. No criterion, no vocabulary entry, no baseline entry, no new script and no new CI step — the negative result is recorded at the same length as a positive one, which is what this header already does for the fall-through criterion. Claude-Session: https://claude.ai/code/session_01PfaSTikked61BkcsB5Rn69 Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent b489d3c commit 3ece130

1 file changed

Lines changed: 143 additions & 0 deletions

File tree

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

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -915,6 +915,149 @@ const FAILURE_PROPAGATION_SITES = new Map([
915915
// writes nothing at all, and the `push` / `++` accumulation shapes #8845
916916
// measured, which are unchanged.
917917

918+
// ── Measured and PARKED — the PARAMETERLESS-CATCH criterion (#12753, from #9165)
919+
//
920+
// A sixth criterion was proposed and is NOT added here: **"a read seam whose
921+
// `catch` binds no error parameter must discriminate or propagate."** Its appeal
922+
// is real and is restated first, because the numbers below are what decline it,
923+
// not a dislike of the idea: a parameterless `catch {` PROVABLY cannot ask
924+
// `isMissingTableError`, so the test is purely syntactic; it adds no name to any
925+
// Map, no script and no CI step; and the cheapest way to satisfy it — bind the
926+
// parameter and ask the declared predicate — is the correct fix rather than a
927+
// workaround. That is the rare gate whose cheapest satisfaction is the right
928+
// behaviour, which is exactly why it was worth measuring instead of assuming.
929+
//
930+
// It is PARKED BEHIND #8901, not declined outright, because what the triage
931+
// found is precisely what #8901 says is missing.
932+
//
933+
// ## RE-MEASURED, not carried forward
934+
//
935+
// #12753 was filed on a red set of 13 taken at `origin/main` @ c07d6e8b9
936+
// (2026-08-18). ONE instrument was run over BOTH trees, so the delta below is a
937+
// census and not a subtraction:
938+
//
939+
// | tree | read seams | parameterless RED |
940+
// |----------------------------------------|-----------:|------------------:|
941+
// | `origin/main` @ c07d6e8b9 (2026-08-18) | 66 | 13 |
942+
// | `origin/main` @ 6f0fec3d0 (2026-08-28) | 65 | 11 |
943+
//
944+
// The 66 and the 13 reproduce the filed figures SITE FOR SITE, same files, same
945+
// lines. That is the calibration: the selector measured here is the criterion as
946+
// filed, not a near neighbour of it. 2 departures, 0 arrivals, each traced to a
947+
// landed commit rather than inferred from the count moving:
948+
//
949+
// - `getMetaItems` guarding `mergePackageAwareOverlay` (metadata-protocol
950+
// protocol.ts) left the READ POPULATION, not the red set. Its catch is
951+
// unchanged and still parameterless, silent and undiscriminated;
952+
// `contradictsDriverReadShape` (#11921) correctly stopped counting
953+
// `list.find()` on a plain local array as a storage read. ⚠️ Its real risk —
954+
// a failed `metadataService.list` served from the registry alone — is
955+
// therefore invisible to this rule now, and this criterion would not have
956+
// covered it either. A shrinking red set is not the same fact as a
957+
// shrinking hazard.
958+
// - `probeInstallOrganizations` (objectql engine.ts) was genuinely REPAIRED
959+
// (#9261, PR #9817): it binds `error`, asks `isMissingTableError` and
960+
// rethrows everything else. A seam leaving this set by being fixed is the
961+
// criterion's own thesis working, one seam at a time, without the gate.
962+
//
963+
// ## The criterion's two escapes are unexercised in scope — so it is not a
964+
// ## conditional, it is a ban
965+
//
966+
// Measured on the 08-28 tree, every zero beside a non-zero control from the same
967+
// run, so none of them is a selector that quietly stopped selecting:
968+
//
969+
// | population | logs | discriminates | propagates |
970+
// |------------------------------------|-----:|--------------:|-----------:|
971+
// | 11 parameterless catches | 0 | 0 | 0 |
972+
// | 54 catches that BIND a parameter | 23 | 19 | 9 |
973+
//
974+
// Nothing in the parameterless half discriminates or propagates today, so the
975+
// criterion does not read as "must do one of two things" — in this population it
976+
// reads as "a read seam may not have a parameterless catch", and all 11 sites
977+
// must be edited or baselined on the day it lands.
978+
//
979+
// ## THE TRIAGE — the whole decision, and it is not the count
980+
//
981+
// All 11 were read at their call sites and graded on one question: does the
982+
// catch swallow a failure it should discriminate or propagate, or is it correct
983+
// but undeclared?
984+
//
985+
// | seam | verdict |
986+
// |-------------------------------------------------------|---------|
987+
// | metadata history-cleanup.ts ×3 (runCleanup) | correct-but-undeclared — `errors++` into the returned `{ deleted, errors }`; #8901's already-named cohort |
988+
// | metadata-protocol protocol.ts findData | correct-but-undeclared — `counted = pageOffset + records.length` is a COMPUTED degradation with its reasoning in-line, already this file's #9165 falsification control |
989+
// | metadata-protocol protocol.ts reportUnhydratableOrgScopedRows | correct-but-undeclared — void advisory, "diagnostics never break boot" |
990+
// | metadata-protocol seed-loader.ts resolveSoleOrganizationId | ⚠️ GENUINELY WRONG — comment names one benign cause, catch swallows every cause; seeds then land org-less |
991+
// | metadata-protocol seed-loader.ts resolveFromDatabase | correct-but-undeclared — probe-chain `continue`; its expected failure is the driver's `INVALID_FILTER` refusal, NOT a missing table |
992+
// | objectql engine.ts referenceExists | correct-but-undeclared — declared tri-state `Promise<boolean \| null>`; ALREADY in `durability-read-invention.baseline.json` as `reviewed-legitimate` |
993+
// | objectql engine.ts readMigrationFlagVerified | correct-but-undeclared — returns `{ verified: false, conclusive: false }`; `conclusive` exists so the caller can tell "asked, and no" from "could not ask" |
994+
// | objectql engine.ts announceOpenMigrationGates | correct-but-undeclared — void advisory; and it is the SAME read as the row above, one hop up |
995+
// | objectql lifecycle-service.ts loadGovernance | ⚠️ GENUINELY WRONG — a failed `sys_organization` read silently drops every TENANT retention override, so a deletion policy runs on partial evidence |
996+
//
997+
// **9 of 11 are already correct.** That is a WORSE precision than the #8845
998+
// proposal this file already declined — 15 red with 7 already correct (47%)
999+
// against 11 red with 9 already correct (82%) — and #12753's own filing named
1000+
// the precision advantage as the thing that was unproven. It is now measured,
1001+
// and it went the other way. Net of the one existing baseline entry
1002+
// (`referenceExists`) the landing cost is still 10 reds against a shrink-only
1003+
// ledger holding one row.
1004+
//
1005+
// ## Why that parks it on #8901 specifically, and is not a taste call
1006+
//
1007+
// Read the nine "correct" mechanisms as a set: an `errors` field in a returned
1008+
// envelope; a `conclusive` flag whose only job is to separate "asked" from
1009+
// "could not ask"; a declared tri-state `boolean | null` with the distinction
1010+
// written into the JSDoc; a documented computed degradation. Every one of them
1011+
// DOES tell the caller — through a channel this rule has no vocabulary to name.
1012+
// That is #8901's sentence exactly ("the read-seam rule has no declared
1013+
// failure-propagation vocabulary, so 'the catch reported the failure' is
1014+
// uncheckable"), reached from a different criterion and a different census.
1015+
// ⛔ Baselining nine correct seams to land this is the "baselined into
1016+
// uselessness" outcome #8901 already priced, in one PR.
1017+
//
1018+
// ## Two counterexamples to "the cheapest satisfaction is the correct fix"
1019+
//
1020+
// The argument is TRUE at most of the 11 and false at three, and the exceptions
1021+
// are the load-bearing part because they are the ones a landing author meets:
1022+
//
1023+
// - `resolveFromDatabase` has NO declared predicate available. Its expected
1024+
// benign failure is the driver's `INVALID_FILTER` refusal on a probe column
1025+
// the object does not declare (see the #9071 note at the seam), and
1026+
// `READ_FAILURE_DISCRIMINATORS` holds exactly one name, `isMissingTableError`.
1027+
// Satisfying the criterion there needs a NEW declared discriminator — which
1028+
// this criterion promised not to add — or a hand-rolled test, which
1029+
// limitation 3 flags on purpose, or a baseline entry.
1030+
// - `reportUnhydratableOrgScopedRows` and `announceOpenMigrationGates` are
1031+
// void ADVISORIES whose stated contract is that they must never fail a boot.
1032+
// "Propagate" is refused by design, and "discriminate, then rethrow the
1033+
// rest" IS propagation on the non-benign branch — so the criterion's two
1034+
// escapes are both the wrong fix, and the right one (say something) is not
1035+
// what it asks for.
1036+
//
1037+
// ## Two smaller readings, recorded so they are not re-derived
1038+
//
1039+
// - `readMigrationFlagVerified` and `announceOpenMigrationGates` are ONE read
1040+
// counted at two nesting levels (callee and caller). Fixing the inner seam
1041+
// does not clear the outer, so the ledger carries two rows for one hazard.
1042+
// - The three `history-cleanup` seams deliver `errors` into a returned
1043+
// envelope, and BOTH production call sites are `void this.runCleanup()` —
1044+
// so the count is correct by contract and read by nobody in-process. That
1045+
// is a fact about #8901's cohort, not about this criterion.
1046+
//
1047+
// ## Reproduce it
1048+
//
1049+
// There is deliberately no flag for this: the criterion is not implemented, and
1050+
// a selector shipped for a criterion that is not shipped is a name in a Map that
1051+
// nothing consumes. Re-derive it by adding `catchParam`, `recovers` and the
1052+
// catch's log set to the `seam` object in `analyzeReadSeams` and counting the
1053+
// seams whose catch clause has no `variableDeclaration`. Calibrate first: the
1054+
// unpatched gate must answer 65 read seams on 6f0fec3d0 and 66 on c07d6e8b9
1055+
// before any derived number from it is quoted.
1056+
//
1057+
// ⛔ Do not land the criterion without re-running the triage. The count is not
1058+
// the argument here — 13 → 11 moved the number and moved nothing else, and the
1059+
// verdict rests on what the 11 catches DO, which no count reports.
1060+
9181061
// ── RECOGNIZER CORRECTION (#11921) — what "66" actually counted ─────────────
9191062
//
9201063
// Every census figure above is quoted against a 66-seam read population. Two of

0 commit comments

Comments
 (0)