Documentation
¶
Overview ¶
Package loopfleet is the cross-ledger loop-health fold (#1196, part of #1173 — the verified loop): one read-only pane that answers "show me EVERY loop's health — last tick, run count, keep/witness rate, and whether it has gone DARK" across the repo's fragmented loop ledgers.
The loop ladder runs on disjoint journals, each owned by a different package and written in its own schema:
loopmgr .fak/loops.jsonl (fak.loop-event.v1) nightrun docs/nightrun/collected.jsonl (fak-nightrun-collect/1) dojo docs/dojo/history.jsonl (fak-dojo-ledger/1) cadence docs/cadence/history.jsonl (fak-cadence-ledger/1) dispatch .dispatch-runs/progress.jsonl (fleet-issue-resolve-progress/1)
loopmgr.FoldHealth already unifies the two ledgers reachable WITHIN loopmgr (the loop-event ledger + the job registry) but, by design, does not import the other packages' journals. This package is the cross-ledger surface that comment names: each ledger gets a small read-only adapter, and one Fold joins them into a single per-loop health view that reuses loopmgr's HealthState vocabulary so the whole ladder speaks one word for "live"/"stale"/"dark"/"unknown".
It is PURE and READ-ONLY: every adapter only os.ReadFile's its journal, `now` is supplied (no clock read in the fold), and nothing is appended or mutated. An unreadable or missing ledger is skipped-and-surfaced (recorded in Report.Skipped), never fatal — the `fak loop rollup` discipline. Adding a ledger to the set changes only the VIEW, never any loop's behavior.
Scope (honest): this folds 5 of the 7 ledgers the issue names. The two not yet wired — rsiloop (its Row carries no timestamp, so no last-tick is derivable) and guardrsi (a config-dir directory of guard-audit.jsonl files, not a single repo-relative file) — are deliberate follow-ons; each would be one more adapter here with the same read-only contract. The acceptance bar is "≥3 of the listed ledgers", which 5 clears.
Index ¶
Constants ¶
const Schema = "fak.loop-fleet-health.v1"
Schema is the versioned payload tag so a `--json` consumer can pin the shape.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type LoopHealth ¶
type LoopHealth struct {
// Kind is the loop's stable identity. For the single-purpose ledgers it is the
// ledger name ("nightrun", "dojo", "cadence", "dispatch"); for loopmgr — whose
// one ledger holds many loops — it is "loopmgr:<loop_id>".
Kind string `json:"kind"`
// Ledger names the journal this row was folded from.
Ledger string `json:"ledger"`
// State is the derived verdict (live/stale/dark/unknown), loopmgr's vocabulary.
State loopmgr.HealthState `json:"state"`
// Dark is the surfaced boolean the issue makes first-class: true iff State==dark,
// so a `--json` consumer (or a scheduler) gates on one field without re-deriving.
Dark bool `json:"dark"`
// LastTickUnixNano is the loop's most recent ledger event time, 0 if never ticked.
LastTickUnixNano int64 `json:"last_tick_unix_nano,omitempty"`
// AgeSeconds is now-lastTick in whole seconds; 0 when the loop has never ticked.
AgeSeconds int64 `json:"age_seconds,omitempty"`
// CadenceSeconds is the expected interval the verdict compared the age against.
CadenceSeconds int64 `json:"cadence_seconds,omitempty"`
// Runs is the count of recorded runs/ticks — the denominator of the keep rate.
Runs int `json:"runs"`
// Keep is the count of runs that landed a kept/positive outcome.
Keep int `json:"keep"`
// Witness is the count of runs that carried an independent witness (an artifact,
// a measurement, a witnessed-done verdict) — the evidence behind the keep.
Witness int `json:"witness"`
// KeepRate is Keep/Runs rounded to 3 decimals; -1 when Runs==0 (no denominator),
// so a brand-new loop is never slandered as 0% kept on an empty base.
KeepRate float64 `json:"keep_rate"`
}
LoopHealth is one loop's row in the cross-ledger pane: its identity (kind + source ledger), the inputs the verdict was derived from (last tick, cadence, counts), and the derived HealthState — so a reader sees not just "dark" but the cadence and last tick that made it dark.
type Report ¶
type Report struct {
Schema string `json:"schema"`
TSUnixNano int64 `json:"ts_unix_nano"`
Loops []LoopHealth `json:"loops"`
Skipped []Skipped `json:"skipped"`
Rollup Rollup `json:"rollup"`
}
Report is the full read-only fold: schema tag, the time it was folded, the per-loop rows (stable kind order), the surfaced skipped ledgers, and the rollup.
func Fold ¶
Fold is the cross-ledger health fold. It runs every adapter against root, derives one LoopHealth per loop found, surfaces every skipped ledger, and tallies the rollup. It is PURE: `now` is supplied, the only I/O is reading the journals, and no input is mutated. A zero-value HealthThresholds is fine — the classifier fills the defaults.
type Rollup ¶
type Rollup struct {
Loops int `json:"loops"`
Live int `json:"live"`
Stale int `json:"stale"`
Dark int `json:"dark"`
Unknown int `json:"unknown"`
Ledgers int `json:"ledgers"` // ledgers present and folded
Skipped int `json:"skipped"` // ledgers missing/unreadable/empty
}
Rollup is the fleet-wide tally: how many loops, how many in each state, and how many ledgers were folded vs skipped. Dark>0 is the signal a scheduler gates on.
type Skipped ¶
type Skipped struct {
Ledger string `json:"ledger"`
Path string `json:"path"`
Reason string `json:"reason"`
}
Skipped is a ledger that could not be folded — missing, unreadable, or holding no parseable rows. It is surfaced (never silent) so "absent" reads as a known gap, not as a healthy zero.