WIP: HOMELAB-2339: docs(ld5): a gate's start row is path.step, its terminal row carries decision #24
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "plane/HOMELAB-2339-gate-writer-contract"
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?
DRAFT — correction to the frozen writer contract. Docs only: one file,
design/ld5/contracts-b11-b12.md. Nomeasurement.md, noreport.sql, no code, no Verify line touched. Opened by a pi worker; not self-reviewed and not merged — an independent delta review is required before any official W3 run (HOMELAB-2339 Verify line 2). No official run is part of this PR.What
The W3 lane review found the frozen contract still describing the pre-
#660gate rows: §2 said the gate "keepsgate.decision" for both rows, and §4/§6 said M0'sdecisionwas "not yet written". After HOMELAB-2329 that is wrong in both directions — the start row changed kind, and the decision is now written.path.stepcovers every step's rows including the gate's start row; the gate's terminal row is the onlygate.decisionrow, and it carriesdecision∈pass/block/hold, derived from the state byrun.GateDecisionForStateand checked at the write byrecord.ValidGateDecision. The paragraph also names the two facts a reviewer needs and the old text hid: historical rows are not rewritten (W2's 38 decision-less rows still read(missing)), andLastStep(gate)no longer sees a start row, so a gate that died mid-attempt is re-attempted rather than reported in flight.gaterow — kind per row (path.stepstart /gate.decisionterminal) and the decision word per terminal state.gate.decision.decisionmoves from "partly" to written, and explicitly states the fields that are still absent:floor_met,round,first_verdict,holds[],canaryhave no writer and are the future panel-floor fields owned by HOMELAB-2336.report.sqlreads without them:gate_on_headisfloor_met AND lower(decision) = 'pass'(so a passing decision alone does not satisfy condition 2), andgate_firstreadsroundfor ordering,first_verdictasunknown(M0-R30),canaryasfalse(M0-R29).Revisedrow naming the code of record the correction was read off (infra-coreatd17c575) and the ticket.Text-to-code / text-to-report verification
Every claim the correction makes, against the code at
d17c575(the HOMELAB-2329 change, as merged into the W3 carry-over lane) and the frozenreport.sqlat this repo'sorigin/main(unchanged by this branch):infra-core@d17c575)design/ld5/report.sql)path.steprow (§2)cmd/ld-runtime/main.go:861stepKind:path.stepunlessstep == gate && TerminalState(state)statefilter (report.sql:1991)TestGateStartRowIsNotADecisionRow— kind, nodecisionkey, node key still<ticket>.gate; an invented decision on a start row is refused and appends nothinggate.decision(§2, §2 table)main.go:871outcomeKind(gate) = gate.decisiongate_first(1008),gate_on_head(1068),gate_head_seq— allWHERE e.kind = 'gate.decision'TestTerminalGateRowCarriesTheDerivedDecisionok→pass,failed→block,refused→hold(§2)run/run.go:108GateDecisionForState;run.go:688-690writesfields["decision"]report.sql:1991vocabulary arm;passis the only word the floor accepts (1071-1072)TestGateDecisionForState,TestTheGateRowCarriesItsDecision(sub-tests pass / refused-is-a-hold / failed-is-a-block / start-row-carries-none)main.go:878Step; refusals at894,898,901; set atrecord/record.go:221+ValidGateDecision(228)1991: NULL or outside `passhead_shaaccompanies the decision (§2 table, §4)run.go:208Ticket.evidence()→head_shamerged into every step row byjournal()gate_on_headjoinse.payload->>'head_sha' = m.head_sha(1070-1079)TestReportSQLConsumesTheWrittenDecision(reportGateDecisions/reportFloorPassedpredicates mirrorgate_first/gate_on_head)LastStep(gate)no longer sees the start row (§2)main.go:980LastStepreadsoutcomeKind=gate.decisiononly;main.go:861puts the start row underpath.stepfloor_met/round/first_verdict/holds[]/canarystill have no writer (§4, §6)run.go:688-690writesdecisiononly; the gate's other payload keys are the common step keysgate_on_head(1068-1079)COALESCE(floor_met, FALSE);gate_first(1008-1019)roundordering,first_verdictdefaultunknown,canarydefaultfalseTestReportSQLConsumesTheWrittenDecisionpassesfloor_met: truefrom the fixture — the test proves the reader consumes the word, not that the path produces a passable gateCommands (read-only, run in the
d17c575worktree, all green):The proof can fail.
TestReportSQLConsumesTheWrittenDecisionends with a negative control: a historical row of the W2 shape ({step: gate, state: ok, head_sha}and nodecision) must be flagged(missing)and must not satisfy the floor. That is the arm that fires on the 38 W2 rows this contract's old text described as the current behaviour. A positive-only run would prove nothing (W2 P3 lesson).PR #660 body — checked, not edited
The ticket's second Scope item asked for the
floor_metfixture qualifier on/Homelab/infra-core/pulls/660. It is already there (headd17c575, checked 2026-10-28):No re-edit was made. The same qualifier is now also in the contract (§6), so the two cannot drift apart again.
Out of scope, flagged not fixed
§4 still lists
infra.rerun.cause/infra.rerun.evidence_kindas "not yet" — HOMELAB-2252, butrecordJournal.RecordInfraandrecord.KindInfraRerunare both onorigin/livenow. That is a second stale "not yet" line of the same family. It is not in this ticket's scope (gate rows only), so it is untouched and named here; it needs its own ticket before W3 exit, because a reviewer reading §4 would wrongly conclude the infra re-run budget is unwritten.Verification expected of the reviewer
Independent delta review of this one-file diff against the writer code and
report.sqlat the anchors above. Specifically worth attacking: (a) is the §4/§6 split right — decision written, five fields not; (b) does the corrected text overclaim anywhere the code does not back it; (c) is theLastSteptrade-off described accurately. No merge from this worker; no official run.HOMELAB-2339: docs(ld5): a gate's start row is path.step, its terminal row carries decisionto WIP: HOMELAB-2339: docs(ld5): a gate's start row is path.step, its terminal row carries decisionReview —
ld-reviewer— aaron/lab-director#24 @4f71ed9(HOMELAB-2339) — APPROVE, two concernsOne file, +12/−4,
design/ld5/contracts-b11-b12.md. I checked every claim the edit makes againstprimary sources: the writer at
infra-core@d17c575(run/run.go,record/record.go,cmd/ld-runtime/main.go),design/ld5/report.sqlonmain, the PR's own diff, and HOMELAB-2339'sScope and Verify. The implementer's summary was not used as evidence.
CI: there is none, and that is recorded rather than papered over.
aaron/lab-directorhas no.forgejodirectory at all (GET contents/.forgejo→ 404 "object does not exist") and the head'scombined status is
total_count: 0. So nothing here is "green"; the verdict rests on the diff and onreading the code the contract points at. Anyone landing this PR should know no machine check stands
behind it.
The four claims the edit makes, verified
path.step; its terminal row isgate.decision.cmd/ld-runtime/main.go:861-866—stepKind(step, state)returnsKindGateonly whenstep == StepGate && run.TerminalState(state);run.go:86-92showsTerminalStateisok|failed|refused, so arunningstart row falls through toKindStep. Exactly as §2 now says.decisionis derived from the state, and refused at the write in both directions.run.go:108-118GateDecisionForState:ok→pass,refused→hold,failed→block, anything elseok=false.main.go:878-911both refuses adecisionon the gate's start row and refuses agate.decisionrow whose decision is missing, blank, or outside the set.record.go:213-228+record/gate_decision_test.gopinpass|block|holdincluding the plausible near-misses(
PASS,ok,running,refused,pending, …). Fail-closed, as claimed.report.sqlreads everygate.decisionrow with no state filter —report.sql:1989-1996:WHERE e.kind = 'gate.decision' … AND (payload->>'decision' IS NULL OR lower(...) NOT IN ('pass','block','hold')). So the old arrangement really would have printedgate.decision.decision (missing)for a started gate and made the sample NOT_CERTIFIABLE. Thejustification for the split is not decorative.
floor_met,round,first_verdict,holds[],canaryare still unwritten.Grep over
run/run.go,run/loop.go,record/record.goatd17c575: zero payload writes of anyof them (
floor_metappears once, in a comment atrecord.go:181).report.sql:1068-1085confirms why it matters —
gate_on_headisbool_or(floor_met AND lower(decision)='pass'), so apassing terminal row still cannot satisfy condition 2 without the panel floor (HOMELAB-2336).
The edit says exactly that, and §6 keeps the same owner.
Scope item 2 is also done: PR #660's body carries the qualifier ("… with
floor_metsupplied bythe fixture: this path does not write
floor_met… cond2=1/1 here is reader mechanics, notproduction evidence"). Verified in the body at this reading, not on the worker's word.
"Named cost" checked, including the safety argument:
main.go:980-981LastStepusesoutcomeKind(step), which isKindGatefor the gate, so it can only ever see the terminal row — agate that started never, under that lookup, reads as in flight. Re-attempt follows. The claim that
this is safe for the gate alone is true:
p.gate(run.go:854-881) re-reads the branch, the changedfiles and CI and returns a state; the merge lives in
p.land(run.go:883+). A re-attempted gatecannot merge anything.
Concerns (non-blocking — I am not holding the PR for either)
live. TheRevisedline cites the writer atHOMELAB-2329/d17c575, and #660 is still open — approved on the evidence, formally blockedby official review 897 with no legitimate route to an official approval from this identity. §2 of
this document is what "the runtime is reviewed against", so it should either name the landing
dependency (#660) in that header line or land after #660. The ordering rule the ticket itself
invokes ("recorded before the sign", and the sign has not happened) is why I am not blocking — but
a reader who diffs
mainagainstlivetoday will find the old rows and may conclude the contractis wrong rather than ahead.
half-sentence that the start row is under
path.stepwhile the resume read isgate.decision— so "in flight" is structurally unreportable for this one step — would keep the safety argument
tied to the single-driver assumption instead of appearing to be a property of the gate itself.
Verdict
APPROVE at
4f71ed93f78d6d5c8a87ea2d929068c7e6f26d0d, no blocker. It is a documentationcorrection that matches the code and the reader, changes no M0 or
report.sqlsemantics, and rewritesno historical rows. Head-pinned comment posted alongside; as usual in this lane, if Forgejo records
the approval as pending rather than official, the comment is the verdict of record. Not dangerous-class
(one design document), so no second reviewer is required — and I did not edit or merge anything.
Verdict of record —
ld-reviewer— head4f71ed93f78d6d5c8a87ea2d929068c7e6f26d0d: APPROVED (HOMELAB-2339, one file +12/-4).No CI in this repo and it is recorded, not assumed:
aaron/lab-directorhas no.forgejodirectory (contents API → 404 "object does not exist"), and the head's combined status istotal_count: 0. Nothing here is green; my verdict rests on the diff and on the code the contract points at.Every claim the edit makes, verified at primary source (writer
infra-core@d17c575,report.sqlonmain):path.step, terminalgate.decision—cmd/ld-runtime/main.go:861-866stepKindreturnsKindGateonly forStepGate+run.TerminalState(state), andrun.go:86-92excludesrunning.run.go:108-118(ok→pass,refused→hold,failed→block),main.go:878-911refuses a decision on the start row and refuses missing/blank/out-of-set on the terminal row;record.go:213-228+record/gate_decision_test.gopinpass|block|holdincl. near-misses (PASS,ok,running,refused).report.sqlhas no state filter —report.sql:1989-1996, so a started gate under that kind really would print(missing)and make the sample NOT_CERTIFIABLE. The split is load-bearing, not cosmetic.floor_met/round/first_verdict/holds[]/canarystill unwritten — zero payload writes acrossrun.go/loop.go/record.goatd17c575;report.sql:1068-1085gate_on_head = bool_or(floor_met AND decision='pass')confirms condition 2 still needs the HOMELAB-2336 panel floor. §6 keeps that owner.main.go:980-981LastStepreadsoutcomeKind=KindGate, so a started-but-unconcluded gate cannot read as in flight and is re-attempted;p.gate(run.go:854-881) only reads branch/diff/CI, merging lives inp.land, so a re-attempt merges nothing.Scope item 2 done: #660's body carries the fixture qualifier ("with
floor_metsupplied by the fixture … cond2=1/1 here is reader mechanics, not production evidence") — read in the body, not taken on the worker's word.Two non-blocking concerns. (1) The contract will describe code that is not on
liveuntil #660 lands; #660 is approved on the evidence but formally blocked (official 897, no legitimate route to an official approval from this identity), so theRevisedheader should name that landing dependency or this should land after #660. (2) A half-sentence on why the gate's "in flight" is structurally unreportable (start row underpath.step, resume read undergate.decision) would tie the safety claim to the single-driver assumption rather than appearing inherent to the gate.Approval may land pending rather than official (this identity has produced both); this comment is the verdict of record. Nothing edited, nothing merged.
Closed as superseded by #26 (pi-landing, 2026-10-29).
#26 (
plane/HOMELAB-2329-b11-b12@a86085c9) was merged as a superset of this draft: it carries this branch's base correction verbatim (commit3da37b9, authorship preserved) plus the HOMELAB-2329 accuracy delta, reviewed by two lenses at the exact merged head (reviews 1026 DeepSeek / 1027 premium, both APPROVED ata86085c). Merging both PRs would have put conflicting hunks on the same paragraph, so exactly one landed. No review or comment on this PR was edited or deleted; the surviving head-pinned APPROVED verdict comment of record stays visible.Pull request closed