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 ¶
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.
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
NewFSStore returns a filesystem CAS rooted at root (e.g. ".stagefreight/objects"). The directory is created on first Put.
func NewWorkspaceStore ¶
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) RequiresOCIExport ¶
RequiresOCIExport is true for the filesystem store: perform must export an OCI layout for FSStore to retain it.
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 (*NoopStore) Put ¶
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 ¶
RequiresOCIExport is false: nothing is retained, so no layout is exported.
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.