ADR 0114 follow-ups: the AC-7 degraded gauge, and VIEW DEFINITION misdiagnosed as a missing proc - #97
Merged
Merged
Conversation
wshallwshall
enabled auto-merge (squash)
July 31, 2026 13:09
wshallwshall
force-pushed
the
adr0114-followups
branch
2 times, most recently
from
July 31, 2026 13:24
20217f6 to
bf2a4b1
Compare
ADR 0114 §4 has always specified the startup gate's probe (a) as "OBJECT_ID of both procs", but the implementation folded (a) into (b) and inferred absence from a NULL OBJECT_DEFINITION. MEASURED: a principal holding only EXECUTE on the proc gets a non-NULL OBJECT_ID and a NULL OBJECT_DEFINITION, and the compat probe still passes. So a deployed, working, correct procedure was reported as *missing* and the operator was sent to grant CREATE PROCEDURE — neither the cause nor the cure. WITH ENCRYPTION produces the identical NULL. This is not hypothetical: the sub-lever B design note in the same module explicitly serves a fleet whose principal can never hold CREATE PROCEDURE (DBA-provisioned procs + a least-privilege app principal), which is exactly the posture that hits it. The probe now returns OBJECT_ID beside the definition and the two conditions get separate reasons; the actual cure, GRANT VIEW DEFINITION, is named. Both still DEGRADE — the gate hashes the body and cannot pass on one it cannot read — so no accept/reject behaviour changed, only the diagnosis. The offline stub pins the probe SQL by exact match (a typo'd probe must fail loudly rather than silently match); that pin moved with the SQL and stayed exact, and now also asserts both placeholders bind the same object. New offline legs cover both arms and fail without the fix. A live leg pins the premise no stub can show — that OBJECT_ID and OBJECT_DEFINITION genuinely disagree on a real server — using WITH ENCRYPTION, which needs no security principal; the permission half stays deferred with AC-10's other permission scenarios. Also adds the store-side half of AC-7's degraded gauge: a claim_proc_status() accessor on the store protocol (None on every backend without the lever and when the flag is off, so "not requested" stays distinguishable from "requested and degraded"). The surfaces that read it land next. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… the console AC-7 requires "a WARNING naming the reason + degraded gauge", and §4's compensating-control story assumes an operator can SEE the degraded state. Nobody could: claim_proc_effective / claim_proc_degraded_reason were read by the store's own tests and nothing else — no /stats, no /status, no /metrics, no console. The whole operator signal was one WARNING at open(), in a log nobody was watching, that until PR #86 named the wrong cause. That is a load-bearing part of why the lever could sit inert in every deployment for its entire life. Surfaces the store's claim_proc_status() on: * GET /status -> claim_proc: effective, the human-readable degraded_reason, and the matched head_forms * GET /metrics -> messagefoundry_store_claim_proc_effective and messagefoundry_store_claim_proc_head_verbatim * /ui/status -> store-panel rows Three shape decisions. The field/series are ABSENT, not 0/false, when the lever was never requested: a constant 0 on every SQLite fleet is unalertable noise, and absence keeps "not requested" distinct from "requested and degraded". No reason LABEL in the exposition — the reason is free text embedding a proc name and, on the probe-failure arm, an exception string, so a label would be unbounded cardinality and a breach of the exporter's strict {connection, destination, status, version, le} allowlist; the string goes to /status and the console. And it deliberately does NOT feed the console's engine-health heart: claims keep flowing on the batch, so a degrade is a lever not paying off, not an unwell store, and making the nav cry wolf would devalue the signal that means it is. head_forms is surfaced for the same reason it is logged: a fleet reporting "verbatim" is a live counterexample to _CLAIM_PROC_STORED_HEADS's compatibility assumption, and was previously visible only at INFO. Observability only — the gate's accept/degrade logic is untouched. The new tests assert the rendered output of each surface, not the existence of a property. Rendering a new SystemStatus field on the console is a seam change, so ENGINE_UI_SEAM and the console's SUPPORTED_ENGINE_SEAMS both move to 16 and the golden snapshot is refreshed. A separate seam rather than a correction to the unreleased v15: v15 is a SecurityPosture change, and folding an unrelated DTO into it would make that note describe a field set it does not cover. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…le 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>
wshallwshall
force-pushed
the
adr0114-followups
branch
from
July 31, 2026 13:48
bf2a4b1 to
8e6988b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The two follow-ups PR #86 deliberately held out of the bug fix. Both are ADR 0114 §4 startup-gate items; neither belonged in a bug-fix PR.
1. A missing
VIEW DEFINITIONgrant was reported as a missing procADR 0114 §4 has always specified the gate's probe (a) as "
OBJECT_IDof both procs", but the implementation folded (a) into (b) and inferred absence from a NULLOBJECT_DEFINITION.MEASURED: a principal holding only
EXECUTEon the proc gets a non-NULLOBJECT_IDand a NULLOBJECT_DEFINITION, and the compat probe still passes. So a deployed, working, correct procedure was reported as missing, and the operator was sent to grantCREATE PROCEDURE— neither the cause nor the cure.WITH ENCRYPTIONproduces the identical NULL.This is not hypothetical: the sub-lever B design note in the same module explicitly serves "a fleet whose DB principal can never hold CREATE PROCEDURE" — DBA-provisioned procs plus a least-privilege app principal, which is exactly the posture that hits it.
OBJECT_IDNULLCREATE PROCEDURE/ALTER-on-schema denied, or a pre-2016-SP1 engineOBJECT_IDnon-NULL,OBJECT_DEFINITIONNULLGRANT VIEW DEFINITION, or the module isWITH ENCRYPTIONBoth still degrade — the gate hashes the body and cannot pass on one it cannot read — so no accept/reject behaviour changed, only the diagnosis.
The offline stub pins the probe SQL by exact match (a typo'd probe must fail loudly rather than silently match). That pin moved with the SQL and stayed exact; it now also asserts both placeholders bind the same object, so a probe reporting one proc's id against another's body fails too. Verified RED against the unfixed store, green with the fix.
2. The AC-7 degraded gauge did not exist
AC-7 requires "a WARNING naming the reason + degraded gauge", and §4's compensating-control story assumes an operator can SEE the degraded state. Nobody could:
claim_proc_effective/claim_proc_degraded_reasonwere read by the store's own tests and nothing else — no/stats, no/status, no/metrics, no console. The whole signal was one WARNING atopen(), in a log nobody was watching, that until #86 named the wrong cause. That is a load-bearing part of why the lever sat inert in every deployment for its entire life.A
claim_proc_status()store accessor now feeds three surfaces:GET /status→claim_proceffective, the human-readabledegraded_reason, the matchedhead_formsGET /metricsmessagefoundry_store_claim_proc_effective,messagefoundry_store_claim_proc_head_verbatim/ui/status)Three shape decisions, recorded in the ADR so they are not re-litigated:
0/false, when the lever was never requested. A constant0on every SQLite fleet is unalertable noise, and absence keeps "not requested" distinct from "requested and degraded".{connection, destination, status, version, le}allowlist. The string goes to/statusand the console.head_formsis surfaced for the same reason it is logged: a fleet reportingverbatimis a live counterexample to_CLAIM_PROC_STORED_HEADS's compatibility assumption, and was previously visible only at INFO.Observability only — the gate's accept/degrade logic, just adjudicated in #86, is untouched.
Seam bump
Rendering a new
SystemStatusfield on the console is a seam change:ENGINE_UI_SEAMandSUPPORTED_ENGINE_SEAMSboth move to 16, golden snapshot refreshed. A separate seam rather than a correction to the unreleased v15 — v15 is aSecurityPosturechange, and folding an unrelated DTO into it would make that note describe a field set it does not cover.ADR
§4 now names the surfaces that publish the gauge and records the three shape decisions. 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, the same one-directional gap the 2026-07-30 amendment found in the gate itself.
Verification
-k "adr0114 or sqlserver or claim or fifo": 468 passed (was 457).MEFOR_TEST_SQLSERVER=1): 7 passed — the required 6 plus a new leg pinning on a real server the premise no offline stub can show, thatOBJECT_IDandOBJECT_DEFINITIONgenuinely disagree. It usesWITH ENCRYPTIONbecause that needs no security principal (self-cleaning; confirmed no stray proc). The EXECUTE-without-VIEW DEFINITIONhalf stays deferred with AC-10's other permission scenarios.ruff check/ruff format --checkclean;mypy --strictclean over the changed modules.Not touched
Per the handoff's scope discipline: the gate's accepted-set design and per-proc keying,
_normalize_tsql, the AC-8 body lint, and_claim_proc_body()'s byte-pinned output (no re-pin, no forced DDL re-apply on any live database).No BACKLOG number was allocated — both items were implemented rather than filed, so no ADR file or
##heading was created. The ledger gate passed on every commit.🤖 Generated with Claude Code