Documentation
¶
Index ¶
Constants ¶
const StatePath = paths.Root + "/pipeline.json"
StatePath is the workspace-relative path where pipeline state is persisted.
const SubsystemsDir = paths.Root + "/subsystems"
SubsystemsDir holds one fragment per subsystem outcome (subsystems/build.json, subsystems/security.json, …). These fragments — NOT pipeline.json — are the single carrier of subsystem state ACROSS jobs.
Why: CI jobs are isolated workspaces whose outputs are recombined by an artifact system with no merge — only file union, where same-path files clobber. A single shared pipeline.json therefore cannot accumulate across jobs: when a downstream job (publish) downloads two upstreams that each wrote pipeline.json, the last-written copy overwrites the other, silently dropping the subsystems only one job recorded. That order-dependent clobber is what made publish's authorization gate see "security did not run". Per-name fragments never collide across jobs (perform→build.json, review→security.json), so ReadState UNIONs them regardless of download order. pipeline.json remains each job's LOCAL merged view (the read model); it is not forwarded as a contested cross-job ledger.
Variables ¶
This section is empty.
Functions ¶
func UpdateState ¶
UpdateState does read-modify-write. The caller mutates individual fields only — never rebuild nested structs wholesale to avoid clobbering prior state written by other subsystems.
func WriteState ¶
WriteState writes pipeline state atomically (tmp + fsync + rename). Normalizes Version to 1 on write and stamps StartedAt on the run's first write (the {duration} fact's zero point — recording time is impure by nature; the RENDER of the recorded value stays deterministic).
Types ¶
type BuildState ¶
type BuildState struct {
ProducedImages bool `json:"produced_images"`
PublishedCount int `json:"published_count"`
ManifestPath string `json:"manifest_path,omitempty"`
}
BuildState holds build-specific domain metadata. Lifecycle tracking (attempted/completed/outcome) is in Subsystems.
type CIState ¶
type CIState struct {
Provider string `json:"provider"`
PipelineID string `json:"pipeline_id"`
Ref string `json:"ref,omitempty"`
Branch string `json:"branch,omitempty"`
Tag string `json:"tag,omitempty"`
SHA string `json:"sha"`
Project string `json:"project,omitempty"` // repo/project name ({project})
Modality string `json:"modality,omitempty"` // lifecycle mode ({modality})
PipelineURL string `json:"pipeline_url,omitempty"` // forge pipeline URL ({pipeline_url})
CommitTitle string `json:"commit_title,omitempty"` // HEAD commit subject ({commit_title})
Version string `json:"version,omitempty"` // detected version string ({version})
// StartedAt is stamped (unix seconds) on the run's FIRST state write — the
// audition, in practice — and rides the state artifact across jobs. narrate
// derives DurationSecs from it at gather time; {duration} renders from the
// recorded value, so the render itself stays deterministic.
StartedAt int64 `json:"started_at,omitempty"`
DurationSecs int `json:"duration_secs,omitempty"`
}
CIState captures the CI environment for this pipeline run, plus the run's identity facts ({project}/{modality}/{pipeline_url}/{commit_title}) recorded by the phases that can compute them (best-effort fill; empty stays empty).
func InitFromCI ¶
InitFromCI populates a CIState from a ci.CIContext.
type ExternalRetentionRecord ¶ added in v0.5.0
type ExternalRetentionRecord struct {
Registry string `json:"registry"`
Prefix string `json:"prefix"`
Total int `json:"total"`
Pruned int `json:"pruned"`
Kept int `json:"kept"`
Errors []string `json:"errors,omitempty"`
}
ExternalRetentionRecord records external cache retention results.
type LocalRetentionRecord ¶ added in v0.5.0
type LocalRetentionRecord struct {
Dir string `json:"dir"`
EntriesBefore int `json:"entries_before"`
Pruned int `json:"pruned"`
PrunedBytes int64 `json:"pruned_bytes"`
}
LocalRetentionRecord records local cache retention results.
type ReleaseState ¶
type ReleaseState struct {
Eligible bool `json:"eligible"`
}
ReleaseState holds release-specific domain metadata. Lifecycle tracking is in Subsystems.
type RetentionState ¶ added in v0.5.0
type RetentionState struct {
Local *LocalRetentionRecord `json:"local,omitempty"`
External *ExternalRetentionRecord `json:"external,omitempty"`
}
RetentionState records cache retention enforcement results. Authoritative — governance and diagnostics can inspect this.
type SecurityState ¶
type SecurityState struct{}
SecurityState holds security-specific domain metadata. Lifecycle tracking is in Subsystems.
type State ¶
type State struct {
Version int `json:"version"`
CI CIState `json:"ci"`
Runner runner.ExecutionReport `json:"runner,omitempty"`
Build BuildState `json:"build"`
Security SecurityState `json:"security"`
Release ReleaseState `json:"release"`
Subsystems []SubsystemState `json:"subsystems,omitempty"`
Retention RetentionState `json:"retention,omitempty"`
}
State is the per-run ledger for the current pipeline workspace. Each subsystem records what it did; downstream stages read the ledger instead of probing files.
func ReadState ¶
ReadState reads pipeline state from the workspace. Returns a zero State (Version: 1) on missing file — missing state is normal when the first subsystem hasn't run yet. Only errors on I/O or parse failures for an existing file.
func (*State) Fact ¶ added in v0.8.0
Fact resolves a stencil fact token against the run's recorded truth. Facts are the deterministic data half of the stencil language (the author owns the framing):
- Bare status facts — {status}, {status_icon}, {status_verb} — derive from PipelineStatus and are status-CARRYING: they resolve differently on pass/fail, which is how bodies alternate without conditionals.
- Dotted facts — {<domain>.<key>} — read a subsystem's recorded Results ({tests.passed}, {security.blocking}, {publish.tags}), plus the lifecycle fields every subsystem carries ({security.outcome}, {security.reason}). {failure.subsystem}/{failure.reason} derive from the first hard-failed subsystem; {retention.pruned} from the retention records.
Resolution semantics honor the naming split: a DOTTED token is fact namespace by construction, so it always resolves (ok=true) — to "" when the domain recorded nothing, which is what makes presence-elision work ("no data → the line vanishes"). A BARE token that isn't a status fact returns ok=false so unknown bare tokens stay visibly literal (typo, or a token for the downstream gitver leaf-pass).
func (*State) GetSubsystem ¶ added in v0.5.0
func (st *State) GetSubsystem(name string) *SubsystemState
GetSubsystem returns the subsystem entry by name, or nil if not found.
func (*State) MergeSubsystemResults ¶ added in v0.8.0
MergeSubsystemResults merges fact key→value pairs into a subsystem's Results, creating the entry if the subsystem has no record yet and leaving every lifecycle field untouched. This is the write path for recorded facts ({domain.key} tokens).
func (*State) PipelineStatus ¶ added in v0.5.0
PipelineStatus derives the aggregate pipeline outcome from all subsystems. States: passing, warning, failing, unknown.
Resolution rules (platform-agnostic, policy-aware):
- Any required subsystem with outcome "failed" → failing
- Any non-required subsystem with outcome "failed" + allow_failure → warning
- Any subsystem with outcome "warning" → warning
- Nothing attempted → unknown
- Otherwise → passing
func (*State) RecordSubsystem ¶ added in v0.5.0
func (st *State) RecordSubsystem(s SubsystemState)
RecordSubsystem upserts a subsystem entry by name. A record carrying no Results preserves the existing entry's Results — lifecycle records (attempted/outcome) and fact records (MergeSubsystemResults) are written by different call sites, and an outcome upsert must not erase the facts a subsystem already recorded.
type SubsystemState ¶ added in v0.5.0
type SubsystemState struct {
Name string `json:"name"`
Attempted bool `json:"attempted"`
Completed bool `json:"completed"`
Skipped bool `json:"skipped"`
AllowFailure bool `json:"allow_failure"` // true = non-vital; failure produces warning, not fail
Required bool `json:"required"` // true = failure is a hard pipeline fail
Outcome string `json:"outcome"` // success | failed | skipped | warning | not_applicable | cancelled
Reason string `json:"reason,omitempty"`
// Blocking is the CONTROL truth about this subsystem's subject: may that subject
// continue producing distributable artifacts? A phase that consumes this NEVER
// branches on anything else. It is deliberately separate from Outcome (what happened)
// and Required (a forge-status policy). A remediated source is Blocking (the fix lives
// in Replacement, not in this subject), so it must never build.
Blocking bool `json:"blocking,omitempty"`
// Replacement is LINEAGE, never control: the commit that supersedes this subject when a
// fix was produced (the remediation candidate). Consumed by narrate / publish / the forge
// status renderer / webhooks — never by the orchestrator's build decision.
Replacement string `json:"replacement,omitempty"`
// Results is the subsystem's recorded FACTS — flat key→value pairs a subsystem
// computed during the run (test: passed/failed/total/coverage; security:
// blocking/low/…; publish: tags/registries). Consumed by the stencil fact
// resolver as {<domain>.<key>} tokens. Riding the subsystem shard keeps facts
// union-safe across forwarded job artifacts (same guarantee as Outcome). Generic
// by design: any subsystem (any modality) records facts without a schema change.
Results map[string]string `json:"results,omitempty"`
}
SubsystemState is the generic lifecycle phase record. All subsystems register here regardless of mode. The resolver uses this list — never hardcoded field names.