WIP: HOMELAB-2339: docs(ld5): a gate's start row is path.step, its terminal row carries decision #24

Closed
aaron wants to merge 1 commit from plane/HOMELAB-2339-gate-writer-contract into main
Owner

DRAFT — correction to the frozen writer contract. Docs only: one file, design/ld5/contracts-b11-b12.md. No measurement.md, no report.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-#660 gate rows: §2 said the gate "keeps gate.decision" for both rows, and §4/§6 said M0's decision was "not yet written". After HOMELAB-2329 that is wrong in both directions — the start row changed kind, and the decision is now written.

  • §2 (kind rule) — path.step covers every step's rows including the gate's start row; the gate's terminal row is the only gate.decision row, and it carries decision ∈ pass/block/hold, derived from the state by run.GateDecisionForState and checked at the write by record.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)), and LastStep(gate) no longer sees a start row, so a gate that died mid-attempt is re-attempted rather than reported in flight.
  • §2 table, gate row — kind per row (path.step start / gate.decision terminal) and the decision word per terminal state.
  • §4 vocabulary table — gate.decision.decision moves from "partly" to written, and explicitly states the fields that are still absent: floor_met, round, first_verdict, holds[], canary have no writer and are the future panel-floor fields owned by HOMELAB-2336.
  • §6 — the remaining gate-row gap restated as exactly those five fields, with what report.sql reads without them: gate_on_head is floor_met AND lower(decision) = 'pass' (so a passing decision alone does not satisfy condition 2), and gate_first reads round for ordering, first_verdict as unknown (M0-R30), canary as false (M0-R29).
  • Header — a Revised row naming the code of record the correction was read off (infra-core at d17c575) 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 frozen report.sql at this repo's origin/main (unchanged by this branch):

