HOMELAB-1850: feat(routines): phase census — split identity, P-ROUT-012, calibration line (R-29) #594

Merged
aaron merged 1 commit from plane/HOMELAB-1850-attribution into epic/LD4 2026-10-03 03:50:32 +00:00
Owner

The Go half of LD4.5d: the check, the finding and the digest line over the phase key
that pi-extensions emits in Homelab/pi-extensions#60.

Brief: .rpiv/artifacts/epic-ld4/design-LD4.5d-attribution.md (ticket HOMELAB-1850).
Territory: routines/rollup.go is NOT touched (its sha256 pin is a promise an auditor
re-derives, and LD4.4 owns R-06). A new file reads RollupResult.

What lands

  • routines/phase.go — the phase census:
    • SplitPhaseTotals / SplitPhases: the identity that is the ticket's verify line 2
      — Σ(build, review, verify) + untagged == total, exactly, integer tokens, no
      tolerance. unphased (the value the telemetry writes when nothing was declared) and
      any label outside the closed enum are UNTAGGED and NAMED — never a fourth bucket,
      never folded into one of the three. A blank-phase sample lands in untagged by
      construction (GroupSamples skips it on purpose).
    • ValidatePhaseSplit / ...WithDeclarations: the enforcement point. A non-summing
      split is an episode-class mismatch (a folding bug); a bucket that DECLARED a phase
      and still carries untagged spend is a dispatcher defect; untagged spend on an
      undeclared/legacy window is reported together with the instrument's own coverage.
    • CalibrationLine / PhaseCensusMarkdown / PhaseCensusDigest: verify lines 3 and
      4. The line quotes THIS fleet's measured validation share with n, the window and
      the untagged share; the vendor 40 % datum appears only as an external,
      single-mission comparison ("not reproduced at this sample size" / "one comparison,
      not corroboration"). An empty window is a visible no-op, never a published 0 %.
      The table gives untagged its own column — unknown is not healthy.
    • PhaseCensusExecutor: the runner seam for R-29 (the R-06 ExecutorAdapter
      precedent) — names the series it emitted, refuses to claim success from a failed
      read.
  • registry.yaml — R-29 phase-census, ACTIVE (its mechanism ships in this build),
    pinned to phase.go's sha256 (re-derived by TestInProcessPinsMatchTheCode).
  • policy.go + policy.md — P-ROUT-012 (both files change together: the doc is
    parsed back by TestPolicyBookMatchesDoc).
  • registry_test.go — the census floor is now DERIVED from the corpus fixture
    instead of a hardcoded number, plus R-29's shape assertion.
  • README.md — the territory section names what 4.5d owns on this side.

One deliberate deviation, flagged

R-29, not R-27 (the slot brief said R-27). The registry header has reserved
R-23/R-26/R-27 for tickets 4.7/4.10/4.11 since LD4.4, and
testdata/registry-4x-shapes.yaml already carries R-27's shape (quarterly
maintainability fitness, emitsWork with one emits) because
TestSchemaAccommodatesHosted468Shapes asserts it. Registering a different R-27 here
would put two R-27s in one repository and break the registry's own "ids are stable
forever" rule. R-28 is LD4.5b's on-call window (per that design brief), so the next free
id is R-29. Same count outcome (26 entries): the id, not the count, is what moved.

Verification

  • go test -race ./... — green (all packages).
  • go vet ./... — clean.
  • routines/phase_test.go (TestC6*): the split identity over a landed-PR-shaped
    fixture, the untagged cases (incl. unphased, Build, deploy, blank), the
    declared-phase defect, the mismatch episode, the calibration line (our share rendered,
    vendor datum external-only, unstated window printed as unstated), the rendered census
    row (untagged never inside a phase column), the no-op receipt and the closed enum.
  • No socket is opened by any test (in-memory FixtureMetricSource).

Not in this PR

  • The Tempo values change — separate PR, flagged OPERATOR REVIEW (protected namespace).
  • The monitoring recording rules (telemetry-phase.yaml): the verify lines are satisfied
    by the Go calibration line and the census artifact, and the cockpit panes are 3.4's
    slot; adding a new alerting rule to a dangerous PR would widen its review surface for
    no evidential gain. Named here so it is not a silent gap.
