catalog

package
v0.10.0 Latest Latest
Warning

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

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

Documentation

Overview

Package catalog implements the signed capability catalog plane described in analysis/2026-10-22-authority-bounded-capability-publication-architecture.md §4.5–§4.7 and §5.

Three immutable artifacts are defined:

  • CLC-CAPABILITY-CATALOG-v1 — a generation-addressed snapshot of published capabilities;
  • CLC-CAPABILITY-CATALOG-DELTA-v1 — an add/replace/remove delta that only applies to an exact base generation and digest;
  • CLC-CATALOG-DISCOVERY-v1 — a candidate pointer that never carries an execution verdict.

The package validates artifacts and recomputes authority. It does not fetch anything, does not trust transport, and does not read an `authorized`, `verified` or `status` boolean from an artifact: all three objects are closed, so such a member is a shape error.

Signature verification is split exactly as in scheme administration: the caller verifies the detached PKCS#7 and hands in the recovered certificate; Verify re-checks the chain, the AIC principal and the key binding so the trust check cannot be skipped.

Index

Constants

View Source
const (
	CatalogVersion   = "CLC-CAPABILITY-CATALOG-v1"
	DeltaVersion     = "CLC-CAPABILITY-CATALOG-DELTA-v1"
	DiscoveryVersion = "CLC-CATALOG-DISCOVERY-v1"
)

Artifact versions.

View Source
const (
	CodeCatalogShape          = "catalog_shape"
	CodeCatalogDigest         = "catalog_digest_mismatch"
	CodeDuplicateEntry        = "catalog_duplicate_entry"
	CodeSchemeUnsupported     = "catalog_scheme_unsupported"
	CodeDeclarationMismatch   = "catalog_declaration_mismatch"
	CodeEntryOutsideAuthority = "catalog_entry_outside_authority"
	CodeIdentityMismatch      = "catalog_identity_mismatch"
	CodeBootstrap             = "catalog_bootstrap_required"
	CodeRollback              = "catalog_generation_rollback"
	CodeFork                  = "catalog_generation_fork"
	CodePreviousDigest        = "catalog_previous_digest_mismatch"
	CodeStateUnverified       = "state_unverified"
	CodeExpired               = "catalog_expired"
	CodeStale                 = "catalog_stale"
	CodeStatusUnavailable     = "catalog_status_unavailable"
	CodeStatusStale           = "catalog_status_stale"
	CodeProviderMismatch      = "catalog_provider_mismatch"

	CodeSignatureMissing    = "signature_missing"
	CodeSignatureInvalid    = "signature_invalid"
	CodeKeyRevoked          = "signature_key_revoked"
	CodeSignatureChain      = "signature_chain"
	CodeSignatureNoAIC      = "signature_no_aic"
	CodePublisherMismatch   = "publisher_mismatch"
	CodePublisherKeyBinding = "publisher_key_binding"

	CodeDeltaShape        = "delta_shape"
	CodeDeltaBase         = "delta_base_mismatch"
	CodeDeltaOperation    = "delta_operation_invalid"
	CodeDeltaDigest       = "delta_digest_mismatch"
	CodeDirectoryConflict = "directory_conflict"
	CodeShardMissing      = "directory_shard_missing"
	CodeDiscoveryShape    = "discovery_shape"
	CodeDiscoveryVerdict  = "discovery_verdict_member"
)

Stable failure codes. A validation returns at most one of these.

View Source
const (
	VerdictAccept = "accept"
	VerdictReject = "reject"
)

Verdicts. A consumer persists a snapshot only on VerdictAccept.

Variables

This section is empty.

Functions

func CanonicalBytes

func CanonicalBytes(c Catalog) ([]byte, error)

CanonicalBytes returns the file form written by the generators: the JCS encoding plus one trailing newline. It is deterministic, so two deployers generating the same catalog produce byte-identical files to sign.

func CodeOf

