Help us shape the future of Go! Take the 2026 Go Developer Survey

cas

package
v0.9.1 Latest Latest
Warning

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

Go to latest
Published: Aug 24, 2026 License: AGPL-3.0, AGPL-3.0-only Imports: 9 Imported by: 0

Documentation

Overview

Package cas is StageFreight's content-addressed artifact store: it retains the exact OCI layout bytes produced by a single perform-stage build so that review and publish operate on those bytes rather than re-deriving the image.

The architectural rule this enforces: within a single pipeline run, cross-phase artifact identity is guaranteed by TRANSPORT — build the bytes once in perform and carry them forward, re-hashing on read, so review and publish provably operate on the same bytes. Transport is the floor: it holds unconditionally, even where a build's reproducibility is imperfect.

Reproducibility is the complementary goal, not a substitute: a deterministic build makes the digest independently re-derivable by any third party (the auditable, supply-chain trust claim). Transport carries the verified bytes; reproducibility proves the digest means what it claims. CAS implements the transport floor.

This package is a PURE content store. It knows nothing about StageFreight phases, manifests, or build plans: it keys OCI layout directories by their content digest and verifies-on-read. The artifact/manifest layer adapts to it from the outside; cas never imports artifact, build, or docker.

Index

Constants

This section is empty.

Variables

View Source
var ErrIntegrity = errors.New("cas: stored bytes do not match digest (integrity failure)")

ErrIntegrity is returned when stored bytes do not hash to their key digest — the verify-on-read failure. CAS never returns bytes it cannot prove match the digest they were stored under.

View Source
var ErrNotFound = errors.New("cas: artifact not found")

ErrNotFound is returned by Resolve when no artifact is stored under the given digest. A missing artifact is always loud: a digest with no retrievable bytes is a claim, not a verified identity, and must never resolve to empty success.

Functions

func AssertStoreCapabilities

func AssertStoreCapabilities(s Store) error

AssertStoreCapabilities enforces the Store capability invariant: a store may not require an OCI export while providing no cross-phase transport (the forbidden quadrant). Such a store would make perform pay the export cost for bytes nothing carries forward.

Enforcement today is CONSUMPTION-time: the build calls this where a store enters execution (src/build/docker, execute.go). For the two current stores (FSStore, NoopStore) the quadrant is fixed in source, so a construction-time check would be tautological — there is no store factory/registry, hence no registration boundary to guard. When a pluggable store with runtime-decided capabilities is added (the documented Transport=true/RequiresOCIExport=false quadrant), its constructor SHOULD call this so an invalid configuration fails loudly at construction rather than only at first use — moving enforcement from consumption-time toward construction-time as soon as there is a construction boundary worth guarding.

func Retire

func Retire(workspaceRoot string) error

Retire deletes a workspace's content store after its last reader (publish) has consumed it. The CAS is RETIRED by publish — deterministic ownership — not swept by a background GC. It is strictly workspace-scoped: RemoveAll of THIS workspace's objects dir only, never a runner-wide path, so it can never reach a concurrent pipeline's store. Idempotent: no error if already gone.

func VerifyLayoutAt

func VerifyLayoutAt(dir string, digest Digest) error

VerifyLayoutAt verifies that the OCI layout at dir corresponds to digest: the digest is bound by index.json and every blob re-hashes to its name. This is the read-side proof a consumer (e.g. review) performs before trusting bytes resolved through a persistence handle — identity is never trusted without re-hashing the actual bytes. Returns nil on success, ErrIntegrity otherwise.

func WorkspaceObjectsDir

func WorkspaceObjectsDir(workspaceRoot string) string

WorkspaceObjectsDir is the one place the content-store path is derived: ALWAYS under the job workspace, never a shared or runner-scoped volume. This is the safety boundary — because the store is inside the per-job workspace, every lifecycle operation is bounded to one pipeline and cannot touch another project's store.

Types

type Digest

type Digest string

Digest is a content-addressable identity in "algo:hex" form (only sha256 is supported). cas uses its own primitive rather than importing the artifact package so the store stays a standalone library; callers convert at the boundary.

type FSStore

type FSStore struct {
	// contains filtered or unexported fields
}

FSStore is a filesystem-backed Store. Layouts live under root/<algo>/<hash>/ as ordinary OCI layout directories. The store is keyed by digest from day one even though the backend is a plain tree — the backend is an implementation detail behind the Store interface, swappable later (e.g. object storage) without changing identity semantics.

func NewFSStore

func NewFSStore(root string) *FSStore

NewFSStore returns a filesystem CAS rooted at root (e.g. ".stagefreight/objects"). The directory is created on first Put.

func NewWorkspaceStore

func NewWorkspaceStore(workspaceRoot string) *FSStore

