application

package
v0.16.1 Latest Latest
Warning

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

Go to latest
Published: Oct 31, 2026 License: Apache-2.0 Imports: 25 Imported by: 0

Documentation

Overview

Package application contains the fetch use cases.

- FetchModuleUseCase orchestrates a single fetch: resolve the version via the proxy, download the module zip, cross-verify it against the Go checksum database and (unless skipped) the upstream VCS, persist the zip to the blob store, and write a FactRecord with its VerificationStatus. The aggregate it produces is fetch/domain's FactRecord. - QueryFetchUseCase provides read-only access to stored FactRecords.

The layer depends only on fetch/domain and fetch/ports — it has no knowledge of HTTP, SQLite, filesystems, or git mechanics. All timestamps come from an injected Clock so fetches are reproducible under a fixed clock; verification logic lives here (the domain only defines the status enum).

Index

Constants

View Source
const PipelineVersion = domain2.PipelineVersion

PipelineVersion identifies this release of the fetch pipeline; bump domain.PipelineVersion, which it names.

Variables

View Source
var ErrGoSumVerification = errors.New("go.sum verification failed")

ErrGoSumVerification marks a hard go.sum verification failure. It is raised in two places, both tamper-evidence:

  • --from-modcache mode, where go.sum is the sole anchor: a module whose computed h1 does not match its go.sum entry, OR which is absent from go.sum entirely (see verifyAgainstGoSum).
  • the normal network path, where a project go.sum is an additional anchor layered onto network sumdb verification: a module present in go.sum whose computed zip/go.mod h1 disagrees (see checkProjectGoSum). Absence from go.sum is NOT a failure there — it falls through to network sumdb.

Callers match it with errors.Is to distinguish a tamper failure from an ordinary fetch error.

View Source
var ErrPrivateModuleUnanchored = errors.New("a private module is read only from the module cache against a project go.sum, and none was given")

ErrPrivateModuleUnanchored refuses a private module when no project go.sum was given: the module-cache route has nothing else to verify its bytes by.

View Source
var ErrVerificationFailed = errors.New("fact record failed verification")

ErrVerificationFailed marks a fact record that failed its integrity verification. Both the write side (Ingest, which refuses to persist) and the read side (ReadVerified, which refuses to present) wrap the underlying domain error with this sentinel, so a consumer can distinguish a tampered or unverifiable record — treat-as-unavailable, fail-closed — from a genuine absence or an infrastructure error.

Functions

func OriginSubdirOf

func OriginSubdirOf(modulePath, gitSubdir string) (string, bool)

OriginSubdirOf recovers the go command's Origin.Subdir (the code directory) from a record's git_subdir by inverting the rule selectModuleDir applies. ok is false when the record names no directory, or when the directory fits no code directory of the module path or fits more than one: a guess is never returned.

Types

type ContentIdentityUseCase

type ContentIdentityUseCase struct{}

ContentIdentityUseCase is the single source of canonical identity shared by signing, bundling, and integrity audit. It exposes the canonical digest of a record/blob and the Merkle root + inclusion proof over a selected set of such digests. It deliberately does not expose the internal *Hasher types: callers receive identities, never the hashing machinery, so a signature or bundle can never drift from core's digest.

The use case is pure and stateless; construct one with NewContentIdentityUseCase and call its methods directly.

func NewContentIdentityUseCase

func NewContentIdentityUseCase() *ContentIdentityUseCase

NewContentIdentityUseCase constructs the content-identity use case.

func (*ContentIdentityUseCase) CanonicalDigest

func (uc *ContentIdentityUseCase) CanonicalDigest(data []byte) ports.SubjectDigest

CanonicalDigest returns the canonical SubjectDigest of raw record/blob bytes. Raw bytes have exactly one digest, so this introduces no canonicalisation choice and matches the content-address the blob store and the fetch signing call sites derive — the digest a Signer attests over.

func (*ContentIdentityUseCase) InclusionProof