func CodeOf(err error) string

CodeOf returns the stable failure code of err, or "" when err is not a catalog failure.

func Digest

func Digest(raw []byte) string

Digest returns the sha256: content address of raw bytes.

func DigestOfDeclaration

func DigestOfDeclaration(raw []byte) string

DigestOfDeclaration returns the content address of a CLC-SCHEME-v1 declaration: sha256 over its JCS form without the `digest` member. Policies and catalog entries pin the same value, so no second digest convention exists.

func KeyID

func KeyID(cert *x509.Certificate) string

KeyID returns the sha256: id of a certificate's SubjectPublicKeyInfo.

func ValidatePolicy

func ValidatePolicy(p TrustPolicy) error

ValidatePolicy checks the bootstrap configuration is usable at all.

func VerifySignedDelta

func VerifySignedDelta(raw, signature []byte, base Catalog, roots []*x509.Certificate, policy TrustPolicy) (Result, *Catalog, error)

VerifySignedDelta verifies a detached signature over the exact delta bytes before parsing, binds the signer to the delta producer, requires the delta to continue the base producer and identity, checks freshness, pins every affected scheme declaration, checks each added/replaced entry against the signer's authority, and only then applies the delta.

Apply alone is a pure transformation and must not be used on untrusted bytes; the CLI and any consumer that persists a next catalog should use this function.

func VerifySignedDeltaWithState

func VerifySignedDeltaWithState(raw, signature []byte, base Catalog, roots []*x509.Certificate, policy TrustPolicy, state RegistryState) (Result, *Catalog, error)

VerifySignedDeltaWithState is the CLI-facing variant: it first requires the supplied base catalogue to be the accepted head in state, so a signed delta cannot be applied to an older but valid snapshot and written as a fork.

func WriteFile

func WriteFile(path string, raw []byte, replace bool) error

WriteFile writes a complete artifact. Without replace it refuses an existing path; with replace it writes a temp file in the same directory, fsyncs it and atomically links/renames it into place, so a crash cannot leave a partially written immutable artifact.

Types

type Catalog

type Catalog struct {
	Version        string  `json:"@version"`
	Domain         string  `json:"domain"`
	CatalogID      string  `json:"catalog_id"`
	Generation     int64   `json:"generation"`
	IssuedAt       string  `json:"issued_at"`
	ValidUntil     string  `json:"valid_until"`
	PreviousDigest string  `json:"previous_digest,omitempty"`
	Producer       string  `json:"producer"`
	Entries        []Entry `json:"entries"`
}

Catalog is the immutable CLC-CAPABILITY-CATALOG-v1 artifact. The struct is the closed schema: every member below is the whole of what a consumer reads.

func Apply

func Apply(base Catalog, d Delta) (*Catalog, error)

Apply checks the delta against base and returns the next catalog generation. The returned entries are ordered by capability, so the target digest does not depend on the order in which a producer happened to write the delta.

func LoadCatalog

func LoadCatalog(path string) (*Catalog, []byte, error)

LoadCatalog reads and validates one catalog file. It also returns the exact bytes, because the detached signature is over those bytes rather than over a re-serialization.

func ParseCatalog

func ParseCatalog(raw []byte) (*Catalog, error)

ParseCatalog decodes exactly one closed catalog object and validates it.

func (Catalog) Digest

func (c Catalog) Digest() (string, error)

Digest returns the content address of the canonical catalog object.

func (*Catalog) Validate

func (c *Catalog) Validate() error

Validate checks the artifact's own shape. It does not check policy, freshness or authority; those need the deployer's inputs.

type CompositeCatalog

type CompositeCatalog struct {
	Domain        string
	CatalogID     string
	Generation    int64
	Entries       []Entry
	Sources       []string
	SourceDigests map[string]string
}

CompositeCatalog is a deterministic merge of verified directory shards. It is deliberately not a Catalog: it has no single publisher signature and must not be passed to VerifySigned/Accept as if it did.