NewWorkspaceStore returns the content store for a job workspace. Callers pass the WORKSPACE ROOT, not an arbitrary path, so the store can only ever live inside that workspace — the structural guard against relocating the CAS onto a shared volume, which would turn cleanup into a cross-tenant footgun. Use this on the lifecycle path; NewFSStore(arbitraryPath) is for tests and inert stores.

func (*FSStore) Put

func (s *FSStore) Put(digest Digest, layoutDir string) (string, error)

Put retains the OCI layout, verifying the digest names a real blob first.

func (*FSStore) RequiresOCIExport

func (*FSStore) RequiresOCIExport() bool

RequiresOCIExport is true for the filesystem store: perform must export an OCI layout for FSStore to retain it.

func (*FSStore) Resolve

func (s *FSStore) Resolve(digest Digest) (string, error)

Resolve returns the stored layout dir for digest after verify-on-read.

func (*FSStore) Root

func (s *FSStore) Root() string

Root returns the store's root directory.

func (*FSStore) Transport

func (*FSStore) Transport() bool

Transport is true: the filesystem store carries artifacts across phases, so distribution becomes the publish phase's sole authority.

type NoopStore

type NoopStore struct{}

NoopStore is the inert Store: it retains nothing and requires no OCI export. Selecting it makes a deployment's perform path behave exactly as before Phase 2 (daemon --load only, no layout export, empty PersistenceHandle). It exists so the store is always non-nil and the capability boundary is the single switch — perform never special-cases "no store".

func NewNoopStore

func NewNoopStore() *NoopStore

NewNoopStore returns the inert store.

func (*NoopStore) Put

func (*NoopStore) Put(_ Digest, _ string) (string, error)

Put is a no-op that retains nothing and returns an empty stored dir. It does not error: a deployment with persistence disabled is valid, not a failure.

func (*NoopStore) RequiresOCIExport

func (*NoopStore) RequiresOCIExport() bool

RequiresOCIExport is false: nothing is retained, so no layout is exported.

func (*NoopStore) Resolve

func (*NoopStore) Resolve(digest Digest) (string, error)

Resolve always reports not-found: the noop store retains nothing, so any resolve is a loud miss rather than a silent empty success.

func (*NoopStore) Transport

func (*NoopStore) Transport() bool

Transport is false: the noop store provides no cross-phase transport, so distribution remains wherever it was (perform's legacy push fallback).

type Store

type Store interface {
	// Transport reports whether this store provides cross-phase artifact
	// transport — i.e. whether the deployment's lifecycle should treat
	// distribution as the publish phase's sole authority (perform retains
	// instead of pushing). This is the POLICY question ("is transport active?"),
	// deliberately separate from the MECHANISM question RequiresOCIExport
	// ("must perform emit an OCI layout?"). They are equivalent for FSStore today
	// but a future store (e.g. registry-backed) could provide transport without
	// the same export mechanism; policy decisions must key on Transport, never on
	// RequiresOCIExport, so the mechanism does not leak into policy.
	Transport() bool

	// RequiresOCIExport reports whether perform must export an OCI layout for
	// this store to retain. This is a MECHANISM query: perform asks the store
	// whether to pay the export cost, and never branches on concrete store type.
	// A store that retains nothing (NoopStore) returns false so no layout is
	// exported and discarded.
	RequiresOCIExport() bool

	// Put retains the OCI layout rooted at layoutDir under digest, and returns
	// the absolute path to the stored layout. The digest MUST name a blob present
	// in the layout (blobs/sha256/<hash>); Put verifies this before retaining, so
	// a bad layout fails at write time, not read time.
	Put(digest Digest, layoutDir string) (storedDir string, err error)

	// Resolve returns the absolute path to the retained OCI layout directory for
	// digest, after verifying the named blob re-hashes to digest. Returns
	// ErrNotFound if absent, ErrIntegrity if the stored bytes have drifted.
	Resolve(digest Digest) (storedDir string, err error)
}

Store retains and retrieves OCI layout artifacts keyed by their content digest (the OCI image manifest/index digest buildx reports as containerimage.digest).

Put and Resolve are the whole contract: Put retains a layout, Resolve returns it only after proving the named blob re-hashes to the key. There is no "trust the store" path — every Resolve verifies.

Transport and RequiresOCIExport are two independent capability axes (policy vs mechanism). Of their four combinations, three are valid and one is forbidden:

Transport  RequiresOCIExport  meaning
false      false              NoopStore — no transport, nothing retained.
true       true               FSStore — transport via a retained OCI layout.
true       false              valid future store — provides transport by a
                              mechanism other than perform exporting a layout
                              (e.g. a registry-/object-backed CAS that ingests
                              the image itself). Lifecycle treats it as
                              transport-active without perform paying the OCI
                              export cost.
false      true               FORBIDDEN — a store that demands perform export
                              an OCI layout but provides no cross-phase
                              transport would make perform pay the export cost
                              for bytes nothing carries forward. Implementations
                              MUST NOT land here; AssertStoreCapabilities
                              enforces it.

Jump to

Keyboard shortcuts

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