func (uc *ContentIdentityUseCase) InclusionProof(members []ports.SubjectDigest, index int) (InclusionProof, error)

InclusionProof returns the proof that the member at index belongs to the ordered set committed by MerkleRoot. It errors when the set is empty, index is out of range, or any member is malformed.

func (*ContentIdentityUseCase) MerkleRoot

func (uc *ContentIdentityUseCase) MerkleRoot(members []ports.SubjectDigest) (ports.SubjectDigest, error)

MerkleRoot returns the canonical digest committing to the ordered set of member digests. Order is significant. It errors when the set is empty (an empty set has no canonical root) or when any member is not a well-formed canonical digest.

func (*ContentIdentityUseCase) VerifyInclusion

func (uc *ContentIdentityUseCase) VerifyInclusion(member ports.SubjectDigest, proof InclusionProof, root ports.SubjectDigest) bool

VerifyInclusion reports whether proof links the member digest to root. It returns false on any malformed digest or structural mismatch, so a caller can treat false as "not proven" without inspecting an error.

type FetchModuleUseCase

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

FetchModuleUseCase orchestrates fetching, verification, and persistence of a single Go module at a pinned version.

func NewFetchModuleUseCase

func NewFetchModuleUseCase(
	proxy ports.ModuleProxy,
	vcs ports.VCSClient,
	blobs ports.BlobStore,
	facts ports.FactStore,
	sumdb ports.SumDBClient,
	clock ports.Clock,
	stopwatch ports.Stopwatch,
	pipelineVersion string,
	logger *slog.Logger,
) *FetchModuleUseCase

NewFetchModuleUseCase constructs a FetchModuleUseCase. pipelineVersion defaults to PipelineVersion if empty.

func (*FetchModuleUseCase) Execute

func (uc *FetchModuleUseCase) Execute(ctx context.Context, req FetchRequest) (_ FetchResult, retErr error)

Execute runs the full fetch-verify-persist pipeline for the given module.

Verification failures (UnverifiedX statuses) do not fail Execute; they are recorded in the FactRecord. Proxy, VCS, and storage errors do fail Execute.

func (*FetchModuleUseCase) WithAllowVerificationDowngrade

func (uc *FetchModuleUseCase) WithAllowVerificationDowngrade(allow bool) *FetchModuleUseCase

WithAllowVerificationDowngrade permits a weaker re-measurement to replace a stronger stored record. It is deliberately NOT --force: forcing means "re-measure this module now", not "permit a weaker anchor to replace a stronger fact", and conflating the two is what let a --from-modcache run silently demote records a network run had anchored to the transparency log. The default (false) keeps the stronger record. Returns uc for chaining.

func (*FetchModuleUseCase) WithAudit

WithAudit wires an assurance-log sink so the write side emits an event when a re-measurement is refused for weakening a record's verification anchor, and when an operator explicitly permits such a weakening. A nil sink (the default) emits nothing and changes no behaviour. Returns uc for chaining.

func (*FetchModuleUseCase) WithHostResolver

func (uc *FetchModuleUseCase) WithHostResolver(f func(ctx context.Context, host string) ([]net.IP, error)) *FetchModuleUseCase

WithHostResolver overrides the resolver used by the Origin address guard.

func (*FetchModuleUseCase) WithModcacheMode

func (uc *FetchModuleUseCase) WithModcacheMode() *FetchModuleUseCase

WithModcacheMode switches the use case into --from-modcache mode. In this mode Execute reads module bytes from the module-cache-backed proxy and verifies each module's h1 against the local go.sum (a mismatch or missing entry is a hard ErrGoSumVerification failure) instead of consulting the network checksum database. Returns uc for chaining.

It used to take a handle deriver, which produced "modcache:zip:<coord>" addresses from the coordinate and wrote them into records instead of calling blobs.Put. Those addresses were mode-locked: only the module-cache adapter resolved them, so a record written here was unreadable by a later ordinary run, and the same artefact measured both ways yielded two irreconcilable records. The mode now stores bytes under the artefact identity it measured, exactly like every other path, so nothing about the record betrays which mode wrote it beyond the acquisition-mode provenance field.