func ComposeVerified

func ComposeVerified(sources []VerifiedSource, required []string) (*CompositeCatalog, error)

ComposeVerified merges verified shards of one catalog identity. Required source names must be present; every shard must be the same generation and digest. Conflicting entries at the same capability are refused rather than resolved by source order.

func (CompositeCatalog) Entry

func (c CompositeCatalog) Entry(capability string) (Entry, bool)

Entry returns the entry for a capability in a composite, or false.

type Delta

type Delta struct {
	Version          string   `json:"@version"`
	Domain           string   `json:"domain"`
	CatalogID        string   `json:"catalog_id"`
	BaseGeneration   int64    `json:"base_generation"`
	TargetGeneration int64    `json:"target_generation"`
	BaseDigest       string   `json:"base_digest"`
	TargetDigest     string   `json:"target_digest"`
	IssuedAt         string   `json:"issued_at"`
	ValidUntil       string   `json:"valid_until"`
	Producer         string   `json:"producer"`
	Add              []Entry  `json:"add,omitempty"`
	Replace          []Entry  `json:"replace,omitempty"`
	Remove           []string `json:"remove,omitempty"`
}

Delta is the immutable CLC-CAPABILITY-CATALOG-DELTA-v1 artifact. A delta is only meaningful against one exact base generation and digest, so a deployer never has to guess which snapshot a change was authored for.

func LoadDelta

func LoadDelta(path string) (*Delta, []byte, error)

LoadDelta reads and validates one delta file.

func ParseDelta

func ParseDelta(raw []byte) (*Delta, error)

ParseDelta decodes exactly one closed delta object and validates it.

func (Delta) Digest

func (d Delta) Digest() (string, error)

DeltaDigest returns the content address of the delta artifact.

func (*Delta) Validate

func (d *Delta) Validate() error

Validate checks the delta's own shape. Base matching needs the base catalog and happens in ApplyDelta.

type DirectoryBundle

type DirectoryBundle struct {
	Snapshot          []byte
	SnapshotSignature []byte
	Delta             []byte
	DeltaSignature    []byte
}

DirectoryBundle is the input-only exchange shape for one catalog generation: an exact signed snapshot and an optional signed delta. It carries bytes, not a verdict; Apply re-verifies whatever it consumes.

func FetchDirectory

func FetchDirectory(ctx context.Context, client *http.Client, base string) (DirectoryBundle, error)

FetchDirectory reads the fixed exchange endpoints from base. It intentionally does not follow redirects or accept unbounded bodies.

func (DirectoryBundle) Apply

func (b DirectoryBundle) Apply(base Catalog, roots []*x509.Certificate, policy TrustPolicy, state RegistryState) (Result, *Catalog, bool, error)

Apply tries the delta first when present; if it cannot be applied, it falls back to the signed full snapshot and only accepts that snapshot through the normal state machine. The bool reports whether the delta was used.

func (DirectoryBundle) Handler

func (b DirectoryBundle) Handler() http.Handler

Handler serves the bundle at fixed paths. It executes nothing and performs no trust decision; consumers still call Apply with local policy/state.

type DirectoryCache

type DirectoryCache struct {
	TTL      time.Duration
	MaxStale time.Duration
	// contains filtered or unexported fields
}

DirectoryCache is a bounded local cache for one directory exchange endpoint. A fresh entry avoids a fetch; if a fetch fails the last valid bundle can be returned as stale, and the caller's normal VerifySigned freshness policy decides whether it is still admissible. The cache never decides trust.

func (*DirectoryCache) Fetch

func (c *DirectoryCache) Fetch(ctx context.Context, client *http.Client, base string, now time.Time) (DirectoryBundle, bool, bool, error)

Fetch returns a cached bundle when fresh, otherwise fetches and caches it. stale reports that the returned bundle is the last successful fetch after a network failure; callers must still validate it.