Contract sentence (§) Writer anchor (infra-core @ d17c575) Report anchor (design/ld5/report.sql) Control test (ran, passed)
gate start row is a path.step row (§2) cmd/ld-runtime/main.go:861 stepKind: path.step unless step == gate && TerminalState(state) vocabulary arm has no state filter (report.sql:1991) TestGateStartRowIsNotADecisionRow — kind, no decision key, node key still <ticket>.gate; an invented decision on a start row is refused and appends nothing
terminal gate row is gate.decision (§2, §2 table) main.go:871 outcomeKind(gate) = gate.decision gate_first (1008), gate_on_head (1068), gate_head_seq — all WHERE e.kind = 'gate.decision' TestTerminalGateRowCarriesTheDerivedDecision
ok→pass, failed→block, refused→hold (§2) run/run.go:108 GateDecisionForState; run.go:688-690 writes fields["decision"] report.sql:1991 vocabulary arm; pass is 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)
the writer refuses a missing, blank or out-of-set decision (§2, §4) main.go:878 Step; refusals at 894, 898, 901; set at record/record.go:221 + ValidGateDecision (228) 1991: NULL or outside `pass block
head_sha accompanies the decision (§2 table, §4) run.go:208 Ticket.evidence() → head_sha merged into every step row by journal() gate_on_head joins e.payload->>'head_sha' = m.head_sha (1070-1079) TestReportSQLConsumesTheWrittenDecision (reportGateDecisions/reportFloorPassed predicates mirror gate_first/gate_on_head)
LastStep(gate) no longer sees the start row (§2) main.go:980 LastStep reads outcomeKind = gate.decision only; main.go:861 puts the start row under path.step — (resume, not report) read off the code; the behaviour change is named in the PR #660 body as a deliberate trade-off
floor_met/round/first_verdict/holds[]/canary still have no writer (§4, §6) run.go:688-690 writes decision only; the gate's other payload keys are the common step keys gate_on_head (1068-1079) COALESCE(floor_met, FALSE); gate_first (1008-1019) round ordering, first_verdict default unknown, canary default false TestReportSQLConsumesTheWrittenDecision passes floor_met: true from the fixture — the test proves the reader consumes the word, not that the path produces a passable gate

Commands (read-only, run in the d17c575 worktree, all green):

go test ./record/ -run TestGateDecisionVocabularyIsTheFrozenSet -v            PASS
go test ./run/    -run 'TestGateDecisionForState|TestTheGateRowCarriesItsDecision' -v   PASS
go test ./cmd/ld-runtime/ -run 'TestGateStartRowIsNotADecisionRow|TestReportSQLConsumesTheWrittenDecision|TestGateDecisionRowIsRefusedWithoutADecision|TestGateDecisionRowRejectsWordsOutsideTheVocabulary|TestTerminalGateRowCarriesTheDerivedDecision' -v   PASS

The proof can fail. TestReportSQLConsumesTheWrittenDecision ends with a negative control: a historical row of the W2 shape ({step: gate, state: ok, head_sha} and no decision) 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_met fixture qualifier on /Homelab/infra-core/pulls/660. It is already there (head d17c575, checked 2026-10-28):

RUN A (rows this fix writes): … condition 2_ci_and_review_floor_on_head = 1/1 pass — with floor_met supplied by the fixture: this path does not write floor_met (0 of 38 live gate rows carry it; review-floor enforcement is not on this path — HOMELAB-2336), so cond2=1/1 here is reader mechanics, not production evidence

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_kind as "not yet" — HOMELAB-2252, but recordJournal.RecordInfra and record.KindInfraRerun are both on origin/live now. 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.sql at 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 the LastStep trade-off described accurately. No merge from this worker; no official run.

DRAFT — correction to the frozen writer contract. Docs only: one file, `design/ld5/contracts-b11-b12.md`. No `measurement.md`, no `report.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-`#660` gate rows: §2 said the gate "keeps `gate.decision`" for both rows, and §4/§6 said M0's `decision` was "not yet written". After HOMELAB-2329 that is wrong in both directions — the start row changed kind, and the decision is now written. - **§2 (kind rule)** — `path.step` covers every step's rows **including the gate's start row**; the gate's **terminal** row is the only `gate.decision` row, and it carries `decision` ∈ `pass`/`block`/`hold`, derived from the state by `run.GateDecisionForState` and checked at the write by `record.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)`), and `LastStep(gate)` no longer sees a start row, so a gate that died mid-attempt is re-attempted rather than reported in flight. - **§2 table, `gate` row** — kind per row (`path.step` start / `gate.decision` terminal) and the decision word per terminal state. - **§4 vocabulary table** — `gate.decision.decision` moves from "partly" to written, and **explicitly states the fields that are still absent**: `floor_met`, `round`, `first_verdict`, `holds[]`, `canary` have no writer and are the **future panel-floor fields owned by HOMELAB-2336**. - **§6** — the remaining gate-row gap restated as exactly those five fields, with what `report.sql` reads without them: `gate_on_head` is `floor_met AND lower(decision) = 'pass'` (so a passing decision alone does **not** satisfy condition 2), and `gate_first` reads `round` for ordering, `first_verdict` as `unknown` (M0-R30), `canary` as `false` (M0-R29). - **Header** — a `Revised` row naming the code of record the correction was read off (`infra-core` at `d17c575`) 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 frozen `report.sql` at this repo's `origin/main` (unchanged by this branch): | Contract sentence (§) | Writer anchor (`infra-core` @ `d17c575`) | Report anchor (`design/ld5/report.sql`) | Control test (ran, passed) | |---|---|---|---| | gate start row is a `path.step` row (§2) | `cmd/ld-runtime/main.go:861` `stepKind`: `path.step` unless `step == gate && TerminalState(state)` | vocabulary arm has no `state` filter (`report.sql:1991`) | `TestGateStartRowIsNotADecisionRow` — kind, no `decision` key, node key still `<ticket>.gate`; an invented decision on a start row is refused and appends nothing | | terminal gate row is `gate.decision` (§2, §2 table) | `main.go:871` `outcomeKind(gate) = gate.decision` | `gate_first` (`1008`), `gate_on_head` (`1068`), `gate_head_seq` — all `WHERE e.kind = 'gate.decision'` | `TestTerminalGateRowCarriesTheDerivedDecision` | | `ok`→`pass`, `failed`→`block`, `refused`→`hold` (§2) | `run/run.go:108` `GateDecisionForState`; `run.go:688-690` writes `fields["decision"]` | `report.sql:1991` vocabulary arm; `pass` is 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) | | the writer refuses a missing, blank or out-of-set decision (§2, §4) | `main.go:878` `Step`; refusals at `894`, `898`, `901`; set at `record/record.go:221` + `ValidGateDecision` (`228`) | `1991`: NULL or outside `pass|block|hold` → `(missing)` → sample `NOT_CERTIFIABLE` | `TestGateDecisionRowIsRefusedWithoutADecision`, `TestGateDecisionRowRejectsWordsOutsideTheVocabulary` (`PASS`, `done`, `approved`, `pending`, `running`, …), `TestGateDecisionVocabularyIsTheFrozenSet` (pins the constants to `pass`/`block`/`hold`) | | `head_sha` accompanies the decision (§2 table, §4) | `run.go:208` `Ticket.evidence()` → `head_sha` merged into every step row by `journal()` | `gate_on_head` joins `e.payload->>'head_sha' = m.head_sha` (`1070-1079`) | `TestReportSQLConsumesTheWrittenDecision` (`reportGateDecisions`/`reportFloorPassed` predicates mirror `gate_first`/`gate_on_head`) | | `LastStep(gate)` no longer sees the start row (§2) | `main.go:980` `LastStep` reads `outcomeKind` = `gate.decision` only; `main.go:861` puts the start row under `path.step` | — (resume, not report) | read off the code; the behaviour change is named in the PR #660 body as a deliberate trade-off | | `floor_met`/`round`/`first_verdict`/`holds[]`/`canary` still have no writer (§4, §6) | `run.go:688-690` writes `decision` only; the gate's other payload keys are the common step keys | `gate_on_head` (`1068-1079`) `COALESCE(floor_met, FALSE)`; `gate_first` (`1008-1019`) `round` ordering, `first_verdict` default `unknown`, `canary` default `false` | `TestReportSQLConsumesTheWrittenDecision` passes `floor_met: true` **from the fixture** — the test proves the reader consumes the word, not that the path produces a passable gate | Commands (read-only, run in the `d17c575` worktree, all green): ``` go test ./record/ -run TestGateDecisionVocabularyIsTheFrozenSet -v PASS go test ./run/ -run 'TestGateDecisionForState|TestTheGateRowCarriesItsDecision' -v PASS go test ./cmd/ld-runtime/ -run 'TestGateStartRowIsNotADecisionRow|TestReportSQLConsumesTheWrittenDecision|TestGateDecisionRowIsRefusedWithoutADecision|TestGateDecisionRowRejectsWordsOutsideTheVocabulary|TestTerminalGateRowCarriesTheDerivedDecision' -v PASS ``` **The proof can fail.** `TestReportSQLConsumesTheWrittenDecision` ends with a negative control: a historical row of the W2 shape (`{step: gate, state: ok, head_sha}` and no `decision`) 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_met` fixture qualifier on `/Homelab/infra-core/pulls/660`. **It is already there** (head `d17c575`, checked 2026-10-28): > RUN A (rows this fix writes): … condition 2_ci_and_review_floor_on_head = 1/1 pass — **with `floor_met` supplied by the fixture**: this path does not write `floor_met` (0 of 38 live gate rows carry it; review-floor enforcement is not on this path — HOMELAB-2336), so cond2=1/1 here is reader mechanics, not production evidence 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_kind` as **"not yet" — HOMELAB-2252**, but `recordJournal.RecordInfra` and `record.KindInfraRerun` are both on `origin/live` now. 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.sql` at 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 the `LastStep` trade-off described accurately. No merge from this worker; no official run.
Corrects design/ld5/contracts-b11-b12.md against the merged writer change
(HOMELAB-2329, d17c575) that the W3 lane review found the frozen contract
still described pre-#660:

- §2: kind rule — the gate's start row is a path.step row; only the terminal
  row is a gate.decision row, and it carries decision pass|block|hold derived
  by run.GateDecisionForState and checked at the write by
  record.ValidGateDecision. Names the LastStep consequence and that historical
  rows are not rewritten.
- §2 table: the gate row's kind and payload per state.
- §4: gate.decision.decision is written; floor_met, round, first_verdict,
  holds[] and canary are not, and are HOMELAB-2336's panel-floor fields.
- §6: the remaining gate-row gap restated as those five fields, with what
  report.sql reads without them (gate_on_head needs floor_met; gate_first
  ordering, first_verdict, canary defaults).

No change to measurement.md, report.sql or any Verify line.
aaron changed title from HOMELAB-2339: docs(ld5): a gate's start row is path.step, its terminal row carries decision to WIP: HOMELAB-2339: docs(ld5): a gate's start row is path.step, its terminal row carries decision 2026-10-28 04:34:48 +00:00
ld-reviewer left a comment
Collaborator

Review — ld-reviewer — aaron/lab-director#24 @ 4f71ed9 (HOMELAB-2339) — APPROVE, two concerns

One file, +12/−4, design/ld5/contracts-b11-b12.md. I checked every claim the edit makes against
primary sources: the writer at infra-core@d17c575 (run/run.go, record/record.go,
cmd/ld-runtime/main.go), design/ld5/report.sql on main, the PR's own diff, and HOMELAB-2339's
Scope 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-director has no
.forgejo directory at all (GET contents/.forgejo → 404 "object does not exist") and the head's
combined status is total_count: 0. So nothing here is "green"; the verdict rests on the diff and on
reading 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

  1. The gate's start row is path.step; its terminal row is gate.decision.
    cmd/ld-runtime/main.go:861-866 — stepKind(step, state) returns KindGate only when
    step == StepGate && run.TerminalState(state); run.go:86-92 shows TerminalState is
    ok|failed|refused, so a running start row falls through to KindStep. Exactly as §2 now says.
  2. decision is derived from the state, and refused at the write in both directions.
    run.go:108-118 GateDecisionForState: ok→pass, refused→hold, failed→block, anything else
    ok=false. main.go:878-911 both refuses a decision on the gate's start row and refuses a
    gate.decision row whose decision is missing, blank, or outside the set. record.go:213-228 +
    record/gate_decision_test.go pin pass|block|hold including the plausible near-misses
    (PASS, ok, running, refused, pending, …). Fail-closed, as claimed.
  3. report.sql reads every gate.decision row 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 printed
    gate.decision.decision (missing) for a started gate and made the sample NOT_CERTIFIABLE. The
    justification for the split is not decorative.
  4. floor_met, round, first_verdict, holds[], canary are still unwritten.
    Grep over run/run.go, run/loop.go, record/record.go at d17c575: zero payload writes of any
    of them (floor_met appears once, in a comment at record.go:181). report.sql:1068-1085
    confirms why it matters — gate_on_head is bool_or(floor_met AND lower(decision)='pass'), so a
    passing 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_met supplied by
the fixture
: this path does not write floor_met … cond2=1/1 here is reader mechanics, not
production evidence"). Verified in the body at this reading, not on the worker's word.

"Named cost" checked, including the safety argument: main.go:980-981 LastStep uses
outcomeKind(step), which is KindGate for the gate, so it can only ever see the terminal row — a
gate 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 changed
files and CI and returns a state; the merge lives in p.land (run.go:883+). A re-attempted gate
cannot merge anything.

Concerns (non-blocking — I am not holding the PR for either)

  1. The contract will describe code that is not on live. The Revised line cites the writer at
    HOMELAB-2329 / d17c575, and #660 is still open — approved on the evidence, formally blocked
    by 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 main against live today will find the old rows and may conclude the contract
    is wrong rather than ahead.
  2. Say why the re-attempt is bounded. The "Named cost" paragraph is accurate as written; a
    half-sentence that the start row is under path.step while the resume read is gate.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 documentation
correction that matches the code and the reader, changes no M0 or report.sql semantics, and rewrites
no 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.

## Review — `ld-reviewer` — aaron/lab-director#24 @ `4f71ed9` (HOMELAB-2339) — APPROVE, two concerns One file, +12/−4, `design/ld5/contracts-b11-b12.md`. I checked every claim the edit makes against **primary sources**: the writer at `infra-core@d17c575` (`run/run.go`, `record/record.go`, `cmd/ld-runtime/main.go`), `design/ld5/report.sql` on `main`, the PR's own diff, and HOMELAB-2339's Scope 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-director` has no `.forgejo` directory at all (`GET contents/.forgejo` → 404 "object does not exist") and the head's combined status is `total_count: 0`. So nothing here is "green"; the verdict rests on the diff and on reading 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 1. **The gate's start row is `path.step`; its terminal row is `gate.decision`.** `cmd/ld-runtime/main.go:861-866` — `stepKind(step, state)` returns `KindGate` only when `step == StepGate && run.TerminalState(state)`; `run.go:86-92` shows `TerminalState` is `ok|failed|refused`, so a `running` start row falls through to `KindStep`. Exactly as §2 now says. 2. **`decision` is derived from the state, and refused at the write in both directions.** `run.go:108-118` `GateDecisionForState`: `ok→pass`, `refused→hold`, `failed→block`, anything else `ok=false`. `main.go:878-911` both refuses a `decision` on the gate's *start* row and refuses a `gate.decision` row whose decision is missing, blank, or outside the set. `record.go:213-228` + `record/gate_decision_test.go` pin `pass|block|hold` including the plausible near-misses (`PASS`, `ok`, `running`, `refused`, `pending`, …). Fail-closed, as claimed. 3. **`report.sql` reads every `gate.decision` row 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 printed `gate.decision.decision (missing)` for a started gate and made the sample NOT_CERTIFIABLE. The justification for the split is not decorative. 4. **`floor_met`, `round`, `first_verdict`, `holds[]`, `canary` are still unwritten.** Grep over `run/run.go`, `run/loop.go`, `record/record.go` at `d17c575`: zero payload writes of any of them (`floor_met` appears once, in a comment at `record.go:181`). `report.sql:1068-1085` confirms why it matters — `gate_on_head` is `bool_or(floor_met AND lower(decision)='pass')`, so a passing 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_met` supplied by the fixture**: this path does not write `floor_met` … cond2=1/1 here is reader mechanics, not production evidence"). Verified in the body at this reading, not on the worker's word. **"Named cost" checked, including the safety argument:** `main.go:980-981` `LastStep` uses `outcomeKind(step)`, which is `KindGate` for the gate, so it can only ever see the terminal row — a gate 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 changed files and CI and returns a state; the merge lives in `p.land` (`run.go:883+`). A re-attempted gate cannot merge anything. ### Concerns (non-blocking — I am not holding the PR for either) 1. **The contract will describe code that is not on `live`.** The `Revised` line cites the writer at `HOMELAB-2329` / `d17c575`, and #660 is still **open** — approved on the evidence, formally blocked by 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 `main` against `live` today will find the old rows and may conclude the contract is wrong rather than ahead. 2. **Say why the re-attempt is bounded.** The "Named cost" paragraph is accurate as written; a half-sentence that the *start* row is under `path.step` while the resume read is `gate.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 documentation correction that matches the code and the reader, changes no M0 or `report.sql` semantics, and rewrites no 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.
Collaborator