func (*FetchModuleUseCase) WithPrivateModules

func (uc *FetchModuleUseCase) WithPrivateModules(match func(path string) (string, bool), anchor ports.SumDBClient) *FetchModuleUseCase

WithPrivateModules routes every module match names as private through the module-cache route, anchored on the project go.sum in anchor, instead of the proxy path. match returns the variable that made the module private. The proxy the use case holds must send those modules to the module cache too.

func (*FetchModuleUseCase) WithProjectGoSum

func (uc *FetchModuleUseCase) WithProjectGoSum(goSum ports.SumDBClient) *FetchModuleUseCase

WithProjectGoSum layers a walk-root go.sum verifier onto the normal network fetch path. It is additive: the network checksum-database check and VCS cross-verification still run. A nil client (the default) disables the cross-check entirely, so the unshared network path is unchanged. Has no effect in --from-modcache mode, which anchors on go.sum via the sumdb field already. Returns uc for chaining.

func (*FetchModuleUseCase) WithSigner

func (uc *FetchModuleUseCase) WithSigner(signer ports.Signer, attestations ports.AttestationStore) *FetchModuleUseCase

WithSigner injects a Signer and the store its attestations persist to, enabling sign-on-process at the fetch-receive and fact-produce call sites. A nil signer (the default) disables signing entirely. The OSS no-op signer yields no attestation, so wiring it changes nothing. Returns uc for chaining.

type FetchRequest

type FetchRequest struct {
	Coordinate coordinate.ModuleCoordinate
	// OriginalCoordinate is the coordinate the project requires this module
	// under, when a replace directive put Coordinate in its place. Zero for an
	// unreplaced module.
	//
	// It is provenance for the verification report, never an identity: the
	// record is written under Coordinate, which is the artefact fetched, the
	// zip hashed and the entry `go.sum` records. What the original buys is the
	// ability to SAY so — "anchored under the fork path, required as the
	// upstream one" — and to refuse, naming both, when the fork has no go.sum
	// entry at all. In an air gap go.sum is the only anchor there is, and a
	// module that falls outside it silently is unverified in the one
	// environment where nothing else can cover for it.
	OriginalCoordinate coordinate.ModuleCoordinate
	// Force re-fetches even if a record for this pipeline version exists.
	Force bool
	// SkipVCSVerify skips the git cross-verification step; sumdb verification
	// still runs. Useful when GitHub rate limits make git operations unreliable.
	SkipVCSVerify bool
	// GoModOnly acquires only the module's go.mod, not its zip. It fetches the
	// .mod from the proxy, verifies its h1 against the checksum database go.mod
	// hash, and persists a record with GoModLocation set and ContentLocation
	// empty (a go.mod-only record, see domain.FactRecord.IsGoModOnly). It exists
	// for module-graph resolution — reading a superseded version's requirements
	// while rebuilding the graph — where the zip is never read. Such a record
	// must never satisfy a scan that needs source; the full path re-fetches over
	// it when a zip is required.
	GoModOnly bool
	// VCSHosts is the effective VCS forge allowlist for this fetch: which https
	// hosts may be handed to a git subprocess for cross-verification. It is
	// resolved from the caller's fetch-stage policy (allowed_vcs_hosts) and the
	// zero value enforces the built-in default set, so a caller that does not
	// set it behaves exactly as before. It never governs WHETHER git runs —
	// that is SkipVCSVerify.
	VCSHosts domain2.VCSHostAllowlist
}

FetchRequest is the input to Execute.

type FetchResult

type FetchResult struct {
	// Record is the artefact as the ledger knows it: the composed view of every
	// measurement of it, not any single row.
	Record    domain2.CompositeRecord
	FromCache bool
}

FetchResult is the output of Execute.

type InclusionProof

type InclusionProof struct {
	// Index is the zero-based position of the proven member in the ordered set.
	Index int
	// Size is the number of members in the set the proof was built over.
	Size int
	// Siblings are the audit-path digests, ordered from the leaf level upward.
	Siblings []ports.SubjectDigest
}

InclusionProof is the published shape of a Merkle inclusion proof: the audit-path digests that, combined bottom-up with a leaf, reconstruct the root. Index and Size pin the proven leaf's position in the ordered set so a proof cannot be replayed at a different position. The sibling hashes are expressed as SubjectDigests, the same canonical-identity type used everywhere else on this surface.

type QueryFetchUseCase

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

QueryFetchUseCase provides read-only access to stored fetch (fact) records.

Every method that takes a pipeline version reads PipelineVersion when given an empty one.

func NewQueryFetchUseCase

func NewQueryFetchUseCase(store ports.FactStore) *QueryFetchUseCase

NewQueryFetchUseCase constructs a QueryFetchUseCase.

func (*QueryFetchUseCase) ComposeFetchRecord

ComposeFetchRecord retrieves the composed fetch record for a coordinate across every fetch pipeline version — what the ledger has measured about the artefact, naming no generation of the pipeline.

It is what a reporting command wants. Pinning the read to the current fetch pipeline version made every module measured only under a retired one report as "(not fetched)" — on the maintainer's store, 1357 of 5652 coordinates — which is a false statement about a module whose artefact is held, not a gap in coverage.

func (*QueryFetchUseCase) GetFetchRecord

func (uc *QueryFetchUseCase) GetFetchRecord(ctx context.Context, coord coordinate.ModuleCoordinate, pipelineVersion string) (domain.CompositeRecord, bool, error)

GetFetchRecord retrieves the composed fetch record for the given coordinate and pipeline version — the artefact as the ledger knows it, folded from every measurement of it, rather than any single row.

func (*QueryFetchUseCase) SupersededArtefacts

func (uc *QueryFetchUseCase) SupersededArtefacts(ctx context.Context, coord coordinate.ModuleCoordinate) (map[string]bool, error)

SupersededArtefacts names the artefacts of coord the ledger holds only as local-source measurements under a published coordinate, so a history view can say which of its generations no read composes.

type ServeModuleUseCase

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

ServeModuleUseCase resolves a single ModuleCoordinate to a servable blob, fetching and verifying on a miss. It is the only individually exported WRITE use case: it powers a gating module proxy that serves only approved packages and fetches-and-verifies on miss. The bulk pipeline stays behind the composition root.

"Miss" means the blob is not servable, which covers two cases: no fact record exists yet, or a record exists but its content blob is absent from the store (e.g. evicted). Both trigger a fetch-and-verify; the underlying verification path (sumdb/hash) is unchanged — Serve never weakens it and never makes a gating decision of its own. The caller inspects VerificationStatus and applies its own fail-closed policy before streaming the bytes behind Handle.

func NewServeModuleUseCase

func NewServeModuleUseCase(fetch *FetchModuleUseCase, blobs ports.BlobStore) *ServeModuleUseCase

NewServeModuleUseCase constructs a ServeModuleUseCase over the fetch pipeline and the blob store the pipeline persists into. blobs is the same store fetch writes to, used here only to confirm a cached record's blob is still present.

func (*ServeModuleUseCase) Serve

Serve resolves req.Coordinate to a servable blob handle, fetching and verifying on a miss. On a cache hit it confirms the referenced blob is still present; if it has been evicted, it re-fetches (forcing past the cache) so the returned Handle always names a blob that exists in the store. It returns an error on infrastructure failure (proxy, VCS, storage) and on held bytes the store refuses as altered — a module that fails integrity verification still returns successfully with the failing VerificationStatus set, so the caller, not Serve, decides whether to serve it.

func (*ServeModuleUseCase) WithAudit

WithAudit wires an assurance-log sink so each served coordinate emits a verification outcome: record_read_verified when the module was positively verified, verification_failed otherwise. It mirrors WithSigner: the sink is optional, so a caller that does not supply one simply logs nothing. Returns the receiver for chaining.