The Go half of LD4.5d: the check, the finding and the digest line over the `phase` key that pi-extensions emits in Homelab/pi-extensions#60. Brief: `.rpiv/artifacts/epic-ld4/design-LD4.5d-attribution.md` (ticket HOMELAB-1850). Territory: `routines/rollup.go` is NOT touched (its sha256 pin is a promise an auditor re-derives, and LD4.4 owns R-06). A new file reads `RollupResult`. ## What lands * **`routines/phase.go`** — the phase census: * `SplitPhaseTotals` / `SplitPhases`: the identity that is the ticket's verify line 2 — `Σ(build, review, verify) + untagged == total`, exactly, integer tokens, no tolerance. `unphased` (the value the telemetry writes when nothing was declared) and any label outside the closed enum are UNTAGGED and NAMED — never a fourth bucket, never folded into one of the three. A blank-phase sample lands in untagged by construction (`GroupSamples` skips it on purpose). * `ValidatePhaseSplit` / `...WithDeclarations`: the enforcement point. A non-summing split is an episode-class mismatch (a folding bug); a bucket that DECLARED a phase and still carries untagged spend is a dispatcher defect; untagged spend on an undeclared/legacy window is reported together with the instrument's own coverage. * `CalibrationLine` / `PhaseCensusMarkdown` / `PhaseCensusDigest`: verify lines 3 and 4. The line quotes THIS fleet's measured validation share with n, the window and the untagged share; the vendor 40 % datum appears only as an external, single-mission comparison ("not reproduced at this sample size" / "one comparison, not corroboration"). An empty window is a visible no-op, never a published 0 %. The table gives untagged its own column — unknown is not healthy. * `PhaseCensusExecutor`: the runner seam for R-29 (the R-06 `ExecutorAdapter` precedent) — names the series it emitted, refuses to claim success from a failed read. * **`registry.yaml`** — R-29 `phase-census`, ACTIVE (its mechanism ships in this build), pinned to `phase.go`'s sha256 (re-derived by `TestInProcessPinsMatchTheCode`). * **`policy.go` + `policy.md`** — **P-ROUT-012** (both files change together: the doc is parsed back by `TestPolicyBookMatchesDoc`). * **`registry_test.go`** — the census floor is now DERIVED from the corpus fixture instead of a hardcoded number, plus R-29's shape assertion. * **`README.md`** — the territory section names what 4.5d owns on this side. ## One deliberate deviation, flagged **R-29, not R-27** (the slot brief said R-27). The registry header has reserved R-23/R-26/R-27 for tickets 4.7/4.10/4.11 since LD4.4, and `testdata/registry-4x-shapes.yaml` **already carries R-27's shape** (quarterly maintainability fitness, `emitsWork` with one emits) because `TestSchemaAccommodatesHosted468Shapes` asserts it. Registering a different R-27 here would put two R-27s in one repository and break the registry's own "ids are stable forever" rule. R-28 is LD4.5b's on-call window (per that design brief), so the next free id is **R-29**. Same count outcome (26 entries): the id, not the count, is what moved. ## Verification * `go test -race ./...` — green (all packages). * `go vet ./...` — clean. * `routines/phase_test.go` (`TestC6*`): the split identity over a landed-PR-shaped fixture, the untagged cases (incl. `unphased`, `Build`, `deploy`, blank), the declared-phase defect, the mismatch episode, the calibration line (our share rendered, vendor datum external-only, unstated window printed as unstated), the rendered census row (untagged never inside a phase column), the no-op receipt and the closed enum. * No socket is opened by any test (in-memory `FixtureMetricSource`). ## Not in this PR * The Tempo values change — separate PR, flagged OPERATOR REVIEW (protected namespace). * The monitoring recording rules (`telemetry-phase.yaml`): the verify lines are satisfied by the Go calibration line and the census artifact, and the cockpit panes are 3.4's slot; adding a new alerting rule to a dangerous PR would widen its review surface for no evidential gain. Named here so it is not a silent gap.
HOMELAB-1850: feat(routines): the phase census — the split identity, P-ROUT-012 and the calibration line (R-29)
All checks were successful
0/0 projects applied successfully.
CI Review / receiver-test (pull_request) Successful in 7s
CI Review / helm-validate (pull_request) Successful in 11s
CI Review / alert-receiver-test (pull_request) Successful in 24s
CI Review / dispatcher-test (pull_request) Successful in 4m26s
4a6e192f9b
The check over what the emission side writes. `rollup.go` is untouched (its
sha256 pin is a promise an auditor re-derives, and LD4.4 owns it); this is a new
file that READS RollupResult:

