cas

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: 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