type ServeRequest

type ServeRequest struct {
	// Coordinate is the module to resolve and serve.
	Coordinate coordinate.ModuleCoordinate
	// SkipVCSVerify skips the git cross-verification step on a fetch; sumdb
	// verification still runs. Forwarded to the fetch pipeline unchanged.
	SkipVCSVerify bool
	// VCSHosts is the effective VCS forge allowlist for a fetch-on-miss,
	// forwarded to the fetch pipeline unchanged. The zero value enforces the
	// built-in default set.
	VCSHosts domain2.VCSHostAllowlist
}

ServeRequest is the input to Serve.

type ServeResult

type ServeResult struct {
	// Identity addresses the module zip, ready to stream to a consumer via
	// BlobStore.Get. Guaranteed present in the store on success.
	Identity ports.BlobIdentity
	// VerificationStatus is the outcome the fetch pipeline recorded for this
	// module. The caller applies its own fail-closed policy against it before
	// serving the bytes.
	VerificationStatus domain2.VerificationStatus
	// Record is the composed fact record backing this identity.
	Record domain2.CompositeRecord
	// FromCache reports whether the record was served from an existing record
	// (true) or freshly fetched and verified (false).
	FromCache bool
}

ServeResult is the output of Serve.

type ValidateAndIngestUseCase

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

ValidateAndIngestUseCase is the verified-fact boundary: records cross into the store (Ingest) and back out to a consumer (ReadVerified) only if they satisfy the same integrity invariants, so an imported airgap-bundle record is held to exactly the bar a freshly extracted record is. Verification is fail-closed — a record that fails is never persisted and never presented; it is treated as unavailable, not as a confident fact.

It is the consume-side seam for the airgap bundle: full signature/Merkle anchoring against bundled signing material is layered on by later consumer work, which extends domain.VerifyFactRecord; this use case wires the seam into the store on both directions.

func NewValidateAndIngestUseCase

func NewValidateAndIngestUseCase(store ports.FactStore) *ValidateAndIngestUseCase

NewValidateAndIngestUseCase constructs the use case over the fact store.

func (*ValidateAndIngestUseCase) Ingest

Ingest verifies a record's integrity invariants and, only if they hold, persists it. A record that fails verification is rejected fail-closed: the returned error wraps ErrVerificationFailed and nothing is written, so a tampered import cannot enter the store masquerading as a confident fact. An infrastructure failure from the store is returned without the sentinel. It deliberately keeps accepting a plain FactRecord. This is the boundary an external caller hands an imported record across, and asking it for a SealedRecord would mean asking it to assert the very property this function exists to check. Rehydrate is where the assertion is made: it verifies the record and either yields the sealed value the store will accept, or fails.

func (*ValidateAndIngestUseCase) ReadVerified

func (uc *ValidateAndIngestUseCase) ReadVerified(ctx context.Context, coord coordinate.ModuleCoordinate, pipelineVersion string) (domain.CompositeRecord, bool, error)

ReadVerified retrieves a record and re-verifies it before returning it (verify-on-read, fail-closed). The returned bool is true only for a record that is present AND passes verification: - absent: zero record, false, nil — a genuine absence; - present and verified: the record, true, nil; - present but failed verification: zero record, false, an error wrapping ErrVerificationFailed — treated-as-unavailable, never returned as found; - infrastructure failure: zero record, false, the store error.

The caller treats any non-nil error as unavailable (non-zero exit per ); only (false, nil) means the record genuinely does not exist.

func (*ValidateAndIngestUseCase) WithAudit

WithAudit wires an assurance-log sink so ReadVerified emits a verification outcome per read: record_read_verified when the record passes re-verification, verification_failed with the rejection reason when it does not. The sink is optional (mirroring WithSigner on the fetch pipeline): a caller that supplies none simply logs nothing. Returns the receiver for chaining.

Jump to

Keyboard shortcuts

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