type Discovery

type Discovery struct {
	Version                  string   `json:"@version"`
	Provider                 string   `json:"provider"`
	CatalogID                string   `json:"catalog_id"`
	CatalogDigest            string   `json:"catalog_digest"`
	Generation               int64    `json:"generation"`
	IssuedAt                 string   `json:"issued_at"`
	ValidUntil               string   `json:"valid_until"`
	StatusReference          string   `json:"status_reference,omitempty"`
	SchemeDeclarationDigests []string `json:"scheme_declaration_digests,omitempty"`
}

Discovery is the CLC-CATALOG-DISCOVERY-v1 candidate record. It is a pointer to a catalog, never a decision: a consumer must fetch the named catalog, verify its signature and run Verify before executing anything.

func ParseDiscovery

func ParseDiscovery(raw []byte) (*Discovery, error)

ParseDiscovery decodes exactly one closed discovery object and validates it.

func (Discovery) Digest

func (d Discovery) Digest() (string, error)

Digest returns the content address of the discovery record.

func (Discovery) Match

func (d Discovery) Match(c Catalog) error

Match checks that a discovery record points at exactly this catalog. It still returns no verdict: a match means "this is the artifact the record named".

func (*Discovery) Validate

func (d *Discovery) Validate() error

Validate checks the record's own shape.

type Entry

type Entry struct {
	Capability        string      `json:"capability"`
	Provider          string      `json:"provider"`
	Scheme            string      `json:"scheme"`
	DeclarationDigest string      `json:"scheme_declaration_digest"`
	Operations        []Operation `json:"operations"`
	Status            *Status     `json:"status,omitempty"`
}

Entry is one published capability.

type Error

type Error struct {
	Code   string
	Detail string
}

Error is a fail-closed validation failure with a stable code.

func (*Error) Error

func (e *Error) Error() string

Error implements error.

type GenerationRecord

type GenerationRecord struct {
	Generation int64  `json:"generation"`
	Digest     string `json:"digest"`
}

GenerationRecord is one entry of the local accepted-generation history.

type Index

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

Index is a deterministic capability lookup index for one catalogue. It keeps the source order for duplicate detection (which Validate already refuses) and shards by the first capability byte so a 100k-entry catalogue does not require a linear scan per lookup.

func BuildIndex

func BuildIndex(c Catalog) *Index

BuildIndex indexes a validated (or at least shape-checked) catalogue.

func (*Index) Lookup

func (i *Index) Lookup(c Catalog, capability string) (Entry, bool)

Lookup returns the entry for a capability.

func (*Index) Shard

func (i *Index) Shard(capability string) []int

Shard returns the entry positions in the first-byte shard, for diagnostics and scale tests. The returned slice is owned by the index.

type Operation

type Operation struct {
	Operation string         `json:"operation"`
	Params    map[string]any `json:"params,omitempty"`
}

Operation is one concrete published operation inside an entry. The params use the ordinary CLC parameter representation, so a JSON array is an enum constraint rather than a list of independent values.

func (Operation) Grant

func (op Operation) Grant(capability string) semantics.Grant

OperationGrant projects one published operation back into the CLC grant form used for the authority comparison.

type RegistryState

type RegistryState struct {
	Domain     string             `json:"domain,omitempty"`
	CatalogID  string             `json:"catalog_id,omitempty"`
	Producer   string             `json:"producer,omitempty"`
	Generation int64              `json:"generation"`
	Digest     string             `json:"digest,omitempty"`
	History    []GenerationRecord `json:"history,omitempty"`
}

RegistryState is the deployer's persisted view of one catalog identity.

func LoadState

func LoadState(path string) (RegistryState, error)

LoadState reads a closed local generation state. An empty path means "nothing accepted yet".

func (RegistryState) Accept

func (s RegistryState) Accept(c Catalog, res Result) (RegistryState, error)

