artifact

package
v0.6.1 Latest Latest
Warning

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

Go to latest
Published: Jun 5, 2026 License: AGPL-3.0, AGPL-3.0-only Imports: 11 Imported by: 0

Documentation

Index

Constants

View Source
const (
	OutputsManifestPath  = ".stagefreight/outputs.json"
	OutputsSchemaVersion = "2"
)
View Source
const (
	ResultsManifestPath  = ".stagefreight/published.json"
	ResultsSchemaVersion = "2"
)

Variables

View Source
var (
	ErrOutputsManifestNotFound = errors.New("outputs manifest not found")
	ErrOutputsManifestInvalid  = errors.New("outputs manifest invalid")
)
View Source
var (
	ErrResultsManifestNotFound = errors.New("results manifest not found")
	ErrResultsManifestInvalid  = errors.New("results manifest invalid")
)

Functions

func WriteOutputsManifest added in v0.6.0

func WriteOutputsManifest(dir string, manifest OutputsManifest) error

WriteOutputsManifest finalizes (if needed) the manifest and writes it atomically to disk.

func WriteResultsManifest added in v0.6.0

func WriteResultsManifest(dir string, manifest ResultsManifest) error

WriteResultsManifest finalizes (if needed) the manifest and writes it atomically to disk.

Types

type ArchiveDescriptor added in v0.6.0

type ArchiveDescriptor struct {
	Format string `json:"format"`
	Path   string `json:"path"`
}

ArchiveDescriptor describes a packaged archive artifact. Plan-time intent only — content SHA256 is observed at archive time and recorded in the corresponding Outcome.

type ArchiveExecutionView added in v0.6.0

type ArchiveExecutionView struct {
	// Identity (intent)
	ArtifactID   ArtifactID
	ArtifactKind string // always "archive"
	ArtifactName string
	Version      string

	// Descriptor (intent)
	Format string
	Path   string

	// Build observation (outcome)
	BuildStatus OutcomeStatus
	SHA256      string
	Size        int64
	BuildError  string

	// Sibling reference — semantically unordered set of binary ArtifactIDs.
	// Consumers MUST NOT depend on the order of this slice.
	Sources []ArtifactID

	// Provenance
	ExpectedCommit string
}

ArchiveExecutionView is the canonical join for archive artifacts. One view per archive_build outcome. Archive is a sibling of its source binaries (Q3) — Sources references binary ArtifactIDs by string only, never embedding binary fields. Sources is treated as an unordered set by consumers; on-disk serialization sorts it for determinism.

Per the same projection-not-resolver rule as BinaryExecutionView, this view does NOT walk into BinaryExecutionViews for its Sources. Consumers that need binary details must look them up separately.

func BuildArchiveExecutionViews added in v0.6.0

func BuildArchiveExecutionViews(outputs *OutputsManifest, results *ResultsManifest) []ArchiveExecutionView

BuildArchiveExecutionViews produces one view per recorded archive_build outcome. Returns nil for nil inputs. Like BinaryExecutionView, surfaces failures broadly; consumers filter on BuildStatus.

type ArchiveOutcome added in v0.6.0

type ArchiveOutcome struct {
	Status  OutcomeStatus `json:"status"`
	SHA256  string        `json:"sha256,omitempty"`
	Path    string        `json:"path,omitempty"`
	Format  string        `json:"format,omitempty"` // "tar.gz" | "zip" | ...
	Size    int64         `json:"size,omitempty"`
	Sources []ArtifactID  `json:"sources,omitempty"`
	Error   string        `json:"error,omitempty"`
}

ArchiveOutcome is the result of building one archive artifact (a packaging operation over one or more binary artifacts). Sources references source binaries by ArtifactID — sibling relationship, not embedding. Sources is semantically unordered (treat as a set); on-disk serialization sorts it for determinism but consumer logic MUST NOT depend on the order.

Size is parallel to BinaryOutcome.Size — a build-time observation of the final archive file's byte length. It is recorded here so consumers (release_create's BinaryRow construction) don't have to re-stat the file at consumer time, which would leak build-layer I/O into the release layer.

type Artifact added in v0.6.0

type Artifact struct {
	ID      ArtifactID `json:"id"`
	Kind    string     `json:"kind"`
	Name    string     `json:"name"`
	Version string     `json:"version,omitempty"`

	// Digest is the content-addressable identity of the produced artifact,
	// materialized at build completion (NOT at publish). For docker artifacts
	// this is the OCI image index digest as reported by buildx
	// `containerimage.digest` — the identity a registry serves for the tag and
	// the thing publish projects, stable across the multi-platform case. For
	// binary/archive artifacts it is the sha256 of the produced bytes.
	//
	// Digest is the sole identity primitive: it is derived from bytes, never
	// from daemon state (NOT docker inspect {{.Id}}, which is a per-platform
	// config ID that diverges from the index digest on multi-platform builds).
	//
	// Phase 1 status: Digest is authoritative identity but NOT yet load-bearing
	// for any approval decision — review still resolves its scan target from
	// publication outcomes until a persistence layer (CAS) can retrieve and
	// re-hash these bytes. A digest without retrievable bytes is a claim, not a
	// verified identity; do not gate approval on it until persistence exists.
	Digest Digest `json:"digest,omitempty"`

	// Persistence is how StageFreight retrieves the artifact's exact bytes in a
	// later phase (review, publish) without re-deriving them. It is a retrieval
	// handle, NOT a distribution location: by construction there is no variant
	// that names a user-facing registry, so "already published" is unexpressible
	// here. The bytes are reachable only through StageFreight-controlled
	// resolution (the cas store), never directly by an external system.
	//
	// Phase 2 status: this handle is written by perform once the build's bytes
	// are retained in the content store, but it is consumed by NOTHING yet — no
	// review or publish decision reads it. It becomes load-bearing only when the
	// review inversion (a later phase) resolves and re-hashes through it. A
	// present handle must never be read as implicit trust.
	Persistence PersistenceHandle `json:"persistence,omitempty"`

	Docker  *DockerDescriptor  `json:"docker,omitempty"`
	Binary  *BinaryDescriptor  `json:"binary,omitempty"`
	Archive *ArchiveDescriptor `json:"archive,omitempty"`

	Targets []Target `json:"targets"`
}

Artifact is a single output produced by perform. ID is the stable identity used to correlate intent ↔ result; Name is human-friendly. Exactly one of the kind-specific descriptor pointers is set, matching Kind.

ID is typed (ArtifactID, not bare string) so identity values cannot be assembled inline from fields elsewhere in the codebase. The only approved constructor is NewArtifactID.

type ArtifactID added in v0.6.0

type ArtifactID string

ArtifactID is the system-wide identity primitive. The typed string alias turns identity construction into a compile-visible gesture rather than a runtime string assembly — any code that needs an ArtifactID must obtain it from a manifest or a view, never assemble one from fields inline. This is the structural lock that prevents the friendly-name shortcut pattern (e.g. `binaryName + "-" + os + "-" + arch`) from reintroducing alternate identity systems.

Format: "<kind>:<name>". The constructor NewArtifactID is the only approved way to mint a new ID inside the package; external callers receive IDs from view/manifest read operations.

func NewArtifactID added in v0.6.0

func NewArtifactID(kind, name string) ArtifactID

NewArtifactID mints the stable identity of an artifact across reruns. Derived purely from (kind, name). Mutable inputs (commit SHA, pipeline ID, time) MUST NOT be embedded — that would defeat intent↔result correlation when publish runs in a separate job from perform. Uniqueness across the artifacts slice within a manifest is enforced at Write time.

type AttestationOutcome added in v0.6.0

type AttestationOutcome struct {
	Status         OutcomeStatus `json:"status"`
	Kind           string        `json:"kind,omitempty"` // "cosign" | "in_toto" | "slsa"
	SignatureRef   string        `json:"signature_ref,omitempty"`
	AttestationRef string        `json:"attestation_ref,omitempty"`
	VerifiedDigest string        `json:"verified_digest,omitempty"`
	Error          string        `json:"error,omitempty"`
}

AttestationOutcome is the result of attempting to sign or attest an already-published reference. Linked to the corresponding PushOutcome via shared ArtifactID + Target — never embedded inside it.

type BinaryDescriptor added in v0.6.0

type BinaryDescriptor struct {
	OS        string `json:"os"`
	Arch      string `json:"arch"`
	Path      string `json:"path"`
	Toolchain string `json:"toolchain,omitempty"`
}

BinaryDescriptor describes a compiled binary artifact. Plan-time intent only — SHA256 and final digest are observed at build time and recorded in the corresponding Outcome, not here.

type BinaryExecutionView added in v0.6.0

type BinaryExecutionView struct {
	// Identity (intent)
	ArtifactID   ArtifactID
	ArtifactKind string // always "binary"
	ArtifactName string
	Version      string

	// Descriptor (intent)
	OS        string
	Arch      string
	Path      string
	Toolchain string

	// Build observation (outcome)
	BuildStatus OutcomeStatus
	SHA256      string
	Size        int64
	BuildID     string
	BuildError  string

	// Provenance
	ExpectedCommit string
}

BinaryExecutionView is the canonical consumer-side join over an OutputsManifest (intent) and a ResultsManifest (execution facts) for binary artifacts. Each successful binary build produces one view.

Binary artifacts are un-targeted by design (Q2): there are no Target coordinates, no registry, no remote ref. The build artifact IS the truth. Distribution destinations are decided at a later layer.

Per the architectural rule: "Views are projections, not resolvers." BinaryExecutionView does NOT include archive information, does NOT resolve dependencies, does NOT walk into other views. Consumers that need cross-domain context (e.g., archives that wrap this binary) must query the relevant view builder separately and join at the consumer level. Sibling artifact relationships are referenced by ArtifactID only.

func BuildBinaryExecutionViews added in v0.6.0

func BuildBinaryExecutionViews(outputs *OutputsManifest, results *ResultsManifest) []BinaryExecutionView

BuildBinaryExecutionViews produces one view per recorded binary_build outcome. Returns nil for nil inputs. Surfaces failed builds too — the view layer is broad; consumers narrow by filtering on BuildStatus.

type BinaryOutcome added in v0.6.0

type BinaryOutcome struct {
	Status  OutcomeStatus `json:"status"`
	SHA256  string        `json:"sha256,omitempty"`
	Path    string        `json:"path,omitempty"`
	Size    int64         `json:"size,omitempty"`
	BuildID string        `json:"build_id,omitempty"`
	Error   string        `json:"error,omitempty"`
}

BinaryOutcome is the result of building one binary artifact. The authoritative identity event for binaries — there is no separate "push to registry" step. SHA256 is the binary's content hash, computed at build time over the final on-disk artifact bytes.

Per the unified determinism rule, SHA256 reflects whatever the build produced; the recording layer does not verify or normalize.

type Digest added in v0.6.0

type Digest string

Digest is a content-addressable artifact identity ("sha256:..."). Named primitive (not bare string) so "this is computed identity" is legible at every call site and a registry tag cannot be passed where a digest belongs.

type DockerDescriptor added in v0.6.0

type DockerDescriptor struct {
	Dockerfile      string   `json:"dockerfile"`
	Context         string   `json:"context"`
	Platforms       []string `json:"platforms"`
	BuildArgsDigest string   `json:"build_args_digest,omitempty"`
}

DockerDescriptor describes a docker image to be built.

type ForgeReleaseAssetTarget added in v0.6.0

type ForgeReleaseAssetTarget struct {
	AssetName string `json:"asset_name"`
}

ForgeReleaseAssetTarget describes a forge release asset destination (GitLab/GitHub/Gitea release with attached file).

type OCILayoutRef added in v0.6.0

type OCILayoutRef struct {
	Path string `json:"path"`
}

OCILayoutRef points at an OCI layout retained in the content store. Path is store-relative; it is resolved only by the cas store, never consumed as a remotely-fetchable address.

type Outcome added in v0.6.0

type Outcome struct {
	Type   OutcomeType    `json:"type"`
	Target *OutcomeTarget `json:"target,omitempty"`

	Push        *PushOutcome        `json:"push,omitempty"`
	Attestation *AttestationOutcome `json:"attestation,omitempty"`
	Binary      *BinaryOutcome      `json:"binary,omitempty"`
	Archive     *ArchiveOutcome     `json:"archive,omitempty"`
}

Outcome is one observed fact about one execution event. Outcomes are atomic and independent: push success and signing success are separate Outcomes with separate Type values. They are NOT combined fields of a single record. That separation is load-bearing — collapsing them would let a missing attestation be silently encoded as a successful push outcome.

Discriminated union: Type selects which of the kind-specific sub-pointers is populated. Target is nil-able because not every outcome type has a target (binary build, archive build) — see Type-vs-Target rule in validateOutcome.

type OutcomeStatus added in v0.6.0

type OutcomeStatus string

OutcomeStatus is a closed enum applied per-sub-outcome. Push and attestation each carry their own Status — never shared.

const (
	OutcomeSuccess OutcomeStatus = "success"
	OutcomeFailed  OutcomeStatus = "failed"
	OutcomeSkipped OutcomeStatus = "skipped"
)

func (OutcomeStatus) Valid added in v0.6.0

func (s OutcomeStatus) Valid() bool

Valid reports whether s is one of the defined OutcomeStatus values. Empty is invalid: every recorded sub-outcome must declare a status.

type OutcomeTarget added in v0.6.0

type OutcomeTarget struct {
	Kind string `json:"kind"`
	Host string `json:"host,omitempty"`
	Path string `json:"path,omitempty"`
	Tag  string `json:"tag,omitempty"`
}

OutcomeTarget identifies the specific target an outcome refers to. Mirrors (a slice of) the intent Target — kind + the identifying coordinates.

type OutcomeType added in v0.6.0

type OutcomeType string

OutcomeType discriminates the Outcome sub-kind. Closed enum.

const (
	OutcomeTypePush        OutcomeType = "push"
	OutcomeTypeAttestation OutcomeType = "attestation"
	OutcomeTypeBinaryBuild OutcomeType = "binary_build"
	OutcomeTypeArchive     OutcomeType = "archive_build"
)

func (OutcomeType) Valid added in v0.6.0

func (t OutcomeType) Valid() bool

Valid reports whether t is one of the defined OutcomeType values.

type OutputsManifest added in v0.6.0

type OutputsManifest struct {
	SchemaVersion string     `json:"schema_version"`
	GeneratedAt   string     `json:"generated_at"`
	Commit        string     `json:"commit,omitempty"`
	Pipeline      *Pipeline  `json:"pipeline,omitempty"`
	Artifacts     []Artifact `json:"artifacts"`
	Checksum      string     `json:"checksum,omitempty"`
}

OutputsManifest is the immutable description of the outputs produced by perform and the publication requirements approved by review. It is written by the perform phase, read by review and publish, and never modified after perform completes. Publish records observed outcomes in a separate ResultsManifest tied to this file via intent_checksum.

Determinism is a property of the type system here, not the encoder: all fields are strictly typed, struct field order is fixed, and no maps appear in the schema. encoding/json's standard MarshalIndent is sufficient to produce byte-deterministic output.

func ReadOutputsManifest added in v0.6.0

func ReadOutputsManifest(dir string) (*OutputsManifest, error)

ReadOutputsManifest reads and verifies the outputs manifest. Verification fails on schema mismatch, malformed JSON, RFC3339 violation, enum violation, or checksum mismatch.

func (*OutputsManifest) Finalize added in v0.6.0