Verdict of record — ld-reviewer — head 4f71ed93f78d6d5c8a87ea2d929068c7e6f26d0d: APPROVED (HOMELAB-2339, one file +12/-4).

No CI in this repo and it is recorded, not assumed: aaron/lab-director has no .forgejo directory (contents API → 404 "object does not exist"), and the head's combined status is total_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.sql on main):

  • start row path.step, terminal gate.decision — cmd/ld-runtime/main.go:861-866 stepKind returns KindGate only for StepGate + run.TerminalState(state), and run.go:86-92 excludes running.
  • decision derived and refused at the write both ways — run.go:108-118 (ok→pass, refused→hold, failed→block), main.go:878-911 refuses 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.go pin pass|block|hold incl. near-misses (PASS, ok, running, refused).
  • report.sql has 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[]/canary still unwritten — zero payload writes across run.go/loop.go/record.go at d17c575; report.sql:1068-1085 gate_on_head = bool_or(floor_met AND decision='pass') confirms condition 2 still needs the HOMELAB-2336 panel floor. §6 keeps that owner.
  • "Named cost" is accurate and safe — main.go:980-981 LastStep reads outcomeKind = 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 in p.land, so a re-attempt merges nothing.

Scope item 2 done: #660's body carries the fixture qualifier ("with floor_met supplied 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 live until #660 lands; #660 is approved on the evidence but formally blocked (official 897, no legitimate route to an official approval from this identity), so the Revised header 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 under path.step, resume read under gate.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.

