Commit e49d720
fix(store): ADR 0114 sub-lever A — the claim-proc startup gate has never passed
OBJECT_DEFINITION() does not return a CREATE OR ALTER module verbatim: SQL Server
deletes the OR and ALTER keyword TOKENS and keeps their separators, so a head
submitted as `CREATE OR ALTER PROCEDURE dbo.x` comes back as `CREATE` + three
spaces + `PROCEDURE dbo.x` (delta exactly 7; everything after the head is
byte-identical). The gate hashed the SUBMITTED text, so it could never match.
Sub-lever A has therefore been INERT in every deployment since it shipped — the
gate degraded to the ad-hoc batch on every open, and no test noticed because the
offline fixture fed the submitted body back as the "deployed" body, modelling the
server as an identity function. Both sides of the comparison were the same
function of the same argument.
Measured on SQL Server 2022 16.0.4255.1 and 2025 17.0.4055.5, compat 130/160/170,
across five deploy paths. Live legs go 5-failed/1-passed -> 6 passed.
THE FIX IS SHIPPED-SIDE ONLY. The gate compares the deployed hash against a small
set of code-controlled constants — the head forms a server may store for a module
THIS build deployed (_CLAIM_PROC_STORED_HEADS: the measured `rewritten` form, plus
`verbatim` for a hypothetical non-rewriting engine). _claim_proc_body() renders
byte-identically, so _claim_proc_ddl, _SCHEMA, _schema_hash() and both golden body
pins are untouched: no re-pin, no forced DDL re-apply on any live database.
The expected map is keyed PER PROC. A single flat accepted-set would take the cid
body served under the dst name — reachable with no tampering intent via sp_rename,
which does not rewrite sys.sql_modules.definition — and silently swap the lane
predicate, claiming zero rows forever.
Head spellings this deploy path cannot emit (CREATE PROC, differing case) keep
failing the gate: each is affirmative evidence of an out-of-band hand deploy,
which is the AC-7 event. A two-sided canonicalization would launder exactly that
signal, and its correctness rests on regex minutiae no finite assertion can pin
(mutation-scored 0/7 against this suite; the shipped-side form scores 3/3).
ALSO IN THIS COMMIT, because the gate fix makes them reachable:
* The proc CALL's 9 parameter pins are PERSISTENT cursor state and were never
cleared. At OUTBOUND the same pooled cursor runs the H2 delivery probe, binding
an NVARCHAR id against the stale SQL_DOUBLE descriptor for @now FLOAT -> client
22018 -> rollback -> outbound delivery collapse. Fixing the gate alone would arm
this for the first operator who sets fifo_claim_proc=true. Cleared the moment
the CALL's result is drained. Verified on pyodbc 5.3.0 / ODBC Driver 18 that a
ZERO-parameter execute tolerates surplus descriptors, so the shielded
`SET LOCK_TIMEOUT -1;` reset in the finally-guard is unaffected and the exposure
is exactly the H2 bind chain.
* AC-11 no-match parity was honoured only on the two flagged branches. The ad-hoc
batch bound the raw lane list into (VALUES (?),...) feeding a
`DECLARE @heads TABLE (lane NVARCHAR(256) NOT NULL` — SQL Server evaluates that
narrowing conversion on the outer constant scan before the CROSS APPLY filters
it, so with ANSI_WARNINGS ON an oversized lane raises 2628 even when zero rows
would match. 2628 is not 1222, so it is not translated to EMPTY-all: it rolls
back and re-raises. The skip is hoisted ahead of the dispatch-path split so all
three branches agree. Clamp still runs before the skip (the tested contract).
TESTS — the fix's only evidence. The offline suite could not previously tell a
working gate from a broken one; three contradictory implementations all scored
51/51. Both fixture doors are closed (the `_gate_rows` default AND the whitespace
test that passed cid_body= explicitly), the "deployed" body now comes from an
INDEPENDENT model of the server rewrite with a liveness receipt, and new tests pin
the accepted set by set-equality, reject cross-proc substitution, reject
un-emittable head spellings, and require the anchor break to degrade diagnosably.
The live tamper leg gains the in-test positive control it lacked — under the old
defect the gate rejected everything, so its degrade assertions were vacuous.
Mutation check: bug 10 failed, flattened-key 3 failed, over-wide 8 failed,
pin-clear no-op 1 failed; correct implementation 64 passed.
DOCS — three docstrings asserted the false premise ("OBJECT_DEFINITION() preserves
the definition text as executed"). The operator-facing degraded reason prescribed
`DELETE FROM schema_meta`, which could not work: the re-apply submits the same
text and mismatches again, forever. ADR 0114 amended; AC-7b added, because AC-7 is
one-directional and was LITERALLY SATISFIED by the defect — nothing anywhere
required a correctly deployed proc to pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>1 parent 7128f5b commit e49d720
6 files changed
Lines changed: 564 additions & 88 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
70 | 70 | | |
71 | 71 | | |
72 | 72 | | |
73 | | - | |
| 73 | + | |
74 | 74 | | |
75 | 75 | | |
76 | 76 | | |
| |||
Lines changed: 56 additions & 10 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
395 | 395 | | |
396 | 396 | | |
397 | 397 | | |
398 | | - | |
399 | | - | |
400 | | - | |
401 | | - | |
402 | | - | |
403 | | - | |
| 398 | + | |
| 399 | + | |
| 400 | + | |
| 401 | + | |
| 402 | + | |
| 403 | + | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
| 413 | + | |
| 414 | + | |
| 415 | + | |
| 416 | + | |
| 417 | + | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
| 425 | + | |
| 426 | + | |
| 427 | + | |
| 428 | + | |
| 429 | + | |
| 430 | + | |
| 431 | + | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
| 435 | + | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
404 | 439 | | |
405 | 440 | | |
406 | 441 | | |
| |||
413 | 448 | | |
414 | 449 | | |
415 | 450 | | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
416 | 454 | | |
417 | 455 | | |
418 | 456 | | |
| |||
641 | 679 | | |
642 | 680 | | |
643 | 681 | | |
644 | | - | |
645 | | - | |
646 | | - | |
647 | | - | |
| 682 | + | |
| 683 | + | |
| 684 | + | |
| 685 | + | |
| 686 | + | |
| 687 | + | |
| 688 | + | |
| 689 | + | |
| 690 | + | |
| 691 | + | |
| 692 | + | |
| 693 | + | |
648 | 694 | | |
649 | 695 | | |
650 | 696 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
327 | 327 | | |
328 | 328 | | |
329 | 329 | | |
330 | | - | |
331 | | - | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
332 | 334 | | |
333 | 335 | | |
334 | 336 | | |
| |||
0 commit comments