func (m *OutputsManifest) Finalize() error

Finalize populates derived fields (schema_version, generated_at, artifact ids), validates and normalizes structure, and computes the embedded SHA-256 checksum. After a successful Finalize, the manifest is byte-deterministic and ready to be either written or used as the intent snapshot for a ResultsBuilder.

Idempotent on already-finalized manifests when content has not changed. Mutates the receiver.

type PersistenceHandle added in v0.6.0

type PersistenceHandle struct {
	Kind      PersistenceKind `json:"kind,omitempty"`
	OCILayout *OCILayoutRef   `json:"oci_layout,omitempty"`
}

PersistenceHandle is an inert, serializable retrieval handle: data that tells StageFreight where to get an artifact's exact bytes, resolved only through the content store. It is a closed union (exactly one variant pointer set, matching Kind); it carries no behavior and no external-fetch capability.

type PersistenceKind added in v0.6.0

type PersistenceKind string

PersistenceKind enumerates how an artifact's bytes are retained for later phases. Closed set by design: a registry/distribution variant is deliberately absent so the persistence handle can never express "lives in a user-facing registry" — persistence is not distribution.

const (
	// PersistenceNone means no bytes are retained (e.g. plan-time, or an
	// artifact whose persistence has not been wired). The zero value.
	PersistenceNone PersistenceKind = ""
	// PersistenceOCILayout means the bytes are an OCI image layout retained in
	// the StageFreight content store, addressed by Digest.
	PersistenceOCILayout PersistenceKind = "oci_layout"
)

type Pipeline added in v0.6.0

type Pipeline struct {
	ID       string `json:"id,omitempty"`
	Provider string `json:"provider,omitempty"`
}

Pipeline captures the CI context that produced the manifest.

type PublicationView added in v0.6.0

type PublicationView struct {
	// ── Identity (intent) ──────────────────────────────────────────────
	ArtifactID   ArtifactID // "<kind>:<name>" — the cross-domain join key
	ArtifactKind string
	ArtifactName string
	Version      string // from Artifact.Version; may be empty

	// ── Target coordinates (observation) ───────────────────────────────
	Host string
	Path string
	Tag  string

	// ── Push observation (results.outcomes[type=push]) ─────────────────
	PushStatus     OutcomeStatus
	Digest         string // immutable digest if known; empty if registry did not return one
	ObservedDigest string
	ObservedBy     string
	PushError      string // empty on success

	// ── Attestation observation (results.outcomes[type=attestation]) ──
	// SigningAttempted is derived: true iff any attestation outcome
	// exists for (ArtifactID, Host, Path, Tag), regardless of status.
	// Absence of an attestation outcome means signing was NOT attempted
	// — per the architectural invariant established in Phase 3.
	SigningAttempted bool
	Attestation      *AttestationOutcome

	// ── Joined intent metadata ────────────────────────────────────────
	ExpectedTags   []string     // all tags configured for this (host, path) registry target
	ExpectedCommit string       // OutputsManifest.Commit
	Requirements   Requirements // intent-side requirements for this target (sign, attest, sbom)
}

PublicationView is the canonical consumer-side join over an OutputsManifest (intent) and a ResultsManifest (execution facts). One PublicationView per (artifact, target, tag) tuple where execution recorded a push outcome — successful or otherwise.

The view is the first read-model primitive in the v2 truth pipeline: instead of reading the dual-purpose v1 PublishManifest, consumers (security_scan, release_create, verify, etc.) consume []PublicationView produced by BuildPublicationViews. All join logic lives here, not in each consumer.

Field provenance is explicit in the comments below: each field is sourced from intent OR observation. Derived fields are reconstructions over outcome existence, not stored state.

func BuildPublicationViews added in v0.6.0

func BuildPublicationViews(outputs *OutputsManifest, results *ResultsManifest) []PublicationView

BuildPublicationViews produces the canonical join over outputs (intent) and results (observations). Returns one view per push outcome.