* `SplitPhaseTotals` / `SplitPhases`: the per-sandbox census whose arithmetic is
  the ticket's verify line 2 — `Σ(build, review, verify) + untagged == total`
  exactly, integer tokens, no tolerance. `unphased` (the value the telemetry
  writes when nothing was declared) and any label outside the closed enum are
  UNTAGGED and NAMED, never a fourth bucket and never folded into one of the
  three. A blank-phase sample lands in untagged by construction (GroupSamples
  skips it deliberately).
* `ValidatePhaseSplit` / `ValidatePhaseSplitWithDeclarations`: the enforcement
  point. A non-summing split is an episode-class mismatch (a folding bug); a
  bucket that DECLARED a phase and still carries untagged spend is a dispatcher
  defect; untagged spend on an undeclared/legacy window is reported with the
  instrument's own coverage. Both cite P-ROUT-012.
* `CalibrationLine` / `PhaseCensusMarkdown` / `PhaseCensusDigest`: verify lines 3
  and 4. The line quotes THIS fleet's measured validation share with n, the
  window and the untagged share, and the vendor datum appears only as an
  external, single-mission comparison ("not reproduced at this sample size" /
  "one comparison, not corroboration"). An empty window is a visible no-op, never
  a published 0 %. The table gives untagged its own column: unknown is not
  healthy.
* `PhaseCensusExecutor`: the runner seam for R-29 (the R-06 ExecutorAdapter
  precedent) — names the series it emitted and refuses to claim success from a
  failed read.

Registry: R-29 `phase-census`, ACTIVE (its mechanism ships in the same build),
pinned to this file's sha256. It takes R-29, NOT the briefed R-27: the header has
reserved R-27 for 4.11 since LD4.4 and `testdata/registry-4x-shapes.yaml` already
carries R-27's maintainability-fitness shape (asserted by
TestSchemaAccommodatesHosted468Shapes), so a second R-27 would break the
registry's own "ids are stable forever" rule. R-28 is LD4.5b's on-call window;
R-29 is the next free id. The census floor in registry_test.go is now derived from
the corpus fixture instead of a hardcoded 24.

policy.go/policy.md: P-ROUT-012 (both files change together — the doc is parsed
back by TestPolicyBookMatchesDoc).

Tests: phase_test.go (TestC6*) covers the identity, the untagged cases, the
declared-phase defect, the mismatch episode, the calibration line, the rendered
census row and the no-op receipt. `go test -race ./...` + `go vet ./...` green.
Author
Owner

Landing review — APPROVE (clean, no blocker findings)

Reviewed sha: 4a6e192f9bbd9dbbcc172f29e8ebce404ea0dab5 (head). Fresh context; sources = PR diff, HOMELAB-1850 + adjudication comments, design-LD4.5d-attribution.md, ticket verify lines. No implementer summary used as the brief.

Verified against the acceptance criteria