**Verdict of record — `ld-reviewer` — head `4f71ed93f78d6d5c8a87ea2d929068c7e6f26d0d`: APPROVED** (HOMELAB-2339, one file +12/-4). **No CI in this repo and it is recorded, not assumed:** `aaron/lab-director` has no `.forgejo` directory (contents API → 404 "object does not exist"), and the head's combined status is `total_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.sql` on `main`): - **start row `path.step`, terminal `gate.decision`** — `cmd/ld-runtime/main.go:861-866` `stepKind` returns `KindGate` only for `StepGate` + `run.TerminalState(state)`, and `run.go:86-92` excludes `running`. - **decision derived and refused at the write both ways** — `run.go:108-118` (`ok→pass`, `refused→hold`, `failed→block`), `main.go:878-911` refuses 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.go` pin `pass|block|hold` incl. near-misses (`PASS`, `ok`, `running`, `refused`). - **`report.sql` has 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[]`/`canary` still unwritten** — zero payload writes across `run.go`/`loop.go`/`record.go` at `d17c575`; `report.sql:1068-1085` `gate_on_head = bool_or(floor_met AND decision='pass')` confirms condition 2 still needs the HOMELAB-2336 panel floor. §6 keeps that owner. - **"Named cost" is accurate and safe** — `main.go:980-981` `LastStep` reads `outcomeKind` = `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 in `p.land`, so a re-attempt merges nothing. **Scope item 2 done:** #660's body carries the fixture qualifier ("with `floor_met` supplied 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 `live`** until #660 lands; #660 is approved on the evidence but formally blocked (official 897, no legitimate route to an official approval from this identity), so the `Revised` header 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 under `path.step`, resume read under `gate.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.
Collaborator

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 (commit 3da37b9, authorship preserved) plus the HOMELAB-2329 accuracy delta, reviewed by two lenses at the exact merged head (reviews 1026 DeepSeek / 1027 premium, both APPROVED at a86085c). 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.

**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 (commit `3da37b9`, authorship preserved) plus the HOMELAB-2329 accuracy delta, reviewed by two lenses at the exact merged head (reviews 1026 DeepSeek / 1027 premium, both APPROVED at `a86085c`). 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.
pi-landing closed this pull request 2026-10-29 10:47:52 +00:00

Pull request closed

Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
aaron/lab-director!24
No description provided.