Returns nil if either manifest is nil. If outputs contains no matching intent for a recorded artifact_id, that artifact's outcomes are still surfaced — the cross-domain validation already happened at ResultsBuilder.Build time; here we only do enrichment.

Per-target attestation outcomes are joined to push outcomes by exact (artifact_id, host, path, tag) tuple. If multiple attestation outcomes exist for the same tuple (e.g. retry behavior), the last one in append order wins — that represents the final attempted state.

func (PublicationView) DigestRef added in v0.6.0

func (v PublicationView) DigestRef() string

DigestRef returns the immutable reference (host/path@digest) when a digest is known, or empty string otherwise. Consumers that require an immutable target (e.g. security scanning) should check for empty before using.

func (PublicationView) Ref added in v0.6.0

func (v PublicationView) Ref() string

Ref returns the canonical mutable image reference: host/path:tag.

type PushOutcome added in v0.6.0

type PushOutcome struct {
	Status         OutcomeStatus `json:"status"`
	Digest         string        `json:"digest,omitempty"`
	ObservedDigest string        `json:"observed_digest,omitempty"`
	ObservedBy     string        `json:"observed_by,omitempty"`
	Error          string        `json:"error,omitempty"`
}

PushOutcome is the result of pushing one artifact reference to one registry target. Independent of any later signing/attestation activity.

type RegistryTarget added in v0.6.0

type RegistryTarget struct {
	Host string   `json:"host"`
	Path string   `json:"path"`
	Tags []string `json:"tags"`
}

RegistryTarget describes a container registry destination.

type Requirements added in v0.6.0

type Requirements struct {
	Sign   bool `json:"sign,omitempty"`
	Attest bool `json:"attest,omitempty"`
	SBOM   bool `json:"sbom,omitempty"`
}

Requirements expresses publication requirements approved by review. Adding a new requirement is a typed field addition (backward compatible: zero value = not required).

type Result added in v0.6.0

type Result struct {
	ArtifactID   ArtifactID `json:"artifact_id"`
	ArtifactName string     `json:"artifact_name"`
	Kind         string     `json:"kind"`
	Outcomes     []Outcome  `json:"outcomes"`
}

Result groups all outcomes for a single intent artifact. ArtifactID is typed (the ArtifactID alias, not bare string) so the cross-domain join key cannot be silently confused with a free-form string elsewhere in the codebase.

type ResultsManifest added in v0.6.0

type ResultsManifest struct {
	SchemaVersion  string   `json:"schema_version"`
	CompletedAt    string   `json:"completed_at"`
	IntentChecksum string   `json:"intent_checksum"`
	Results        []Result `json:"results"`
	Checksum       string   `json:"checksum,omitempty"`
}

ResultsManifest records what was actually externalized by publish. It is cryptographically tied to the OutputsManifest it fulfills via IntentChecksum. Written by publish, read by narrate and external auditors.

func ReadResultsManifest added in v0.6.0

func ReadResultsManifest(dir string) (*ResultsManifest, error)

ReadResultsManifest reads and verifies the results manifest. Verification fails on schema mismatch, malformed JSON, RFC3339 violation, enum violation, or checksum mismatch.

func (*ResultsManifest) Finalize added in v0.6.0

func (m *ResultsManifest) Finalize() error

Finalize populates derived fields, validates structure, and computes the embedded SHA-256 checksum. Mirrors OutputsManifest.Finalize.

type Target added in v0.6.0

type Target struct {
	Kind string `json:"kind"`

	Registry          *RegistryTarget          `json:"registry,omitempty"`
	ForgeReleaseAsset *ForgeReleaseAssetTarget `json:"forge_release_asset,omitempty"`

	Requirements Requirements `json:"requirements,omitempty"`
}

Target is one destination an artifact must be distributed to. Exactly one of the kind-specific target pointers is set, matching Kind. Credentials are not part of this contract — they are deployment configuration.

Jump to

Keyboard shortcuts

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