Check Result
Split identity Σ(build,review,verify) + untagged == total is unit-tested ✅ TestC6PhaseSplitSumsToSandboxTotal (exact, integer, no tolerance; all three phases asserted non-empty)
P-ROUT-012 fires when the sum breaks ✅ TestC6SplitMismatchIsAnEpisode (ByPhase sums > Tokens → SeverityEpisode + P-ROUT-012); declared-phase-with-untagged → defect finding (TestC6DeclaredPhaseWithUntaggedSpendIsAFinding)
Untagged is named, never a fourth bucket ✅ TestC6UntaggedSpendIsNeverOneOfTheThree (unphased/Build/deploy/blank → Untagged, labels named), TestC6CensusRendersUnphasedAsItsOwnRow (own column, no untagged token in a phase column)
CalibrationLine shape supports the digest quote ✅ our share + n + window + coverage + untagged share; vendor 40 % appears only as "external datum … one self-reported mission"; empty window → visible no-op, never a published 0 %
rollup.go UNTOUCHED (pin check) ✅ absent from the diff (git diff --stat 782b346..HEAD = README, phase.go, phase_test.go, policy.go, policy.md, registry.yaml, registry_test.go). R-06 pin sha256:6fcec5b4… unchanged
R-29 pin ✅ re-derived: sha256sum phase.go = 48e1427b59ad4b5cee4a05888ead03284b89d719868fa89dfd2b2cd1002adaed = registry.yaml scriptRef.rev; TestInProcessPinsMatchTheCode green
Reserved-id discipline / deviation ✅ R-27 untouched and still carries its testdata/registry-4x-shapes.yaml shape (TestSchemaAccommodatesHosted468Shapes green). R-29 is the next free id; R-28 is the sibling LD4.5b slot. R-29 entry mirrors R-06's field-for-field shape; TestRealRegistryLoads + TestPolicyBookMatchesDoc green
No sockets / race ✅ phase_test.go uses in-memory FixtureMetricSource; no net/http. Local go test -race -run 'TestC6|TestInProcessPinsMatchTheCode|TestRealRegistryLoads|TestPolicyBookMatchesDoc|TestSchemaAccommodatesHosted468Shapes' ./routines/ → ok, 1.2s
CI on head ✅ run #1776 @ 4a6e192f9b: dispatcher-test 4m26s, helm-validate, receiver-test, alert-receiver-test all success
Territory (4.4 owns sandbox_env+R-06; this owns phase) ✅ respected — no rollup.go, no sandbox.ts, no swarm-view UI
"No prod PR" claim ✅ verified: prod/apps/tempo.yaml has no values of its own; it pulls $values/core/charts/platform/tempo/values.yaml from infra-core @ live. No tempo values file exists in prod/ outside worktrees

Concerns (non-blocking, for the record)

  1. Registry header over-states the current branch. The header now reads "Active today (6): … R-28 (LD4.5b's on-call window) and R-29", but R-28 is not in this branch's registry — it exists only on the sibling plane/HOMELAB-1847-oncall branch (whose own header still says "Active today (4)"). On this branch there are 5 active entries (R-03, R-06, R-19, R-24, R-29), 25 entries total. The PR body's "26 entries" inherits the same off-by-one. The sentence becomes true at the Wave-4 union; whoever lands the sibling should reconcile the header count. Comment-only, no functional effect — not a blocker.
  2. Infra-side recipe binding: nothing here, by design. identity/claim_bind.go renders LD_ROLE via Env()/EnvPairs() (which has no non-test consumer in-repo), and §2.2 of the brief explicitly keeps phase out of the claim binding (identity ≠ stage). The dispatch recipe ships as data in pi-extensions #60. #594's territory is correct.
  3. Live halves of verify lines 1/2 remain unmet (they need the pi-extensions change in the live checkout) — honestly disclosed in both the PR body and the HOMELAB-1850 comment. Deferring the recording rules (PromQL = watch only) is likewise the adjudicated position and is named in the PR body, not a silent gap.

Merging on green CI + clean verdict.

