HOMELAB-1929: LD5 measurement rules (M0) and single report query — draft for freeze #7
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "plane/HOMELAB-1929-m0"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
Two files under
design/ld5/, no existing file touched.design/ld5/measurement.md— the LD5 measurement rules (M0), written ascitable rules
M0-R1…M0-R41: the sample and the sign, the five per-ticketsuccess conditions, the four gate clauses, the preconditions to sign, the two
informational labels, the running rules (caps, leases, landing, interventions,
frozen AC, fixed denominator, drain rule), the void rule and the closed
infra-cause list, the report contract, the stop rule, and §8's recorded source
tensions X1–X15.
design/ld5/report.sql— the single report query. Long-format output(
report_scope, ticket, rule, metric, subkey, numerator, denominator, value, unit, note) so every reported number names the M0 rule that requires it andthe source table / event kind it comes from. Per ticket and in total, and
it ends with the verdict rows: clause 1 against 70%, clause 2, clause 3,
clause 4, and the overall
PASS/FAIL/VOID/NOT_CERTIFIABLE.Scope
This encodes already-approved rulings; it decides nothing new. Every rule
traces to a source named in
measurement.md§9.It supersedes 07 §7.2's gate (rule 8) per the operator's 2026-10-21
re-ruling in B1 (HOMELAB-1929): the gate is the 4 clauses, revision 5's 11
safety tests and the 100% injection-canary proof move to E1's exit, the canary
clause is replaced by preconditions-to-sign (planted-defect bar 6 of 8, auditor
recall 4 of 6, 6 safety tests, shakedown, N0), and the two informational labels
("fail — auditor-dependent", "panel drift") are recorded.
design/ld5/measurement.md§8 lists the places where the sources genuinelydisagree, numbered X1–X15 (renamed from T1–T12 so they cannot be confused
with the T1–T6 safety tests of M0-R17). X1–X12 are readings this PR
encodes. X13–X15 are OPEN items for the operator, implemented fail-closed so
a sample cannot be gamed while the question is open:
checks come back inconclusive (M0-R41). The report certifies the condition only
on a conclusive check and prints the literal reading beside it, so the cost of
the cap is visible.
condition 5 on it and prints the failures separately.
an oversight path while a sample runs"): if the tag file is under
deploy/**,every pin bump is blocked. Build/ruling question, no query change.
Fix round 3 (Opus check, 2026-10-22)
This revision closes every blocker and every fix-before-freeze item from the
Opus check of head
3e0ba08:::numeric; 14/20 prints0.70,not
0, anddfpy_confirmedis correct below 100%.landing.mergedcarrieshead_shaandmerge_commit_sha; condition 2 reads the reviewed head, condition 3 requiresthe probe to name the merge commit (a passing
probe_okalone no longercounts).
at its own
recorded_at; drains carry adrain_idand pair on it, never byposition; only operator-actor drains are declared, and a re-baseline is only
used when the operator recorded it inside a declared drain (a runtime can no
longer mark its own drift as declared).
sample_idon every record and every CTE; the signedsample is capped at the 20 slots;
decided_unsignedcounts only events afterthe sign.
goal.checkevent's own id.revert or red CI on live needs no flag); image digests compared by containment
(
@>); replacements must be a signed spare, released by the operator, not aspare of a spare, before the claim; a zero-slot sample cannot print a clause-4
pass; CI stated as implied by
decision='pass'.[needs Opus check before freeze]
This is a draft for freeze. It must pass the Opus re-check before it is
frozen at the LD5 sign (B20). At the sign, the blob SHAs of
measurement.mdandreport.sqlare snapshotted with the gate text, imagedigests and config (M0-R1 / M0-R16), so a query or ruleset edited after the sign
cannot quietly change the number.
report.sqlis not executable against the real DB yet: B5 (HOMELAB-1933,the CNPG record DB) has not landed and its schema review is still open. The
header comment states every schema and event-kind assumption. When B5 lands,
only the source CTEs change; the rules do not.
Checks
report.sqlexecutes on a local Postgres 15 against a B5-shaped fixture(3 tables:
workflows,node_instances,events) — not just parses. 24scenarios were run: 14/20 at the 70% boundary, declared drain + re-baseline,
undeclared runtime re-baseline, in-window vs out-of-window mismatch, open
drain, drain without an id, gate decision on the wrong commit, probe on the
wrong commit, re-spec, five replacement-rejection paths, revert / fix-ticket /
failed-probe escapes, five infra re-runs (void), off-list infra cause,
all-inconclusive checks, a fresh sample, duplicate slot orders, replay
reproduced / not reproduced, and the empty schema.
literals, balanced parentheses,
sqlglotparses 1 statement).M0-R1…M0-R41); everyM0-Rncited inreport.sqlexists in
measurement.md; all 15 tensionsX1–X15defined.Ticket: HOMELAB-1929 (LD5-B1), epic HOMELAB-1714.
Review verdict (landing worker, fresh context)
Reviewed the diff at head
00fb54aagainst primary sources only: HOMELAB-1929 (description + all 3 comments), HOMELAB-1714 (via the 1929 epic frame),design/where-we-are/after.html§03–§05,design/where-we-are/next.htmlB1/B12/B16/B19/B20,design/fleet-design-claude/07-layered-fleet-steer.md§7.2 + D40 + §4.6. I did not write this code and had no implementer summary.Verdict: REQUEST CHANGES — 4 blockers. The rule text is faithful in most places, but two query defects change the number itself, clause 4 is not answerable from the report, and one approved ruling is missing from the frozen text.
Blockers
gate_firstkeeps the earliestgate.decisionrow (round ASC) andcond2then requiresmerged_sha = that row's head_sha. Every ticket that had an in-workflow fix round (M0-R8: "in-workflow fix rounds inside that first workflow do count as first-pass") or a landing-conflict fix round (M0-R23: "the new SHA needs a fresh panel round. It does not start a second workflow") has a later round for the merged SHA and therefore failscond2— a documented non-miss becomes a miss and DFPY is understated. Needs two separate reads: the merged-SHA decision forcond2, the first-workflow first verdict for M0-R30.report.sql:214-224(ORDER BY round ASC),:300-303(cond2); rulesmeasurement.md:83-89(M0-R8),:207-210(M0-R23)releasewithreplaces)", butsign_ticketderivesis_spare/replaces_ticketfromsign.snapshotandsample_ticketfiltersWHERE NOT s.is_spare. With doc-compliant data nothing is marked: the replaced ticket and the spare both stay in the denominator andsignaturereportsreleased_unsigned > 0for a legitimate spare release. With the marker put onsign.snapshotinstead, the spare is dropped and only the replaced ticket is removed — denominator 19. Either shape breaks M0-R4/M0-R26 "the denominator is fixed: all 20 stay in", which is a gate input.report.sql:151-173,:73(release payload doc),:465-476(signature);measurement.md:61-65(M0-R5),:57-60(M0-R4),:230-234(M0-R26)sign.samplepayload documentsimage_digests(and M0-R1 freezes "the image digests"), butsign_sampleselects only config/dispatcher/agent-config SHAs. The report's only clause-4 outputs are per-ticketprovenance_mismatchcounts and drain start/end counts — there is no "undeclared change" determination, no image-digest comparison, and no detection of a drain left open at report time, which M0-R16 requires ("a change made without a drain event voids the run"). Since M0-R35 makes this file "the only report of the LD5 number", a fresh reader cannot produce the clause-4 verdict from the doc + query.report.sql:136-149(no image digest),:66(payload),:443-462,:478-485,:805-809;measurement.md:139-146(M0-R16),:27-40(M0-R1),:292-297(M0-R35)measurement.md, and §8's tension list — which exists precisely to record rulings that narrow earlier ones — has no entry for it, although it narrows the 10-15 full-read-access ruling. HOMELAB-1929 explicitly asks that this trade-off be "record and freeze in the gate text", and next.html B1/B7 record it. It also bears on how M0-R19 precision and M0-R11 evidence are read. One paragraph in §8 (or a rule line) fixes it; if the intent is that M0 is measurement-only, say so and cite B7/B18 explicitly.measurement.md(no occurrence),:349-393(§8 tensions T1–T6); HOMELAB-1929 trade-offs #1;next.htmlB1 (line 118), B7 (line 217)Concerns (post-merge acceptable, but should be fixed before the sign)
infraCTE counts anyinfra.rerunwhosecauseis on the closed list, grouping byevidence_kindwithout requiring it to be the one the table mandates (I1 kernel record, I2 eviction event, …). An unevidenced claim can void a run.report.sql:429-441vsmeasurement.md:264-279.measurement.md:276;report.sql:437-440.measurement.md:277vs07:3975.measurement.md:153-155.replayCTE countsreplayevents, so a FAIL replayed twice inflates the denominator and depresses precision (and can trip the "auditor-dependent" label).report.sql:364-372,:666-674,:717-727vsmeasurement.md:183-187.measurement.md:313;report.sql:392-404.measurement.md:339-343.Suggestions
report.sql:709-716; the 70% comparison does not), so the gate is readable without arithmetic.report.sql:681-690.report.sql:374-383,:750-759.measurement.md:153,:55,:325.source/actor, not just a count: after.html §05 characterises the flag as "read-only attach or the exec audit".report.sql:205-209.measurement.md:115-124;report.sql:815-825.measurement.md:286-290.Checked and found faithful (no finding)
The 4-clause gate with safety tests and the injection-canary proof removed and moved to E1's exit (M0-R13–R16, R20); 6 of 8 planted defects with 16 planted PRs and "every hold kind fires" (M0-R17.3); auditor recall 4 of 6 (M0-R17.4); caps 10 in flight / 32 agents / 6 at once / static $4 with overrun = miss (M0-R21); labels "fail — auditor-dependent" and "panel drift" as informational only, with precision reported n/a rather than 0 (M0-R18, R37); canaries at 1 in 10 outside the 20 with the gate-decision canary flag (M0-R29) and the T1 tension explicitly recorded rather than silently decided; operator-only
/failure-review(M0-R24, R31, R39); void threshold 4 of 20 on distinct tickets; provisional vs confirmed split; the five conditions matching after.html §03 and 07 §7.2 rule 4 including the fail-closed / replay / false-positive asymmetry; the fixed denominator and no-operator-marks rules. §8 T2 (step vs model node) and T6 (precision denominator) are the right calls and are recorded as rulings to make rather than made silently. M0-R36's report list covers 07 §7.2 rule 7 completely and adds the size bucket, base-test reason, drain and signature-integrity rows after.html §05 asks for.B1 (report.sql) - condition 2 is now read from the gate decisions on the MERGED sha, any round (new gate_merged CTE). An in-workflow fix round (M0-R8) and a landing-conflict fix round (M0-R23) both put the panel's pass on a later round of the same workflow; the first round now feeds only the raw first-verdict rate (M0-R30). Adds gate_rounds_on_merged_sha. B2 (report.sql) - the sample is now the 20 signed SLOTS (order 1..20) with replacements applied: a spare enters through a release event carrying replaces plus an eligible replace_reason (dor_failed_before_claim | accept_test_green_on_base) and no earlier claim; the replaced ticket leaves through that same event. The denominator stays 20, and a replacement is no longer reported as an unsigned release by the signature check. B3 (report.sql) - clause 4 is now computed: runtime_prov compares each node's recorded image digests, runtime config SHA, dispatcher digest and agent-config SHA with the sign.sample snapshot; drain_window (start->end in event order) declares in-window mismatches; a newer 'clause4' metric prints per ticket and in total with the mismatched component named, and reports 'not certifiable' when a drain is still open. Comparison inputs are stated as header assumptions (still non-executable until B5). B4 (measurement.md) - new M0-R41 records the approved 2026-10-21 ruling that before LD5 the auditor holds only the Claude token plus the evidence bundle (narrows the 10-15 read-access ruling; read-only identities return in E2). It binds the delivery check, precision and the failure-review mode. Adds the §9 sources row for HOMELAB-1929's ruling comment. Also: C1 (infra re-runs require the evidence kind their cause mandates), C3 (retry-budget step-vs-node widening recorded in §8 T2), C4 (10-file unit suite cited to next.html B14), C5 (precision denominator counts cited FAILs, not replay attempts), C6 (model served per node printed, promised by after.html §05), C7 ('one at a time' dropped: not in the source). C2 is left open and recorded as §8 T10 instead of silently decided; new §8 entries T7-T9 record the readings B1-B3 required.Fix worker: blockers B1–B4 addressed (
9a6530e)Commit
9a6530eonplane/HOMELAB-1929-m0. Diff touches onlydesign/ld5/.report.sqlstill parses as one Postgres statement (libpg_query), and every column reference resolves in its query block (scoped linter over all 42 CTEs); every M0-Rn cited in the SQL exists inmeasurement.md; rule IDs unchanged, one new rule (M0-R41).gate_mergedCTE reads the gate decisions on the merged SHA, any round (bool_or(floor_met AND decision='pass'));cond2is now that read.gate_firstis kept only for the raw first-verdict rate (M0-R30). Comment cites M0-R8/M0-R23; new per-ticketgate_rounds_on_merged_shamakes a fix round visible. M0-R9 gains the merged-SHA-round sentence; reading recorded as §8 T7.order1..sample_size). A spare enters through areleaseevent carryingreplacesplus an eligiblereplace_reason(dor_failed_before_claim|accept_test_green_on_base) and no earlierclaim; the replaced ticket leaves through that same event, so the denominator stays 20.sign_ticketno longer derivesis_sparefrom the snapshot;signatureno longer reports a replacement as an unsigned release. An unevidencedreplacessurfaces as an anomaly instead of silently moving the denominator. Recorded as §8 T8.runtime_provcompares each node's recorded image digests, runtime config SHA, dispatcher digest and agent-config SHA against thesign.samplesnapshot;drain_window(start→end paired in event order) declares in-window mismatches; newclause4metric per ticket and in total, withnotenaming the mismatched component(s), a separateclause4/declared_in_drainrow, anddrains/open_at_report. A drain open at report time reports clause 4 as "not certifiable" (M0-R16 pauses, not exempts). Comparison inputs stated as header assumptions + §8 T9; still non-executable until B5.Concerns fixed: C1 (infra re-runs must carry the evidence kind their cause mandates —
infra_evidencemap from the M0-R32 table), C3 (§8 T2 now records the retry-budget step-vs-node widening instead of applying it silently), C4 (the 10-file number is sourced — next.html B14 line 291 — so the wording now matches it and B14 is cited in §9), C5 (precision denominator = distinct test-/command-cited FAILs, not replay attempts; also fixeshas_false_positive), C6 (model served per node printed, as after.html §05 promises; M0-R36 updated), C7 ("one at a time" removed — not in the source).Skipped: C2 — defining which condition an unlisted-cause re-run fails is a rule change needing an operator ruling, so it is not decided here: those re-runs now print in their own
infra_reruns_unqualifiedcolumn and the open question is recorded as §8 T10. Suggestions (clause-1 verdict row, computed "panel drift" label, inline T1–T6 list, intervention source/actor, failed-probe reading, N in M0-R34) untouched — none is clearly right without a ruling.Re-check verdict (landing worker, fix delta
9a6530e)Scope: the delta only,
00fb54a..9a6530e,design/ld5/. Primary sources re-read directly, not through the fix comment: HOMELAB-1929's ruling comment of 2026-10-21 (the string "Before LD5 the auditor holds only the Claude token." is there verbatim), HOMELAB-1714,design/where-we-are/next.htmlB1/B7/B12/B14/B16/B18,after.html§03–§05,07-layered-fleet-steer.md§7.2. I did not write this code and was given no implementer context beyond the PR text.Mechanical claims spot-checked, not trusted.
report.sqlparses as 1 statement (pglast= Postgres' own grammar:statements: 1,SelectStmt). EveryM0-Rncited in the SQL exists inmeasurement.md(comm -23on both ID sets → empty). Rule headers are unchanged apart from one addition:diffof^**M0-Rn —between the two heads yields only31a32 > **M0-R41.Per-blocker
gate_merged(report.sql:335-350) joinsgate.decisiontomergedonpayload->>'head_sha' = merged_shaand takesbool_or(floor_met AND decision='pass')over all rounds;cond2is now that read (:430), andgate_firstno longer touches any condition (:305-321). M0-R9 gained the merged-SHA-round sentence (measurement.md:99-101) and the reading is §8 T7 (:430-440). Correctness re-checked for weakening:gate_mergedis inner-joined tomerged, socond2still requires an actual merge; a second workflow is still a miss viacond1(:418-423); "any round" is exactly what M0-R8 and M0-R23 license. Nit: the header says round 1 "feeds only M0-R30", butfirst_verdictandis_canarycome fromgate_first(:441-442); harmless (the flag is on every gate row per M0-R29), only the wording is loose.signed20byorder1..sample_size (:218-221),sample_ticketemits one row per slot withCOALESCE(r.replacement_ticket, s.ticket)(:244-269). A replacement is read from the event:kind='release'+replaces+replace_reason IN ('dor_failed_before_claim','accept_test_green_on_base')+ no priorclaim(:223-243).is_spareis gone fromsign_ticket(grep is_spare→ 0 hits) andsignatureexcludes both the replaced ticket and the replacement ticket fromreleased_unsigned(:688-716), with an unevidencedreplacesstill surfacing as an unsigned release. Denominator is the slot count (20), so M0-R4/M0-R26 hold. M0-R5 text updated (measurement.md:61-67), reading recorded as §8 T8 (:441-451), and the header states the sign-time shape (spares are signed, carry noorder,:79-84).runtime_prov(:606-625) checks image digests, runtime-config SHA, dispatcher digest and agent-config SHA againstsign.sample;drain_window(:594-604) pairs starts to ends;clause4_ticket/clause4(:640-670) name the mismatched component, splitdeclared_in_drain, and an open drain reports "not certifiable" (:1142-1145,drains/open_at_report:1130-1134). But a ticket with zeronode_instancesrows passes clause 4.clause4_ticketLEFT JOINs fromsample_ticket, so every slot yields a row whose mismatch counts are 0;undeclaredsums to 0 and the total row printspass: no undeclared change outside a drain windowwith0 / 20. Nothing counters that:node_provis derived fromruntime_prov(:627-638), so a slot with no provenance prints nonodesrow at all (:945-946) — the absence is silent. This contradicts the fix's own recorded reading (T9,measurement.md:452-463: "A node with no recorded provenance time counts as outside any window (fail-closed)") and it is the state the file is in today, sincenode_instancesprovenance is an assumption for B5. M0-R16 makes a change made without a recorded drain void; provenance that was never recorded cannot certify "no undeclared change". Fix: the total clause-4 row must return NULL/"not certifiable" (same branch as the open-drain case) whenever any sample slot has no provenance row, and the same for a slot whoserecorded_atis NULL rather than relying on theIS DISTINCT FROMpath.measurement.md:267-289, in §4) records the ruling, names its source (HOMELAB-1929's ruling comment, next.html B1/B7/B18), states that it narrows the 10-15 full-read-access ruling with the read-only identities returning in E2, and confines the consequences to what the approved text already says: bundle-only evidence ⇒inconclusiveand never lowers the number (M0-R11 unchanged), precision's denominator (M0-R19), and operator-typed failure reviews with gaps reported rather than acted on (M0-R24/R39). §9 gained the ruling-comment row and B7/B14 (:477-478). Verified against the sources: HOMELAB-1929 (2026-10-21) lists "Before LD5 the auditor holds only the Claude token." under approved trade-offs;next.htmlB1 carries the same wording plus "narrows your 10-15 ruling … the read-only identities return in E2 (B18)", and B14 is the source of the "at least 10 files" clause R41's neighbours cite. Nit: R41 sits before R32–R40 in document order; a frozen doc read by ID would rather have it last, but every ID resolves.C2 — confirmed surfaced, not decided
§8 T10 (
measurement.md:464-471) says "Recorded, not decided" and invents no condition; the rows print in their own column, per ticket (report.sql:935-941, note: "NOT counted as infra … the effect on the number is an open ruling (sec 8 T10)") and in total (:1112-1118). No rule was added and no condition was silently failed. That part is right.New blocker introduced by the delta (C1's fix)
N1 — the evidence requirement now removes tickets from the void count.
infrakeeps only re-runs whose recordedevidence_kindis the oneinfra_evidencemandates (report.sql:566-584), and the M0-R33 void row countsFROM infraalone (:1022-1029). Before this delta every listed-cause re-run counted (see00fb54a:429-440). Consequence: five tickets with causeOOM_kernel_recordbut no kernel record now printnot void (threshold 4). The approved clause is "at most 4 of 20 tickets needed an infrastructure re-run" (HOMELAB-1929) / M0-R33 (measurement.md:310-314); whether the evidence was captured is not part of "needed", and M0-R32's "anything not on this list is a quality miss" governs off-list causes, not listed ones. T10 covers only the off-list case, so this is an unrecorded, fail-open change to a gate clause — and the per-ticket note claims the effect is "an open ruling" while the run-level void verdict is in fact decided. Fix: keep listed causes in the M0-R33 count regardless of evidence (evidence gates cause classification and the printed note, not whether a re-run happened), or — only if the operator rules the other way — record it as its own §8 entry and make the void row's note say which side it is on. My reading is the fail-closed one: a missing-evidence list-cause re-run still counts.Found faithful in the delta (no finding)
§8 T7/T8/T9 record their readings instead of applying them silently; C4's "10 files" is genuinely sourced (next.html B14: "Unit tests (at least 10 files) prove that bad workflow settings are rejected") and the wording was changed to match rather than the source being stretched; C5's precision denominator is now distinct
goal.checkrows (:393-398) withhas_false_positivefixed; C6 prints model served/requested per node (:654-664,:867-873) and M0-R36 was updated; C7's "one at a time" is removed and B16's actual wording quoted; the fixed-20 denominator, the canary gate and the five conditions are untouched.B1, B2 and B4 are closed. B3 needs one more commit (clause 4 must not certify a pass on missing provenance), and N1 needs the void count made fail-closed or the reading recorded with the void row aligned to it. Both are small and mechanical.
Verdict: REQUEST CHANGES
Fix worker: B3 (clause 4 on missing data) and N1 (void count) closed (
3e0ba08)Commit
3e0ba08onplane/HOMELAB-1929-m0, over9a6530e. Delta touches onlydesign/ld5/(measurement.md + report.sql). Mechanical checks:report.sqlparses as 1 statement (pglast→SelectStmt); a scoped column-resolution pass over all 42 CTEs and the final union reports the same 9 pre-existing correlated-subquery alias references as the base blob and no new unresolved/unknown columns (714 → 778 refs checked; a deliberately broken control was caught, so the pass is sensitive); everyM0-Rncited in the SQL still exists inmeasurement.md(common both ID sets → empty); rule headers unchanged (41 rules, no new ID); §8 now carries T1–T12.runtime_provgains two flags:no_provenance_time(recorded_at IS NULL) andno_comparison_value(any of the four compared values not recorded — the case whereIS DISTINCT FROMwould call two NULLs "equal").clause4_ticketgainsnodes,undeclared,certifiable= at least one provenance row AND no node missing a provenance time or a compared value, and agaptext. Per ticket the clause-4 value is now pass only when certifiable and undeclared = 0; a non-certifiable slot printsNULL("not certifiable (n/a)", per M0-R37) with the gap named, plus a newclause4/certifiablerow for every slot so a silent absence is visible.clause4addscertifiable_slots/not_certifiable_slots; the run-level row returnsNULLwhen any slot is uncertifiable, plus newclause4/slots_not_certifiableandslots_certifiablerows. Precedence: a detected mismatch is VOID even with a drain open (detection outranks pause), then open drain → paused, then uncertifiable → n/a, else pass. Recorded as §8 T11; M0-R36's clause-4 bullet and T9 now state it.infra_eventnow separatescause_listed(cause on M0-R32's list) fromevidenced(evidence_kind is the mandated one).infra= every listed cause, withnandn_unevidenced;infra_unqualifiedis now off-list causes only. The M0-R33 void row countsDISTINCT ticket FROM infra, so an unevidenced listed-cause re-run counts again (it did pre-00fb54a); its void note names how many of those tickets had missing evidence. The unevidenced set is additionally printed as its own number —infra_reruns_unevidencedper ticket and in total, plusinfra_reruns_unevidenced_tickets— so the report shows both. Off-list causes stay ininfra_reruns_unqualified(§8 T10). Recorded as §8 T12; M0-R36's infra bullet names the extra number and T10 is narrowed to off-list causes.No rule was re-interpreted or added; both fixes are the query/doc becoming fail-closed and each reading is recorded in §8 in the file's own convention.
Re-check verdict (landing worker, fix delta
3e0ba08)Scope: the delta only,
9a6530e..3e0ba08,design/ld5/. Sources read directly, not through the fix comment: the two filed findings (my own round-1 re-check on the PR),measurement.mdrules M0-R16/R19/R32/R33/R36/R37 and §8 T1–T12 at3e0ba08, plus the round-1 primary sources (HOMELAB-1929 ruling comment, HOMELAB-1714,after.html§04–§05). I did not write this code.Mechanical, not trusted:
report.sqlparses as 1 statement (pglast→SelectStmt); the 33 distinctM0-RnIDs cited in the SQL are all present amongmeasurement.md's 41 (comm -23→ empty); rule headers unchanged (diffof^\*\*M0-Rn→ no output: no rule added, none renumbered); diff touches onlydesign/ld5/measurement.md+design/ld5/report.sql. I also executed the newcertifiable/verdict logic on a scratch engine in the same SQL shape (three slots: zero provenance rows / fully recorded / one NULL compared value, plus a zero-row sample) to check the branches rather than read them.Per-finding
node_instancesrowsruntime_provgainsno_provenance_timeandno_comparison_value(any of the four compared values unrecorded — the both-NULL case whereIS DISTINCT FROMcalls two NULLs equal);clause4_ticketgainsnodes = count(rp.node_key),undeclared,certifiable = count(node_key) > 0 AND count(*) FILTER (no_provenance_time OR no_comparison_value) = 0, and agaptext. Per ticket the value isCASE WHEN undeclared > 0 THEN 0 WHEN certifiable THEN 1 ELSE NULL— a zero-provenance slot now yieldsNULLwith the gap named, never 1 (my scratch run:nodes=0, certifiable=0, value=NULL), and aclause4/'certifiable'row is emitted for every slot, which closes the round-1 observation that the absence was silent in the per-node section. Run level:CASE WHEN undeclared > 0 THEN 0 WHEN open_drains > 0 OR not_certifiable_slots > 0 THEN NULL ELSE 1 END, withclause4/slots_not_certifiableandclause4/slots_certifiableprinted. Precedence (detection ⇒ VOID, then open drain ⇒ paused, then uncertifiable ⇒ n/a, else pass) matches the recorded reading in §8 T11 and M0-R36's clause-4 bullet (measurement.md:353-358) and T9's cross-reference. No approved ruling is weakened: M0-R16 still voids an undeclared change, and then/adirection is strictly stricter than the alternative T11 explicitly names as fail-open and leaves to an operator ruling.infra_eventnow separatescause_listed(on M0-R32's I1–I5 list) fromevidenced;infrais filtered onWHERE cause_listedonly, carryingnandn_unevidenced, so the M0-R33 void row (count(DISTINCT ticket) FROM infravssample_size) once again counts every listed cause, evidenced or not — the pre-00fb54abehaviour the finding asked for.infra_unqualifiedis now off-list causes only (a NULL/missing cause lands there too, since theLEFT JOINleavescause_listedfalse — fail-closed). The mandate is surfaced instead of applied:infra_reruns_unevidencedper ticket and in total,infra_reruns_unevidenced_ticketsas a distinct-ticket count, and the void row's note now names how many of the counted tickets lacked evidence, so the round-1 inconsistency (per-ticket "open ruling" note beside a decided run-level verdict) is gone. §8 T10 is narrowed to off-list causes and T12 records this reading with the fail-open argument. Direction is stricter than before on a gate clause (clause 3), and M0-R32's "each needs recorded evidence" is still enforced as a printed, named flag.Findings on the delta
clause4aggregates withoutGROUP BY, so with an emptyclause4_ticket—sample_ticketempty, i.e. no orderedsign.snapshotrows —sum(undeclared)is NULL andnot_certifiable_slotsis 0, soundeclared > 0is NULL, bothWHENs fall through and the row printsvalue = 1withpass: … all 0 slots compared(confirmed on the scratch engine: verdict 1, tickets 0). At9a6530ethe same input returned0/VOID via itsELSE 0branch, so this is a small regression introduced by the rewrite. Reachability is low and self-announcing (an unsigned sample also printssignature_integrity 0 released / 0 signedand NULL dfpy denominators, and the note itself says "all 0 slots"), which is why this is a concern and not a blocker, but the fix's own principle — empty denominator printsn/a, never a pass — should cover it. Suggested one-liner:CASE WHEN tickets = 0 THEN NULL WHEN undeclared > 0 THEN 0 WHEN open_drains > 0 OR not_certifiable_slots > 0 THEN NULL ELSE 1 END, with a note branch "no signed slot to compare (n/a)".n/a, never0" convention to M0-R37, whose text scopes it to precision and canary recall. The doc is honest about this ("applies the M0-R37 reading"), and the direction is stricter, but naming it an extension of that convention would make the provenance of the extension unambiguous for a frozen doc.Checked for weakening (no finding)
Rule IDs and their text outside the two edited bullets are untouched; §8 grows T11/T12 in the file's existing "recorded reading" convention rather than inventing rules; the
undeclaredtotal is still the sum of the four component reads (sum(undeclared)replaces the identical inline sum);declared_in_drainis still split out and never counted against the clause; the fixed-20 denominator, clause 1–3, the canary gate and the five conditions are unchanged by this delta.Both filed findings are closed and fail-closed. One adjacent concern (zero-slot run-level pass) and one wording suggestion remain; neither re-opens finding 1 (a slot with zero provenance rows now cannot pass) nor finding 2 (every listed cause counts again, with the evidence gap printed beside it).
Verdict: APPROVE
Fix round 3 pushed (
a4e0176) — Opus-check blockers closedEvery blocker and every fix-before-freeze item from the 2026-10-22 Opus check of
3e0ba08is addressed in this head.report.sqlnow actually executes — it is run against a local Postgres 15 with a B5-shaped fixture (3 tables), not merely parsed. The old head had never been executed and carried real defects a parser cannot see (a baret.ticketin thepredecessorsUNION arm, forward CTE references, integer division).Blockers
::numeric; 14/20 prints0.70000000000000000000,dfpy_confirmedcorrect below 100%landing.merged→head_shaandmerge_commit_sha(legacymerged_sharead as the merge commit). Condition 2 reads the reviewed head; condition 3 requiresprobe_sha = merge_commit_sha—probe_okalone no longer passesrecorded_at; drains carry adrain_idand pair on it, never by position; only operator-actor drains aredeclared; a re-baseline counts only when the operator wrote it inside a declared drain (baselines/rejectedprints refusals)sample_idon every record and every CTE;signed20capped by the order bound andLIMIT sample_size;decided_unsignedcounts onlyat >= signed_atgoalexposesg.id; the replay joinsr.payload->>'goal_check_id' = g.id::textFix before freeze
ac_sha_mismatchfails condition 1 and printsre_spec+ miss reasonre_spec_ac_sha.evidence_ok; printed by kind.node @> baseline); NULL on either side = not certifiable, not a mismatch.order, own AC sha), released by the operator, not another signed slot, not a spare of a spare, before the claim. Five rejection paths each verified.n/a("no signed slots"), verdictNOT_CERTIFIABLE; an empty sample can never print a clause-4 pass.clause1_vs_70,clause2,clause3_void,clause4,gate_verdict∈ {PASS, FAIL, VOID, NOT_CERTIFIABLE}.decision='pass'implies green CI on that head; CI is never fetched separately.New §8 open items (implemented fail-closed, not decided)
cond4_uncertifiable_slots. This is the one open item that moves the gate number.live_probe_failures.deploy/**, every pin bump is blocked during the sample. Build/ruling question, no query change.Verification (assertions, not eyeballing)
45 assertions across 24 scenarios, all green: empty schema, 20/20, 14/20 at the boundary, declared drain + re-baseline, undeclared runtime re-baseline, in-window vs out-of-window mismatch, open declared drain, drain without an id, gate decision on the merge commit, probe on the wrong commit, legacy
merged_sha, re-spec, 5 replacement-rejection paths, revert / unevidenced fix / evidenced fix / failed-probe escapes, 5 infra re-runs (void), off-list cause, all-inconclusive checks, fresh sample S2, duplicate slot order (still 20), replay reproduced / not reproduced, precision 0/1.Mechanical: one statement (1 semicolon outside comments/strings, 590 balanced parens, sqlglot parses 1), 41 rules defined, every
M0-Rncited exists, X1–X15 defined.Do not merge. This freezes only after the operator's Opus re-check; X13/X14/X15 need rulings before the sign.
Verdict: APPROVE — head
a4e0176(no blockers; 1 item to land before freeze)Fresh-context review (deepseek landing worker), primary sources only: the diff at
a4e0176, the 2026-10-22T05:13 Opus check and the ticket's rulings on HOMELAB-1929,design/ld5/measurement.md,design/ld5/report.sql. Reviewer ≠ implementer; I did not write this change.Not merged. After this APPROVE the operator runs the Opus re-check before freeze — that review, not this one, decides whether M0 is frozen at the LD5 sign (B20).
I executed the query myself, not the claim
Fresh Postgres 15 cluster on my own port (
initdb→/tmp/pgdata, socket/tmp, port 55433; the fix worker's instance on 55432 was not reachable and its TCP port is held by another process). Re-ran the worker's harness unmodified except for the port:24 scenarios, incl. empty schema, 14/20 boundary, drain/re-baseline, replacement rejections, replay reproduced/not, fresh-sample isolation).0.65000000000000000000, clause 1FAIL: 65.0% < 70%, verdict FAIL ·probe_ok=truewithprobe_sha=NULL→ cond3 false, clause 219/20, verdict FAIL · 14/20 →PASS: 70.0% >= 70%(boundary is>=, correct) · operator-start/runtime-end drain → not declared → clause 4 VOID · one slot with nonode_instancesrow → clause 4not certifiable (n/a), verdictNOT_CERTIFIABLE.select 14/20prints0; the query prints0.70, so the cast is real and load-bearing.Opus blockers — all five closed (code read + executed)
gate_numberscasts every scalar to::numeric(report.sql:1187-1205); no bigint/bigint division remains (I machine-scanned every/outside comments/strings:report.sql:1558,1587,1594,1602,1624-1633,1641,1676,1691,1731,1739,1961-1990— all numeric-operand). Verified 14/20 =0.7000…, and 13/20 =0.65below the line.landing.mergedexposeshead_shaandmerge_commit_sha(:716-742, legacymerged_sharead as the merge commit only). Condition 2 readsgate_on_headjoined onm.head_sha— the reviewed head, any round (:744-761,:884). Condition 3 ispin_moved AND probe_ok AND probe_sha IS NOT NULL AND probe_sha = merge_commit_sha(:886-890), soprobe_okalone no longer passes. Verified both ways (d1,d2, and my ownprobe_sha=NULLscenario).in_force_from <= recorded_at(:555-563). Starts/ends paired ondrain_id,FULL JOINnever by position (:324-362).declaredrequires operator actor at the start and at the end and a realdrain_id(:359-361); a re-baseline is a baseline only when the operator wrote it inside a declared drain (:367-403), refusals counted and printed (:404-422,baselines/rejected). Verified: in-drain declared vs out-of-window VOID, open drain → NOT_CERTIFIABLE, no-drain_id→ not declared, operator-start/runtime-end → not declared and VOID.sample_idfilters on every events/workflows CTE; the one CTE that readsnode_instancesis scoped throughsample_ticket(:555-557), so no cross-sample leak (asserted by a fresh S2 → 0 slots).signed20capped twice — order bound andLIMIT sample_size(:438-448), duplicateorderflagged while the denominator stays 20.decided_unsignedcounts onlyat >= signed_atand excludes canary/replacement tickets (:1157-1166).goalexposesg.id::textand both thefail_standsandconclusivelookups join replays onr.payload->>'goal_check_id' = g.id::text(:762-813). The payload key lives on the replay event, not on the header ofgoal.check, so a header-faithful runtime now gets its replay counted. Verified reproduced → lowers DFPY; not reproduced → does not, and enters precision 0/1.Fix-before-freeze list — closed
ac_sha_mismatchin cond1 (:877), miss reasonre_spec_ac_sha(:958),re_specmetric (:1310-1319). One gap below.revert/red_ci_live/ci_red_on_livecount with no flag; every other kind needsevidence_ok(:831-847), each row printed withcountsmarked.NOT (n.image_digests @> b.image_digests)(:549); NULL on either side →no_comparison_value→ not certifiable, not a mismatch (:544-553).order, operator actor, not a signed slot, no spare-of-spare, before the claim (:450-491); all five rejection paths verified, rejections counted (sample_replacements_rejected), and the spare's own AC sha is used, so nothing inherits the replaced ticket's sha (:500-511).tickets = 0→ clause 4 value NULL + "not certifiable (n/a): the sample has no signed slots" (:1825-1851), overallNOT_CERTIFIABLE(:1953-1980). Also holds per-slot when provenance is missing (my own scenario).clause1_vs_70(:1621),clause2(:1638),clause3_void(:1655),clause4(:1825),gate_verdictPASS/FAIL/VOID/NOT_CERTIFIABLE (:1953-2005).:119-122,:242-244) and on the condition-2 row (:1250) —decision='pass'implies green CI on the reviewed head because landing refuses the merge first.measurement.md:285-290); §8 renamed X1–X15 with the explicit "cannot be confused with the T1–T6 safety tests" note and M0-R3's "safety test T4" intact (:452-461); M0-R2 says rule 3 does not carry over unchanged, restated as widened by X2 (:45-53); 41 rules defined, everyM0-Rncited in the SQL exists, one statement, balanced parentheses (I re-checked all of these mechanically;sqlglotis not installed here, but the file executed in Postgres, which is a stronger check).Finding to land before the freeze (1)
re_spechas no total row —measurement.md:282andmeasurement.md:411both promise "the re-spec count per ticket and in total", but the query emitsre_speconly inper_ticket(report.sql:1310); there is noper_totalrow. Gate impact: none (cond1 counts the re-spec per ticket, so a re-specced ticket is already a miss) — but the report contradicts its own contract, which is the class of defect this check exists to catch. Cheapest closure: oneper_totalrow summingrespec_nodes. My verdict coversa4e0176; a commit adding this needs only a delta re-check, not a new full review.Nits (non-blocking)
report.sql:4still reads "NOT EXECUTABLE YET". True against the real DDL, but it now reads as "never run"; say instead that it was executed against a B5-shaped fixture on 2026-10-22 and is not yet runnable against the real B5 schema — that is the honest status and it is what the PR body says./tmp(worker's tree and mine). It is not in the diff, so the "45 assertions pass" claim cannot be re-run after/tmpis cleared — the Opus re-check will have to take it on trust. Recommend committing the fixture, the runner and the assertions (e.g.design/ld5/verify/) before freeze. I have re-run them here, so the claim is true today; it just isn't reproducible.d3assertion labelled "legacymerged_shaok" keepshead_sha. A payload with onlymerged_shanow fails cond2 (correct, fail-closed), but the notes read "legacy payload → pass" without that caveat; worth one line in the notes rather than a test.Open for the operator — not decided by the implementer (verified)
dfpy_provisional_default_pass+cond4_uncertifiable_slotsbeside the gate reading (measurement.mdX13,report.sql:1602-1613). This is the one open item that moves the gate number.live_probe_failures(report.sql:848-863,:1566-1572).Next: operator Opus re-check → freeze (snapshot both blob SHAs at the sign, M0-R1/R35). I did not merge.
Both pre-freeze nits closed (
38fd00f)NIT 1 —
re_spechad no total rowreport.sqlnow emits there_spectotal arm (M0-R25) alongside the per-ticket one, so the M0-R36 promise ("per ticket and in total") is met by the query rather than by the reader:sum(respec_nodes)over the signed slots; denominator =NULLIF(sum(nodes),0)— the same reading as the per-ticket row. NoCOUNT-over-subquery pattern exists among the other total arms, so the plain aggregate mirror (likeinterventions/budget_exhausted) is what was used.a,e1,m1) shows exactly one added line per run and zero modified lines.e1-respec-ac-shathe row reads1|20|1and the mechanical miss reasonre_spec_ac_shais 1.NIT 2 — the suite is committed
design/ld5/testdata/(source only — no data dirs, no dumps, no captured output):Run:
Connection comes from
PGHOST/PGPORT/PGUSER/PGDATABASEwith the documented defaults;REPORT_SQL=points it at a different copy of the query.Assertion run
63/63 pass, on two independent clusters:
initdbPostgreSQL 15.19 on port 55434 (TCP, throwaway data dir, since removed);The five boundaries from the re-check are now permanent assertions, not one-off probes:
x1-exactly-70-percentx4-clause1-65-percent-failx2-probe-ok-null-shaprobe_oktrue withprobe_shaNULL → condition 3 fails, clause 2 FAIL, gate FAILx3-operator-start-runtime-end-drainx5-zero-node-slotnode_instancesrow → clause 4 n/a, NOT_CERTIFIABLE, never a pass(Note for anyone re-deriving these:
T20is legally replaced bySPARE1in the base fixture, soT14..T20moves only 6 in-slot tickets — that is why the 70%-exact case isT14..T20and the 65% case isT13..T19.)Mechanical checks
sqlglot.parse(read='postgres')→ 1 statement (Select).M0-Rncited inreport.sqlexists inmeasurement.md(41 rules defined, no undefined citations).Not merged — the operator runs the Opus re-check before freeze.
Addendum (
6b1c188) — the third nit from the re-checkThe re-check also noted the header still said "NOT EXECUTABLE YET" while the query had been executed. Fixed as a comment-only change (no SQL change): the header now says it executes against
design/ld5/testdata/today and cannot run against the real record DB until B5 lands — and that the suite proves the query is coherent, not that its schema assumptions match the landed DDL.Re-verified after the edit:
sqlglot→ 1 statement;PGHOST=/tmp PGPORT=55433 python3 design/ld5/testdata/assert.py→ all 63 assertions passed.PR head is now
6b1c188. Still not merged.Verdict: APPROVE — delta only (head
6b1c188, over the approveda4e0176)Fresh-context landing worker (deepseek), primary sources only:
git diff a4e0176..6b1c188fetched fromorigin/plane/HOMELAB-1929-m0, the committed files themselves, anddesign/ld5/measurement.mdas the frozen ruleset. Reviewer ≠ implementer; I wrote none of this and was given no implementer summary. Scope is the delta only — the approval ofa4e0176(comment above) is not re-litigated here, and nothing in this verdict covers it.Not merged. The operator's Opus re-check remains the gate before the freeze at the LD5 sign (B20).
(1)
report.sql— only there_spectotal arm movedTwo commits, two hunks in this file, nothing else:
38fd00fM0-R21 usd_totaltotal arm (report.sql:1717-1727)re_spectotal arm; no other arm touched (the diff has no other hunk)6b1c188report.sql:1-16)The arm itself is what was asked for:
sum(t.respec_nodes)::numericas numerator and value,NULLIF(sum(t.nodes), 0)::numericas denominator,FROM scored t. I checkedscoredis not a fan-out:scoredreadsFROM ticket, andticketreadsFROM sample_ticket s— one row per signed slot (report.sql:943-991). So the sum is over the signed 20 exactly, with no double counting, andrespec_nodes/nodesare the same per-ticket columns the per-ticket arm uses (report.sql:926-927,1317-1321). Reading is identical to the per-ticket row ("nodes recorded"), which is the right choice for a denominator.I executed the boundary rather than reading it:
so the aggregate is a real sum (not a
count(*), which would have printed 20) and theNULLIFdenominator collapses to NULL when no node was recorded, matching the per-ticket arm's behaviour.(2) The committed testdata is source-only
37 files,
design/ld5/testdata/+ nothing outside it:schema.sql,fixture.sql,assert.py,README.md,scenarios/*.sql(33).git diff --numstatreports no binary entries;fileon every added path returns UTF-8/ASCII text; a name scan forpgdata|base/|global/|*.dump|*.tar|*.gz|*.zip|*.bin|*.pdf|*.pngreturns nothing; the tree is 176 KB total.schema.sqlis 10 lines and starts withdrop table if exists events, node_instances, workflows— the runner cannot write anywhere except the DB named byPGDATABASE, and it refuses to silently pass becauseassert.pysets-v ON_ERROR_STOP=1on everypsqlcall and exits non-zero on any failure.(3) I ran the suite on my own cluster
Fresh
initdbPostgreSQL 15.19, own throwaway data dir, own free port, unix socket only (listen_addresses='') — I did not reuse the fix worker's instance. Then:63/63. The three assertions the delta exists for pass and are the right assertions:
base: re_spec total present,base: re_spec total value=0,base: re_spec total denominator=20; pluse1-respec-ac-sha→ per-ticketT05= 1, total = 1, andmiss_reason_mechanical/re_spec_ac_sha= 1.Mechanical checks, re-run by me and not taken from the commit message:
sqlglot.parse(read='postgres')→ 1 statement (sqlglot 30.24.0).6b1c188's claim ("one parseable statement") holds.M0-Rncited inreport.sqlexists inmeasurement.md: 36 distinct IDs cited,comm -23against the 41 defined → empty.6b1c188's claim ("63/63 assertions still pass") holds on an independent cluster.measurement.mdwas not touched in this delta —git diff --name-only a4e0176..6b1c188 -- design/ld5/measurement.mdis empty. Correct: a nit fix must not edit the frozen ruleset, and it didn't. The rules did not move, so no assertion was "fixed to match the query" (the README's own updating rule).Concerns (non-blocking, for the freeze record, not for this merge)
report.sql:1720—sum()is exercised by only one planted mismatch in the committed suite.e1re-specs one ticket, so1 of 1is indistinguishable frommax(). My two-ticket probe confirmed the summation is correct, but the committed suite would not catch a futuremax()/count(*)regression. Suggest adding a secondupdate ... where key in ('n-T05','n-T07')scenario asserting2/20.## Checkssection still says "24 scenarios were run"; the committed suite is 33 scenarios / 63 assertions (README andreport.sql:4both say 63). Worth correcting before the description is read as the freeze record.M0-R41is cited inmeasurement.mdand in scenarioj1but nowhere inreport.sql(the X13 rows citesec 8 X13instead). Pre-existing ata4e0176, not introduced here.No blockers. No open question is decided by this delta, no rule text moved, and the header change is honest about what has and has not been proven (executes against the fixture; not executable against the real record DB until B5 lands).
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.Merge
Merge the changes and update on Forgejo.Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.