Accept returns the state a deployer persists after accepting c. History keeps every accepted generation exactly once, so a later snapshot can still be compared against it; there is no rollback path and no recovery flag. The method re-checks identity, generation continuity, previous_digest and producer so a caller cannot bypass Verify's generation rules by calling Accept directly.

func (RegistryState) AcceptSigned

func (s RegistryState) AcceptSigned(raw, signature []byte, roots []*x509.Certificate, policy TrustPolicy) (RegistryState, Result, error)

AcceptSigned verifies a detached catalogue signature and only then advances the state. This is the safe state-writer entry point; Accept alone requires the resulting signed marker and is intended for callers that already ran VerifySigned.

type Result

type Result struct {
	Verdict    string `json:"verdict"`
	Reason     string `json:"reason,omitempty"`
	Detail     string `json:"detail,omitempty"`
	Domain     string `json:"domain"`
	CatalogID  string `json:"catalog_id"`
	Producer   string `json:"producer,omitempty"`
	Generation int64  `json:"generation"`

	CatalogDigest string `json:"catalog_digest,omitempty"`
	SignerKeyID   string `json:"signer_key_id,omitempty"`
	// SignerCertificateDigest binds the exact certificate (validity/AIC
	// container) and AICDigest binds the raw AIC extension value, so a reissued
	// certificate with the same key is visible in the decision record.
	SignerCertificateDigest string `json:"signer_certificate_digest,omitempty"`
	AICDigest               string `json:"aic_digest,omitempty"`
	PolicyDigest            string `json:"policy_digest,omitempty"`
	StateDigest             string `json:"state_digest,omitempty"`
	InputsDigest            string `json:"inputs_digest,omitempty"`
	// contains filtered or unexported fields
}

Result is the replayable decision record. It binds the artifact, the signer, the policy, the prior state and the recomputed verdict; it never carries a bare trust boolean.

func Verify

func Verify(v Verification) (Result, error)

Verify is retained only as a fail-closed compatibility stub. Use VerifySigned: it verifies the detached signature over the exact bytes before parsing or admitting the catalogue.

func VerifySigned

func VerifySigned(raw, signature []byte, roots []*x509.Certificate, policy TrustPolicy, state RegistryState) (Result, error)

VerifySigned is the consumer entry point that cannot forget the detached signature. Verify alone takes a recovered certificate; a caller that skipped the PKCS#7 step could hand in an arbitrary trusted certificate and conclude that an artifact was signed by a principal that never saw it. This function closes that gap: it verifies the detached signature over the exact bytes first, then parses the artifact and runs Verify with the recovered signer.

The lower-level split is kept for callers that verify the signature elsewhere (the CLI decodes the PEM envelope first, and scheme administration has the same shape); those callers pass the recovered certificate to Verify directly and remain responsible for binding it to the bytes.

The signature covers raw rather than a re-serialization, so callers must pass the bytes they received, not catalog.CanonicalBytes output.

type SchemePolicy

type SchemePolicy struct {
	Declaration json.RawMessage `json:"declaration"`
}

SchemePolicy pins one supported scheme to the declaration that defines its parameter domains. Pinning the declaration itself (not only a digest) lets the deployer build the CLC-1.19 domain map without fetching anything.

type Status

type Status struct {
	Reference string `json:"reference"`
	CheckedAt string `json:"checked_at"`
}

Status is an explicit status reference. Its presence is meaningful; an absent status is never "healthy".

type TrustPolicy

