fleetreconcile

package
v0.8.1-dryrun.27 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 5, 2026 License: Apache-2.0 Imports: 3 Imported by: 0

Documentation

Overview

Package fleetreconcile holds the run-ledger reconcile core that backs the fleet-reconcile reusable workflow. It is fleet maintainer tooling, not part of cascade's generated output: a scenario suite registers every run it gates (its id and the conclusion it expects) into a ledger, and after the suite finishes this core enumerates every run the repo produced in the scenario window and fails if any run is unaccounted for.

Keeping the decision logic here (rather than inline in YAML) makes the gate unit-testable against synthetic run lists with no live GitHub, so the core guarantee - an unregistered failing run reds the gate - is proven by a table test rather than asserted by hand.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func FormatReport

func FormatReport(rep Report) string

FormatReport renders a deterministic, human-readable reconcile report. The failing set is printed last and most loudly so a red gate is unambiguous.

Types

type Bucket

type Bucket int

Bucket is the reconcile classification of a single run.

const (
	// Accounted: the run is in the ledger and its actual conclusion matched
	// the registered expectation (success-matched or failure-matched).
	Accounted Bucket = iota
	// BenignUnregistered: not in the ledger but allowed - a success or a
	// skipped run needs no explicit registration.
	BenignUnregistered
	// SupersededCancelled: a cancelled/skipped run provably superseded by a
	// later run of the same workflow + concurrency lane. Never a failure.
	SupersededCancelled
	// InFlight: the run has not concluded yet (e.g. the suite's own run, or a
	// late sibling). Not counted as a gap; reported for transparency.
	InFlight
	// FailGap: an unregistered non-success (the fire-and-forget the gate
	// exists to catch), or a registered run whose conclusion did not match
	// its expectation. These fail the gate.
	FailGap
)

type LedgerEntry

type LedgerEntry struct {
	RunID    int64  `json:"run_id"`
	Expected string `json:"expected"` // "success" or "failure"
	Reason   string `json:"reason"`
}

LedgerEntry is one line a suite appended via the register-run action: the run it gated, the conclusion it expects that run to reach, and a short tag describing which scenario step registered it.

type Options

type Options struct {
	// SelfRunID is this reconcile run's own id; it is always excluded so the
	// gate never reconciles against itself. Zero means "no self to exclude".
	SelfRunID int64
	// AllowWorkflows, when non-empty, scopes reconcile to runs whose
	// WorkflowName is in the set. A run of any other workflow is ignored
	// (an unrelated sibling workflow's runs are out of scope). Empty means
	// "reconcile every workflow's runs in the window".
	AllowWorkflows map[string]struct{}
}

Options tune the classification without weakening it.

type PageCursor

type PageCursor struct {
	CreatedAt string
	// DatabaseID is the run id at the cursor. Together with CreatedAt it forms
	// the strict lower bound: the next page is everything ordered after this
	// run in newest-first order.
	DatabaseID int64
}

PageCursor is a strict-backward paging position over runs ordered newest first by (CreatedAt, DatabaseID). A page fetched at a cursor returns only runs that sort strictly OLDER than the cursor: an earlier CreatedAt, or the same CreatedAt with a smaller DatabaseID. Using the (timestamp, id) pair as the cursor - rather than the timestamp alone - is what lets the walk advance through a cluster of runs that share one CreatedAt: a shared-timestamp boundary can never stall the loop, because the id half of the cursor still strictly decreases.

func (PageCursor) IsZero

func (c PageCursor) IsZero() bool

IsZero reports whether the cursor is the initial "no upper bound" position, i.e. the first page (newest runs).

type PageFetcher

type PageFetcher func(cursor PageCursor) ([]Run, error)

PageFetcher fetches one newest-first page of runs strictly older than the cursor. A zero cursor (PageCursor{}) means "the newest page, no upper bound". It returns at most a page-sized slice; a returned page shorter than the page size signals the source is exhausted. The real fetcher (in the cmd binary) shells out to `gh run list --created ">=<window-start>" --json ...` and slices the result by the cursor; tests inject a synthetic fetcher.

type Report

type Report struct {
	Verdicts []Verdict
	// Failing is the subset of Verdicts in the FailGap bucket, in input order.
	Failing []Verdict
}

Report is the full reconcile outcome over a window of runs.

func Reconcile

func Reconcile(runs []Run, ledger []LedgerEntry, opts Options) Report

Reconcile classifies every run against the ledger and returns the report. It never lets a `failure` conclusion be skipped as benign or superseded: only `cancelled`/`skipped` runs are eligible for the superseded path, and only when a strictly-later registered (accounted) run shares the same (workflow, concurrency lane) so the cancellation is provably a supersede, not a masked failure.

func (Report) Passed

func (r Report) Passed() bool

Passed reports whether the gate should exit zero (no failing runs).

type Run

type Run struct {
	DatabaseID   int64  `json:"databaseId"`
	WorkflowName string `json:"workflowName"`
	Event        string `json:"event"`
	Conclusion   string `json:"conclusion"` // success|failure|cancelled|skipped|"" (in-flight)
	Status       string `json:"status"`     // completed|in_progress|queued
	HeadBranch   string `json:"headBranch"`
	// CreatedAt is the run's ISO-8601 creation timestamp (the gh `createdAt`
	// key). It is the cursor the enumerator pages backward on. ISO-8601 UTC
	// timestamps in the gh output sort lexicographically in time order, so the
	// enumerator compares them as strings.
	CreatedAt string `json:"createdAt"`
}

Run is the subset of `gh run list --json ...` fields the reconcile needs. Field names mirror the gh JSON keys so the workflow can pass the raw list straight through.

func EnumerateRuns

func EnumerateRuns(windowStart string, pageSize, maxPages int, fetch PageFetcher) ([]Run, error)

EnumerateRuns walks every run created at or after windowStart, strictly backward in (CreatedAt, DatabaseID) order, deduping by run id, and returns the full set. It is the fail-CLOSED replacement for the old inline 20-page `break` loop: it never returns a truncated window.

windowStart is an ISO-8601 timestamp; runs with CreatedAt < windowStart are outside the scenario window and are excluded. pageSize is the fetcher's page limit (used to tell a final short page from a full one). maxPages caps the walk so a misbehaving source cannot loop forever.

Fail-closed guarantee: if maxPages is reached while the last fetched page was still FULL (so more in-window runs may remain unenumerated), EnumerateRuns returns an error rather than a partial set. A truncated window could drop an unaccounted failing run and silently pass the gate, so truncation is always an error, never a quiet partial result.

type Verdict

type Verdict struct {
	Run     Run
	Bucket  Bucket
	Detail  string // human-readable why, for the report
	Expects string // for accounted runs, the registered expectation
}

Verdict classifies one run against the ledger. Outcome buckets are mutually exclusive so the report can never double-count a run.

Directories

Path Synopsis
Command fleet-reconcile is the runnable wrapper around the fleetreconcile core.
Command fleet-reconcile is the runnable wrapper around the fleetreconcile core.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL