Skip to content

Commit 8e6988b

Browse files
wshallwshallclaude
andcommitted
docs(adr-0114): record the degraded gauge and the missing-vs-unreadable split
Amends §4 and the acceptance criteria for the two follow-ups the 2026-07-30 amendment deliberately held out of the bug fix. "Sets a degraded gauge" stops being aspirational: §4 now names the three surfaces that publish it, and records the three shape decisions (absent rather than 0 when the lever is not requested; no free-text reason label in the exposition; it does not feed the console's engine-health heart) so they are not re-litigated. AC-7 gains the unreadable-definition condition. New AC-7c requires the gauge to be a surface an operator can READ, not merely an attribute — AC-7 as written required a gauge and nothing required anyone to be able to see it, which is the same one-directional gap the 2026-07-30 amendment found in the gate itself. Also corrects the record on probe (a): the ADR always specified an OBJECT_ID probe, and the implementation had folded it into the OBJECT_DEFINITION read. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 16697ba commit 8e6988b

1 file changed

Lines changed: 74 additions & 7 deletions

File tree

docs/adr/0114-phase-4-claim-path-call-complexity-reduction-driver-interface-redesign-ingress-routed-reset-fold.md

Lines changed: 74 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -398,9 +398,9 @@ compat-120 database and under a DDL-denied principal.
398398
`OBJECT_ID` of **both** procs; (b) a SHA-256 of each deployed body via `OBJECT_DEFINITION()` against the
399399
**stored forms** of the shipped DDL text (normalized) — **existence alone cannot catch a hand-edited body**, and
400400
the ADR 0064 marker covers only in-repo edits, while the proc *is* the claim logic; (c) `compatibility_level ≥
401-
130`. Any failure → the store records `claim_proc_effective = False`, logs a **WARNING naming the reason** and
402-
runs the shipped batch — never a lane outage; the hot path contains **no error-2812 handling**. Out-of-band
403-
drift is caught at the next open.
401+
130`. Any failure → the store records `claim_proc_effective = False`, logs a **WARNING naming the reason**,
402+
publishes the degraded gauge (see the second amendment below), and runs the shipped batch — never a lane
403+
outage; the hot path contains **no error-2812 handling**. Out-of-band drift is caught at the next open.
404404

405405
> **AMENDMENT (2026-07-30) — `OBJECT_DEFINITION()` does not return the submitted text, and this gate was
406406
> inert until it was fixed.**
@@ -437,6 +437,65 @@ drift is caught at the next open.
437437
> work** — the re-apply submits the same text, the engine rewrites it the same way, and the hash mismatches
438438
> again — so the advice has been removed from the ADR and from the operator-facing degraded reason.
439439
440+
> **AMENDMENT (2026-07-31) — the degraded gauge now exists, and probe (a) is a real probe again.** Two
441+
> follow-ups the amendment above deliberately held out of the bug fix.
442+
>
443+
> **1. The gauge was aspirational.** AC-7 requires "a WARNING naming the reason **+ degraded gauge**", and this
444+
> section's compensating-control story assumes an operator can SEE the degraded state. Until this amendment
445+
> nobody could: `claim_proc_effective` / `claim_proc_degraded_reason` were read by the store's own tests and
446+
> **nothing else** — no `/stats`, no `/status`, no `/metrics`, no console. The entire operator signal was one
447+
> WARNING line at `open()`. That is not a missing nicety, it is a load-bearing part of *why the amendment above
448+
> was needed*: a fleet running the flag degraded on every open, forever, and the only thing that could have
449+
> told anyone was a log line nobody was watching which named the wrong cause.
450+
>
451+
> The gauge is now a store accessor, `claim_proc_status()`, surfaced on three operator surfaces:
452+
>
453+
> | surface | carries |
454+
> |---|---|
455+
> | `GET /status``claim_proc` | `effective`, the human-readable `degraded_reason`, and the matched `head_forms` |
456+
> | `GET /metrics` | `messagefoundry_store_claim_proc_effective` (0/1) and `messagefoundry_store_claim_proc_head_verbatim` (0/1) |
457+
> | the console's store panel (`/ui/status`) | active-vs-degraded, plus the reason when degraded / the head forms when green |
458+
>
459+
> Three shape decisions, so they are not re-litigated. **`None` when the flag is off**, so "not requested" is a
460+
> distinct state from "requested and degraded"; the Prometheus series are correspondingly **absent**, not a
461+
> constant `0` that every SQLite fleet would publish unalertably. **No reason label in the exposition** — the
462+
> reason is free text embedding a proc name and, on the probe-failure arm, an exception string, so a label
463+
> would be unbounded cardinality *and* a breach of the exporter's strict `{connection, destination, status,
464+
> version, le}` allowlist; the string lives on `/status` and the console instead. **It does not feed the
465+
> console's engine-health heart**: a degrade is a performance lever not paying off, claims keep flowing, and
466+
> making the nav cry wolf about it would devalue the signal that means the store is actually unwell.
467+
>
468+
> `head_forms` (proc name → `rewritten` | `verbatim`) is surfaced for the same reason it is logged: a fleet
469+
> reporting `verbatim` is a live counterexample to `_CLAIM_PROC_STORED_HEADS`'s compatibility assumption — no
470+
> engine measured to date stores the `CREATE OR ALTER` head unrewritten — and it was previously visible only
471+
> at INFO. Observability only: the accept/degrade logic is untouched.
472+
>
473+
> **2. A missing `VIEW DEFINITION` grant was reported as a missing proc.** This section has always specified
474+
> probe (a) as "`OBJECT_ID` of **both** procs", but the implementation folded (a) into (b) and inferred absence
475+
> from a NULL `OBJECT_DEFINITION`. **MEASURED** (2026-07-31, on the lab SQL Server): a principal holding only
476+
> `EXECUTE` on the proc gets a non-NULL `OBJECT_ID` and a **NULL** `OBJECT_DEFINITION`; the compat probe still
477+
> passes. So a deployed, working, correct procedure was reported as *missing*, and the operator was sent to fix
478+
> a `CREATE PROCEDURE` permission that was neither the cause nor the cure. `WITH ENCRYPTION` produces the
479+
> identical NULL and the identical misdiagnosis — and because *that* half needs no security principal, it is
480+
> now a live test leg (`test_a_deployed_proc_can_return_a_null_definition`), which pins on a real server the
481+
> one thing an offline stub cannot show: that the two functions genuinely disagree. The permission half stays
482+
> deferred with AC-10's other permission scenarios to a purpose-configured server.
483+
>
484+
> This is not a hypothetical posture here: §5's sub-lever B design explicitly serves "a fleet whose DB
485+
> principal can never hold `CREATE PROCEDURE`" — DBA-provisioned procs plus a least-privilege app principal —
486+
> which is exactly the deployment shape that hits it. The probe now returns `OBJECT_ID` beside the definition
487+
> and the two conditions get separate reasons:
488+
>
489+
> | condition | reason |
490+
> |---|---|
491+
> | `OBJECT_ID` NULL | genuinely absent — guarded DDL skipped, `CREATE PROCEDURE`/ALTER-on-schema denied, or a pre-2016-SP1 engine |
492+
> | `OBJECT_ID` non-NULL, `OBJECT_DEFINITION` NULL | deployed but unreadable — **`GRANT VIEW DEFINITION`**, or the module is `WITH ENCRYPTION` |
493+
>
494+
> Both still **degrade** — the gate hashes the body and cannot pass on one it cannot read — so no accept/reject
495+
> behaviour changed; only the diagnosis did. The probe SQL is pinned by an exact-match assertion in the
496+
> offline suite (a typo'd probe must fail loudly rather than silently match), so that pin moved with it and
497+
> stayed exact.
498+
440499
**Versioning, mixed vintages, downgrade.** Procs are **name-versioned** (`_v1`, `_v2`, …): engine sharding runs
441500
N processes against ONE unified store (ADR 0037/0063), so a rolling upgrade briefly runs two builds against one
442501
database — each build calls exactly the body it shipped; a newer build's `_v2` never touches `_v1`. A retired
@@ -679,10 +738,18 @@ states, including the mismatch and 1222 translations). **Any miss = the flag sta
679738
injected-row test.
680739
- **AC-6** — The three flags SHALL be provable no-ops on SQLite and Postgres (neither backend references them).
681740
→ sentinel test (the ADR 0075 precedent).
682-
- **AC-7** — WHEN `fifo_claim_proc` is ON and a proc is missing, its `OBJECT_DEFINITION` hash mismatches every
683-
form this build deploys, or compat < 130, the store SHALL degrade loudly to the shipped batch (WARNING naming
684-
the reason + degraded gauge), never a lane outage; the hot path SHALL contain no error-2812 handling. →
685-
startup-gate tests incl. a hand-edited-body leg.
741+
- **AC-7** — WHEN `fifo_claim_proc` is ON and a proc is missing, is deployed but its definition unreadable
742+
(`OBJECT_ID` resolves, `OBJECT_DEFINITION` NULL), its `OBJECT_DEFINITION` hash mismatches every form this
743+
build deploys, or compat < 130, the store SHALL degrade loudly to the shipped batch (WARNING naming the
744+
reason + degraded gauge), never a lane outage; the hot path SHALL contain no error-2812 handling. → startup-
745+
gate tests incl. a hand-edited-body leg and an unreadable-definition leg.
746+
- **AC-7c** — The degraded gauge SHALL be a surface an operator can READ, not merely an attribute: `/status`
747+
(with the reason string), `/metrics` (numeric, label-less, ABSENT rather than 0 when the lever is not
748+
requested) and the console store panel SHALL each emit it. → surface-emission tests
749+
(`test_adr0114_claim_proc_surfaces.py`), asserting the rendered output, not the property.
750+
> Added by the 2026-07-31 amendment. AC-7 as written required a gauge and nothing required anyone to be able
751+
> to see it; the two properties existed and were read by the store's own tests alone. A "loud" degrade whose
752+
> only audience is a log line is how this lever stayed inert in every deployment for its whole life.
686753
- **AC-7b** — WHEN `fifo_claim_proc` is ON and both procs are deployed **by this build's own DDL**, the gate
687754
SHALL **PASS** and `claim_proc_effective` SHALL be True, verified against a **real SQL Server** (not a stub
688755
that echoes the submitted text back as the deployed body). → `test_adr0114_claim_proc_live.py`, plus an

0 commit comments

Comments
 (0)