type TrustPolicy struct {
	Domain    string `json:"domain"`
	CatalogID string `json:"catalog_id"`
	// Producer, when set, pins the catalog identity to one AIC principal.
	// State continuity enforces the same binding even when it is empty.
	Producer string                  `json:"producer,omitempty"`
	Schemes  map[string]SchemePolicy `json:"schemes"`
	// MaxStalenessSeconds bounds issued_at age.  Zero disables the age check
	// (valid_until still applies).
	MaxStalenessSeconds int64 `json:"max_staleness_seconds,omitempty"`
	// MaxStatusStalenessSeconds bounds status.checked_at age.  Zero disables it.
	MaxStatusStalenessSeconds int64 `json:"max_status_staleness_seconds,omitempty"`
	// RequireStatus refuses an entry without a status reference.  Absence is
	// never treated as healthy.
	RequireStatus bool `json:"require_status,omitempty"`
	// StatusValues pins the resolved health value for each status reference.
	// When RequireStatus is set, a reference is healthy only if it maps to the
	// literal "healthy"; a bare reference/timestamp is not a health verdict.
	StatusValues map[string]string `json:"status_values,omitempty"`
	// StatusResolver, when set, obtains the current health value for a status
	// reference at verification time.  It is deployment code, not policy JSON;
	// an error or a non-healthy value fails closed.
	StatusResolver func(reference string) (string, error) `json:"-"`
	// ProviderBindings optionally binds each published provider to the catalog
	// producer principal.  A non-empty map is enforced for every entry.
	ProviderBindings map[string]string `json:"provider_bindings,omitempty"`
	ObservedAt       string            `json:"observed_at"`
}

TrustPolicy is the deployer-local admission policy. It is an input, never a member of a catalog artifact.

func LoadPolicy

func LoadPolicy(path string) (TrustPolicy, error)

LoadPolicy reads a closed deployer policy file.

func (TrustPolicy) DomainMap

func (p TrustPolicy) DomainMap(scheme, capability string) semantics.DomainMap

DomainMap exposes the policy-pinned CLC-1.19 domain map for one capability. A consumer that compares requested parameters against a published operation (for example an admission gate) must use the same map the catalog authority check used, otherwise the two readings of the same declaration can diverge. It returns nil (shape inference) when the declaration does not declare the capability.

type TrustRotation

type TrustRotation struct {
	Current      []*x509.Certificate
	Next         []*x509.Certificate
	OverlapUntil time.Time
	RevokedKeys  map[string]bool
}

TrustRotation is the catalog trust-anchor overlap profile. Current roots are always accepted before OverlapUntil; during the overlap both current and next roots are accepted; after it only the next set is accepted, so an old anchor cannot silently remain trusted after the transition.

func (TrustRotation) RootsAt

func (r TrustRotation) RootsAt(now time.Time) ([]*x509.Certificate, error)

RootsAt returns the roots accepted at now.

func (TrustRotation) Validate

func (r TrustRotation) Validate() error

Validate checks the rotation shape. A next set without a finite overlap deadline is rejected: "rotate forever" would make the old key impossible to retire.

func (TrustRotation) VerifySigned

func (r TrustRotation) VerifySigned(raw, signature []byte, policy TrustPolicy, state RegistryState) (Result, *Catalog, error)

VerifySigned verifies a catalog under the rotation window and rejects a signer key explicitly revoked by the profile. The returned catalog is the parsed artifact, not a re-serialization.

type Verification

type Verification struct {
	Catalog    Catalog
	SignerCert *x509.Certificate
	Roots      []*x509.Certificate
	Policy     TrustPolicy
	State      RegistryState
}

Verification is one deployer check: the artifact plus every local input the decision depends on.

type VerifiedSource

type VerifiedSource struct {
	Name    string
	Catalog Catalog
	Result  Result
	// contains filtered or unexported fields
}

VerifiedSource is one directory shard after detached-signature verification. The marker is unexported: a caller cannot label an unchecked catalog as verified without going through VerifySource.

func VerifySource

func VerifySource(name string, raw, signature []byte, roots []*x509.Certificate, policy TrustPolicy, state RegistryState) (VerifiedSource, error)

VerifySource verifies one source catalog with the same policy/state path as the single-directory loader. A source name is required for required-shard accounting.

Jump to

Keyboard shortcuts

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