## Landing review — APPROVE (clean, no blocker findings) Reviewed sha: `4a6e192f9bbd9dbbcc172f29e8ebce404ea0dab5` (head). Fresh context; sources = PR diff, HOMELAB-1850 + adjudication comments, `design-LD4.5d-attribution.md`, ticket verify lines. No implementer summary used as the brief. ### Verified against the acceptance criteria | Check | Result | |---|---| | Split identity `Σ(build,review,verify) + untagged == total` is unit-tested | ✅ `TestC6PhaseSplitSumsToSandboxTotal` (exact, integer, no tolerance; all three phases asserted non-empty) | | P-ROUT-012 fires when the sum breaks | ✅ `TestC6SplitMismatchIsAnEpisode` (ByPhase sums > Tokens → `SeverityEpisode` + `P-ROUT-012`); declared-phase-with-untagged → defect finding (`TestC6DeclaredPhaseWithUntaggedSpendIsAFinding`) | | Untagged is named, never a fourth bucket | ✅ `TestC6UntaggedSpendIsNeverOneOfTheThree` (`unphased`/`Build`/`deploy`/blank → Untagged, labels named), `TestC6CensusRendersUnphasedAsItsOwnRow` (own column, no untagged token in a phase column) | | `CalibrationLine` shape supports the digest quote | ✅ our share + n + window + coverage + untagged share; vendor 40 % appears only as "external datum … one self-reported mission"; empty window → visible no-op, never a published 0 % | | `rollup.go` UNTOUCHED (pin check) | ✅ absent from the diff (`git diff --stat 782b346..HEAD` = README, phase.go, phase_test.go, policy.go, policy.md, registry.yaml, registry_test.go). R-06 pin `sha256:6fcec5b4…` unchanged | | R-29 pin | ✅ re-derived: `sha256sum phase.go` = `48e1427b59ad4b5cee4a05888ead03284b89d719868fa89dfd2b2cd1002adaed` = `registry.yaml` `scriptRef.rev`; `TestInProcessPinsMatchTheCode` green | | Reserved-id discipline / deviation | ✅ R-27 untouched and still carries its `testdata/registry-4x-shapes.yaml` shape (`TestSchemaAccommodatesHosted468Shapes` green). R-29 is the next free id; R-28 is the sibling LD4.5b slot. R-29 entry mirrors R-06's field-for-field shape; `TestRealRegistryLoads` + `TestPolicyBookMatchesDoc` green | | No sockets / race | ✅ `phase_test.go` uses in-memory `FixtureMetricSource`; no `net/http`. Local `go test -race -run 'TestC6\|TestInProcessPinsMatchTheCode\|TestRealRegistryLoads\|TestPolicyBookMatchesDoc\|TestSchemaAccommodatesHosted468Shapes' ./routines/` → **ok, 1.2s** | | CI on head | ✅ run **#1776** @ `4a6e192f9b`: dispatcher-test 4m26s, helm-validate, receiver-test, alert-receiver-test all success | | Territory (4.4 owns `sandbox_env`+R-06; this owns `phase`) | ✅ respected — no `rollup.go`, no `sandbox.ts`, no swarm-view UI | | "No prod PR" claim | ✅ verified: `prod/apps/tempo.yaml` has no values of its own; it pulls `$values/core/charts/platform/tempo/values.yaml` from `infra-core` @ `live`. No tempo values file exists in `prod/` outside worktrees | ### Concerns (non-blocking, for the record) 1. **Registry header over-states the current branch.** The header now reads "Active today (6): … R-28 (LD4.5b's on-call window) and R-29", but **R-28 is not in this branch's registry** — it exists only on the sibling `plane/HOMELAB-1847-oncall` branch (whose own header still says "Active today (4)"). On this branch there are **5** active entries (R-03, R-06, R-19, R-24, R-29), 25 entries total. The PR body's "26 entries" inherits the same off-by-one. The sentence becomes true at the Wave-4 union; whoever lands the sibling should reconcile the header count. Comment-only, no functional effect — not a blocker. 2. **Infra-side recipe binding: nothing here, by design.** `identity/claim_bind.go` renders `LD_ROLE` via `Env()`/`EnvPairs()` (which has no non-test consumer in-repo), and §2.2 of the brief explicitly keeps `phase` out of the claim binding (identity ≠ stage). The dispatch recipe ships as data in pi-extensions #60. #594's territory is correct. 3. **Live halves of verify lines 1/2 remain unmet** (they need the pi-extensions change in the live checkout) — honestly disclosed in both the PR body and the HOMELAB-1850 comment. Deferring the recording rules (PromQL = watch only) is likewise the adjudicated position and is named in the PR body, not a silent gap. Merging on green CI + clean verdict.
aaron merged commit 2607c6e20c into epic/LD4 2026-10-03 03:50:32 +00:00
Sign in to join this conversation.
No reviewers
No labels
operator-review
No milestone
No project
No assignees
1 participant
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
Homelab/infra-core!594
No description provided.