HOMELAB-1847: LD4.5b — alert intake: episode ledger + the ONE on-call sandbox #596
No reviewers
Labels
No labels
operator-review
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Homelab/infra-core!596
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "plane/HOMELAB-1847-oncall"
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?
Closes/configures HOMELAB-1847 (LD4.5b, epic LD4). Do not merge from the agent side — the prod half is E-adj.
Built vs brief
episode/(8 files + tests)episode/ledger.godigest/suppression//healthz, window loop, P1 ACT page with the decision-ready packagealertintake/,cmd/alertintake/claims/policy.go,reaper.go,claimer.gocustodian/hygiene.go,reclaim.goon-callidentity (4 surfaces)routines/registry.yaml,identity/example-registry.yamloncall→ld-oncallconfig.TemplateKindOncall,cmd/dispatcher/context_plane.golab-director/alerts/digest-classes.yamllab-director/alerts/deploy/alert-intake.{service,env.example}The claims fix (the scout's sharpest finding)
claims.ReleaseStatewas globalTodoand the dispatcher watchesTodo, so a reaped episode ticket would be spawned as builder work. Now: class-resolved from the ticket's label (episode),LeaseTTLByClass(15m → 2h),ReleaseStateByClass(Todo→Backlog),DispatchableStatesByClass(Todo→Backlogso an episode is claimable from Backlog without making Backlog dispatchable). Unknown class names are refused at load, and the class-less path is byte-identical. Tests:claims/class_test.go(incl.TestReapedEpisodeClaimReturnsToBacklogNeverTodo,TestEpisodeReleaseStateIsNotDispatchable),custodian/hygiene_test.go.Verify lines (mechanized)
alertintake.TestBurstAcrossTwoGroupsProducesOneDigestAndOneEpisodePerGroup(16 alerts, 2 groups → 1 digest, 2 episodes) +episode.TestOneGroupIsExactlyOneEpisode(N=1..64 → 1 row).alertintake.TestClaimedEpisodeIsFilteredFromTheDigestUnownedList,custodian.TestSilentEpisodeClaimFilesExactlyOneHygieneTicket,custodian.TestRepeatedEpisodeReapAppendsObservationNotASecondTicket.suppression.TestOpenFixPROnwardsZeroDispatch,alertintake.TestOpenFixPRSuppressesTheSecondDispatch(open PR ⇒ P3, 0 episode items minted, suppression event logged, digest says "do NOT dispatch").alertintake.TestP1EscalatesImmediatelyAndBypassesTheWindow(ACT page + decision-ready package + escalation ledger row + immediate digest in the same request).go vet ./...andgo test -race ./...green inlab-director/dispatcher.Deviations to note
alertintake/alertintake.go.Row.Groups[]is retained for auditability.compiler.Roles()grew to 15 (on-call). The identity registry needs the role for R-28'srunsAs, and a page signed by an unattributable identity is not attributable (§8). Role count/pin tests updated in the same commit.custodian/reclaim.go) — required byTestInProcessPinsMatchTheCode.role-lore/on-call.mdships the charter. Flagged as a follow-up.ld-oncall: both deliberate. Until a CNP exists,ld-oncallpods are not covered byld-sandbox-class(it selectsapp.kubernetes.io/name), so merge order matters — see the prod PR.FORGEJO_TOKEN); the intake fails closed without it.fleet.ledger.alert.>(mgmt cannot reach the bus; the publisher is the in-cluster dispatcher — named follow-up), and the mgmt systemd unit + ufw rule are committed as artefacts but not applied (host config).E-adj (operator review) — all in the prod PR
Alertmanager receiver +
continue: trueroute (monitoring), the newalert-intake-tokensecret, theld-oncallSandboxTemplate, the mgmt systemd unit + ufw 8091 rule, and (deferred, flagged) a CNP for theld-oncallclass.Shared hunks
claims/(per-class release state),config/config.go(claim-class keys +TemplateKindOncall),routines/registry.yaml(my entries only: R-03 description+pin, R-10 pausedReason, R-28),identity/example-registry.yaml+compiler/roles.go.80b2667d3cca94541f95Landing review — VERDICT: APPROVE (merge)
Reviewed head:
ca94541f9570c9da75f4194621feaf19de0fc2a6. Fresh-context review; primary sources only (PR diff, HOMELAB-1847's four Verify lines, the repo's claims/registry/digest contracts). No prior PR comments existed, so no open blocker findings.Rebase (stale base corrected)
The branch was cut before #594 (LD4.5d, R-29) merged, so its diff vs
epic/LD4removed the R-29 section. Rebased ontoorigin/epic/LD4(2607c6e) by the landing worker. Git auto-mergedroutines/registry.yamlinto the required union:phase-census) section retained;oncall-window) entry added (paused fordep:routine-executor);epic/LD4(documents R-28 as LD4.5b's reservation, R-29, and the corpus/R-24/R-25 entries).One follow-up commit
ca94541: the merged header claimed "Active today (6)" counting R-28, but R-28 shipsstatus: paused— corrected to (5) with R-28 named beside R-25 in the paused sentence. Force-with-lease from80b2667→ca94541. Ticket state not moved.Verification on the new head
go build ./...clean,go vet ./...clean.go test -race -count=1 ./...green across the whole module (incl. routines/identity/config/compiler).alertintake.TestBurstAcrossTwoGroupsProducesOneDigestAndOneEpisodePerGroup(16 alerts/2 groups → 1 digest, 1 episode/group),episode.TestOneGroupIsExactlyOneEpisode.digest.TestClaimedEpisodeIsFilteredFromUnowned,alertintake.TestClaimedEpisodeIsFilteredFromTheDigestUnownedList,custodian.TestSilentEpisodeClaimFilesExactlyOneHygieneTicket,custodian.TestRepeatedEpisodeReapAppendsObservationNotASecondTicket,claims.TestReapedEpisodeClaimReturnsToBacklogNeverTodo,claims.TestEpisodeReleaseStateIsNotDispatchable.suppression.TestOpenFixPROnwardsZeroDispatch,alertintake.TestOpenFixPRSuppressesTheSecondDispatch.alertintake.TestP1EscalatesImmediatelyAndBypassesTheWindow.alertintake.TestLedgerFailureAnswers503AndWritesNoRows(202 only after the durable append).Review dimensions
episode),LeaseTTLByClass15m→2h,ReleaseStateByClassTodo→Backlog,DispatchableStatesByClassTodo→Backlog. Backlog is claimable but NOT indispatchableStates, so a reaped episode cannot be spawned as builder work — asserted by an explicit test. Unknown class names are refused at load; the class-less path is unchanged (TestClassLessTicketKeepsTodaysBehaviour). Writer scope is derived from the config (AllowedClaimStates) so policy and writer cannot disagree at startup.direction.WindowKey,episode.Ledger,claims,notifier,comms. Registry pin re-derived byTestInProcessPinsMatchTheCode(green).10.10.10.5:8091(VLAN-10 only), bearer token in constant time, fails closed on any missing credential; Forgejo uses a read-only token and counts only OPEN PRs. No new ServiceAccount — the dispatcher's existing Plane writer is reused (interface is the blast-radius boundary).rollup.gountouched; no new SA/RBAC/CNP/secret in this repo. Changes arelab-director/dispatcher/*plus thelab-director/alerts/data/artefact files. No E-adj in this PR (E-adj lives in prod #887).Non-blocking findings
ld-oncallpods are not covered by theld-sandbox-classCNP yet. Merging infra-core first is inert (no unit applied, no Alertmanager receiver) and moves the dispatcher image/binary; the prod PR carries the SandboxTemplate +continue: trueroute. A CNP for the class remains a named follow-up.Sibling
#592 (R-28→R-30 re-id) had NOT landed on
epic/LD4at review time, so no R-30 in the registry totals.ca94541f9586316feac5Landing review (updated) — VERDICT: APPROVE (merge)
Reviewed head:
86316feac522862451a5748e46b20228e7f162f1. This supersedes the earlier verdict; the review below re-validates that head after the second rebase.Re-rebase note
While the first landing attempt was in flight, #592 (HOMELAB-1690, LD4.6) landed on
epic/LD4(8d8cbd7) and re-id'd its L7 triage sink R-28 → R-30 (R-28 is 4.5b's reservation). The branch was re-based onto8d8cbd7. Theroutines/registry.yamlconflict was resolved as a union of all three entries:oncall-window(this PR,paused—dep:routine-executor) at line 791;phase-census(#594) retained at line 707;friction-triage(#592,paused) retained at line 849;epic/LD4's reserved-ids paragraph (now naming R-28/R-29/R-30) and corrects "Active today" from (6) to (5) — R-28 and R-30 both ship paused, so neither is active.The diff vs
epic/LD4is now purely additive for #594/#592's entries (no R-29/R-30 removal).epic/LD4is an ancestor of the head;Mergeable: true. Force-with-leaseca94541→86316fe.Verification on
86316fego build ./...+go vet ./...clean.go test -race -count=1 ./...green across the whole module (all 28 packages; includesdispatcher/friction/from #592).TestBurstAcrossTwoGroupsProducesOneDigestAndOneEpisodePerGroup,TestOneGroupIsExactlyOneEpisode.TestClaimedEpisodeIsFilteredFromTheDigestUnownedList,TestSilentEpisodeClaimFilesExactlyOneHygieneTicket,TestRepeatedEpisodeReapAppendsObservationNotASecondTicket,TestReapedEpisodeClaimReturnsToBacklogNeverTodo,TestEpisodeReleaseStateIsNotDispatchable.TestOpenFixPROnwardsZeroDispatch,TestOpenFixPRSuppressesTheSecondDispatch.TestP1EscalatesImmediatelyAndBypassesTheWindow.TestLedgerFailureAnswers503AndWritesNoRows.Review dimensions (unchanged from the earlier verdict; re-checked on the rebased content)
Correctness vs the four ACs; claims per-class release fix (episode → Backlog, non-dispatchable, asserted); 202-after-durable-append ordering accepted (the argued Plane→ledger→202 deviation); codebase fit (reuses
direction.WindowKey/episode.Ledger/claims/notifier/comms); test adequacy (behavioural asserts, not implementation); security (VLAN-10 bind, constant-time bearer, fail-closed credentials, read-only Forgejo token, no new SA/RBAC/CNP/secret); territory (rollup.gountouched; code underlab-director/dispatcher/+lab-director/alerts/artefacts only; no E-adj in this PR).Non-blocking findings
ld-oncallis not yet covered by theld-sandbox-classCNP. Landing infra-core first is inert at runtime (no host unit applied, no Alertmanager receiver); the prod PR (#887) carries the SandboxTemplate +continue: trueroute. A CNP for the class remains a named follow-up, as does the JetStream mirror.No open blocker findings.
Plan Error