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

release

package
v1.0.2 Latest Latest
Warning

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

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

Documentation

Overview

Package release is forge's release ledger vocabulary: what a RELEASE is (an immutable, content-addressed set of artifacts under a version label), what a PROMOTION is (one append-only event binding an environment to a release), and the invariants both must hold no matter where they are stored.

One model, two backends

forge records releases and promotions in two places, and they are the SAME model:

  • the file backend, inside a project: .forge/releases/<version>.json and one append-only .forge/promotions/<env>.jsonl per environment;
  • a hosted control plane, over its DeployService RPCs, when an environment declares forge.ControlPlane.

Both backends read and write these types, and both enforce the rules in this package. The hosted side additionally enforces them structurally in its database (CHECKs, append-only triggers); the Go rules here are the ones a directory of files can hold, and they are the acceptance test for the hosted side, not a weaker copy of it.

Why it is public

A hosted ledger is implemented OUTSIDE forge (Reliant's control plane is one), and it must speak exactly this vocabulary or the two backends drift into two products. Declaring the types once, in an importable package, is what makes "the hosted ledger stores what forge cut" a compile-time fact rather than a comment in two repos.

Closed enums, validated on read

Kind, Mode, PromotionKind, GateStatus, StageKind and StageStatus are CLOSED. An unknown or empty value fails to decode rather than defaulting to something plausible — an artifact whose kind nobody recognises must not be read as an image, and a promotion whose kind is unknown must not be read as a forward move. The earlier "empty kind means OCI" convention is gone: every ledger states what it holds.

Evidence and runs

A Gate is one check's result attached to a promotion, and it is EVIDENCE, not enforcement: nothing in this package refuses a promotion on a gate's status. What a gate buys is attribution — this principal claimed this verdict at this time, with a link to the full report — which is the most a recorded claim can ever prove. A gate that must BLOCK is checked before the entry is written.

A Run is the identity of one pipeline attempt, and it is the join key that turns scattered ledger rows into a RunTimeline. There is no run entity: every Stage is either derived from the ledger (the cut, the promotions, each promotion's rollout) or recorded as a gate (build, lint, test, smoke, wait). A parallel event log would be a second source of truth free to disagree with the ledger, so there is none.

One closed set, two parse paths

Enum values arrive from two places, and conflating them is how a ledger becomes unreadable:

Gate.Validate closes the loop: a gate carrying a RawStatus can be read but not written, so leniency on read never becomes leniency on write.

Index

Constants

View Source
const (
	BundleArtifactType    = "application/vnd.forge.bundle.v1"
	BundleConfigMediaType = "application/vnd.forge.bundle.config.v1+json"
	BundleManifestsLayer  = "application/vnd.forge.bundle.manifests.v1.tar+gzip"
	// RedactedSecretPrefix marks a Secret value replaced by its hash before a
	// render is hashed or packaged: "forge.dev/redacted: sha256:<hex>".
	RedactedSecretPrefix = "forge.dev/redacted: "
)

Bundle OCI media types. One bundle is one OCI image manifest whose config blob is the BundleDoc and whose single layer is the manifest stream.

THE MANIFEST LAYER IS WHAT FLUX APPLIES. A Flux OCIRepository selects it by media type and `operation: extract`, which untars it into the source controller's artifact; a Kustomization then applies the YAML under `spec.path`. So the layer's media type and its internal layout are a CONTRACT with the reconciler, not an internal detail — see BundleClusterPath.

There is deliberately no charts layer. A second layer would be invisible to Flux (a layerSelector picks exactly one), so anything put there would be recorded as shipped and never applied. Platform charts are a cluster-bootstrap concern and stay off the bundle entirely; see internal/bundle's package doc.

View Source
const (
	BundleObservabilityPrefix = "observability"
	BundleDashboardsPath      = BundleObservabilityPrefix + "/dashboards"
	BundleMetricsSchemaPath   = BundleObservabilityPrefix + "/metrics-schema.json"
	// BundleFrontendDeclarationsPath is bounded non-applied frozen frontend metadata.
	BundleFrontendDeclarationsPath = "forge/frontend-declarations.json"
	// BundleServiceCatalogPath carries browse-only typed code/RPC metadata.
	BundleServiceCatalogPath = "forge/service-catalog.json"
)

The observability files a bundle carries beside its manifests (console v2 contract K4), in the SAME layer, under a top-level directory the reconciler never applies.

WHY THE SAME LAYER AND NOT A SECOND ONE. A Flux layerSelector selects exactly one layer, so a second layer is invisible to the reconciler — which is right for data nothing applies, but it would also change what a bundle IS (one manifest, one config, one layer) for every reader that counts on that, and Fetch refuses a layer it does not know. A Kustomization's spec.path is always BundleClusterPath, under BundleManifestsPrefix, so a sibling directory in the extracted artifact is never reached by anything that applies.

observability/dashboards/<name>.json   one renderer-neutral dashboard each
observability/metrics-schema.json      the metrics schema (forge.metrics/v1)

A dashboard file is named <name>_gen.json when forge generated it and <name>.json when the project owns it; a project's file wins over the generated one of the same name, so a name never appears twice.

View Source
const (
	// UnitApplierForge: `forge env deploy` applies the unit's objects directly.
	UnitApplierForge = "forge"
	// UnitApplierHub: the control plane's hub converges the unit's trees from
	// the recorded bundle, and forge applies none of its objects.
	UnitApplierHub = "hub"
)

Who applies a delivery unit (ShapeUnit.Applier).

View Source
const (
	// ControlPlaneSecretTelemetryToken is the per-environment OTLP ingest
	// token.
	ControlPlaneSecretTelemetryToken = "telemetry_token"
	// ControlPlaneSecretIdentity is a managed identity's outputs for the
	// workloads that consume it: issuer, audience, scopes, and per
	// application the client id, redirect and login URIs and the broker
	// token. Derived from Bundle.identity; never declared by hand.
	ControlPlaneSecretIdentity = "identity"
	// ControlPlaneSecretIdentityMachineKey is a machine user's key.
	ControlPlaneSecretIdentityMachineKey = "identity_machine_key"
	// ControlPlaneSecretIdentityJWKS is the snapshot of the instance's public
	// signing keys a validator falls back to when the issuer is unreachable.
	ControlPlaneSecretIdentityJWKS = "identity_jwks"
)

Control-plane secret kinds: what the control plane mints into the Secret.

View Source
const (
	// AlertSourceUser is a rule the project declares (forge.AlertRule).
	AlertSourceUser = "user"
	// AlertSourceDefault is one of forge's generated defaults.
	AlertSourceDefault = "default"
)

Alert rule sources.

View Source
const (
	AlertReceiverInApp = "reliant-inapp"
	AlertReceiverSlack = "slack"
)

The receivers every control plane provides, and the routes a shape carries when the author declares none: in-app and Slack, never a pager.

View Source
const (
	ObsCategoryTraces   = "traces"
	ObsCategoryMetrics  = "metrics"
	ObsCategoryLogs     = "logs"
	ObsCategoryProfiles = "profiles"
	ObsCategoryMetadata = "metadata"
	ObsCategoryBrowser  = "browser"
)

Observability opt-out categories: the closed vocabulary of ShapeObservabilityPolicy.Disabled. "metadata" is customer metadata, the per-workload container data the platform's node agent collects (pod CPU/memory, restarts, last exit reason, events, image and version).

View Source
const (
	DefaultReplayDays        = 14
	DefaultErrorsDays        = 30
	DefaultProductEventsDays = 90

	MaxReplayDays        = 30
	MaxErrorsDays        = 365
	MaxProductEventsDays = 400
)

Browser telemetry retention defaults and bounds, in days. The defaults are forge.ObservabilityRetention's (kcl/schema.k); the bounds are its checks.

View Source
const (
	SourcemapsPublish = "publish"
	SourcemapsPublic  = "public"
	SourcemapsOff     = "off"
)

Source map handling, ShapeFrontendTelemetry.Sourcemaps.

View Source
const (
	IdentityType              = "type"
	IdentityLoadBalancerIP    = "load_balancer_ip"
	IdentityLoadBalancerClass = "load_balancer_class"
	IdentityStaticIP          = "static_ip"
)

Identity keys. Closed.

View Source
const (
	// BundleFeatureCommitIdentity is Provenance.CommitSubject and
	// Provenance.CommitAuthor (forge#850).
	BundleFeatureCommitIdentity = "provenance-commit-identity"
	// BundleFeatureObjectSummaries is BundleDoc.Objects, the per-object
	// summaries (forge#850).
	BundleFeatureObjectSummaries = "object-summaries"
	// BundleFeatureFrontendTelemetry is Shape.FrontendTelemetry and
	// ShapeObservabilityPolicy.Retention: what a frontend declares about its
	// browser telemetry, and how long the platform keeps it. The shape is
	// strict-decoded on two paths, the bundle document and the environment
	// declaration, so both are limited by it ([BundleReader.LimitShape]).
	BundleFeatureFrontendTelemetry = "shape-frontend-telemetry"
	// BundleFeatureStoreSecrets is Shape.StoreSecrets, Shape.GeneratedSecrets,
	// Shape.OpenBao and Shape.ExternalSecretsOperators: the store names each
	// namespace reads, the values to mint, and the components the env
	// declares. A control plane that predates them cannot derive the access a
	// store-backed Secret needs, so a shape carrying them is written only to
	// one that names the feature. It also owns
	// ShapeControlPlaneSecret.Delivery: a control plane that cannot read the
	// store bindings cannot write the store, so it is left delivering by push.
	BundleFeatureStoreSecrets = "shape-store-secrets"
	// BundleFeatureDataAccess is Shape.DataAccess: whether the env's database
	// may be queried (`forge db query`) openly, audited, or not at all. A
	// control plane that predates it cannot enforce the declaration, so a shape
	// carrying it is written only to one that names the feature.
	BundleFeatureDataAccess = "shape-data-access"
	// BundleFeatureReleaseHealth is Shape.ReleaseHealth: the thresholds the
	// env's promotions are judged by (forge.ReleaseHealth). A control plane
	// that predates it judges with the defaults.
	BundleFeatureReleaseHealth = "shape-release-health"
	// BundleFeatureUptimeChecks is Shape.UptimeChecks: the outside-in checks
	// the control plane's prober runs. A control plane that predates it
	// probes nothing for the env.
	BundleFeatureUptimeChecks = "shape-uptime-checks"
	// BundleFeatureServiceCatalog is the manifests layer's
	// [BundleServiceCatalogPath] artifact, of any size up to the layer's
	// bound. It is a layer feature, not a document one: a control plane whose
	// [ValidateManifestsLayer] predates it refuses the whole layer ("only the
	// dedicated frozen frontend declaration asset is permitted under forge/"),
	// so forge uploads the catalog only to one that names it.
	BundleFeatureServiceCatalog = "layer-service-catalog"
)

Optional parts of the bundle document (BundleDoc) that a reader may not decode yet.

WHY THIS EXISTS. A backend that records a bundle decodes its config blob with DecodeBundleDoc, which refuses an unknown field — deliberately: a backend must not record a summary of a document it only half understood. The flip side is that every field added to the document breaks every control plane that predates it. forge#850 added three (the commit subject and author in the provenance, and the per-object summaries), and a forge carrying it could no longer record a release on a control plane built before it: `bundle document: json: unknown field "commit_subject"`, with no way to ship the control plane that understands the field, because shipping is recording.

So an addition to the document is a FEATURE: written only to a reader that says it decodes it. A control plane advertises the features its own DecodeBundleDoc accepts from DeployService/GetCapabilities (BundleDocFeatures); forge asks before it builds a hosted bundle and leaves out every part the server did not name (BundleReader.Limit). A server that predates the capability, or the RPC, names none, and gets the document its strict decoder knew.

The snapshot fields (Snapshot, SnapshotDigest) are not features: a snapshot bundle is a different schema, and composing one already requires the server's snapshot capability and fails closed without it.

Adding an optional field to BundleDoc means adding a feature here. TestEveryBundleDocAdditionIsAFeature (bundle_features_test.go) fails until it is mapped.

View Source
const (
	TreeTimeoutSeconds       = 90
	TreeRetryIntervalSeconds = 15
)

Per-attempt Flux settings recorded on every tree. A reconcile holds one of a shared controller's workers for its health wait, so a tree never asks Flux to wait longer than one short attempt; the control plane's observer decides "failed" against the tree's own budget instead (design §8.3).

View Source
const (
	// BudgetMarginSeconds is added to the largest deadline: two minutes.
	BudgetMarginSeconds = 120
	// DefaultJobBudgetSeconds is a Job's allowance when it declares no
	// activeDeadlineSeconds: ten minutes, which is what direct apply waits for
	// a Job before it fails the gate (cluster.DefaultRolloutTimeout is five;
	// control-plane's migrations are the Jobs that need the difference).
	DefaultJobBudgetSeconds = 600
	// DefaultRolloutBudgetSeconds is a workload's allowance when it states no
	// progressDeadlineSeconds (a StatefulSet has none): Kubernetes' ten
	// minutes.
	DefaultRolloutBudgetSeconds = int(DefaultProgressDeadlineSeconds)
	// MinBudgetSeconds is what a tree with nothing to judge it on is given: its
	// support objects apply in one attempt, so it only needs to outlast one.
	MinBudgetSeconds = BudgetMarginSeconds
)

The budget model (design §8.3): a rollout tree is allowed the LARGEST progressDeadlineSeconds among its Deployments and StatefulSets plus a margin for the apply and the readiness probe's first pass; a Job tree is allowed the largest Job allowance plus the same margin.

View Source
const (
	CaptureTreeTimeout  = 5 * time.Second
	CaptureMaxDirtySize = 2 << 30 // bytes of untracked-unignored + modified content
)

Capture budget for the dirty-tree hash (F-16). Past either bound the tree is recorded as unhashed rather than making a developer wait: a clean tree's hash is HEAD^{tree} and costs nothing, so the bound only ever applies to a dirty checkout, which no protected path relies on.

View Source
const (
	// MaxRangeCommits is the most commits one release records.
	MaxRangeCommits = 100
	// MaxCommitSubjectBytes bounds a recorded subject line.
	MaxCommitSubjectBytes = 256
	// MaxCommitAuthorBytes bounds a recorded author name.
	MaxCommitAuthorBytes = 128
	// CaptureRangeTimeout bounds the git work [CaptureCommitRange] does. Past
	// it the cut records no range rather than making anyone wait.
	CaptureRangeTimeout = 10 * time.Second
)
View Source
const (
	SigningKeyOrgKMS   = "org_kms"
	SigningKeyCustomer = "customer"
	SigningKeyKeyless  = "keyless"
)

Signing key models (ShapeSigning.Key).

View Source
const (
	AnnotationFluxForce = "kustomize.toolkit.fluxcd.io/force"
	FluxForceEnabled    = "enabled"
)

Flux's per-object recreate policy: when Enabled, kustomize-controller deletes and re-creates an object whose update fails on an immutable field. The value is Flux's, not forge's.

View Source
const (
	DefaultProgressDeadlineSeconds int32 = 600
	DefaultRolloutPercent                = "25%"
)

Defaults a Deployment states explicitly. They are Kubernetes' own, so stating them changes nothing at runtime: the render just says what the cluster would have said, instead of leaving a reconciler to see fields another manager owns.

View Source
const (
	RuleJobTTL      = "flux-job-ttl"
	RuleJobUnhashed = "flux-job-unhashed"
	RuleForceData   = "flux-force-on-data"
)

Rule IDs. Stable: lint output, suppressions and tests name them.

View Source
const (
	// IdentityRuntimeHosted is an instance on the control plane's shared
	// identity deployment.
	IdentityRuntimeHosted = "hosted"
	// IdentityRuntimeHost is the env's own host-run IdP (forge.HostInfra,
	// engine zitadel): `forge env up`'s dev loop.
	IdentityRuntimeHost = "host"
)

Where a managed identity runs (ShapeIdentity.Runtime).

View Source
const (
	// IdentityLoginNative is the app's own sign-in form, with the server-side
	// broker. The default.
	IdentityLoginNative = "native"
	// IdentityLoginHosted is the identity provider's hosted login pages.
	// Accepted by the schema and recorded, but phase 2: nothing converges it
	// yet.
	IdentityLoginHosted = "hosted"
)

Login modes (ShapeIdentityLogin.Mode).

View Source
const (
	IdentityAppNativeBroker = "native_broker"
	IdentityAppOIDCWeb      = "oidc_web"
)

Application kinds (ShapeIdentityApplication.Kind).

View Source
const (
	// IdentityMembersConsole: members are managed in the console and never
	// touched by a reconcile. The default.
	IdentityMembersConsole = "console"
	// IdentityMembersDeclared: members are a set converged from the
	// declaration, read-only in the console.
	IdentityMembersDeclared = "declared"
)

Where a role's members are managed (ShapeIdentityRole.MembersFrom).

View Source
const (
	// IdentityDomain is the registrable domain every managed instance's
	// issuer lives under: https://<slug>.reliantidentity.com.
	IdentityDomain = "reliantidentity.com"
	// IdentitySecretPrefix starts the name of every Kubernetes Secret the
	// control plane delivers an identity's outputs in:
	// forge-identity-<name> (+ -jwks for the signing-key snapshot).
	IdentitySecretPrefix = "forge-identity-"
	// IdentityManagedSecretPrefix starts the name of every managed secret a
	// HOSTED consumer reads an identity output from.
	IdentityManagedSecretPrefix = "RELIANT_IDENTITY_"
)

The names a managed identity reserves. A consumer reads its outputs from a Secret or a managed secret whose name starts with one of these, and the control plane is the only writer, so neither prefix is an env's own secret.

View Source
const (

	// IdentitySlugMinHash is the hash length an assignment starts at.
	IdentitySlugMinHash = 6
	// IdentitySlugMaxHash is the longest hash a retry reaches.
	IdentitySlugMaxHash = 12
)

Issuer slugs.

An instance's issuer is https://<slug>.reliantidentity.com, and an issuer is the one thing a deployed app cannot change without invalidating every token it has issued. So the slug is ASSIGNED ONCE, by the control plane, when the env's instance is first provisioned, and stored: nothing recomputes it afterwards, which is what makes a later env rename harmless. forge records the declaration, not the slug; this is the rule the control plane assigns by, kept here so every program that predicts or checks one agrees.

<project>-<env>-<hash>

The two labels make an issuer readable in a log line. The hash makes it unique: the first N hex digits of sha256(envID), where envID is the control plane's immutable id for the env (never its name, which can be reused after a delete). A label is lower-case [a-z0-9] with every other run folded to one '-', at most 24 characters. N starts at IdentitySlugMinHash; the control plane's unique index decides uniqueness, and on a collision it retries with the next N, so uniqueness never rests on the hash's luck. The longest slug is 24+1+24+1+12 = 62 characters, inside the 63 of a DNS label.

View Source
const (
	// ManifestsLayerMaxFileBytes bounds any single manifest file. Data
	// artifacts are bounded only by [ManifestsLayerMaxBytes].
	ManifestsLayerMaxFileBytes = 1 << 20
	// ManifestsLayerMaxBytes bounds the sum of every file's bytes,
	// uncompressed.
	ManifestsLayerMaxBytes = 32 << 20
	// ManifestsLayerMaxFiles bounds the regular files, which is also the
	// document count: one document per manifest file.
	ManifestsLayerMaxFiles = 5000
)

Bounds on a manifests layer. They sit far above a real bundle (control-plane prod is 2.3 MiB uncompressed) and far below anything that threatens the controller's memory, so they exist to make hostile input fail loud and early.

View Source
const (
	// MaxMigrationSets bounds how many migration directories one release
	// records: the project's own plus each sibling checkout it builds from.
	MaxMigrationSets = 16
	// MaxMigrationFiles bounds one set. Far above any real project (the
	// largest measured had 205); a bound so a hosted column has one too.
	MaxMigrationFiles = 10000
	// MaxMigrationFileBytes bounds one recorded file name.
	MaxMigrationFileBytes = 255
	// MaxMigrationSourceBytes bounds one set's source identity.
	MaxMigrationSourceBytes = 512
)
View Source
const (
	// MaxObjectSummaryBytes bounds one object's Summary line.
	MaxObjectSummaryBytes = 200
	// MaxObjectParts bounds one object's Parts. Past it the remainder folds
	// into one [PartOverflow] digest.
	MaxObjectParts = 64
	// MaxPartNameBytes bounds a part name.
	MaxPartNameBytes = 200
	// PartDigestLen is the hex length of a part digest: 48 bits, enough that
	// two different sections of one object never read as the same, and short
	// enough to keep a bundle's summaries small. A part digest names WHICH
	// section changed; the object Hash remains the authority on whether
	// anything did.
	PartDigestLen = 12
	// MaxObjectSummariesBytes bounds the JSON of a bundle's whole summary
	// set. [BoundObjectSummaries] drops Parts, then everything, to fit.
	MaxObjectSummariesBytes = 1 << 20
	// PartOverflow is the part that folds whatever exceeds MaxObjectParts.
	PartOverflow = "+more"
)
View Source
const (
	DeployPhasePreRollout  = "pre-rollout"
	DeployPhasePostRollout = "post-rollout"
)

The values AnnotationDeployPhase accepts.

View Source
const (
	FindingObjectAdded      = "object_added"
	FindingObjectChanged    = "object_changed"
	FindingObjectRemoved    = "object_removed"
	FindingImageChanged     = "image_changed"
	FindingConfigChanged    = "config_changed"
	FindingSecretNeeded     = "secret_needed"
	FindingStatefulDeletion = "stateful_deletion"
	FindingLBIdentityChange = "lb_identity_change"
	// FindingTreeMoved: an object changes delivery tree between releases.
	// Under a pruning reconciler that is a delete in the tree it leaves and a
	// create in the one it joins, with the object's data in between.
	FindingTreeMoved = "tree_moved"
	FindingDrift     = "drift"
	// FindingUnknown: a section could not be computed (no recorded live
	// config, drift not observable, secret presence not readable). It is a
	// WARN, never silence: a blank section must not read as "nothing
	// changes" (F-20).
	FindingUnknown = "unknown"
)

Finding codes. Closed: a consumer switches on them, and a stop-class code is what an operator names to acknowledge it.

View Source
const (
	SectionObjects  = "objects"
	SectionStateful = "stateful_deletions"
	SectionLB       = "lb_identity"
	SectionTrees    = "tree_moves"
	SectionImages   = "images"
	SectionConfig   = "config"
	SectionSecrets  = "secrets"
	SectionDrift    = "drift"
)

Plan sections, in display order (§8.6).

View Source
const (
	MaxTargets     = 64
	MaxTargetBytes = 253
)

MaxTargets bounds a promotion's Targets, and MaxTargetBytes each name.

View Source
const (
	// HealthErrorRate: the error rate rose by more than MaxErrorRateIncrease.
	HealthErrorRate = "error_rate"
	// HealthLatency: p95 latency grew by more than MaxP95Ratio.
	HealthLatency = "latency"
	// HealthNewIssue: an issue first seen in this release, with at least
	// NewIssueMinEvents events.
	HealthNewIssue = "new_issue"
	// HealthRegressedIssue: a resolved issue regressed in the window.
	HealthRegressedIssue = "regressed_issue"
	// HealthAlertFiring: an alert opened after the promotion is still firing.
	HealthAlertFiring = "alert_firing"
	// HealthWorkloadRestarts: restarts or OOM kills after the promotion.
	HealthWorkloadRestarts = "workload_restarts"
)

Release-health finding codes: what `forge obs check` and the control plane's GetReleaseHealth can report about a promotion, comparing the window after it with the window before it.

View Source
const (
	MinHealthWindow = time.Minute
	MaxHealthWindow = 6 * time.Hour
)

Release-health window bounds.

View Source
const (
	// AnnotationSecretPins is set on a consumer's pod template: the store
	// versions its ExternalSecrets are pinned to, one "<secret>/<NAME>=<version>"
	// per pin, sorted and comma-joined. A pin bump changes it, which changes
	// the pod template, which rolls the consumer; and because the applier
	// holds workloads until the ExternalSecret has synced the new version,
	// the roll happens only after the Secret holds it.
	AnnotationSecretPins = "forge.dev/secret-pins"

	// AnnotationSecretSync on an ExternalSecret says whether a deploy waits for
	// it. Absent means await it: the Secret is something a workload reads, so
	// no workload is applied until it has synced.
	AnnotationSecretSync = "forge.dev/secret-sync"
	// SecretSyncDeferred means the value is written AFTER the deploy (the
	// control plane renews a token in place), so a first deploy would wait for
	// a value that cannot exist yet.
	SecretSyncDeferred = "deferred"
	// SecretSyncShadow means the ExternalSecret syncs to a shadow Secret no
	// workload reads (a migration's comparison copy), so it gates nothing.
	SecretSyncShadow = "shadow"
)

Annotations forge stamps for the ordering.

View Source
const (
	// StoreHosted is the secret store the env's hosted control plane runs.
	StoreHosted = "hosted"
	// StoreOpenBao is the OpenBao the env runs itself (Bundle.openbao).
	StoreOpenBao = "openbao"
	// StoreGCPSecretManager is Google Secret Manager. Its values are written
	// by the project's owner, so a binding to it derives no store policy and
	// carries no generated secret.
	StoreGCPSecretManager = "gcpsm"
)

Store kinds a binding reads from (ShapeStoreSecret.Store).

View Source
const (
	// UptimeSourceUser is a check the author declared (forge.UptimeCheck).
	UptimeSourceUser = "user"
	// UptimeSourceDefault is a check forge derived: one per public frontend
	// and per public HTTP route of a hosted env.
	UptimeSourceDefault = "default"
)

Uptime check sources: where a ShapeUptimeCheck came from.

View Source
const (
	MinUptimeInterval = 30 * time.Second
	MaxUptimeInterval = 10 * time.Minute
	MinUptimeTimeout  = time.Second
	MaxUptimeTimeout  = time.Minute
	// MaxUptimeExpectBody bounds the body substring a check looks for.
	MaxUptimeExpectBody = 256
)

Bounds of a check's schedule. The floor keeps one check from becoming a load generator against the endpoint it watches; the ceiling keeps a check from being so sparse that a consecutive-failure alert could never fire.

View Source
const (
	// UptimeMetricUp is 1 when the last probe passed (the status and the body
	// were as expected), 0 when it failed. A failed probe also carries
	// [UptimeAttrError]; one that got a response carries
	// [UptimeAttrStatus].
	UptimeMetricUp = "uptime.check.up"
	// UptimeMetricDuration is the last probe's wall time in seconds, from
	// request to the end of the response (or to the failure).
	UptimeMetricDuration = "uptime.check.duration"
	// UptimeMetricCertExpiry is the seconds until the certificate the endpoint
	// presented expires (negative once it has). Written for https checks
	// whenever a certificate was seen, including one that failed verification.
	UptimeMetricCertExpiry = "tls.cert.expiry_seconds"

	// UptimeAttrCheck is the check's name.
	UptimeAttrCheck = "check"
	// UptimeAttrStatus is the HTTP status of the last response, on
	// [UptimeMetricUp].
	UptimeAttrStatus = "http.response.status_code"
	// UptimeAttrError names how a probe failed when it got no acceptable
	// response: timeout, dns, connect, tls, refused (a destination the SSRF
	// guard will not dial), status or body.
	UptimeAttrError = "error.type"
)

The metrics the uptime prober writes into the env's telemetry, and the attributes they carry. The prober (control plane), the UptimeCheckFailing and CertificateExpiring alert defaults (internal/obsdash) and any dashboard read these names, so they are declared once, here.

All three are gauges. They carry the resource attribute deployment.environment.name (the env's name, which every other rule scopes by) and the metric attribute UptimeAttrCheck.

View Source
const AnnotationDeployPhase = "forge.dev/deploy-phase"

AnnotationDeployPhase is the Job metadata annotation that places a one-shot Job relative to the workloads. It lives on the Job's metadata, never its pod template: the Job's name carries a hash of the pod spec, and declaring a phase must not rename (and so re-run) a Job that already completed.

View Source
const ApplierForge = "forge"

ApplierForge is Shape.Applier for an env forge applies by declaration. It is a transitional state: an env whose every delivery unit is still applied by forge. The end state is every unit converged by the hub (UnitApplierHub).

View Source
const ArtifactPlaceholder = "forge.dev/artifact:"

ArtifactPlaceholder is what a release-bound image digest becomes in the document a ConfigHash is taken over: `forge.dev/artifact:<key>`.

View Source
const BundleHostedCluster = "hosted"

BundleHostedCluster is the cluster key under which a bundle carries an env's HOSTED tier records (forge.dev Workload / ManagedDatabase / StaticSite).

Hosted workloads run on the platform's own clusters, which forge cannot name as a kube context, so they live under ONE well-known tree, BundleClusterPath(BundleHostedCluster). The control plane maps that tree to the placement it resolved for the environment; forge never says where.

View Source
const BundleManifestsPrefix = "manifests"

BundleManifestsPrefix is the directory the manifest layer's entries live under, inside the extracted layer. Every entry is at `<prefix>/<cluster>/<file>.yaml`.

View Source
const BundleSchema = "forge.dev/bundle/v1"

BundleSchema is the BundleDoc schema this package reads and writes.

View Source
const ComponentOpenBao = "openbao"

ComponentOpenBao is the LabelComponent value of every object the OpenBao component renders.

View Source
const ControlPlaneSecretDeliveryStore = "store"

ControlPlaneSecretDeliveryStore is the Delivery of a control-plane secret the control plane writes into the env's secret store, for ESO to deliver.

View Source
const ControlPlaneSecretStoreName = "RELIANT_OTEL_HEADERS"

ControlPlaneSecretStoreName is the store name the control plane writes a store-delivered telemetry_token under: one value, `authorization=Bearer <jwt>`. The Secret's keys are derived from it by the ExternalSecret (kcl/lib/external_secret.k TELEMETRY_TOKEN_STORE_NAME is the same string, pinned by a render test), so a control plane that writes this name needs nothing else from forge.

View Source
const DefaultDeliveryUnit = "base"

DefaultDeliveryUnit is the unit an object belongs to when nothing declares one. It is also the unit every other undeclared unit waits for.

View Source
const ESOGroup = "external-secrets.io"

ESOGroup is the API group of ExternalSecret and SecretStore.

View Source
const LabelComponent = "forge.dev/component"

LabelComponent names the platform component an object belongs to.

View Source
const LabelDeliveryUnit = "forge.dev/delivery-unit"

LabelDeliveryUnit is the object label that places an object in a delivery unit. It rides the object itself so it survives every expansion between the KCL declaration and the rendered stream, and is sealed under the bundle's digest.

View Source
const LabelSecretStage = "forge.dev/secret-stage"

LabelSecretStage places an object in the secret ordering by declaration. KCL stamps it on the login identity, SecretStores and ExternalSecrets of a store-backed source (SecretStageSecrets is the only value in use).

View Source
const MaxComparedCommits = 250

MaxComparedCommits caps the commits a comparison lists. Total still counts them all when every composed range could.

View Source
const MaxShapeBytes = 512 << 10

MaxShapeBytes bounds a shape's canonical JSON. It is what a hosted ledger indexes per bundle, and what Live reads per env in one round trip, so it is a summary and never the render itself.

The shape grows with the env's object count (Objects carries two digests per object), so the bound is sized for a large env, not a typical one. It was 256 KiB until control-plane's own prod env reached 95% of it (2026-10-28). A storing control plane backs it with a column CHECK on the stored jsonb (control-plane: pg_column_size < 1 MiB), and jsonb is larger than the JSON text it came from: about 7% for a shape (275,779 bytes of JSON measured 295,216 as jsonb on Postgres 16). The CHECK must stay comfortably above this bound plus that overhead; raising this bound past ~900 KiB needs the column widened first.

View Source
const MinAlertInterval = time.Minute

MinAlertInterval is the evaluation-interval floor a shape may record.

View Source
const ObsOptInReplay = "replay"

ObsOptInReplay is session replay: the browser SDK's recorded DOM events, dropped by the platform unless the env opts in.

View Source
const PruneMarker = "[prune]"

PruneMarker is the prefix an owned-object prune finding's detail carries.

View Source
const SessionStaleAfter = 3 * time.Minute

SessionStaleAfter is how long a live session may go unseen before a reader greys it out. Heartbeats are every 60s.

View Source
const SharedVariant = "*"

SharedVariant is the sole Digests key of a Mode ModeShared artifact: one digest serves every environment.

View Source
const SnapshotBundleSchema = "forge.dev/bundle/v2"

SnapshotBundleSchema embeds a full immutable desired-state snapshot.

View Source
const SnapshotSchema = "forge.dev/snapshot/v1"

SnapshotSchema versions the complete desired-environment contract. Historical bundles without a snapshot remain readable, but are not composition bases.

View Source
const SourcemapsArtifactSuffix = ".sourcemaps"

SourcemapsArtifactSuffix ends the name of the release artifact that records a frontend's source maps.

The release records, beside the frontend that shipped, an OCI artifact:

artifacts["web.sourcemaps"] = Artifact{
    Kind:    KindOCI,
    Mode:    ModeShared,
    Digests: {SharedVariant: "sha256:…"},   // the artifact's manifest digest
    Version: "3f9c0e1a2b4d",                // the build id, exactly as the bundle reports it
    URI:     "registry.example/<org>/<project>/web-sourcemaps",
}

Unlike an image, it is NOT keyed by its repository: the key is the frontend's name plus this suffix, so a reader holding a frontend and a release finds the maps without composing a registry path. The repository is URI, whole, host included — never a prefix to join the name onto (a dot is not legal in a repository segment, so `web.sourcemaps` can never be one).

View Source
const SourcemapsRepositorySuffix = "-sourcemaps"

SourcemapsRepositorySuffix ends the repository segment the maps live in. It is a dash, not a dot: the registry admits only RFC-1123 labels below the project, so the segment is an ordinary image-shaped name.

Variables

ApplyStatuses is the closed set.

ControlPlaneSecretKinds is every kind a control-plane secret can have.

DBQueryPolicies is the closed set, in a stable order.

View Source
var DefaultUptimeExpectStatus = []string{"2xx", "3xx"}

DefaultUptimeExpectStatus is what a check with no declared statuses accepts: any 2xx or 3xx. A redirect is an answer, not an outage.

EnvKinds is the closed set, in a stable order.

View Source
var ErrInvalid = errors.New("invalid release ledger entry")

ErrInvalid is wrapped by every validation failure in this package, so a backend can map "the caller sent a malformed release" to one status code without string-matching.

View Source
var ErrInvalidManifestsLayer = errors.New("invalid manifests layer")

ErrInvalidManifestsLayer is wrapped by every refusal from ValidateManifestsLayer, so a caller can tell "this bundle is hostile or malformed" from an IO failure reading it.

View Source
var ErrReleaseConflict = errors.New("release version already names a different artifact set")

ErrReleaseConflict is returned when a version label is re-cut with a DIFFERENT artifact set. A version that meant two digest sets would void every guarantee promotion rests on, so it is refused rather than resolved.

FindingClasses is the closed set, least to most severe.

FindingCodes is the closed set of codes.

GateStatuses is the closed set, in a stable order (for error messages and for the hosted schema's CHECK, which must list exactly these).

HealthFindings is every finding code, in a stable order.

IdentityKeys is the closed set of ShapeObject.Identity keys.

View Source
var IdentityOmittableOutputs = []string{"jwks", "login_uri", "roles_claim", "scopes"}

IdentityOmittableOutputs are the outputs a consumer may decline to have projected (ShapeIdentityConsumer.Omit). The rest define the identity.

View Source
var IdentityOutputs = []string{
	"audience", "broker_token", "client_id", "idp_base", "issuer",
	"jwks", "login_uri", "redirect_uri", "roles_claim", "scopes",
}

IdentityOutputs is the closed set of outputs a consumer's config can be filled from, and the keys of ShapeIdentityConsumer.Fields.

Kinds is the closed set, in a stable order (for error messages and for the hosted schema's CHECK, which must list exactly these).

Modes is the closed set.

ObsCategories is every opt-out category, sorted.

View Source
var ObsOptIns = []string{ObsOptInReplay}

ObsOptIns is every opt-in category, sorted.

Phases is the closed set, in apply order.

PlanSections is the closed set, in display order.

SecretStages is the closed set, in apply order.

StageKinds is the closed set, in PIPELINE order: a run's stages sort by time, but this is the order the kinds happen in.

StageStatuses is the closed set, in a stable order.

StoreKinds is every store kind a binding can name.

Functions

func BindingReference added in v1.0.0

func BindingReference(a SnapshotArtifact, b ConsumerBinding) string

BindingReference is the exact image reference a binding renders and every bound container must carry: `Location[:Tag]@Content`.

func BuildInputDigest added in v0.1.46

func BuildInputDigest(in BuildInputs) (string, error)

BuildInputDigest names an effective recipe/output contract, not a workload.

func BundleClusterPath

func BundleClusterPath(cluster string) string

BundleClusterPath is the path, within the extracted manifests layer, that holds the documents a deploy routes to one cluster. It is what the control plane sets as `Kustomization.spec.path` for that cluster.

THIS FUNCTION IS THE CONTRACT, AND BOTH SIDES CALL IT. forge writes the tar entries under it and the control plane's Kustomization builder reads it. Two spellings of the same rule would be a silent, total failure: a Kustomization whose path does not exist in the artifact reconciles to "nothing to apply" and reports Ready — so the env would look converged while running the previous release forever. That is why the sanitizer lives here, beside the media type, rather than in whichever package happened to write the tar.

An empty cluster name is the unclustered tree. A multi-cluster env has one path per cluster and ONE bundle: a document routed to two clusters appears under both paths, because each cluster's Kustomization applies its own path and prunes anything it owns that the path no longer carries.

func BundleDocFeatures added in v0.1.46

func BundleDocFeatures() []string

BundleDocFeatures is every optional bundle feature this version's DecodeBundleDoc and ValidateManifestsLayer accept. A control plane advertises exactly this list, so what it claims to decode and what its decoder accepts are one fact.

func BundlePathSegment

func BundlePathSegment(s string) string

BundlePathSegment makes one name safe as a single path component: anything that is not alphanumeric, dash, underscore or dot becomes a dash.

A Kubernetes name cannot contain a separator, but a CLUSTER name is a kubectl context and very much can (`gke_project_region_name` is the tame case; a context is free-form). An unsanitized segment would put entries at an unexpected depth, and the unpacker would be the only thing standing between a context name and a path the archive should never ask for.

Exported because the control plane sanitizes the same cluster names when it builds a Kustomization path, and a second implementation that disagreed on one character would point it at a path the artifact does not hold.

func BundleTreePath added in v0.1.46

func BundleTreePath(cluster, unit string, phase Phase) string

BundleTreePath is the path, within the extracted manifests layer, of one v2 tree. Like BundleClusterPath it is the contract both sides call: forge writes the tar entries under it and the control plane sets it as Kustomization.spec.path.

func CanonicalRepo

func CanonicalRepo(url string) string

CanonicalRepo normalizes a git remote URL to "host/owner/repo", so the SSH and HTTPS spellings of one repository compare equal and no credential in a URL is ever recorded.

func ChartMarker added in v0.1.46

func ChartMarker(chart string) string

ChartMarker is the prefix every chart finding's detail carries, so a reviewer can tell chart-owned objects from the release bundle's.

func CheckRecut

func CheckRecut(existing, requested Release) error

CheckRecut decides what re-cutting `existing` as `requested` means: nil when the two name the same bytes (an idempotent retry), and ErrReleaseConflict when they do not. Both backends call this, so "a version label names exactly one artifact set" is one rule.

func CheckSnapshotSeal added in v0.1.46

func CheckSnapshotSeal(r Release, doc BundleDoc, seal SnapshotSeal) error

CheckSnapshotSeal validates all three immutable associations together. A ledger must enforce append-only uniqueness of (release,environment) and org scope transactionally after verifying registry descriptors/layer bytes.

func CommitText added in v0.1.46

func CommitText(s string, max int) string

CommitText makes s safe to record as one line of at most max bytes: control characters become spaces, runs of space collapse, and an over-long value is cut at a rune boundary and marked with "…".

func ConfigDigest

func ConfigDigest(shape Shape) (string, error)

ConfigDigest is the digest of one env's whole render with every release-bound image reference normalized to its artifact key (doc §2.1).

It is computed from the per-object ConfigHashes a shape already holds, rather than over the manifest bytes, and that is the point: the objects are taken in their canonical (sorted) order, so a render that emits the same objects in a different sequence — or reflows their YAML — produces the same config digest. A digest over the packed layer would move on both, and would therefore report a cosmetic render change as a config change on every deploy.

It covers the object identities as well as their hashes. Renaming an object while leaving its body alone IS a config change, and hashing only the per-object hashes would miss it.

func ConsumerDigest added in v0.1.46

func ConsumerDigest(s Snapshot, key ConsumerKey) (string, error)

ConsumerDigest binds compatibility evidence to exact declarations, artifacts (including original provenance), slots and all resources this consumer owns. It excludes snapshot lineage and evidence, avoiding a hash cycle.

func DescribeObjectChange added in v0.1.46

func DescribeObjectChange(from, to ShapeObject, parts []PartChange) string

DescribeObjectChange is the one line for a modified object: which images moved, which sections changed, and whether it moved between delivery trees — "image control-plane; env FORCE_RECONCILE removed". parts are the sections that moved, or nil when either side recorded none; then a config change is reported as just that.

func FlatCluster added in v0.1.46

func FlatCluster(cluster string) bool

FlatCluster reports whether a cluster's tree stays in the v1 flat layout: the unclustered tree (objects nothing applies) and the hosted tree (records the control plane renders). Every other cluster is split into v2 trees.

func ForceRecreateKind added in v0.1.46

func ForceRecreateKind(kind string) bool

ForceRecreateKind reports whether the bundle build stamps kind with the per-object force annotation.

func HashDocument

func HashDocument(doc any) (string, error)

HashDocument is the object hash: sha256 over the document's CANONICAL JSON, returned as `sha256:<hex>`.

Canonical JSON rather than the rendered YAML, because the hash has to mean "this object" and not "these bytes". encoding/json sorts an object's keys, so a renderer that reorders a map, reflows a list or changes its indentation produces the same hash — while any change to a VALUE changes it. Comparing YAML text would make every cosmetic render change look like drift, which is the signal this hash exists to carry.

It does NOT redact. Callers hashing a rendered document want ObjectHashes; this is exported for the one case that has already redacted and needs the digest of some other canonical value.

func HostID

func HostID() string

HostID is a stable, non-identifying machine id: the first 16 hex of sha256(hostname). Used when no daemon id is available.

func IdentityIssuer added in v0.1.46

func IdentityIssuer(slug string) string

IdentityIssuer is the issuer of a slug: https://<slug>.reliantidentity.com.

func IdentitySlug added in v0.1.46

func IdentitySlug(envID, project, env string, hashChars int) string

IdentitySlug is the issuer slug for an env: <project>-<env>-<hash>. hashChars is clamped to [IdentitySlugMinHash, IdentitySlugMaxHash]. The result is always a valid DNS label (ValidIdentitySlug).

func ImagesIn

func ImagesIn(renderedDoc string, pins map[string]string) map[string]string

ImagesIn reports which release artifacts' pinned digests appear in a rendered document, as artifact key → digest. It is what populates ShapeObject.Images.

Searched as TEXT over the whole document, for the reason NormalizeImages is: an image reference is not confined to a container's `image` field. forge's own operators carry the image they launch as an env-var value (control-plane's workspace-controller pins the workspace base image in DAEMON_IMAGE), and a chart's values can put one anywhere. A reader that only looked at pod specs would report such an object as carrying no image while a deploy very much changes which bytes it runs.

func IsESOKind added in v1.0.0

func IsESOKind(apiVersion, kind string) bool

IsESOKind reports whether apiVersion/kind is an External Secrets Operator SecretStore or ExternalSecret.

func IsIdentityKind added in v0.1.46

func IsIdentityKind(kind string) bool

IsIdentityKind reports whether a control-plane secret kind is one a managed identity derives.

func IsNestedCheckout

func IsNestedCheckout(dir string) bool

IsNestedCheckout reports whether dir is the root of a git checkout: a repository (`.git` directory) or a linked worktree (`.git` file), whatever it is named.

func MigrationVersion added in v1.0.0

func MigrationVersion(file string) (uint64, bool)

MigrationVersion is the numeric version a migration file name carries — the digits before the first "_" — and whether it has one. It is the rule golang-migrate parses by, restated here because pkg/ may not import forge's internal version package.

func NormalizeImages

func NormalizeImages(doc any, pins map[string]string) any

NormalizeImages rewrites every release-bound image DIGEST in a document to its artifact key, returning a copy. It is what makes a ConfigHash the identity of a deploy's SHAPE rather than of its images: two renders with equal ConfigHashes differ, at most, in which release they pin, so "promote a new release" and "the KCL moved" are told apart by one comparison instead of by re-rendering and diffing YAML.

pins maps artifact key → pinned digest, as a render resolved them.

ONLY THE DIGEST IS REPLACED, not the whole reference: the repository is part of the config (pushing an image somewhere else IS a change) and the digest is the only part a promotion moves.

Every string in the document is searched, not just a container's `image` field, because an image reference is not confined to one: forge's own operators carry the image they launch as an env-var value, and a chart's values can put one anywhere. A normalizer that only looked at pod specs would leave a digest in the ConfigHash and make every promotion look like a config change.

Artifacts are applied in sorted order, so two artifacts that happen to share a digest normalize the same way every time rather than by map iteration order.

func NormalizeTargets added in v0.1.46

func NormalizeTargets(targets []string) []string

NormalizeTargets is the canonical form of a scope: trimmed, empty names dropped, duplicates removed, sorted. Nil for an unscoped deploy.

func ObjectExternalIdentity added in v0.1.46

func ObjectExternalIdentity(kind string, body any) map[string]string

ObjectExternalIdentity is the shared planning projection of destructive external identity fields. Both normal bundle producers and ingest use it.

func ObjectHashes

func ObjectHashes(doc any, pins map[string]string) (hash, configHash string, err error)

ObjectHashes is the per-object pair a ShapeObject carries, and the only entry point a caller should need.

  • hash is the document's digest after secret redaction: what drift detection compares a live object against.
  • configHash is the digest of the same document with release-bound image references normalized away: equal configHash with a different hash means only images changed.

It redacts FIRST and unconditionally. A two-call API ("redact, then hash") would make the safe thing the thing a caller has to remember, and the cost of forgetting is a permanent leak.

func RecreatableKind

func RecreatableKind(kind string) bool

RecreatableKind reports whether kind may be deleted and re-applied by the immutable-field recovery.

func RedactSecrets

func RedactSecrets(doc any) any

RedactSecrets replaces every value under a `kind: Secret` document's `data` and `stringData` with the hash of that value, returning the document to hash or package.

NO HASH INPUT AND NO PACKAGED BYTE MAY EVER CARRY A SECRET VALUE (F-13). A shape is stored forever and shown to every member of an org, and a bundle is cached and shared, so a leak in either is permanent. This is the one function that makes that true, which is why ObjectHashes calls it rather than trusting its caller to have called it.

THE KEYS SURVIVE. "Which keys does this Secret carry" is a shape fact an operator needs; "what are they" is not. The hash is what keeps "a secret value changed" visible without the value — the signal a What-if diff shows.

It is IDEMPOTENT: a value already carrying the marker is left alone, so redacting a document twice yields the same hash as redacting it once. Re- hashing a marker would make the safe entry point and the correct hash two different calls.

Anything that is not a Secret is returned unchanged, and the input is never mutated — the caller still needs it.

func RolloutWorkloadKind added in v0.1.46

func RolloutWorkloadKind(kind string) bool

RolloutWorkloadKind reports whether kind runs the release's code.

func SecretPrerequisiteKey added in v0.1.46

func SecretPrerequisiteKey(ref BundleSecretRef) string

SecretPrerequisiteKey names an immutable provider-version identity in Snapshot.Prerequisites. The identity is never a hash of the secret value.

func SecretSyncAwaited added in v1.0.0

func SecretSyncAwaited(annotations map[string]string) bool

SecretSyncAwaited reports whether a deploy waits for an ExternalSecret with these annotations.

func SetExplicitRollout added in v0.1.46

func SetExplicitRollout(doc map[string]any) bool

SetExplicitRollout writes a Deployment's strategy and progressDeadlineSeconds into a decoded document where they are absent, with Kubernetes' defaults. It never overrides what the author wrote, and reports whether it changed anything. A document that is not a Deployment is left alone.

func SnapshotDelivery added in v0.1.46

func SnapshotDelivery(s Snapshot) ([]BundleClusterTree, []BundleTree, error)

SnapshotDelivery derives full cluster indexes, readiness/dependency graph, timeouts and budgets from approved objects and delivery-unit declarations. These actuator semantics cannot be weakened without changing snapshot input.

func SourcemapsArtifactName added in v0.1.46

func SourcemapsArtifactName(frontend string) string

SourcemapsArtifactName is the release artifact name for a frontend's maps.

func SourcemapsFrontend added in v0.1.46

func SourcemapsFrontend(artifact string) (string, bool)

SourcemapsFrontend is the inverse of SourcemapsArtifactName: the frontend an artifact name holds the maps of, and false for any other artifact.

func SourcemapsRepository added in v0.1.46

func SourcemapsRepository(registryBase, frontend string) (string, error)

SourcemapsRepository is the repository a frontend's maps are pushed to: `<registryBase>/<frontend>-sourcemaps`. registryBase is the project's push base (`<registry host>/<org>/<project>`). The frontend must be a lower-case RFC-1123 label short enough that the suffixed segment still is one.

func SourcemapsTag added in v0.1.46

func SourcemapsTag(buildID string) (string, error)

SourcemapsTag is the registry tag a build's maps are filed under: the build id, which is also the bundle's service.version. An id that is already a valid OCI tag (the 12-hex id forge derives always is) is the tag verbatim. A project-set version may hold `+` (semver build metadata), which a tag cannot; it becomes `_`, the convention Helm and Docker use for the same reason. Any other character a tag cannot hold is an error rather than a silent rewrite, because a rewrite the symbolicator did not apply would file the maps where nothing looks.

func SpecDigest added in v0.1.46

func SpecDigest(b []byte) string

SpecDigest addresses the exact original serialized bytes, not canonical YAML.

func StatefulKind

func StatefulKind(kind string) bool

StatefulKind reports whether a kind's removal destroys data or everything under it — [statefulKinds] as a predicate.

Exported because the PLAN and the BUNDLE WRITER need the same answer, for two halves of one guarantee. The plan calls a removal of one of these a stop-class finding that must be acknowledged; the bundle writer stamps `kustomize.toolkit.fluxcd.io/prune: disabled` on it so a reconciler cannot delete it at all. Two spellings of this list would mean an object the plan treats as irreplaceable is one the reconciler happily prunes, and the divergence would only ever be discovered by losing the data.

func SummarizeObject added in v0.1.46

func SummarizeObject(kind string, body any) (summary string, parts map[string]string)

SummarizeObject describes one rendered document (decoded YAML, secrets already redacted) of the given kind: its ObjectSummary.Summary line and its ObjectSummary.Parts.

func TreeBudget added in v0.1.46

func TreeBudget(phase Phase, objects []TreeObject) int

TreeBudget is a tree's budget: the largest allowance among the objects it is judged on, plus BudgetMarginSeconds. A tree it judges nothing on gets MinBudgetSeconds.

func ValidDigest

func ValidDigest(d string) bool

ValidDigest reports whether d is a canonical `sha256:<64 hex>` digest.

func ValidIdentitySlug added in v0.1.46

func ValidIdentitySlug(s string) bool

ValidIdentitySlug reports whether s is a slug IdentitySlug could have produced: a DNS label of at most 63 characters.

func ValidImageTag added in v1.0.0

func ValidImageTag(tag string) bool

ValidImageTag is the OCI distribution-spec tag grammar.

func ValidObjectID

func ValidObjectID(s string) bool

ValidObjectID reports whether s is a full git object id (SHA-1 or SHA-256).

func ValidateObjectSummaries added in v0.1.46

func ValidateObjectSummaries(shape Shape, summaries []ObjectSummary) error

ValidateObjectSummaries checks a bundle's summary set against its shape: every summary valid, none duplicated, and each describing an object the shape holds.

func ValidateSnapshotBindings added in v0.1.46

func ValidateSnapshotBindings(s Snapshot) error

ValidateSnapshotBindings checks all runtime container images against exact workload/slot identities. Every image-bearing resource needs known owners; no bare repository or container-list-position fallback is permitted.

func ValidateSnapshotLayer added in v0.1.46

func ValidateSnapshotLayer(s Snapshot, layer []byte) error

ValidateSnapshotLayer proves exact config/layer agreement, including named runtime container slots. Registry callers must additionally verify OCI manifest/config/layer descriptor digests and admission/authorization.

func ValidateSnapshotTransition added in v0.1.46

func ValidateSnapshotTransition(base *Snapshot, candidate Snapshot) error

ValidateSnapshotTransition independently verifies that candidate is a complete legal transition from the recorded base, including carry bytes, provenance, closure and exact compatibility subjects. Servers must fetch and verify the recorded base bundle and bind candidate.Base to its promotion under the lock. Reports' authenticity and policy authority are additional admission checks.

func ValidateUptimeURL added in v1.0.0

func ValidateUptimeURL(raw string) error

ValidateUptimeURL checks a check's url: absolute http(s), a host, no credential, and a host that is not obviously private. The last is the same predicate the prober's dialer enforces (pkg/netguard), applied early so the author hears about a bad address when it is declared; it resolves no name, so the dial-time guard remains the boundary.

Types

type Actor

type Actor struct {
	User  string `json:"user,omitempty"`
	Actor string `json:"actor,omitempty"`
}

Actor is who recorded a promotion: a human user, or a named automation ("ci", "preview-bot"). A file ledger records whatever the caller states.

type AppliedManifest added in v1.0.0

type AppliedManifest struct {
	// Release is the version label, for the refusal's wording.
	Release string
	// Manifest is what that release recorded.
	Manifest MigrationManifest
}

AppliedManifest is one release an environment applied, with the migrations it carried: the input PassedMigrations judges a target against.

type Apply

type Apply struct {
	ID       string `json:"id"`
	Env      string `json:"env"`
	BundleID string `json:"bundle_id"`
	// PromotionID is the binding this apply realizes; "" only for an
	// unreleased bundle (a dev-shaped "deploy my worktree").
	PromotionID string `json:"promotion_id,omitempty"`
	AppliedBy   string `json:"applied_by,omitempty"`
	Run         Run    `json:"run,omitempty"`
	// PlanDigest is the approved plan this apply realizes (O-13).
	PlanDigest string `json:"plan_digest,omitempty"`
	// AcknowledgedFindings are the stop-class finding codes the approver
	// accepted by name.
	AcknowledgedFindings []string `json:"acknowledged_findings,omitempty"`
	// SupersededInFlight records that this apply was started over one that
	// had not finished — a deliberate override.
	SupersededInFlight bool `json:"superseded_in_flight,omitempty"`
	// DeadlineAt is forge's wait budget. Past it with no outcome, the apply
	// reads as abandoned ([ApplyAbandoned]).
	DeadlineAt   time.Time `json:"deadline_at"`
	ImportedFrom string    `json:"imported_from,omitempty"`
	CreatedAt    time.Time `json:"created_at"`
}

Apply is ONE attempt to make an environment run a bundle — "who tried to make it so, from where" — recorded before anything moves. Its outcome is a SEPARATE record (ApplyOutcome) written at most once, so both halves stay append-only and "the apply never reported back" is visible as the absence of an outcome rather than as a status somebody had to remember to set.

forge writes applies for the envs it applies (self-managed, and the declaration half of a hosted deploy). A converger writes none: a hosted promotion's outcome is DERIVED from observation, and a derived timeline is never also stored.

func (Apply) Validate

func (a Apply) Validate() error

Validate checks an apply before it is recorded.

type ApplyOutcome

type ApplyOutcome struct {
	ApplyID string      `json:"apply_id"`
	Status  ApplyStatus `json:"status"`
	Summary string      `json:"summary,omitempty"`
	// Workloads is the applier's per-resource rollout result: one entry per
	// resource it waited on.
	Workloads []ApplyWorkload `json:"workloads,omitempty"`
	// ReportedBy is set by the BACKEND from the credential, never by the
	// client.
	ReportedBy string    `json:"reported_by,omitempty"`
	FinishedAt time.Time `json:"finished_at"`
}

ApplyOutcome is the END of an apply, written at most once. It is a REPORT: for a cluster the platform does not observe, the applier is the only observer there is, and a reader must label it as such ("reported by forge"). It never feeds billing or policy.

func (ApplyOutcome) SameReport

func (o ApplyOutcome) SameReport(other ApplyOutcome) bool

SameReport reports whether two outcomes say the same thing — the idempotency rule for a retried report: the same outcome again is a retry, a different one is a conflict. Who reported it and when the retry arrived do not change what was reported.

func (ApplyOutcome) Validate

func (o ApplyOutcome) Validate() error

Validate checks an outcome before it is recorded.

type ApplyState

type ApplyState string

ApplyState is an apply's state as a READER sees it, DERIVED from the apply, its outcome (if any) and the clock. Never stored: "abandoned" in particular is a conclusion about the absence of a report, and a stored conclusion written by a sweeper could be wrong in a way a derivation cannot.

const (
	ApplyRunning   ApplyState = "running"
	ApplyStateOK   ApplyState = "succeeded"
	ApplyStateFail ApplyState = "failed"
	ApplyStateTO   ApplyState = "timed_out"
	// ApplyAbandoned: no outcome arrived by the deadline. "We could not
	// look" is its own answer — never read as success.
	ApplyAbandoned ApplyState = "abandoned"
)

func DeriveApplyState

func DeriveApplyState(a Apply, outcome *ApplyOutcome, now time.Time) ApplyState

DeriveApplyState is the one derivation every backend and every reader uses.

func (ApplyState) InFlight

func (s ApplyState) InFlight() bool

InFlight reports whether an apply in this state still blocks the next one on the same env. An abandoned apply does not: blocking on a report that will never come would wedge the env.

type ApplyStatus

type ApplyStatus string

ApplyStatus is how an apply ENDED, as its applier reported it. Closed.

const (
	ApplySucceeded ApplyStatus = "succeeded"
	ApplyFailed    ApplyStatus = "failed"
	// ApplyTimedOut: the applier stopped waiting before the rollout
	// answered. Not a success and not a failure.
	ApplyTimedOut ApplyStatus = "timed_out"
)

func (*ApplyStatus) UnmarshalJSON

func (s *ApplyStatus) UnmarshalJSON(data []byte) error

UnmarshalJSON refuses an empty or unknown status.

func (ApplyStatus) Valid

func (s ApplyStatus) Valid() bool

Valid reports whether s is one of ApplyStatuses.

type ApplyWorkload

type ApplyWorkload struct {
	Name    string `json:"name"`
	Cluster string `json:"cluster,omitempty"`
	State   string `json:"state"`
	Detail  string `json:"detail,omitempty"`
}

ApplyWorkload is one resource's rollout result. State is forge's rollout vocabulary: ready | failed | timed_out | not_waited.

type Artifact

type Artifact struct {
	Kind Kind `json:"kind"`
	Mode Mode `json:"mode"`
	// Digests maps a variant key → canonical digest. OCI only: for
	// [ModeShared] the only key is [SharedVariant].
	Digests map[string]string `json:"digests,omitempty"`
	// Platforms is the OS/arch set the image manifest advertises.
	// Informational, and NOT part of the artifact's identity.
	Platforms []string `json:"platforms,omitempty"`
	// Source is set exactly for [ModeSource].
	Source *Source `json:"source,omitempty"`
	// Version is how a consumer ASKS for a non-OCI artifact.
	Version string `json:"version,omitempty"`
	// Integrity is how a consumer KNOWS they got the right bytes, in the
	// ecosystem's native hash format. Stored verbatim.
	Integrity string `json:"integrity,omitempty"`
	// URI locates the artifact when name+version does not (a download URL,
	// a non-default registry, or the registry an image was pushed to).
	URI string `json:"uri,omitempty"`
	// BuiltFrom is the git checkout the artifact's build command ran in, when
	// that is a checkout forge can name — a ShellBuild's cwd. Informational
	// provenance like Platforms, and NOT part of the identity: the digest
	// names the bytes, this says where they were built.
	BuiltFrom *BuildSource `json:"built_from,omitempty"`
}

Artifact is ONE thing a release froze. Which fields carry the identity depends on Kind and Mode; Artifact.Validate is the rule.

func (Artifact) SharedDigest

func (a Artifact) SharedDigest() (string, bool)

SharedDigest returns the digest of a shared OCI artifact. Every other artifact answers ("", false) — that is the guard that keeps packages and source pins out of a pod spec without every caller remembering to check.

func (Artifact) Validate

func (a Artifact) Validate(name string) error

Validate enforces the per-kind addressing rules. name is used only in the error.

type ArtifactBuildEvent added in v0.1.46

type ArtifactBuildEvent struct {
	Consumer ConsumerKey    `json:"consumer"`
	Artifact ArtifactID     `json:"artifact"`
	Origin   ArtifactOrigin `json:"origin"`
}

ArtifactBuildEvent records a selected rebuild attestation separately when identical content retains the original catalog provenance for carried users.

type ArtifactChange added in v0.1.46

type ArtifactChange struct {
	Name   string             `json:"name"`
	Kind   Kind               `json:"kind"`
	Change ArtifactChangeKind `json:"change"`
	From   string             `json:"from,omitempty"`
	To     string             `json:"to,omitempty"`
}

ArtifactChange is one artifact whose identity moved. For an OCI image the identity is the digest, one entry per variant ("web[staging]" for a variant image); for a source-built artifact the resolved commit; for a package its version.

func CompareArtifacts added in v0.1.46

func CompareArtifacts(from, to Release) (changed []ArtifactChange, unchanged int)

CompareArtifacts lists the artifacts whose identity differs between from and to, sorted by name, and counts the ones that did not.

type ArtifactChangeKind added in v0.1.46

type ArtifactChangeKind string

ArtifactChangeKind is how one artifact differs between two releases.

const (
	ArtifactAdded   ArtifactChangeKind = "added"
	ArtifactRemoved ArtifactChangeKind = "removed"
	ArtifactChanged ArtifactChangeKind = "changed"
)

type ArtifactID added in v0.1.46

type ArtifactID string

ArtifactID addresses artifact bytes, not a build unit or a runtime consumer.

func ArtifactIdentity added in v0.1.46

func ArtifactIdentity(a SnapshotArtifact) (ArtifactID, error)

ArtifactIdentity excludes attestations but includes the immutable location.

type ArtifactOrigin added in v0.1.46

type ArtifactOrigin struct {
	External *ExternalArtifact `json:"external,omitempty"`
	Release  string            `json:"release"`
	Source   *Source           `json:"source,omitempty"`
	Receipt  *BuildReceipt     `json:"receipt,omitempty"`
}

type Attest

type Attest struct {
	Provider string `json:"provider"`
	Subject  string `json:"subject"`
	RunID    string `json:"run_id,omitempty"`
}

Attest is a CI identity claim (an OIDC subject). A hosted backend verifies it before it means anything; a file ledger records it as stated.

type BuildInputs added in v0.1.46

type BuildInputs struct {
	Recipe        string            `json:"recipe"`
	Sources       []Source          `json:"sources"`
	ContextDigest string            `json:"context_digest"`
	RecipeDigest  string            `json:"recipe_digest"`
	ConfigDigest  string            `json:"config_digest"`
	Tools         map[string]string `json:"tools"`
	Platforms     []string          `json:"platforms"`
	Args          map[string]string `json:"args,omitempty"`
	External      map[string]string `json:"external,omitempty"`
	Outputs       []string          `json:"outputs"`
	Cacheable     bool              `json:"cacheable"`
}

BuildInputs is a stable recipe/input identity, independent of consumer selection. Maps contain non-secret normalized identities, never credentials. Dynamic shell/external inputs must set Cacheable=false.

type BuildReceipt added in v0.1.46

type BuildReceipt struct {
	Inputs           BuildInputs       `json:"inputs"`
	InputDigest      string            `json:"input_digest"`
	WorkingDirectory *BuildSource      `json:"working_directory,omitempty"`
	Outputs          map[string]string `json:"outputs"`
}

BuildReceipt is produced by the actual builder after exporting pinned inputs. WorkingDirectory is attribution only and never substitutes for Inputs.Sources.

func (BuildReceipt) Validate added in v0.1.46

func (r BuildReceipt) Validate() error

Validate checks a builder receipt's effective input and output contract. It does not establish receipt authenticity or authorize automatic reuse: callers must verify origin authority and require Inputs.Cacheable separately.

type BuildSource

type BuildSource struct {
	// Repo is the canonical remote ("github.com/org/repo"); "" when the
	// checkout had none.
	Repo string `json:"repo,omitempty"`
	// Commit is HEAD, full hex.
	Commit string `json:"commit"`
	// Dirty: the checkout had uncommitted changes when the build started.
	Dirty bool `json:"dirty,omitempty"`
}

BuildSource is the checkout an artifact was built in: its repository, HEAD and dirty flag at build time. Like Provenance.Dirty it is a claim the building machine makes, recorded as evidence.

It is distinct from Source, which PINS a source-mode artifact (the commit IS its identity). A BuildSource rides beside an image's digest and explains it; changing it does not change what the release ships.

type BundleClusterTree

type BundleClusterTree struct {
	// Cluster is the kubectl context the documents are routed to, or ""
	// for the unclustered tree (objects nothing applies).
	Cluster string `json:"cluster,omitempty"`
	// Path is [BundleClusterPath]'s answer for Cluster: what a
	// Kustomization sets as spec.path.
	Path string `json:"path"`
	// Documents is how many documents the path holds. Carried so a reader
	// can tell an empty tree from an absent one without fetching the
	// layer, and so a Kustomization is never built over zero documents.
	Documents int `json:"documents"`
}

BundleClusterTree is one cluster's documents inside the manifest layer.

type BundleDoc

type BundleDoc struct {
	Schema         string    `json:"schema"`
	Snapshot       *Snapshot `json:"snapshot,omitempty"`
	SnapshotDigest string    `json:"snapshot_digest,omitempty"`
	Project        string    `json:"project"`
	Env            string    `json:"env"`
	// Release is the version the bundle pins, or "" for an unreleased
	// bundle (dev, or a Preview deploy) pinned to a build's own digests.
	Release string `json:"release"`
	// ConfigDigest is the digest of the render with every release-bound
	// image reference normalized to its artifact key: the shape of the
	// deploy independent of which release it pins. Two bundles with equal
	// ConfigDigests differ, at most, in images.
	ConfigDigest string     `json:"config_digest"`
	Pins         BundlePins `json:"pins"`
	Provenance   Provenance `json:"provenance"`
	Shape        Shape      `json:"shape"`
	// Objects describes Shape.Objects for reading — a summary line and a
	// digest per section of each ([ObjectSummary]) — so two bundles can say
	// what changed between them without either's manifests. Beside the shape
	// rather than in it: the shape is bounded and read on every Live page,
	// these are read on a release page. Empty on a bundle built before it.
	Objects []ObjectSummary `json:"objects,omitempty"`
	// ClusterPaths is one entry per path the manifest layer actually
	// carries: the cluster, and the path under which its documents live.
	//
	// THE BUNDLE DESCRIBES ITS OWN LAYOUT so the reconciler's operator
	// builds one Kustomization per cluster without re-deriving anything.
	// It could compute the paths from Shape.Clusters and
	// [BundleClusterPath], and that is exactly the coupling this removes:
	// Shape.Clusters is the env's DECLARED cluster list, while these are
	// the paths the layer HOLDS, and the two differ whenever a declared
	// cluster ends up with no documents routed to it. A Kustomization
	// built for the declared-but-empty cluster points at a path that does
	// not exist, which reconciles green over an env that was never
	// applied.
	//
	// So it is written from the tar entries themselves, after they are
	// written, and a reader can trust it the way it trusts the digest.
	ClusterPaths []BundleClusterTree `json:"cluster_paths,omitempty"`
	// Trees is the v2 layout: one entry per cluster × delivery unit × phase,
	// for every cluster that is not [FlatCluster]. Each carries its
	// dependsOn, its per-attempt timeout and what its health is judged on,
	// so a reconciler builds one Kustomization per entry without deriving
	// anything. A cluster listed here has a [BundleClusterTree] too (its
	// Documents is the sum of its trees'); a flat cluster has no entry.
	// Sorted by Path.
	Trees []BundleTree `json:"trees,omitempty"`
	// Secrets names the Secret objects the env requires in each cluster.
	// NAMES ONLY: a Secret never rides the manifest layer, not even
	// redacted, so the reconciler neither writes nor prunes one. `forge env
	// deploy` syncs them to the cluster itself, before pointing the
	// reconciler at this bundle; a hub-reconciled env reads this list to
	// know what its clusters must already hold.
	Secrets   []BundleSecretRef `json:"secrets,omitempty"`
	CreatedAt time.Time         `json:"created_at"`
}

BundleDoc is the config blob of a bundle: what one env's render, from one checkout, pinned to one release, IS — without its manifests. It lives inside the content-addressed bundle, so the bundle digest covers it, and a backend that records a bundle reads the shape and provenance out of these verified bytes rather than trusting a request field that describes them.

func DecodeBundleDoc

func DecodeBundleDoc(data []byte) (BundleDoc, error)

DecodeBundleDoc strictly decodes a bundle config blob: unknown fields and a foreign schema are refused rather than ignored, because a backend records what it decoded and must not record a summary of a document it only half understood.

func (BundleDoc) Validate

func (d BundleDoc) Validate() error

Validate checks the document. It is the check a backend runs on bytes it was handed, so it is strict about everything it can be strict about.

type BundleHealthCheck added in v0.1.46

type BundleHealthCheck struct {
	Kind      string `json:"kind"`
	Namespace string `json:"namespace,omitempty"`
	Name      string `json:"name"`
}

BundleHealthCheck is one object a tree's readiness is judged on.

type BundlePins

type BundlePins struct {
	Images  map[string]string `json:"images,omitempty"`
	Sources map[string]Source `json:"sources,omitempty"`
}

BundlePins is the pin set a bundle was rendered with: the same two halves a promotion freezes.

type BundleReader added in v0.1.46

type BundleReader struct {
	Features []string
}

BundleReader is what the backend a bundle is recorded in decodes: the optional document features it named. A nil *BundleReader is this forge's own decoder — every feature — which is what a machine ledger reads with.

func (*BundleReader) Limit added in v0.1.46

func (r *BundleReader) Limit(d BundleDoc) BundleDoc

Limit returns d without every optional part the reader does not decode. The parts are omitted entirely (their fields are `omitempty`), never sent empty: an empty key is still an unknown key to a decoder that predates it.

func (*BundleReader) LimitShape added in v0.1.46

func (r *BundleReader) LimitShape(s Shape) Shape

LimitShape returns s without every optional part the reader does not decode. Limit applies it to a bundle document's shape; the environment declaration, which a control plane strict-decodes into the same Shape, applies it to the shape it sends. A copy: s is not modified.

func (*BundleReader) Supports added in v0.1.46

func (r *BundleReader) Supports(feature string) bool

Supports reports whether the reader decodes feature. A nil reader decodes every feature this forge writes.

type BundleRecord

type BundleRecord struct {
	Snapshot       *Snapshot `json:"snapshot,omitempty"`
	SnapshotDigest string    `json:"snapshot_digest,omitempty"`
	ID             string    `json:"id"`
	Env            string    `json:"env"`
	// Release is the version pinned, "" for an unreleased bundle.
	Release string `json:"release,omitempty"`
	// Digest is the OCI MANIFEST digest: the bundle's identity, and the
	// revision a reconciler reports.
	//
	// THIS IS THE VALUE THE CONTROL PLANE WRITES TO THE HUB OCIRepository'S
	// `spec.ref.digest`, and therefore the value a convergence read compares
	// `Kustomization.status.lastAppliedRevision` against. Measured
	// read-only on the dev hub, the chain is one unchanged string:
	//
	//	spec.ref.digest  ==  status.artifact.revision  ==  lastAppliedRevision
	//	sha256:<64 hex>      sha256:<64 hex>              sha256:<64 hex>
	//
	// and this field is produced in exactly that form ([ValidDigest]
	// enforces it). So the comparison is a plain string equality, which is
	// what control-plane's fluxobserve.judge performs — no normalization,
	// no tag prefix, nothing to strip on either side.
	//
	// It is the MANIFEST digest rather than the manifests LAYER's, and the
	// distinction is the one thing to get right when the hub moves from
	// per-deployment config artifacts to `bundle.v1/<env>`: a bundle's
	// identity, what a promotion names, and what Flux resolves `ref.digest`
	// to are all this one value, while the layer digest is an internal
	// detail of the artifact that nothing outside the bundle addresses.
	// Selecting a layer with `layerSelector` changes which bytes land in the
	// artifact, not which revision the source reports.
	//
	// That last sentence is reasoned from Flux's model (the revision is the
	// resolved manifest digest, fixed before layer extraction) and NOT
	// measured: no OCIRepository on the dev hub sets a layerSelector today,
	// and confirming it needs a cluster that does. It is worth one assertion
	// in the first integration test that points a Kustomization at a bundle
	// — if it were ever false, every env would read as permanently
	// progressing while being perfectly converged.
	Digest string `json:"digest"`
	// Reference is repo@digest, or an OCI-layout reference for a file
	// ledger.
	Reference    string `json:"reference"`
	ConfigDigest string `json:"config_digest"`
	Shape        Shape  `json:"shape"`
	// Objects is the document's [BundleDoc.Objects], kept with the record so
	// a release comparison reads it without fetching the bundle.
	Objects      []ObjectSummary `json:"objects,omitempty"`
	Provenance   Provenance      `json:"provenance"`
	Run          Run             `json:"run,omitempty"`
	CreatedBy    string          `json:"created_by,omitempty"`
	ImportedFrom string          `json:"imported_from,omitempty"`
	CreatedAt    time.Time       `json:"created_at"`
}

BundleRecord is a bundle as a LEDGER holds it: the document's indexable half plus where the bytes are and who recorded them. Immutable.

type BundleSecretRef

type BundleSecretRef struct {
	// Cluster is the kubectl context the Secret belongs in, or "" when the
	// render attributed it to no cluster.
	Cluster   string `json:"cluster,omitempty"`
	Namespace string `json:"namespace,omitempty"`
	Name      string `json:"name"`
}

BundleSecretRef is one Secret the env requires: where it lives and what it is called. It carries no value and no key names.

type BundleTree added in v0.1.46

type BundleTree struct {
	Cluster string `json:"cluster"`
	Unit    string `json:"unit"`
	Phase   Phase  `json:"phase"`
	// Path is [BundleTreePath]'s answer: Kustomization.spec.path, and the
	// tree's identity within the bundle.
	Path      string `json:"path"`
	Documents int    `json:"documents"`
	// DependsOn lists the Paths of the trees that must be Ready first, in
	// this bundle. Sorted.
	DependsOn []string `json:"depends_on,omitempty"`
	// TimeoutSeconds and RetryIntervalSeconds are the per-attempt Flux
	// settings ([TreeTimeoutSeconds], [TreeRetryIntervalSeconds]).
	TimeoutSeconds       int `json:"timeout_seconds"`
	RetryIntervalSeconds int `json:"retry_interval_seconds"`
	// Health is what the tree is ready on: the Jobs and awaited ExternalSecrets
	// of a pre or post tree, the Deployments and StatefulSets of a rollout tree.
	// A DaemonSet is not listed: a node-level rollout (an image prepull) is not
	// a gate, which is also what direct apply waits on. Sorted.
	Health []BundleHealthCheck `json:"health,omitempty"`
	// BudgetSeconds is how long the control plane lets this tree stay
	// "progressing" before it calls it failed. Flux's own wait is one short
	// attempt ([TreeTimeoutSeconds]); this is the whole-rollout allowance the
	// observer enforces instead (design §8.3), so a long rollout is never a
	// false alarm and a stuck one is never forgiven forever. It is derived
	// from the objects the tree is judged on ([TreeBudget]), recorded here so
	// the observer reads it rather than re-deriving it.
	BudgetSeconds int `json:"budget_seconds"`
}

BundleTree is one cluster × unit × phase tree in a v2 manifests layer. It is everything a reconciler needs to build ONE Kustomization: where to read, what to wait for, and what to judge health on.

func PlanTrees added in v0.1.46

func PlanTrees(objects []TreeObject, units []DeliveryUnit) ([]BundleTree, error)

PlanTrees groups objects into trees and wires their dependencies. The result is a pure function of its inputs, sorted by Path, with every list sorted: the same render is the same trees, byte for byte.

Flat clusters (FlatCluster) are not trees and must not be passed.

type CaptureOptions

type CaptureOptions struct {
	// ForgeVersion is the rendering binary's version.
	ForgeVersion string
	// WorktreeKey is forge's devstack key for the checkout ("" = primary).
	WorktreeKey string
	// Host names the machine (a daemon id, or HostID()). Never a raw
	// hostname.
	Host string
	// Attestation is the CI identity, when there is one.
	Attestation *Attest
}

CaptureOptions are the facts CaptureProvenance cannot learn from git.

type Changes

type Changes struct {
	// Paths are the checkout's own uncommitted changes — tracked edits,
	// deletions, and untracked-unignored files — relative to the top of the
	// checkout, in git's order.
	Paths []string
	// Nested are untracked directories that are the root of ANOTHER git
	// checkout (a parked worktree, a cloned repo), with git's trailing slash.
	// They are not this checkout's content and are not in Paths.
	Nested []string
}

Changes is what `git status` reports for a checkout, split by whether it is this checkout's own content.

func InspectCheckout

func InspectCheckout(ctx context.Context, top string) (Changes, error)

InspectCheckout reads `git status` for the checkout whose top is top.

A NESTED GIT CHECKOUT IS NOT DIRT. An untracked directory with its own .git is another repository (agents park linked worktrees inside the project directory), and the status of the repository around it says nothing about the bytes this checkout would build. Counting it made a clean checkout "dirty", which cut and built a whole new release for an unchanged tree. Every other untracked file still counts.

func InspectDir

func InspectDir(ctx context.Context, dir string) (Changes, error)

InspectDir is InspectCheckout for any directory inside a checkout. A directory that is not in a git checkout has no changes and is not an error, as in CaptureProvenance.

func (Changes) Dirty

func (c Changes) Dirty() bool

Dirty reports whether the checkout has changes of its own.

type ChartChange added in v0.1.46

type ChartChange struct {
	// Chart is the forge.HelmChart name that owns the object.
	Chart string
	// Key names the object; Key.Cluster is the kubectl context it lives in.
	Key ObjectKey
	Op  ChartOp
	// Fields are the changed field paths of a ChartChanged object.
	Fields []string
	// Fingerprint is the hash of the desired document (for a removal, of the
	// chart and object). Digested, so an approval names this exact change.
	Fingerprint string
	// LoadBalancer marks a pruned Service holding a load balancer: its
	// external address goes with it.
	LoadBalancer bool
}

ChartChange is one chart-owned object a deploy adds, changes or prunes.

type ChartOp added in v0.1.46

type ChartOp string

ChartOp is how a deploy changes one chart-owned object.

const (
	ChartAdded   ChartOp = "added"
	ChartChanged ChartOp = "changed"
	// ChartRemoved is a chart prune: the object left the chart's render and
	// the deploy deletes it after the chart applies.
	ChartRemoved ChartOp = "removed"
)

type ChartPlan added in v0.1.46

type ChartPlan struct {
	Changes   []ChartChange
	Unplanned []ChartUnplanned
	// Pruned are the env's own objects the deploy deletes after its rollout
	// (the owned-object prune), and Kept the ones it leaves and why. They ride
	// the chart plan because they, too, are effects of the apply that no bundle
	// diff can see: the bundle says an object left the render, not that the
	// deploy deletes it.
	Pruned []OwnedRemoval
	Kept   []OwnedKept
	// PruneUnplanned says why the prune set could not be read, when it could not.
	PruneUnplanned string
}

ChartPlan is the observed effect of an env's charts.

type ChartUnplanned added in v0.1.46

type ChartUnplanned struct {
	Chart  string
	Reason string
}

ChartUnplanned is a chart whose changes could not be computed. It is a finding, never silence: omitting a chart the plan could not read would read as "the chart does not change".

type ClosureError added in v0.1.46

type ClosureError struct {
	Required []ConsumerKey
	Reasons  []string
}

ClosureError refuses implicit selector widening. Required is the additional explicit selection; Reasons are deterministic and suitable for native plans.

func (*ClosureError) Error added in v0.1.46

func (e *ClosureError) Error() string

func (*ClosureError) Unwrap added in v0.1.46

func (e *ClosureError) Unwrap() error

type Commit added in v0.1.46

type Commit struct {
	// SHA is the full object id.
	SHA string `json:"sha"`
	// Subject is the first line of the message, bounded at
	// [MaxCommitSubjectBytes].
	Subject string `json:"subject"`
	// Author is the author's name. Never an email: the ledger is shown to
	// every member of an org.
	Author string `json:"author,omitempty"`
	// Time is the committer date, UTC — the same clock as
	// [Provenance.CommitTime].
	Time time.Time `json:"time"`
}

Commit is one commit in a recorded range. A claim, like Provenance's Branch: what the cutting checkout's git said.

type CommitComparison added in v0.1.46

type CommitComparison struct {
	State CommitComparisonState `json:"state"`
	// Reverts is true when To is the OLDER release. Commits are then the
	// ones From has and To does not: what moving From → To takes away.
	Reverts bool `json:"reverts,omitempty"`
	// Commits are newest first, at most MaxComparedCommits, never nil.
	Commits []Commit `json:"commits"`
	// Total is the number of commits between the two when every composed
	// range could count its own; 0 when one could not.
	Total int `json:"total,omitempty"`
	// Truncated says Commits is not every commit between the two.
	Truncated bool `json:"truncated,omitempty"`
	// Releases are the versions whose recorded ranges were composed, newest
	// first.
	Releases []string `json:"releases,omitempty"`
}

CommitComparison is the source history between two releases.

func CompareCommits added in v0.1.46

func CompareCommits(from, to Release, releases []Release) CommitComparison

CompareCommits composes the commits between from and to out of the ranges recorded on releases (the project's ledger; from and to need not be in it).

type CommitComparisonState added in v0.1.46

type CommitComparisonState string

CommitComparisonState says how a CommitComparison was assembled. Check it before reading Commits: an empty list means "nothing between them" only when the state is computed.

const (
	// CommitsComputed: the later release's range was walked back, base by
	// base, to the earlier one.
	CommitsComputed CommitComparisonState = "computed"
	// CommitsSame: the two are one release.
	CommitsSame CommitComparisonState = "same"
	// CommitsByCutTime: no base chain joins the two (a release in between
	// was cut from a shallow clone, or before ranges were recorded), so the
	// commits are those recorded by the releases cut between them on the
	// later one's branch. Approximate, and marked Truncated when any of
	// those ranges was.
	CommitsByCutTime CommitComparisonState = "by_cut_time"
	// CommitsUnrecorded: no release between them recorded any commits.
	CommitsUnrecorded CommitComparisonState = "unrecorded"
)

type CommitRange added in v0.1.46

type CommitRange struct {
	// BaseRelease is the earlier release this range starts after: the newest
	// release of the project, by cut time, whose clean commit is an ancestor
	// of this release's. BaseCommit is that release's commit. Both are empty
	// when the cutting checkout held no such release in its history — the
	// project's first release, or a shallow clone ([CommitRange.Truncated]).
	BaseRelease string `json:"base_release,omitempty"`
	BaseCommit  string `json:"base_commit,omitempty"`
	// Commits are reachable from the release's commit and not from
	// BaseCommit, newest first, at most [MaxRangeCommits]. Empty, never nil,
	// when the release re-cuts its base's commit.
	Commits []Commit `json:"commits"`
	// Total is how many commits the range holds. It equals len(Commits)
	// unless Truncated, and is 0 when Truncated and the count is unknown.
	Total int `json:"total,omitempty"`
	// Truncated says Commits is not the whole range. Either the range is
	// longer than [MaxRangeCommits] (Total says how long), or the checkout's
	// history ended before the base was reached — a shallow clone, which is
	// how CI checks out by default — so the rest, and the count, are
	// unknowable (Total is 0).
	Truncated bool `json:"truncated,omitempty"`
}

CommitRange is the source history a release adds over the release it was cut after.

func CaptureCommitRange added in v0.1.46

func CaptureCommitRange(ctx context.Context, dir, head string, earlier []Release) (*CommitRange, error)

CaptureCommitRange records the history head adds over the newest of earlier whose commit is an ancestor of it, read from the git checkout around dir.

earlier is the project's other releases; dirty ones and ones with no commit are never a base (their commit does not describe their bytes). The release being cut must not be among them.

It returns (nil, nil) when there is nothing to record: head is not a commit, or dir is not a git checkout. An error means git failed or ran out of CaptureRangeTimeout; a cut records no range then, and should say so.

func (CommitRange) CountKnown added in v0.1.46

func (r CommitRange) CountKnown() bool

CountKnown reports whether Total is the range's real length.

func (CommitRange) Validate added in v0.1.46

func (r CommitRange) Validate() error

Validate checks the range's shape. Like Provenance.Validate, it cannot check the claims' truth.

type CompatibilityEvidence added in v0.1.46

type CompatibilityEvidence struct {
	Consumer          ConsumerKey `json:"consumer"`
	Peer              ConsumerKey `json:"peer"`
	Contract          string      `json:"contract"`
	BaseConsumer      string      `json:"base_consumer"`
	CandidateConsumer string      `json:"candidate_consumer"`
	BasePeer          string      `json:"base_peer"`
	CandidatePeer     string      `json:"candidate_peer"`
	PolicyDigest      string      `json:"policy_digest"`
	Validator         string      `json:"validator"`
	ReportDigest      string      `json:"report_digest"`
}

CompatibilityEvidence permits one peer to remain carried. The four subject digests are ConsumerDigest outputs, binding evidence to exact old/new bytes. ReportDigest is an immutable validator report reference, not a boolean claim.

type CompositionDecision added in v0.1.46

type CompositionDecision struct {
	Consumer ConsumerKey `json:"consumer"`
	Action   string      `json:"action"` // build, reuse, carry
	Reason   string      `json:"reason"`
}

type CompositionInput added in v0.1.46

type CompositionInput struct {
	Base      *Snapshot
	Candidate Snapshot
	BaseRef   *SnapshotBase
	Selected  []ConsumerKey
}

Input is captured before build and never automatically rebased. Candidate is an isolated full inventory; unselected candidate drift is never deployed.

type Consumer added in v0.1.46

type Consumer struct {
	// False is unknown compatibility, not independent release permission.
	DependenciesDeclared bool                 `json:"dependencies_declared"`
	Key                  ConsumerKey          `json:"key"`
	Slots                []string             `json:"slots"`
	DependsOn            []ConsumerDependency `json:"depends_on,omitempty"`
}

Consumer declares all artifact slots and conservative peer requirements. Slot spellings are container:NAME, init:NAME, frontend, build-only, or input:NAME. Names, never container list positions, identify a slot.

type ConsumerBinding added in v0.1.46

type ConsumerBinding struct {
	Consumer ConsumerKey `json:"consumer"`
	Slot     string      `json:"slot"`
	Artifact ArtifactID  `json:"artifact"`
	// Tag is the OCI tag the consumer DECLARED for an external image
	// (`repo:tag@sha256:X` keeps `tag`), and empty for a built image. It is
	// part of the rendered reference, never of the artifact's identity: the
	// bytes are Location@Content either way. Keeping it makes sealing a
	// declared, already-pinned reference byte-identical to what the workload
	// ran before, so sealing alone changes no pod template and rolls nothing.
	Tag string `json:"tag,omitempty"`
}

type ConsumerDependency added in v0.1.46

type ConsumerDependency struct {
	Consumer ConsumerKey `json:"consumer"`
	Contract string      `json:"contract"`
}

type ConsumerKey added in v0.1.46

type ConsumerKey struct {
	Cluster  string `json:"cluster"`
	Workload string `json:"workload"`
}

ConsumerKey is environment-relative; project/environment are on Snapshot. Workload is a declared stable name, including non-Kubernetes consumers.

type DBQueryPolicy added in v1.0.0

type DBQueryPolicy string

DBQueryPolicy is what an env's declaration says about reading its database through `forge db query` (forge.DataAccess.db_query in KCL).

It is how the control plane learns the rule to enforce: the flag on the CLI is the client-side interface, and the enforcement reads this from the env's RECORDED shape, so a client cannot choose a weaker policy than the env declared.

const (
	// DBQueryOpen: anyone with the env may query, with no confirmation. The
	// default for a local or ephemeral env, whose data is scratch.
	DBQueryOpen DBQueryPolicy = "open"
	// DBQueryAudited: only a person holding the db:query grant, naming the env
	// and a reason, and every call is recorded. The default for every other
	// env: reading real data is allowed, and leaves a trail.
	DBQueryAudited DBQueryPolicy = "audited"
	// DBQueryDeny: nobody.
	DBQueryDeny DBQueryPolicy = "deny"
)

type DeliveryMode added in v0.1.46

type DeliveryMode string

DeliveryMode is how one delivery control behaves.

const (
	// DeliveryOff does nothing.
	DeliveryOff DeliveryMode = "off"
	// DeliveryAudit records and alerts, and never blocks.
	DeliveryAudit DeliveryMode = "audit"
	// DeliveryEnforce blocks.
	DeliveryEnforce DeliveryMode = "enforce"
)

func (DeliveryMode) Valid added in v0.1.46

func (m DeliveryMode) Valid() bool

Valid reports whether m is one of the three modes.

type DeliveryUnit added in v0.1.46

type DeliveryUnit struct {
	Name string `json:"name"`
	// After names the units whose trees must be Ready before this unit's
	// first tree applies. nil means "not declared" (the default applies);
	// a non-nil empty slice means "waits for nothing".
	After []string `json:"after"`
}

DeliveryUnit declares one delivery unit's place in the apply order.

type DriftObservation

type DriftObservation struct {
	Drifted []ObjectKey
}

DriftObservation is what an observer saw of the live cluster.

type EnvKind

type EnvKind string

EnvKind is how an environment runs, and therefore who applies it and whether its secrets can be read back. Closed. It is a PREDICATE over an env's rendered declaration — forge derives it, nobody selects it — and a backend treats it as immutable once recorded: an env whose kind changed is a different env.

const (
	// EnvPersistent: the platform runs it (something is bound OnHosted).
	// A converger applies its promotions; its secrets are write-only.
	EnvPersistent EnvKind = "persistent"
	// EnvPreview: a persistent env with a TTL.
	EnvPreview EnvKind = "preview"
	// EnvSelfManaged: a control plane keeps its ledger, nothing is hosted,
	// and something runs on a cluster the author operates. forge applies
	// it; the platform places nothing. Its secrets are write-only, which is
	// the point of separating it from EnvLocal: a cluster env's secrets must
	// never be pullable.
	EnvSelfManaged EnvKind = "self_managed"
	// EnvLocal: every workload runs on a developer machine (`forge env
	// up`). Never a deploy target; its secrets are readable back.
	EnvLocal EnvKind = "local"
)

func ParseEnvKind

func ParseEnvKind(s string) (EnvKind, error)

ParseEnvKind reads a stored or wire kind, refusing anything outside the set.

func (EnvKind) Placed

func (k EnvKind) Placed() bool

Placed reports whether the platform holds placement (a namespace on its fleet) for an env of this kind, and so converges and observes it.

func (EnvKind) Promotable

func (k EnvKind) Promotable() bool

Promotable reports whether an env of this kind can be bound to a release. Everything but LOCAL: a self-managed env IS promoted; it is just applied by forge rather than converged by the platform.

func (*EnvKind) UnmarshalJSON

func (k *EnvKind) UnmarshalJSON(data []byte) error

UnmarshalJSON refuses an empty or unknown kind.

func (EnvKind) Valid

func (k EnvKind) Valid() bool

Valid reports whether k is one of EnvKinds.

type EnvRecord

type EnvRecord struct {
	Name          string      `json:"name"`
	Kind          EnvKind     `json:"kind"`
	DeclaredShape *Shape      `json:"declared_shape,omitempty"`
	DeclaredBy    *Provenance `json:"declared_by,omitempty"`
	DeclaredAt    *time.Time  `json:"declared_at,omitempty"`
}

EnvRecord is an environment as a ledger holds it: its identity, its kind, and the most recently DECLARED shape — what its KCL said the last time someone built it. DeclaredShape is what lets a reader answer "what kind, which secrets, which provider" without rendering anything, which is what makes the Live view work with no daemon.

func (EnvRecord) Validate

func (r EnvRecord) Validate() error

Validate checks the record and, when present, its declared shape.

The record's kind and the shape's are NOT required to agree any more. Placement is per workload (docs/design/per-workload-placement.md): an env recorded self-managed that gains a hosted frontend declares a shape a placement-derived kind calls persistent, and it is the same env. Both kinds are retired with the control plane's kind column.

type ExternalArtifact added in v0.1.46

type ExternalArtifact struct {
	Verifier         string `json:"verifier"`
	DescriptorDigest string `json:"descriptor_digest"`
}

ArtifactOrigin survives carry-forward verbatim. Receipt identifies the actual effective build inputs, not merely the command's working directory. ExternalArtifact records registry verification rather than inventing build provenance for third-party bytes. DescriptorDigest must match OCI Content; the resolver verifies descriptor/platform bytes before issuing this record.

type FindingClass

type FindingClass string

FindingClass is how much a finding gates a deploy. Closed.

const (
	// ClassInfo: a change the operator should see. Approval covers it.
	ClassInfo FindingClass = "info"
	// ClassWarn: something to read before approving — a missing secret,
	// drift the deploy will overwrite, a section that could not be
	// computed. Approval covers it.
	ClassWarn FindingClass = "warn"
	// ClassStop: a change a general approval must NOT cover — a stateful
	// deletion, a load balancer's identity changing. It needs its own
	// acknowledgement BY CODE, even with --yes, because --yes is the flag
	// that ends up hard-coded in CI and would otherwise pre-approve every
	// future destructive change.
	ClassStop FindingClass = "stop"
)

func (*FindingClass) UnmarshalJSON

func (c *FindingClass) UnmarshalJSON(data []byte) error

UnmarshalJSON refuses an unknown class.

func (FindingClass) Valid

func (c FindingClass) Valid() bool

Valid reports whether c is one of FindingClasses.

type FluxFinding added in v0.1.46

type FluxFinding struct {
	Rule      string
	Kind      string
	Namespace string
	Name      string
	// Unit is the delivery unit the object is in ([DefaultDeliveryUnit] when it
	// declares none), so a caller that judges only the units a reconciler
	// applies can tell which findings are its own.
	Unit   string
	Reason string
	Fix    string
}

FluxFinding is one object that is not safe under a reconciler.

func CheckFluxSafety added in v0.1.46

func CheckFluxSafety(docs []any) []FluxFinding

CheckFluxSafety returns every finding in a decoded document set, sorted. Each document is a decoded Kubernetes object (map[string]any); anything else is skipped, because parsing is the caller's job and it has refused those.

func (FluxFinding) String added in v0.1.46

func (f FluxFinding) String() string

type Gate

type Gate struct {
	// Name is the check: "lint", "test", "smoke", "rollout", "qa-signoff".
	Name string `json:"name"`
	// Status is the verdict, from the closed set.
	Status GateStatus `json:"status"`
	// RawStatus is the stored status when it was NOT in the closed set —
	// free text from a ledger written before the set closed. Empty on
	// every gate whose status is recognised, so a non-empty value is
	// exactly the signal "this entry predates the vocabulary".
	// Set only by the READ path; never sent.
	RawStatus string `json:"raw_status,omitempty"`
	// URL is where the full report lives (a CI run, an artifact).
	URL string `json:"url,omitempty"`
	// Summary is one line: "412 passed, 0 failed, 3 skipped".
	Summary string `json:"summary,omitempty"`
	// StartedAt and FinishedAt are the check's own window, not when it was
	// recorded. They are what makes a gate a run stage with a duration.
	StartedAt  *time.Time `json:"started_at,omitempty"`
	FinishedAt *time.Time `json:"finished_at,omitempty"`
	// RunID ties this check to one [Run]. Optional: a manual sign-off
	// belongs to a promotion and to no run.
	RunID string `json:"run_id,omitempty"`
	// Details is small, structured and optional — counts and verdicts, not
	// logs. A hosted backend caps it (8 KiB); the full report is behind
	// URL.
	Details map[string]any `json:"details,omitempty"`
	// RecordedBy and RecordedAt are SET BY THE BACKEND that recorded the
	// gate, from the authenticated principal — a user id, or
	// "token:<token-id>". Never accepted from a request, because evidence
	// whose author the author chose is not attributable. A file ledger
	// leaves them empty: it has no principal to attest to.
	RecordedBy string     `json:"recorded_by,omitempty"`
	RecordedAt *time.Time `json:"recorded_at,omitempty"`
}

Gate is one check's result, attached to a promotion. EVIDENCE, not enforcement: a gate that must block a promotion is checked BEFORE the entry is written, because a recorded claim proves only that it was made, and by whom.

A gate carries timing, so a gate IS a run stage (see Stage): everything a run does that is not derivable from the ledger — build, lint, test, smoke, wait — is recorded as one of these.

func (*Gate) UnmarshalJSON

func (g *Gate) UnmarshalJSON(data []byte) error

UnmarshalJSON is the LENIENT read path: a status outside the closed set becomes GateStatusError with the original kept on RawStatus, so a promotion recorded before the vocabulary existed still reads back. Use Gate.Validate to refuse writing one.

func (Gate) Validate

func (g Gate) Validate() error

Validate is the WRITE path. It refuses a gate that cannot be recorded: no name, a status outside the closed set, a finish before its start, or a status that only survived a read because it was mapped.

type GateStatus

type GateStatus string

GateStatus is the verdict a check reported. CLOSED: see the package doc.

There are two parse paths on purpose, because a status arrives from two very different places:

  • ParseGateStatus is the WRITE path — a --gate flag, an RPC request. It refuses anything outside the set, so a typo fails before the entry is recorded.
  • GateStatusFromStored is the READ path — a ledger line, a database row. Entries written before the set was closed carry free text, and refusing to read them would make old promotions unrenderable. They map to GateStatusError and the raw value is kept verbatim on Gate.RawStatus.
const (
	// GateStatusPassed: the check ran and found nothing wrong.
	GateStatusPassed GateStatus = "passed"
	// GateStatusFailed: the check ran and found something wrong. This is a
	// VERDICT, not an error — the check itself worked.
	GateStatusFailed GateStatus = "failed"
	// GateStatusSkipped: the check did not apply. A hosted smoke with no
	// URL-bearing workload is skipped, never passed: a green check that
	// checked nothing is the failure mode this value exists to prevent.
	GateStatusSkipped GateStatus = "skipped"
	// GateStatusError: the check could not reach a verdict — it crashed,
	// timed out, or could not look. Distinct from failed for the same
	// reason exit code 2 is distinct from exit code 1.
	GateStatusError GateStatus = "error"
)

func GateStatusFromStored

func GateStatusFromStored(stored string) (status GateStatus, raw string)

GateStatusFromStored reads a STORED status. It returns the status to display and, when the stored value was NOT in the closed set, that value verbatim so nothing is lost. An unrecognised status displays as GateStatusError: a claim nobody can interpret must not render as a pass.

An EMPTY stored status is not mapped. Absent is absent, not an error verdict: a gate that recorded no status made no claim, and inventing one for it would turn a hole in the data into a reported outcome. It comes back empty, which Gate.Validate refuses — the same rule as a missing artifact kind.

func ParseGateStatus

func ParseGateStatus(s string) (GateStatus, error)

ParseGateStatus reads a status a CALLER supplied — a `--gate` flag, an RPC request field — and refuses anything outside the closed set. Empty is refused too: a check that reported no verdict has not reported.

func (*GateStatus) UnmarshalJSON

func (s *GateStatus) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes through ParseGateStatus. It is STRICT, because a bare GateStatus is only decoded where a caller supplied one; a stored entry is decoded through Gate.UnmarshalJSON, which is lenient.

func (GateStatus) Valid

func (s GateStatus) Valid() bool

Valid reports whether s is one of GateStatuses.

type Git

type Git struct {
	Commit string `json:"commit,omitempty"`
	Tag    string `json:"tag,omitempty"`
	// Dirty means the release was cut from a tree with uncommitted changes:
	// its bytes correspond to no reviewable commit.
	Dirty bool `json:"dirty,omitempty"`
}

Git is a release's source provenance. Best-effort: empty on a non-git tree.

type ImageChange

type ImageChange struct {
	Artifact string `json:"artifact"`
	From     string `json:"from,omitempty"`
	To       string `json:"to,omitempty"`
}

ImageChange is one artifact's digest moving.

type Kind

type Kind string

Kind says WHAT an artifact is, and therefore how it is addressed and verified. Closed: see the package doc.

const (
	// KindOCI is a container image, addressed by digest alone.
	KindOCI Kind = "oci"
	// KindNPM is an npm package, addressed by version + integrity.
	KindNPM Kind = "npm"
	// KindGoModule is a Go module, addressed by version + go.sum hash.
	KindGoModule Kind = "gomod"
	// KindFile is a published file, addressed by version/URI + content hash.
	KindFile Kind = "file"
	// KindGit is source built at deploy time (a forge.GitSource frontend),
	// addressed by the commit its declared ref resolved to.
	KindGit Kind = "git"
)

func (*Kind) UnmarshalJSON

func (k *Kind) UnmarshalJSON(data []byte) error

UnmarshalJSON refuses an empty or unknown kind.

func (Kind) Valid

func (k Kind) Valid() bool

Valid reports whether k is one of Kinds.

type KindChange deprecated

type KindChange struct {
	Live      EnvKind `json:"live"`
	Candidate EnvKind `json:"candidate"`
}

KindChange is a disagreement about an env's kind.

Deprecated: see ShapeDiff.KindChanged.

type LayerObject added in v0.1.46

type LayerObject struct {
	Path       string
	Cluster    string
	Unit       string // "" in the v1 layout
	Phase      string // "" in the v1 layout
	APIVersion string
	Kind       string
	Namespace  string
	Name       string
}

LayerObject is one validated manifest: where it sits and what it declares. The control plane's per-env kind check reads these rather than re-parsing.

type LayerViolation added in v0.1.46

type LayerViolation struct {
	// File is the archive entry's path as written, or "(archive)" for a
	// problem with the stream itself (not gzip, not tar, too large).
	File   string
	Reason string
}

LayerViolation is one refusal, naming the offending file.

func (LayerViolation) String added in v0.1.46

func (v LayerViolation) String() string

type LocalSession

type LocalSession struct {
	// ID is minted by forge and stable across heartbeats.
	ID         string     `json:"id"`
	Env        string     `json:"env"`
	Worktree   Worktree   `json:"worktree"`
	Provenance Provenance `json:"provenance"`
	// BundleDigest names the LOCAL bundle the stack runs; the blob never
	// leaves the machine.
	BundleDigest string     `json:"bundle_digest,omitempty"`
	StartedAt    time.Time  `json:"started_at"`
	LastSeenAt   time.Time  `json:"last_seen_at"`
	StoppedAt    *time.Time `json:"stopped_at,omitempty"`
}

LocalSession is one `forge env up` stack, on one machine, in one checkout: PRESENCE, not ledger. It names no promotion and no release, is mutable (heartbeats), is garbage-collected, and nothing authorizes or bills on it. It exists so Live can show what is running where without asking a daemon.

func (LocalSession) Live

func (s LocalSession) Live() bool

Live reports whether the session has not been stopped.

func (LocalSession) Stale

func (s LocalSession) Stale(now time.Time) bool

Stale reports whether a live session has gone quiet.

func (LocalSession) Validate

func (s LocalSession) Validate() error

Validate checks a session report.

type ManifestsLayerError added in v0.1.46

type ManifestsLayerError struct {
	Violations []LayerViolation
	// Truncated is true when more violations existed than are listed.
	Truncated bool
}

ManifestsLayerError is what ValidateManifestsLayer returns for an invalid layer: every violation found, up to a cap, each naming its file.

func (*ManifestsLayerError) Error added in v0.1.46

func (e *ManifestsLayerError) Error() string

func (*ManifestsLayerError) Is added in v0.1.46

func (e *ManifestsLayerError) Is(target error) bool

Is makes errors.Is(err, ErrInvalidManifestsLayer) hold.

type ManifestsLayerSummary added in v0.1.46

type ManifestsLayerSummary struct {
	// Files counts every regular file: manifests plus data artifacts.
	Files int
	// Bytes is the sum of the files' bytes, uncompressed.
	Bytes int64
	// Objects is every manifest, sorted by Path.
	Objects []LayerObject
	// Artifacts is every non-manifest data file (observability JSON),
	// sorted. Nothing applies them.
	Artifacts []string
}

ManifestsLayerSummary describes a layer that passed.

func ValidateManifestsLayer added in v0.1.46

func ValidateManifestsLayer(r io.Reader) (ManifestsLayerSummary, error)

ValidateManifestsLayer validates a bundle's manifests layer: a gzipped tar read from r (the blob of media type BundleManifestsLayer).

It enforces, over the bytes actually read rather than anything the archive declares:

  • the layout grammar in this file's header;
  • entry types: regular files and directories only — no symlink, hardlink, device, fifo or global header;
  • no `..` or `.` segment, no absolute path, no backslash, no duplicate;
  • no kustomization file at any depth (`kustomization.yaml`, `.yml`, `Kustomization`, any case), and no manifest of apiVersion `kustomize.config.k8s.io/*`;
  • `.yaml` for manifests, and only the observability JSON for data;
  • per-file, total, file-count, path-length and decompressed-stream bounds;
  • strict YAML: exactly one document per file, no anchors, aliases, merge keys or non-core tags, no duplicate keys, a nesting limit;
  • every manifest carries a string apiVersion, kind and metadata.name.

A nil error means the layer is safe to hand to Flux. Otherwise the error is a *ManifestsLayerError naming every offending file it found (up to a cap). Kind scope — which kinds an env may carry — is deliberately NOT decided here: it depends on the env, and the summary's Objects hand it to the caller.

type MigrationManifest added in v1.0.0

type MigrationManifest struct {
	// Sets is one entry per migrations directory, sorted by Source.
	Sets []MigrationSet `json:"sets"`
}

MigrationManifest is every migration directory a release's artifacts embed, each as the files it held at the cut.

func (MigrationManifest) Equal added in v1.0.0

func (m MigrationManifest) Equal(other MigrationManifest) bool

Equal reports whether two manifests record the same files under the same sources. Commit is a claim about where the files were read, not part of what was recorded, and is ignored.

func (*MigrationManifest) Normalize added in v1.0.0

func (m *MigrationManifest) Normalize()

Normalize sorts the sets by Source and each set's files by version, so two manifests describing the same history compare equal.

func (MigrationManifest) Validate added in v1.0.0

func (m MigrationManifest) Validate() error

Validate checks the bounds and that every recorded name is a forward migration with a version. Duplicate VERSIONS within a set are allowed: two files claiming one version is a defect the lint reports, and the manifest must still be able to describe a release that carries it.

type MigrationSet added in v1.0.0

type MigrationSet struct {
	// Source identifies the directory across every machine and every
	// release: the canonical repository and the directory's path within it,
	// e.g. "github.com/reliant-labs/reliant/db/migrations". Two releases'
	// sets are compared only when their Sources are equal, because a
	// migration's version is meaningful only within its own directory —
	// two projects' databases have unrelated schema histories.
	Source string `json:"source"`
	// Commit is the commit the files were read at: the release's own commit
	// for the project's directory, the sibling checkout's for a sibling's.
	// Empty when the bytes were no commit's (a dirty checkout). A claim, for
	// the reader; the check never consults it.
	Commit string `json:"commit,omitempty"`
	// Files are the forward migrations' base names
	// ("20261024035148_hosted_http_action_tickets.up.sql"), sorted by
	// version then name. A name, not only a version, because what a database
	// recorded is one specific file: two branches can claim one version.
	Files []string `json:"files"`
}

MigrationSet is one migrations directory as a release carried it.

func NewMigrationSet added in v1.0.0

func NewMigrationSet(source, commit string, names []string) MigrationSet

NewMigrationSet builds a set from a directory listing: only forward migrations with a parseable version, sorted. A listing is whatever the caller read (a directory, a git tree); the filter is the one place that decides what counts.

type Mode

type Mode string

Mode says how an artifact's identity is PINNED — a separate axis from Kind. Closed.

const (
	// ModeShared: built once, one identity, promoted byte-identical to
	// every environment. OCI digests live under [SharedVariant].
	ModeShared Mode = "shared"
	// ModeVariant: genuinely different per-environment builds (a
	// NEXT_PUBLIC_* baked at build time). Digests is keyed by variant name.
	ModeVariant Mode = "variant"
	// ModeSource: built from a pinned commit at deploy time; the identity
	// is [Artifact.Source]. Only [KindGit] uses it.
	ModeSource Mode = "source"
)

func (*Mode) UnmarshalJSON

func (m *Mode) UnmarshalJSON(data []byte) error

UnmarshalJSON refuses an empty or unknown mode.

func (Mode) Valid

func (m Mode) Valid() bool

Valid reports whether m is one of Modes.

type ObjectChange

type ObjectChange struct {
	Live      ShapeObject `json:"live"`
	Candidate ShapeObject `json:"candidate"`
	// ConfigChanged: the non-image half of the object changed. When either
	// side lacks a ConfigHash the split cannot be made, and ConfigChanged
	// is true whenever the hashes differ (the conservative reading).
	ConfigChanged bool `json:"config_changed"`
	// Images are the release-bound images whose pinned digest changed.
	Images []ImageChange `json:"images,omitempty"`
}

ObjectChange is one object present on both sides whose hash differs.

type ObjectChangeKind added in v0.1.46

type ObjectChangeKind string

ObjectChangeKind is how one object differs between two bundles.

const (
	ObjectAdded    ObjectChangeKind = "added"
	ObjectModified ObjectChangeKind = "modified"
	ObjectRemoved  ObjectChangeKind = "removed"
)

type ObjectComparison added in v0.1.46

type ObjectComparison struct {
	Deltas    []ObjectDelta `json:"deltas"`
	Unchanged int           `json:"unchanged"`
	// SummariesMissing says at least one bundle carried no object
	// summaries (it was built before they existed), so added, removed and
	// modified objects are named without saying what they are or what in
	// them changed.
	SummariesMissing bool `json:"summaries_missing,omitempty"`
}

ObjectComparison is the object half of a release comparison.

func CompareObjects added in v0.1.46

func CompareObjects(from, to BundleRecord) ObjectComparison

CompareObjects diffs two bundles of one env, object by object: added and removed by key, modified by hash.

type ObjectDelta added in v0.1.46

type ObjectDelta struct {
	Change   ObjectChangeKind `json:"change"`
	Key      ObjectKey        `json:"key"`
	Workload string           `json:"workload,omitempty"`
	// Summary is the one line: the object's own summary when it was added
	// or removed, and what changed ([DescribeObjectChange]) when modified.
	Summary string `json:"summary,omitempty"`
	// Images are the release-bound images whose digest moved in it.
	Images []ImageChange `json:"images,omitempty"`
	// Parts are the sections that moved, when both sides recorded them.
	Parts []PartChange `json:"parts,omitempty"`
}

ObjectDelta is one object two bundles disagree on.

type ObjectKey

type ObjectKey struct {
	Cluster   string `json:"cluster"`
	Kind      string `json:"kind"`
	Namespace string `json:"namespace,omitempty"`
	Name      string `json:"name"`
}

ObjectKey is an object's identity within a shape: (cluster, kind, namespace, name). The API version is deliberately not part of it — moving a Deployment from apps/v1beta2 to apps/v1 is a change to the same object.

func (ObjectKey) String

func (k ObjectKey) String() string

String renders the key as "<cluster>/<kind>/<namespace>/<name>".

type ObjectMove added in v0.1.46

type ObjectMove struct {
	Key  ObjectKey `json:"key"`
	From string    `json:"from"`
	To   string    `json:"to"`
}

ObjectMove is one object changing delivery tree between two shapes. From and To are "<unit>/<phase>"; "" is the v1 flat layout, so a move from "" is the layout migration.

type ObjectSummary added in v0.1.46

type ObjectSummary struct {
	Key ObjectKey `json:"key"`
	// Summary is one value-free line saying what the object declares; ""
	// when there is nothing worth saying beyond its kind and name.
	Summary string `json:"summary,omitempty"`
	// Parts maps a section name to a [PartDigestLen]-hex digest of that
	// section's canonical JSON. Images are excluded: [ShapeObject.Images]
	// already says which artifact digest an object runs.
	Parts map[string]string `json:"parts,omitempty"`
}

ObjectSummary describes one ShapeObject of the same bundle, joined to it by Key.

func BoundObjectSummaries added in v0.1.46

func BoundObjectSummaries(summaries []ObjectSummary) []ObjectSummary

BoundObjectSummaries fits summaries under MaxObjectSummariesBytes: as given when they fit, without Parts when only that fits, and nil otherwise. A reader treats a missing summary as "not recorded", so shedding detail is honest where refusing the bundle would block a deploy over a description.

func (ObjectSummary) Validate added in v0.1.46

func (s ObjectSummary) Validate() error

Validate checks one summary: a bounded single line, and parts whose names are plain identifiers and whose values are digests and nothing else.

type OwnedKept added in v1.0.0

type OwnedKept struct {
	Key ObjectKey
	Why string
}

OwnedKept is an owned object the prune found absent from the render and will NOT delete, with the reason. Informational: it is never digested.

type OwnedRemoval added in v1.0.0

type OwnedRemoval struct {
	Key ObjectKey
	// Fingerprint is the hash of the object's identity and UID, so an approval
	// names this exact object and not a replacement of the same name.
	Fingerprint string
}

OwnedRemoval is an object a deploy deletes because the env stopped rendering it: forge stamped it (forge.dev/owner), the render no longer carries it, and it is an inert kind. Key.Cluster is the kubectl context it lives in.

type PartChange added in v0.1.46

type PartChange struct {
	Part   string           `json:"part"`
	Change ObjectChangeKind `json:"change"`
}

PartChange is one section of an object that moved.

type PassedMigration added in v1.0.0

type PassedMigration struct {
	// Source is the migrations directory (MigrationSet.Source).
	Source string `json:"source"`
	// File and Version are the migration that can never apply.
	File    string `json:"file"`
	Version uint64 `json:"version"`
	// Head is the highest version the environment's applied releases
	// carried from Source; HeadFile names it and HeadRelease the first
	// release in the applied list (newest first, as callers pass it) that
	// carried it.
	Head        uint64 `json:"head"`
	HeadFile    string `json:"head_file"`
	HeadRelease string `json:"head_release"`
}

PassedMigration is one migration a release carries that an environment's schema has already gone past without applying.

func PassedMigrations added in v1.0.0

func PassedMigrations(target MigrationManifest, applied []AppliedManifest) []PassedMigration

PassedMigrations returns every migration in target that an environment which applied the releases in applied can never run: per source, a file NONE of those releases carried, at a version at or below the highest one any of them did.

THE FILE, NOT THE VERSION, decides "already applied" — the rule pkg/migratekit's own check uses, for the same reason: two branches can claim one version, and a database recorded one specific file.

A source no applied release carried is a directory the environment has never migrated from this ledger's point of view, and nothing in it is judged. applied should list only releases whose deploy did not fail: a release whose rollout was refused applied nothing, and counting its files as applied would wave through the very file that refused it. Order matters only to HeadRelease; the result is sorted by source then version.

type Phase added in v0.1.46

type Phase string

Phase places an object relative to the release's workloads.

const (
	// PhasePre is applied, and its Jobs must COMPLETE, before any workload.
	PhasePre Phase = "pre"
	// PhaseRollout is the workloads themselves.
	PhaseRollout Phase = "rollout"
	// PhasePost is applied with the workloads and awaited after their
	// rollout: a Job that needs this release's workloads serving.
	PhasePost Phase = "post"
)

func ClassifyPhase added in v0.1.46

func ClassifyPhase(kind, name string, annotations map[string]string) (Phase, error)

ClassifyPhase is the phase of one object, from its kind and its metadata annotations. name is used only in the error.

An unknown AnnotationDeployPhase value is an error naming the Job and the accepted values; callers refuse before applying anything.

func (Phase) Valid added in v0.1.46

func (p Phase) Valid() bool

Valid reports whether p is one of Phases.

type Plan

type Plan struct {
	Digest string `json:"digest"`
	// BaseDigest is the digest before chart findings were folded in by
	// [Plan.WithCharts]; empty when none were. Never serialized: it exists for
	// the write to a control plane that recomputes the plan without charts.
	BaseDigest     string        `json:"-"`
	EnvironmentID  string        `json:"environment_id"`
	BundleID       string        `json:"bundle_id"`
	ReleaseVersion string        `json:"release_version,omitempty"`
	LiveBasis      PlanBasis     `json:"live_basis"`
	Findings       []PlanFinding `json:"findings"`
	// ConfigIdentical: the bundle's config digest equals the live one, so
	// the deploy changes images at most. A no-op deploy says so instead of
	// manufacturing a diff to approve.
	ConfigIdentical bool      `json:"config_identical"`
	ComputedAt      time.Time `json:"computed_at"`
}

Plan is one reviewed deploy. Field names match controlplane.v1.DeployPlan.

func BuildPlan

func BuildPlan(in PlanInput) (Plan, error)

BuildPlan computes the plan. Pure and deterministic: the same input yields the same findings in the same order and the same digest. ComputedAt is the only clock-dependent field and is left for the caller to set.

func (Plan) StopCodes

func (p Plan) StopCodes() []string

StopCodes is the set of stop-class finding codes in the plan, sorted.

func (Plan) Unacknowledged

func (p Plan) Unacknowledged(acknowledged []string) []string

Unacknowledged returns the stop-class codes the acknowledgements do not name. Empty means the plan may be approved. Acknowledgement is by CODE, so one acknowledgement covers every finding of that code in THIS plan — and, because the digest binds the approval to this plan, nothing beyond it.

func (Plan) VerifyDigest

func (p Plan) VerifyDigest() (bool, error)

VerifyDigest recomputes the digest and reports whether it matches Digest.

func (Plan) WithCharts added in v0.1.46

func (p Plan) WithCharts(c ChartPlan) (Plan, error)

WithCharts returns p with c folded in and the digest recomputed, for a plan computed elsewhere (a control plane's) that cannot see charts. BaseDigest keeps the digest the plan had before, which is the one its computer can recompute and so the one a write must carry.

type PlanBasis

type PlanBasis struct {
	CurrentPromotionID  string `json:"current_promotion_id,omitempty"`
	AppliedBundleID     string `json:"applied_bundle_id,omitempty"`
	AppliedConfigDigest string `json:"applied_config_digest,omitempty"`
	DriftObserved       bool   `json:"drift_observed"`
}

PlanBasis is what a plan was computed AGAINST. Any change to it — another promotion, another bundle applied, drift appearing — changes the digest, so an approval of a plan computed against an older Live is refused as stale.

type PlanFinding

type PlanFinding struct {
	Code    string       `json:"code"`
	Class   FindingClass `json:"class"`
	Section string       `json:"section"`
	// Subject is "<cluster>/<kind>/<ns>/<name>" for an object, else the
	// workload, artifact or secret name, or the section for an unknown.
	Subject string `json:"subject"`
	// Detail is human-readable and NEVER a secret value. Not digested.
	Detail string `json:"detail,omitempty"`
	// Fingerprint pins the CONTENT a finding is about when no bundle does:
	// the hash of a chart object's desired document. It is digested, so an
	// approval of a plan names the exact chart change and not just the
	// object's name. Empty for every bundle-backed finding, which keeps their
	// digests what they always were.
	Fingerprint string `json:"fingerprint,omitempty"`
}

PlanFinding is one thing the plan says.

type PlanInput

type PlanInput struct {
	EnvironmentID  string
	BundleID       string
	ReleaseVersion string

	Candidate             Shape
	CandidateConfigDigest string

	// Live is the applied bundle's shape, else the env's declared shape;
	// nil when nothing is recorded (every object then reads as added, and
	// the plan carries an unknown finding).
	Live *Shape
	// Basis is the live state the plan is bound to. AppliedConfigDigest is
	// also what ConfigIdentical compares against.
	Basis PlanBasis

	// Drift is the live-cluster observation: nil when drift is not
	// observable for this env (an unknown finding), else the objects found
	// diverged from the applied bundle (possibly none).
	Drift *DriftObservation
	// SecretPresence maps a secret name to whether its value is set, for
	// the secrets whose presence the caller COULD read: a managed store, or
	// the target cluster's Secrets when the caller is the one applying to
	// it. A name the map does not contain is UNVERIFIABLE — a store or a
	// cluster that could not be read — never "missing": one env commonly
	// mixes providers, and reading every unread secret as missing would make
	// the warning meaningless. nil means nothing could be read.
	//
	// It is consulted for every declared secret: a newly declared one is a
	// finding either way (info when present), and a previously declared one
	// is a finding only when explicitly false — declared at the last apply,
	// missing now.
	SecretPresence map[string]bool

	// Charts is what the env's forge.HelmChart platform deps would change in
	// their clusters. A chart is applied on its own path and rides no bundle,
	// so the shapes above cannot see it; the caller observes it and BuildPlan
	// folds it into the same sections, under the same digest.
	Charts ChartPlan
}

PlanInput is everything BuildPlan reads. It is assembled by the caller from records it already holds — the candidate bundle and the live state — so BuildPlan itself does no I/O.

type Promotion

type Promotion struct {
	// Frozen complete desired state; absent only on historical promotions.
	Snapshot       *Snapshot `json:"snapshot,omitempty"`
	SnapshotDigest string    `json:"snapshot_digest,omitempty"`
	BundleID       string    `json:"bundle_id,omitempty"`
	BundleDigest   string    `json:"bundle_digest,omitempty"`
	// ID is assigned by the backend that recorded the entry.
	ID string `json:"id,omitempty"`
	// Env is the environment name.
	Env string `json:"env"`
	// Release is the version label bound.
	Release string        `json:"release"`
	Kind    PromotionKind `json:"kind"`
	// FromEnv is the environment this release was promoted FROM, or empty
	// for a first deploy or a direct promote. Makes the promotion PATH auditable.
	FromEnv string `json:"from_env,omitempty"`
	// Resolved is the image → digest pin set FROZEN at promote time. A
	// deploy pins exactly these; nothing re-reads the release later.
	Resolved map[string]string `json:"resolved"`
	// Sources is the source-built half of the same snapshot.
	Sources map[string]Source `json:"sources,omitempty"`
	// Targets names the components a SCOPED deploy moved (`--target api`,
	// `--frontends-only`): workload, frontend or group names as the env
	// declares them, sorted. Empty means the whole release moved.
	//
	// It is what keeps "prod runs v2" honest after `forge env deploy prod v2
	// --target api`: the entry still binds prod to v2 — the release whose
	// pins the deploy shipped — and Targets says only api took them, so a
	// reader asking what a workload runs walks back to the newest entry that
	// [Promotion.Moved] it. Resolved and Sources stay the release's whole
	// pin set either way.
	Targets    []string `json:"targets,omitempty"`
	PromotedBy Actor    `json:"promoted_by,omitempty"`
	// Gates is the PRE-promote evidence, frozen into the entry: what had
	// already passed when the environment was bound.
	Gates []Gate `json:"gates,omitempty"`
	// RecordedGates is the POST-promote evidence — the wait, the smoke, a
	// manual sign-off — appended after the binding. It is a separate field
	// because the promotion entry is append-only: evidence that arrives
	// later cannot be written into Gates without rewriting history, and
	// the distinction is itself meaningful (what was known BEFORE the
	// environment moved, versus what was learned after).
	//
	// A file ledger leaves it empty: there is nowhere to append to a line
	// already written. A hosted backend fills it from its child table.
	RecordedGates []Gate `json:"recorded_gates,omitempty"`
	// Run is the pipeline attempt that made this promotion, or the zero
	// Run for a promotion that belongs to none.
	Run Run `json:"run,omitempty"`
	// FromPromotionID is the SOURCE promotion this one copied pins from —
	// the exact entry, not just the environment. FromEnv names where the
	// bytes came from; this names WHEN, which is what makes a promote
	// chain reconstructible after the source env has moved on.
	FromPromotionID string `json:"from_promotion_id,omitempty"`
	// SupersededInFlight records that this promotion replaced one whose
	// rollout had not finished — a deliberate override, not an accident.
	// Set by the backend that admitted the override; it is the audit trail
	// for "who decided to interrupt a rollout".
	SupersededInFlight bool `json:"superseded_in_flight,omitempty"`
	// Note is the free-form "why" — the most valuable field on a promote
	// that moves an environment backwards.
	Note string `json:"note,omitempty"`
	// PlanDigest is the approved deploy plan this promotion was written
	// under ([Plan.Digest]). Writing the promotion IS the deploy wherever a
	// converger applies it, so the review precedes the record, and this
	// names which review.
	PlanDigest string `json:"plan_digest,omitempty"`
	// ApprovedBy is who approved that plan. A backend sets it from the
	// credential; a client never supplies it as identity.
	ApprovedBy string `json:"approved_by,omitempty"`
	// AcknowledgedFindings are the stop-class finding codes the approver
	// accepted by name.
	AcknowledgedFindings []string `json:"acknowledged_findings,omitempty"`
	// PromotedAt is when the entry was written. It is PROMOTE time, not
	// deploy time: a promotion moves no bytes.
	PromotedAt time.Time `json:"promoted_at"`
}

Promotion is ONE append-only ledger entry: environment Env ran release Release from At onward. An environment's CURRENT binding is simply its most recent entry; there is no separate pointer that could disagree with the history.

func Decide

func Decide(history []Promotion, requested Promotion) (*Promotion, error)

Decide applies the ledger's append rule to one environment's history (OLDEST FIRST) and a requested entry. It returns:

  • (current, nil) when the request is already the current state — the same release, and the current entry already moved every component the request names. A CI retry must not append a second entry claiming the environment moved where it already was. The key is the CURRENT entry, not "ever promoted": v1→v2→v1 is three real moves, and the third must be recorded. So is a scoped v2 followed by v2 for the rest: the second entry moves components the first did not.
  • (nil, nil) when the entry should be appended.

Both backends call this inside whatever serializes their appends (the hosted one under a row lock), so the rule has one implementation. The error return is kept so a future rule can refuse without a signature change across both backends.

func NewPromotion

func NewPromotion(env string, r Release, kind PromotionKind) Promotion

NewPromotion freezes a release's pin set into a promotion of env. The digests are resolved HERE, at promote time, so a later edit to the release cannot change what this promotion ships.

func (Promotion) AllGates

func (p Promotion) AllGates() []Gate

AllGates is the whole evidence trail in reading order: the pre-promote gates frozen into the entry, then the post-promote gates appended after it. One call, so a renderer cannot show half the evidence by forgetting a field.

func (Promotion) Moved added in v0.1.46

func (p Promotion) Moved(component string) bool

Moved reports whether this promotion moved component: every component for a whole-release promotion, only its Targets for a scoped one.

func (Promotion) Scoped added in v0.1.46

func (p Promotion) Scoped() bool

Scoped reports whether the promotion moved only some components.

func (Promotion) Validate

func (p Promotion) Validate() error

Validate checks a promotion before it is appended, and every entry read back.

type PromotionKind

type PromotionKind string

PromotionKind records what a ledger entry is. There is ONE kind: a promotion binds an environment to a release. Moving an environment to an OLDER release is still a promotion — the plan labels its direction `behind` — because there is no such thing as undoing a release: the newer one already wrote data and ran migrations. Recovery is roll forward.

The field survives because it is the ledger's wire format, and an explicit kind keeps a future entry type from being read as a promotion.

const (
	// KindPromote binds an environment to a release.
	KindPromote PromotionKind = "promote"
)

func ParsePromotionKind

func ParsePromotionKind(stored string) (PromotionKind, error)

ParsePromotionKind reads a STORED kind — a ledger line, a database row — into the kind it means today. It is the one place the retired "rollback" kind is recognised, so every backend that holds old entries reads them the same way: as the promote of their release. Unknown and empty are refused, never defaulted — a kind nobody recognises must not read as a binding.

func (*PromotionKind) UnmarshalJSON

func (k *PromotionKind) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes through ParsePromotionKind.

func (PromotionKind) Valid

func (k PromotionKind) Valid() bool

Valid reports whether k is a kind a ledger entry may carry.

type Provenance

type Provenance struct {
	// Repo is the canonical remote, e.g. "github.com/reliant-labs/forge".
	// Empty when the checkout has no remote.
	Repo string `json:"repo"`
	// Commit is HEAD, full hex. Empty when the source is not a git tree.
	Commit string `json:"commit"`
	// CommitTime is when Commit was made: the committer date, UTC. It is the
	// start of a lead-time measurement (commit to production), so it is only
	// ever the time of the commit that was BUILT: nil when the checkout was
	// dirty (the bytes are no commit's) or the commit is unknown. It is a
	// claim like Branch, not authority.
	CommitTime *time.Time `json:"commit_time,omitempty"`
	// CommitSubject is Commit's subject line (the first line of its message)
	// and CommitAuthor its author's name, as git reported them, so a release
	// can be read as "abc1234 fix(gateway): …, by alice" without a repository
	// at hand. Set whenever Commit is, dirty or not: a dirty build's Commit is
	// the base it was built on, and Dirty says the bytes are more than that.
	// Claims like Branch. The subject is bounded at [MaxCommitSubjectBytes].
	CommitSubject string `json:"commit_subject,omitempty"`
	CommitAuthor  string `json:"commit_author,omitempty"`
	// Branch is the symbolic ref at build time; empty on a detached HEAD.
	Branch string `json:"branch,omitempty"`
	// Tag is an exact tag at HEAD, if any.
	Tag string `json:"tag,omitempty"`
	// Dirty: tracked OR untracked-unignored changes existed at build time.
	Dirty bool `json:"dirty"`
	// Tree is the git tree hash of the BUILT content, dirty changes
	// included, computed through a temporary index (the user's index is
	// never touched). Empty means it was not hashed — no git, or the
	// working tree was too large to hash within the capture budget. A
	// clean checkout's Tree equals HEAD^{tree}.
	Tree string `json:"tree"`
	// Worktree identifies the checkout on its machine.
	Worktree Worktree `json:"worktree"`
	// ForgeVersion is the binary that rendered. A render is a function of
	// (forge, KCL, config), so the same commit rendered by two forges is
	// two different artifacts.
	ForgeVersion string `json:"forge_version"`
	// Attestation is the CI identity that built it, or nil from a laptop.
	Attestation *Attest `json:"attestation,omitempty"`
}

Provenance is WHERE a release or a bundle came from: the source tree, the checkout it was built in, and the forge that rendered it. It is shared by releases (the images) and bundles (the config), so "images from main@abc, config from feat-x@def, dirty" is two values of one type.

Claims and checkable facts

What a CLIENT may claim and what a SERVER can establish are kept apart, and every reader must keep them apart too:

  • Commit is checkable against the repository.
  • Tree is checkable when Dirty is false: a clean tree's hash IS the commit's tree, so a clean claim can be verified without trusting the client.
  • Branch, Worktree, Dirty and CommitTime are claims. They are evidence and are rendered as such ("reported by forge"), never used as authority.
  • Ancestry ("is this commit on main") is never a field here. It changes over time — a branch is merged, a force-push orphans a commit — so it cannot sit on an immutable record. A backend derives it.

Release.Git predates this type and is kept, filled from it (see Release.SetProvenance), so ledgers written before Provenance existed still decode and readers that only know Git keep working.

func CaptureProvenance

func CaptureProvenance(ctx context.Context, dir string, opts CaptureOptions) (Provenance, error)

CaptureProvenance records where the content in dir came from. It is best-effort in exactly one way: a directory that is not a git checkout yields a Provenance with no commit (and an error only when git itself is broken in a way the caller should hear about). It NEVER touches the user's index: the tree hash is computed through a temporary one.

Exported so the daemon and CI capture provenance with the same code forge does — the field set is ledger vocabulary every backend must agree on.

func (Provenance) ForHosted

func (p Provenance) ForHosted() Provenance

ForHosted is the copy that may leave this machine: the worktree path is dropped. Everything else is already path-free by construction.

func (Provenance) Git

func (p Provenance) Git() Git

Git is the legacy three-field view of this provenance.

func (Provenance) Unhashed

func (p Provenance) Unhashed() bool

Unhashed reports whether this provenance names a commit but could not hash what was built (F-16). Such a record is honest about the gap rather than pretending the tree is HEAD's.

func (Provenance) Validate

func (p Provenance) Validate() error

Validate checks the shape of the claims. It cannot check their truth.

type Release

type Release struct {
	// Version is the label ("v1.4.0"). Releases are ADDRESSED by it, in
	// every backend.
	Version string `json:"release"`
	// Project is the forge project (forge.yaml `name`) the version belongs
	// to. A version label is unique within a project, not across an org:
	// two projects may both cut v1.0.0. "" is a release recorded before
	// releases carried a project.
	Project string `json:"project,omitempty"`
	// Git is the legacy provenance view, kept filled from Provenance (see
	// SetProvenance) so readers that predate Provenance keep working.
	Git Git `json:"git"`
	// Provenance is where the release's images were built from. Nil on a
	// release recorded before it existed.
	Provenance *Provenance `json:"provenance,omitempty"`
	// Commits is the source history this release adds over the release it
	// was cut after ([CaptureCommitRange]). Nil when none was recorded: a
	// non-git tree, a forge that predates it, or a capture that failed. Like
	// Provenance it is evidence about the release and not part of its
	// identity — [Release.SameContent] ignores it.
	Commits *CommitRange `json:"commits,omitempty"`
	// Migrations is every migrations directory the release's artifacts
	// embed, as the files each held at the cut ([MigrationManifest]) — what
	// lets a cut and a deploy refuse a release an environment's schema has
	// already passed ([PassedMigrations]). Nil when none was recorded: a
	// release cut before it existed (until backfilled), or a project with no
	// migrations. Evidence like Commits: [Release.SameContent] ignores it.
	Migrations *MigrationManifest `json:"migrations,omitempty"`
	// CreatedAt is when the release was cut.
	CreatedAt time.Time `json:"created_at"`
	// CreatedBy is who cut it, when the backend knows (a hosted ledger
	// does; a file does not).
	CreatedBy string `json:"created_by,omitempty"`
	// Artifacts maps the bare artifact name (an image name, a package name,
	// a frontend name) → its identity.
	Artifacts map[string]Artifact `json:"artifacts"`
	// Catalog and SnapshotIntents freeze complete composition intent. Absent on
	// historical releases, which remain readable but are not snapshot candidates.
	Catalog         map[ArtifactID]SnapshotArtifact `json:"catalog,omitempty"`
	SnapshotIntents map[string]string               `json:"snapshot_intents,omitempty"`
	// Run is the pipeline attempt that cut the release, or the zero Run
	// when a human cut it. It is NOT part of the release's identity:
	// [Release.SameContent] ignores it, so a CI retry that re-cuts the
	// same bytes under a new run id is still an idempotent re-cut.
	Run Run `json:"run,omitempty"`
}

Release is an immutable, content-addressed set of artifacts under a version label.

func (Release) ArtifactNames

func (r Release) ArtifactNames() []string

ArtifactNames returns the artifact names, sorted.

func (Release) SameContent

func (r Release) SameContent(other Release) bool

SameContent reports whether two releases name the same bytes: the same artifact set with the same identities. Provenance and timestamps are not compared — a re-cut from CI's retry carries a fresh CreatedAt and is still the same release.

func (*Release) SetProvenance

func (r *Release) SetProvenance(p Provenance)

SetProvenance records where the release was built from, and keeps the legacy Git view in step with it. A release is shared — promoted between envs, imported, shown to every member — so it never carries the build machine's checkout path (Provenance.ForHosted).

func (Release) SharedDigests

func (r Release) SharedDigests() map[string]string

SharedDigests is the image-name → digest pin set a promotion freezes: every shared OCI artifact, and nothing else.

func (Release) Sources

func (r Release) Sources() map[string]Source

Sources is the frontend-name → source pin set a promotion freezes.

func (Release) Validate

func (r Release) Validate() error

Validate checks the release and every artifact in it.

type RenderedExternalSecretsOperator added in v1.0.0

type RenderedExternalSecretsOperator struct {
	Cluster          string `json:"cluster"`
	ConnectedCluster string `json:"connected_cluster,omitempty"`
	Namespace        string `json:"namespace"`
	Version          string `json:"version"`
}

RenderedExternalSecretsOperator is one rendered `external_secrets_operators` record.

type RenderedGeneratedSecret added in v1.0.0

type RenderedGeneratedSecret struct {
	Kind       string `json:"kind"`
	Bytes      int    `json:"bytes,omitempty"`
	Encoding   string `json:"encoding,omitempty"`
	Prefix     string `json:"prefix,omitempty"`
	Length     int    `json:"length,omitempty"`
	Symbols    string `json:"symbols,omitempty"`
	Companion  string `json:"companion,omitempty"`
	CommonName string `json:"common_name,omitempty"`
	ValidDays  int    `json:"valid_days,omitempty"`
}

RenderedGeneratedSecret is a rendered forge.GeneratedSecret. The render emits the schema defaults for the parameters of kinds the entry is not, so the projection keeps only the ones its kind reads.

type RenderedOpenBao added in v1.0.0

type RenderedOpenBao struct {
	Name             string `json:"name"`
	Namespace        string `json:"namespace"`
	Image            string `json:"image"`
	Cluster          string `json:"cluster"`
	ConnectedCluster string `json:"connected_cluster,omitempty"`
}

RenderedOpenBao is the rendered `openbao` record.

type RenderedStore added in v1.0.0

type RenderedStore struct {
	Type    string `json:"type"`
	Project string `json:"project,omitempty"`
}

RenderedStore is a rendered store kind. Type is the store's discriminator ("hosted", "openbao", "gcpsm").

type RenderedStoreSecret added in v1.0.0

type RenderedStoreSecret struct {
	Name      string               `json:"name"`
	Namespace string               `json:"namespace"`
	Source    *RenderedStoreSource `json:"source,omitempty"`
	// Clusters are the clusters the Secret is delivered to, resolved by the
	// caller. Not part of the render record.
	Clusters []StoreCluster `json:"-"`
}

RenderedStoreSecret is one `required_secrets` record.

type RenderedStoreSource added in v1.0.0

type RenderedStoreSource struct {
	Store     RenderedStore                      `json:"store"`
	Data      map[string]string                  `json:"data,omitempty"`
	Generate  map[string]RenderedGeneratedSecret `json:"generate,omitempty"`
	Pin       map[string]int                     `json:"pin,omitempty"`
	Immutable []string                           `json:"immutable,omitempty"`
}

RenderedStoreSource is a rendered forge.StoreSource.

type ResourceKey added in v0.1.46

type ResourceKey struct {
	Cluster   string `json:"cluster"`
	Group     string `json:"group"`
	Kind      string `json:"kind"`
	Namespace string `json:"namespace"`
	Name      string `json:"name"`
}

ResourceKey identifies an object in one destination, independent of API version.

type Run

type Run struct {
	// ID is opaque and CALLER-chosen, stable across retries of one
	// attempt: "github:acme/app/12345/1". Stable-across-retries is the
	// property that makes gate recording idempotent on
	// (promotion, name, run id) — a re-run of one CI step must not
	// double-record.
	ID string `json:"id"`
	// URL links to the run in whatever ran it.
	URL string `json:"url,omitempty"`
	// Provider is "github", "gitlab", "manual". DISPLAY ONLY — nothing
	// branches on it, and it is not part of the run's identity. A verb
	// that behaved differently per provider is the thing this field exists
	// to avoid needing.
	Provider string `json:"provider,omitempty"`
}

Run is the identity of ONE pipeline attempt, carried by everything that attempt wrote: the release it cut, the promotions it made, the gates it recorded. It is what turns a scatter of ledger rows into a timeline somebody can read.

It is deliberately thin. A run is not stored as an entity anywhere: there is no runs table and no run events, because every stage already leaves a durable, timestamped fact in the ledger (a release's CreatedAt, a promotion's PromotedAt, a gate's window). The ID is the join key over those facts, and nothing more. A parallel event log would be a second source of truth free to disagree with the first.

func (Run) Validate

func (r Run) Validate() error

Validate refuses a run that carries detail but no identity. URL or provider without an ID cannot be joined to anything, so it would be detail about a run nobody can find.

func (Run) Zero

func (r Run) Zero() bool

Zero reports whether no run was supplied. A human at a terminal with --no-run writes one of these, and it is not an error: a promotion that belongs to no run is still a promotion.

type RunTimeline

type RunTimeline struct {
	Run Run `json:"run"`
	// Release is the version label the run concerns, empty when unknown.
	Release string `json:"release,omitempty"`
	// Stages are ordered by StartedAt, oldest first.
	Stages []Stage `json:"stages"`
}

RunTimeline is one run, assembled. The release is the one a run cut, or the one its promotions bound when the run cut nothing.

func (RunTimeline) Verdict

func (t RunTimeline) Verdict() StageStatus

Verdict collapses a timeline into one answer, which is what a final "did this run pass" pipeline step needs. The order is deliberate and is NOT worst-wins alphabetically:

failed > errored > running > passed

A failure outranks a still-running stage because a run with a failed stage has failed whatever the rest does. Running outranks passed because a run that has not finished has not passed. A run with no stages at all is StageRunning: an id nothing has written under is not a pass.

type RuntimeChange

type RuntimeChange struct {
	Workload    string `json:"workload"`
	From        string `json:"from"`
	To          string `json:"to"`
	FromCluster string `json:"from_cluster,omitempty"`
	ToCluster   string `json:"to_cluster,omitempty"`
}

RuntimeChange is a workload moving between runtimes ("web: hosted → bucket") or clusters.

type SecretStage added in v1.0.0

type SecretStage string

SecretStage places an object in the secret-store ordering.

const (
	// SecretStageNone is every object this ordering does not touch.
	SecretStageNone SecretStage = ""
	// SecretStageBootstrap is a store component object that is neither a
	// workload nor a Job: its support objects and its own SecretStore and
	// ExternalSecrets.
	SecretStageBootstrap SecretStage = "bootstrap"
	// SecretStageStorePre is a store component Job that must complete before
	// the component's workloads roll.
	SecretStageStorePre SecretStage = "store-pre"
	// SecretStageStore is the store component's workloads and post-rollout
	// Jobs.
	SecretStageStore SecretStage = "store"
	// SecretStageSecrets is any other SecretStore or ExternalSecret.
	SecretStageSecrets SecretStage = "secrets"
)

func ClassifySecretStage added in v1.0.0

func ClassifySecretStage(apiVersion, kind, name string, labels, annotations map[string]string) (SecretStage, error)

ClassifySecretStage is the secret stage of one object. An unknown AnnotationDeployPhase on a store component Job is the same error ClassifyPhase returns.

An object carrying LabelSecretStage is in that stage by declaration: the env's SecretStores log in as a service account through a token Role, none of which is an ESO kind, and all of which must be in the cluster before the store is judged. A hand-written SecretStore or ExternalSecret carries no label and is placed by its kind.

type Shape

type Shape struct {
	Kind      EnvKind         `json:"kind"`
	Workloads []ShapeWorkload `json:"workloads"`
	Secrets   []ShapeSecret   `json:"secrets"`
	Domains   []string        `json:"domains"`
	Clusters  []string        `json:"clusters"`
	Objects   []ShapeObject   `json:"objects"`
	// ControlPlaneSecrets are the Secrets the env's declared control plane
	// supplies and renews (forge.ControlPlaneSecret): where each lands and what
	// to mint into it. It is how the control plane learns what to deliver, so
	// it is recorded on every ensure. Omitted when empty, so an env that
	// declares none encodes, and hashes, exactly as it did before the field.
	ControlPlaneSecrets []ShapeControlPlaneSecret `json:"control_plane_secrets,omitempty"`
	// StoreSecrets are the store names each namespace of each cluster reads
	// from a secret store (forge.ExternalSecret with a forge.StoreSource), with
	// their version pins: what a control plane builds a secretaccess.Request
	// from. GeneratedSecrets are the values to mint when the store has none,
	// OpenBao and ExternalSecretsOperators the components the env declares and
	// where. Names, versions and recipes; never a value. All omitted when the
	// env declares none, so every shape recorded before them encodes and hashes
	// exactly as it did; a control plane that predates them refuses the shape,
	// so they are a bundle-document feature ([BundleFeatureStoreSecrets]). See
	// store_secrets.go.
	StoreSecrets             []ShapeStoreSecret             `json:"store_secrets,omitempty"`
	GeneratedSecrets         []ShapeGeneratedSecret         `json:"generated_secrets,omitempty"`
	OpenBao                  *ShapeOpenBao                  `json:"openbao,omitempty"`
	ExternalSecretsOperators []ShapeExternalSecretsOperator `json:"external_secrets_operators,omitempty"`
	// Alerts are the env's resolved alert rules: its own (forge.AlertRule) plus
	// the generated defaults it did not disable, each tagged with its source.
	// The control plane evaluates them; a rollback restores the rules of the
	// release it returns to. Each entry serializes to pkg/alerting's RuleSpec.
	// Omitted when empty, like ControlPlaneSecrets.
	Alerts []ShapeAlert `json:"alerts,omitempty"`
	// AlertRoutes say which receiver an alert goes to, by label, first match
	// wins unless a route continues. Receivers are NAMES: their secrets live
	// in the control plane. Order is significant and is preserved.
	AlertRoutes []ShapeAlertRoute `json:"alert_routes,omitempty"`
	// ObservabilityPolicy is the env's resolved observability opt-out
	// (forge.Observability): what the author switched off. It is how the
	// platform learns what NOT to collect, enforced at the source (the node
	// agent, the operator's injection, the browser ingest). Omitted when the
	// env declares no observability or opts out of nothing, so every shape
	// recorded before the field encodes and hashes exactly as it did.
	ObservabilityPolicy *ShapeObservabilityPolicy `json:"observability_policy,omitempty"`
	// FrontendTelemetry is what each frontend declares about its browser
	// telemetry beyond the env's observability policy (forge.BrowserTelemetry):
	// the error filters the issue worker applies, the funnels, and what becomes
	// of the build's source maps. One entry per frontend that declares
	// something other than the defaults, sorted by frontend. Omitted when no
	// frontend does, so every shape recorded before the field encodes and
	// hashes exactly as it did. A control plane that predates it refuses the
	// whole shape, so it is a bundle-document feature
	// ([BundleFeatureFrontendTelemetry]).
	FrontendTelemetry []ShapeFrontendTelemetry `json:"frontend_telemetry,omitempty"`
	// DeliveryPolicy is what the env's releases must prove before they ship
	// (forge.DeliveryPolicy): one control per hardening control, each with a
	// mode and its parameters. It is how the control plane learns what to
	// enforce. Omitted when every value is the default, so every shape
	// recorded before the field encodes and hashes exactly as it did; a
	// reader treats absence as [DefaultDeliveryPolicy], never as "off".
	DeliveryPolicy *ShapeDeliveryPolicy `json:"delivery_policy,omitempty"`
	// DataAccess is how the env's data may be read (forge.DataAccess): today
	// whether `forge db query` is open, audited or denied. The control plane
	// enforces it from this recorded shape. Omitted when it is the default, so
	// every shape recorded before the field encodes and hashes exactly as it
	// did; a reader treats absence as [DefaultDataAccess], never as "open". A
	// control plane that predates it refuses the whole shape, so it is a
	// bundle-document feature ([BundleFeatureDataAccess]).
	DataAccess *ShapeDataAccess `json:"data_access,omitempty"`
	// ReleaseHealth is how the env's promotions are judged
	// (forge.ReleaseHealth): the before/after window, the thresholds, and the
	// findings that fail a release. Nil when every value is the default; a
	// reader treats absence as [DefaultReleaseHealth].
	ReleaseHealth *ShapeReleaseHealth `json:"release_health,omitempty"`
	// UptimeChecks are the env's outside-in HTTP checks (forge.UptimeCheck and
	// the ones forge derives for every public frontend and route), sorted by
	// name. The control plane's prober reads them from here. Omitted when the
	// env has none, so every shape recorded before the field encodes and
	// hashes exactly as it did; a control plane that predates it refuses the
	// whole shape, so it is a bundle-document feature
	// ([BundleFeatureUptimeChecks]).
	UptimeChecks []ShapeUptimeCheck `json:"uptime_checks,omitempty"`
	// Identity is the env's managed identity as declared
	// (forge.ManagedIdentity): the instance the control plane's identity
	// reconciler converges, and which workloads consume it. Names and
	// destinations only; every issued value is delivered, never recorded.
	// Omitted when the env declares none, so every shape recorded before the
	// field encodes and hashes exactly as it did.
	Identity *ShapeIdentity `json:"identity,omitempty"`
	// Applier is who applies this env when it DECLARES one: "forge" for a real
	// environment none of whose delivery units has moved onto the hub yet. It
	// is a transitional state (see [ApplierForge]). Empty means undeclared, and
	// omitted, so every shape recorded before the field encodes and hashes as
	// it did.
	Applier string `json:"applier,omitempty"`
	// Units is who applies each delivery unit of the env: the split between the
	// units the hub converges and the units forge still applies directly. It is
	// recorded only when the env declares one (delivery units, or
	// hub_converges), and omitted otherwise, so every shape recorded before the
	// field encodes and hashes exactly as it did. Sorted by name.
	//
	// It is a DECLARATION about who applies, so it is not part of the config
	// digest: moving a unit onto the hub changes nobody's configuration.
	Units []ShapeUnit `json:"units,omitempty"`
}

Shape is the indexable projection of one env's render: what runs, where, which secrets it needs, which domains it binds, and one hash per object. It is computed in the SAME pass that writes the manifests, so the two cannot disagree, and it is small enough to store per bundle and read per env.

IT NEVER CARRIES A VALUE. Secrets are names and providers; objects are identities and hashes (a Secret's hash is taken after its values are redacted to their own hashes). Shape.Validate refuses anything that could hold a value, because a shape is stored forever and shown to every member.

func ProjectSnapshotShape added in v0.1.46

func ProjectSnapshotShape(s Snapshot) (Shape, error)

ProjectSnapshotShape independently derives every planning object from exact approved resource bytes, with binding-derived immutable pins. No producer supplied hash, LB identity, image list or tree annotation is trusted.

func (Shape) Canonical

func (s Shape) Canonical() Shape

Canonical returns the shape with every list sorted, so two projections of the same render encode to the same bytes regardless of render order.

func (Shape) Encode

func (s Shape) Encode() ([]byte, error)

Encode is the canonical JSON of the shape, refused when it exceeds MaxShapeBytes.

func (Shape) HasFrontendTelemetry added in v0.1.46

func (s Shape) HasFrontendTelemetry() bool

HasFrontendTelemetry reports whether the shape carries any part of the frontend-telemetry feature: a frontend entry or a non-default retention.

func (Shape) HasOptionalParts added in v1.0.0

func (s Shape) HasOptionalParts() bool

HasOptionalParts reports whether s carries any part a reader that decodes NO optional feature would withhold — so whether sending s to a control plane needs its feature list first.

It is DERIVED from LimitShape rather than listing fields, and that is the point: the environment declaration once asked only when the shape had frontend telemetry, so when forge #1285 added uptime checks to the shape a control plane predating them strict-decoded the declaration, refused it whole ("spec.shape is not a release.Shape"), and every release cut against it failed before building. A feature added to LimitShape is covered here with no second edit.

func (Shape) SecretNamed

func (s Shape) SecretNamed(name string) (ShapeSecret, bool)

SecretNamed returns the shape's secret of that name.

func (Shape) StoreBindings added in v1.0.0

func (s Shape) StoreBindings(store string) []secretaccess.Binding

StoreBindings returns the bindings secretaccess derives roles and policies from, for one store kind (StoreHosted or StoreOpenBao): the namespaces of that store and the names each reads, by exact path.

A binding's cluster is its ConnectedCluster when set, else its Cluster; a control plane that keys its mounts by another id maps from there. Wildcards are never produced: a shape cannot ask for one. The result is sorted by (cluster, namespace). A StoreGCPSecretManager binding derives nothing, and asking for it returns none.

func (Shape) Validate

func (s Shape) Validate() error

Validate checks a shape: a known kind (or none), well-formed object identities, no duplicate objects, canonical hashes, and nothing that could carry a value.

func (Shape) WithStoreSecrets added in v1.0.0

func (s Shape) WithStoreSecrets(p StoreProjection) Shape

WithStoreSecrets returns the shape with the projection's fields set.

type ShapeAlert

type ShapeAlert struct {
	Alert string `json:"alert"`
	Expr  string `json:"expr"`
	// For and Interval are Go duration strings ("5m", "1h30m").
	For           string `json:"for,omitempty"`
	Interval      string `json:"interval,omitempty"`
	KeepFiringFor string `json:"keep_firing_for,omitempty"`
	// Labels always carry `severity`: critical | warning | info.
	Labels      map[string]string `json:"labels,omitempty"`
	Annotations map[string]string `json:"annotations,omitempty"`
	// Source is AlertSourceUser or AlertSourceDefault.
	Source string `json:"source"`
	// DefaultID names the generated default this rule came from.
	DefaultID string `json:"default_id,omitempty"`
}

ShapeAlert is one resolved alert rule: the Prometheus rule shape with the condition inside Expr, a single ClickHouse SELECT whose every row is a firing instance (non-`value` columns are labels, `value` the sample). The json tags are pkg/alerting's RuleSpec, so a consumer decodes this straight into it; KeepFiringFor and DefaultID are extra fields RuleSpec ignores.

type ShapeAlertRoute

type ShapeAlertRoute struct {
	Match    map[string]string `json:"match,omitempty"`
	Receiver string            `json:"receiver"`
	Continue bool              `json:"continue,omitempty"`
}

ShapeAlertRoute routes alerts whose labels contain every Match pair (an empty Match matches all) to Receiver, a name the control plane resolves.

type ShapeApprovals added in v0.1.46

type ShapeApprovals struct {
	Mode      DeliveryMode `json:"mode"`
	Required  int          `json:"required"`
	Approvers []string     `json:"approvers"`
}

ShapeApprovals: Required distinct people must approve a promotion, the requester included. 1 is one-person approval. Approvers are the org members who may approve; more than one required needs at least that many.

type ShapeBundleKinds added in v0.1.46

type ShapeBundleKinds struct {
	Mode  DeliveryMode `json:"mode"`
	Allow []string     `json:"allow"`
	Deny  []string     `json:"deny"`
}

ShapeBundleKinds narrows, never widens, the mandatory ingest baseline: Allow non-empty means only those kinds; Deny refuses these. `Kind` or `group/Kind`.

type ShapeControl added in v0.1.46

type ShapeControl struct {
	Mode DeliveryMode `json:"mode"`
}

ShapeControl is a control with no parameters beyond its mode.

type ShapeControlPlaneSecret

type ShapeControlPlaneSecret struct {
	Name      string `json:"name"`
	Namespace string `json:"namespace"`
	// Cluster is the kube context the Secret lands in, the same key the
	// shape's workloads and objects use.
	Cluster string `json:"cluster"`
	// ConnectedCluster is the name `forge cluster connect` registered: the
	// credential the control plane delivers with.
	ConnectedCluster string `json:"connected_cluster"`
	// Kind says what the control plane mints into it
	// (ControlPlaneSecretTelemetryToken).
	Kind string   `json:"kind"`
	Keys []string `json:"keys,omitempty"`
	// Delivery is ControlPlaneSecretDeliveryStore when the control plane
	// writes the value into the env's secret store and the External Secrets
	// Operator delivers it (a floating ExternalSecret); empty means the control
	// plane pushes the Secret into the cluster itself. The store the value goes
	// to is in Shape.StoreSecrets, under ControlPlaneSecretStoreName.
	Delivery string `json:"delivery,omitempty"`
	// Required says a workload reads the Secret without an optional marker,
	// so its pods wait for the first delivery instead of starting without it.
	// The control plane reports such an env as waiting on the delivery.
	// Omitted when false, so a shape recorded before the field is unchanged.
	Required bool `json:"required,omitempty"`
}

ShapeControlPlaneSecret is one Secret the control plane supplies: a DESTINATION and a KIND, never a value. Keys names what the control plane writes under the Secret, so a reader knows the contract without the kind's definition.

func (ShapeControlPlaneSecret) Key

Key identifies the Secret within a shape: (cluster, namespace, name).

type ShapeDataAccess added in v1.0.0

type ShapeDataAccess struct {
	DBQuery DBQueryPolicy `json:"db_query"`
}

ShapeDataAccess is the env's declared access to its data (forge.DataAccess).

func DefaultDataAccess added in v1.0.0

func DefaultDataAccess() ShapeDataAccess

DefaultDataAccess is what an env with a real database declares nothing about gets. It must agree with the KCL default for an env with no lifecycle (TestDataAccessDefaultsAgreeWithKCL); a local or ephemeral env defaults to open in KCL and records that, which is how a reader can tell it apart.

func (*ShapeDataAccess) IsDefault added in v1.0.0

func (d *ShapeDataAccess) IsDefault() bool

IsDefault reports whether d is nil or the default: the shape then omits it, so every shape recorded before the field encodes and hashes as it did.

func (*ShapeDataAccess) Resolved added in v1.0.0

func (d *ShapeDataAccess) Resolved() ShapeDataAccess

Resolved returns the data access in force: the default for nil.

type ShapeDeliveryPolicy added in v0.1.46

type ShapeDeliveryPolicy struct {
	TrustedBuilder   ShapeTrustedBuilder `json:"trusted_builder"`
	Signing          ShapeSigning        `json:"signing"`
	ProvenanceSBOM   ShapeControl        `json:"provenance_sbom"`
	FluxVerify       ShapeControl        `json:"flux_verify"`
	AdmissionVerify  ShapeControl        `json:"admission_verify"`
	SignedPromotions ShapeControl        `json:"signed_promotions"`
	VulnGate         ShapeVulnGate       `json:"vuln_gate"`
	BundleKinds      ShapeBundleKinds    `json:"bundle_kinds"`
	Approvals        ShapeApprovals      `json:"approvals"`
}

ShapeDeliveryPolicy is what an env's releases must prove before they ship, one control per hardening control of the delivery design, each with a mode and its parameters. It is how the control plane learns what to enforce: recorded on every ensure, enforced at record (builder, signature, provenance, SBOM, vulnerabilities, kinds), at promote (approvals, the promotion's signature), at the converger (Flux verification) and at admission (pod images).

Isolation controls between bindings (per-binding identity, kubeconfig-ref binding, narrowed impersonation, ledger and pointer integrity, ingest validation, head-of-line fairness, the hub's PSA and NetworkPolicy baseline) are NOT here: they are not an env's choice.

Shape.DeliveryPolicy is nil when every value is the default (DefaultDeliveryPolicy), so every shape recorded before the field encodes and hashes as it did. A reader treats nil as the defaults, never as "off".

func DefaultDeliveryPolicy added in v0.1.46

func DefaultDeliveryPolicy() ShapeDeliveryPolicy

DefaultDeliveryPolicy is every control at its default: the hybrid rule of the delivery design. A control that cannot block a deploy and costs the customer nothing is enforced (signing and provenance/SBOM are done by forge or the platform builder on the customer's behalf, and Flux verification only checks what that signing produced). A control that can refuse a deploy is audited until the env turns it up.

It must agree with the defaults of forge.DeliveryPolicy in kcl/schema.k; TestDeliveryPolicyDefaultsAgreeWithKCL fails if they drift.

func (*ShapeDeliveryPolicy) IsDefault added in v0.1.46

func (p *ShapeDeliveryPolicy) IsDefault() bool

IsDefault reports whether p, once canonical, is DefaultDeliveryPolicy. A nil policy is the default.

func (*ShapeDeliveryPolicy) Resolved added in v0.1.46

Resolved returns the policy in force: the defaults for nil.

type ShapeDiff

type ShapeDiff struct {
	// LiveUnknown: there was no live shape to compare against (nothing
	// recorded yet). Every candidate object then appears in Added, and a
	// reader must say "no recorded config" rather than present the diff as
	// a real comparison — empty and unknown must never render alike.
	LiveUnknown bool `json:"live_unknown,omitempty"`
	// KindChanged is never set.
	//
	// Deprecated: an env has no placement kind any more — placement is per
	// workload (docs/design/per-workload-placement.md), and a part that moves
	// between placements is reported in RuntimeChanges. Kept only so a reader
	// built against an older forge still decodes the document; removed with
	// Shape.Kind.
	KindChanged *KindChange `json:"kind_changed,omitempty"`

	Added   []ShapeObject  `json:"added,omitempty"`
	Removed []ShapeObject  `json:"removed,omitempty"`
	Changed []ObjectChange `json:"changed,omitempty"`
	// Moved are objects present on both sides, in the same cluster, whose
	// delivery tree differs. Independent of Changed: a move can leave the
	// object's bytes untouched.
	Moved []ObjectMove `json:"moved,omitempty"`

	WorkloadsAdded   []string        `json:"workloads_added,omitempty"`
	WorkloadsRemoved []string        `json:"workloads_removed,omitempty"`
	RuntimeChanges   []RuntimeChange `json:"runtime_changes,omitempty"`
	SecretsAdded     []ShapeSecret   `json:"secrets_added,omitempty"`
	SecretsRemoved   []ShapeSecret   `json:"secrets_removed,omitempty"`
	DomainsAdded     []string        `json:"domains_added,omitempty"`
	DomainsRemoved   []string        `json:"domains_removed,omitempty"`
	ClustersAdded    []string        `json:"clusters_added,omitempty"`
	ClustersRemoved  []string        `json:"clusters_removed,omitempty"`
}

ShapeDiff is what changes between two shapes of one environment: the shape that is live (the applied bundle's, or the env's declared shape) and a candidate (a checkout's render, or the bundle about to be deployed).

It is a SET comparison over per-object hashes, which is why a shape carries one hash per object: a Live-vs-candidate diff never fetches or parses a manifest.

func DiffShapes

func DiffShapes(live *Shape, candidate Shape) ShapeDiff

DiffShapes compares a live shape (nil = nothing recorded) with a candidate. Pure, and shared by `forge env diff`, the deploy plan, and any backend that needs the same answer: one implementation, so a CLI and a server cannot disagree about what changed. Every list in the result is sorted.

func (ShapeDiff) Empty

func (d ShapeDiff) Empty() bool

Empty reports whether nothing differs. An unknown live side is never empty.

type ShapeExternalSecretsOperator added in v1.0.0

type ShapeExternalSecretsOperator struct {
	Cluster          string `json:"cluster"`
	ConnectedCluster string `json:"connected_cluster,omitempty"`
	Namespace        string `json:"namespace"`
	// Version is the chart version.
	Version string `json:"version"`
}

ShapeExternalSecretsOperator records that the env declares the ESO component on a cluster (Bundle.external_secrets_operators): what a promotion needs to check that the CRDs exist and that this env owns the install. One per cluster.

type ShapeFrontendTelemetry added in v0.1.46

type ShapeFrontendTelemetry struct {
	// Frontend is the forge frontend's name (the forge.frontend resource
	// attribute its telemetry carries).
	Frontend string `json:"frontend"`
	// Sourcemaps is what becomes of the build's source maps: "public" or
	// "off". Omitted for the default, [SourcemapsPublish].
	Sourcemaps string `json:"sourcemaps,omitempty"`
	// ErrorsIgnore are the declared error filters, in declaration order: a
	// plain entry matches an exception type exactly or a message substring, a
	// "re:" entry is a regular expression over the message. An issue that
	// matches is born ignored.
	ErrorsIgnore []string `json:"errors_ignore,omitempty"`
	// Funnels are the declared product funnels, sorted by id.
	Funnels []ShapeFunnel `json:"funnels,omitempty"`
}

ShapeFrontendTelemetry is one frontend's browser telemetry declaration. It carries NAMES and definitions only: no endpoint, no key.

func (ShapeFrontendTelemetry) IsDefault added in v0.1.46

func (t ShapeFrontendTelemetry) IsDefault() bool

IsDefault reports whether the entry declares nothing a reader would act on.

type ShapeFunnel added in v0.1.46

type ShapeFunnel struct {
	ID    string `json:"id"`
	Title string `json:"title,omitempty"`
	// Steps are product event names, in order.
	Steps []string `json:"steps"`
	// Window and By are always set; the KCL schema defaults them.
	Window string `json:"window"`
	By     string `json:"by"`
}

ShapeFunnel is one declared product funnel (forge.Funnel).

type ShapeGeneratedSecret added in v1.0.0

type ShapeGeneratedSecret struct {
	// Store says who mints it: [StoreHosted] (the control plane, at promotion)
	// or [StoreOpenBao] (the component's converge Job, inside the cluster).
	Store string `json:"store"`
	// Kind is a secretgen.Kind.
	Kind string `json:"kind"`
	// Names are the store names written, in secretgen's order: the key first,
	// then its companion for the two-name kinds. Not sorted: the order is
	// meaningful.
	Names []string `json:"names"`

	// Parameters of the kinds that take them; the others carry zero values.
	// Canonical fills the defaults, so a shape that spells them out and one
	// that leaves them to the default are one shape.
	Bytes      int    `json:"bytes,omitempty"`
	Encoding   string `json:"encoding,omitempty"`
	Prefix     string `json:"prefix,omitempty"`
	Length     int    `json:"length,omitempty"`
	Symbols    string `json:"symbols,omitempty"`
	CommonName string `json:"common_name,omitempty"`
	ValidDays  int    `json:"valid_days,omitempty"`
}

ShapeGeneratedSecret is one value (or one atomic pair) to mint when the store holds none. Its fields are secretgen.Spec's, with the lifetime in days so the shape stays JSON-plain; ShapeGeneratedSecret.Spec converts. It is minted once per store name and never overwritten.

func (ShapeGeneratedSecret) Spec added in v1.0.0

Spec is the secretgen.Spec this entry stands for.

type ShapeIdentity added in v0.1.46

type ShapeIdentity struct {
	// Name names the outputs (forge-identity-<name>) and the declaration.
	Name string `json:"name"`
	// Runtime is where the instance runs: [IdentityRuntimeHosted] or
	// [IdentityRuntimeHost].
	Runtime string `json:"runtime"`
	// ShareWith is the env of the SAME project that owns the instance this
	// env's applications live in. Empty when this env owns its instance.
	ShareWith string `json:"share_with,omitempty"`
	// ProjectName is the identity project's name. Empty means the env's
	// project; an adopted instance keeps its legacy name here.
	ProjectName string             `json:"project_name,omitempty"`
	Login       ShapeIdentityLogin `json:"login"`
	// Public is the declaration's own claim that the instance needs a public
	// route (hosted login, an explicit public = True). The control plane also
	// raises it for a consumer outside the platform network, which only it
	// can see.
	Public bool `json:"public,omitempty"`
	// Domain is a custom issuer domain (login.acme.com), instead of the
	// assigned <slug>.reliantidentity.com.
	Domain       string                     `json:"domain,omitempty"`
	Roles        []ShapeIdentityRole        `json:"roles,omitempty"`
	Applications []ShapeIdentityApplication `json:"applications,omitempty"`
	Consumers    []ShapeIdentityConsumer    `json:"consumers,omitempty"`
	MachineUsers []ShapeIdentityMachineUser `json:"machine_users,omitempty"`
	SMTP         *ShapeIdentitySMTP         `json:"smtp,omitempty"`
	SeedUsers    []ShapeIdentitySeedUser    `json:"seed_users,omitempty"`
	// Deliveries are the Secrets the control plane writes this identity's
	// outputs into on a connected cluster. Each is ALSO an entry of
	// Shape.ControlPlaneSecrets, which is how the existing delivery path sees
	// it; this list says which of those entries are the identity's.
	Deliveries []ShapeIdentityDelivery `json:"deliveries,omitempty"`
	// ManagedSecrets are the reserved managed-secret names the control plane
	// writes the outputs under for HOSTED consumers. They are not the env's
	// secrets: nobody sets them.
	ManagedSecrets []string `json:"managed_secrets,omitempty"`
}

ShapeIdentity is the env's managed identity as DECLARED (forge.ManagedIdentity): what the control plane's identity reconciler converges an instance to. It records names only — a SMTP password, a seed user's password and a machine user's key are secret NAMES, and every issued value is delivered, never recorded.

Shape.Identity is nil when the env declares no identity, so every shape recorded before the field encodes and hashes exactly as it did.

type ShapeIdentityApplication added in v0.1.46

type ShapeIdentityApplication struct {
	Name string `json:"name"`
	// Workload is the broker: the workload that runs the sign-in endpoint.
	Workload     string   `json:"workload"`
	Kind         string   `json:"kind"`
	RedirectURIs []string `json:"redirect_uris,omitempty"`
	LogoutURIs   []string `json:"logout_uris,omitempty"`
	// LoginURI is the declared literal override. Empty means the control plane
	// derives it from the env's frontend.
	LoginURI string `json:"login_uri,omitempty"`
}

ShapeIdentityApplication is one OIDC application registered in the instance.

type ShapeIdentityConsumer added in v0.1.46

type ShapeIdentityConsumer struct {
	Workload string `json:"workload"`
	// Application is the application this consumer brokers for. Empty for a
	// consumer that only validates tokens.
	Application string `json:"application,omitempty"`
	// Fields maps an output ([IdentityOutputs]) to the config field it fills
	// when that is not the standard one (the 20%: an app that validates two
	// issuers).
	Fields map[string]string `json:"fields,omitempty"`
	// DeliverOnly says the control plane delivers this consumer's Secrets but
	// forge projects nothing into the workload: it still reads its own config.
	DeliverOnly bool `json:"deliver_only,omitempty"`
	// Omit lists outputs ([IdentityOmittableOutputs]) forge does not project
	// into the workload, for an app whose config has no field for them. The
	// Secrets still carry them.
	Omit []string `json:"omit,omitempty"`
}

ShapeIdentityConsumer is one workload whose typed config the identity's outputs fill.

type ShapeIdentityDelivery added in v0.1.46

type ShapeIdentityDelivery struct {
	Kind             string `json:"kind"`
	Name             string `json:"name"`
	Namespace        string `json:"namespace"`
	Cluster          string `json:"cluster"`
	ConnectedCluster string `json:"connected_cluster"`
}

ShapeIdentityDelivery names one Secret of Shape.ControlPlaneSecrets as belonging to the identity.

type ShapeIdentityKeySecret added in v0.1.46

type ShapeIdentityKeySecret struct {
	Name             string `json:"name"`
	Namespace        string `json:"namespace"`
	Cluster          string `json:"cluster"`
	ConnectedCluster string `json:"connected_cluster"`
}

ShapeIdentityKeySecret is a delivery destination: a Secret on a connected cluster.

type ShapeIdentityLogin added in v0.1.46

type ShapeIdentityLogin struct {
	Mode string `json:"mode"`
}

ShapeIdentityLogin is how users sign in.

type ShapeIdentityMachineUser added in v0.1.46

type ShapeIdentityMachineUser struct {
	Name  string   `json:"name"`
	Roles []string `json:"roles,omitempty"`
	// KeySecret is where its key is delivered. Required for a hosted
	// instance; a host-run one keeps the key in the env's secret store.
	KeySecret *ShapeIdentityKeySecret `json:"key_secret,omitempty"`
}

ShapeIdentityMachineUser is a non-human account whose key is delivered.

type ShapeIdentityRole added in v0.1.46

type ShapeIdentityRole struct {
	Key         string `json:"key"`
	DisplayName string `json:"display_name,omitempty"`
	Group       string `json:"group,omitempty"`
	// MembersFrom is [IdentityMembersConsole] or [IdentityMembersDeclared].
	MembersFrom string `json:"members_from"`
	// Members are the declared members' emails. Only with MembersFrom
	// declared.
	Members []string `json:"members,omitempty"`
}

ShapeIdentityRole is one role of the identity project.

type ShapeIdentitySMTP added in v0.1.46

type ShapeIdentitySMTP struct {
	Host           string `json:"host"`
	Port           int    `json:"port"`
	User           string `json:"user,omitempty"`
	Sender         string `json:"sender"`
	PasswordSecret string `json:"password_secret"`
}

ShapeIdentitySMTP is a bring-your-own mail relay. PasswordSecret is a managed-secret NAME.

type ShapeIdentitySeedUser added in v0.1.46

type ShapeIdentitySeedUser struct {
	Email          string   `json:"email"`
	Roles          []string `json:"roles,omitempty"`
	PasswordSecret string   `json:"password_secret,omitempty"`
}

ShapeIdentitySeedUser is a user created in a local or ephemeral env. PasswordSecret is a secret NAME.

type ShapeObject

type ShapeObject struct {
	Cluster    string `json:"cluster"`
	APIVersion string `json:"api_version,omitempty"`
	Kind       string `json:"kind"`
	Namespace  string `json:"namespace,omitempty"`
	Name       string `json:"name"`
	// Workload is the forge workload the object belongs to
	// (forge.dev/workload), for grouping.
	Workload string `json:"workload,omitempty"`
	// Hash is the digest of the pinned document after secret redaction:
	// what drift detection compares the live object against.
	Hash string `json:"hash"`
	// ConfigHash is the digest of the same document with release-bound
	// image references normalized to their artifact keys. Equal ConfigHash
	// with a different Hash means only images changed.
	ConfigHash string `json:"config_hash,omitempty"`
	// Images maps the release artifact key → pinned digest for every
	// release-bound image the object runs.
	Images map[string]string `json:"images,omitempty"`
	// Stateful marks an object that holds data beyond its own spec — a
	// declared database's objects. The well-known stateful kinds
	// ([statefulKinds]) are recognised without it.
	Stateful bool `json:"stateful,omitempty"`
	// Tree is the object's place in a v2 bundle, "<unit>/<phase>", or ""
	// for an object on a flat cluster. An object whose Tree changes between
	// releases is MOVING between Kustomizations: the one it leaves prunes
	// it, the one it joins creates it. The plan stops on that
	// ([FindingTreeMoved]).
	Tree string `json:"tree,omitempty"`
	// Identity carries the attributes that give an object an EXTERNAL
	// identity, which a deploy can silently replace: a Service's type and
	// load balancer, an Ingress's static IP. Keys are closed
	// ([IdentityKeys]) so the map can never become a place a value hides.
	Identity map[string]string `json:"identity,omitempty"`
}

ShapeObject is one rendered Kubernetes object.

func (ShapeObject) Key

func (o ShapeObject) Key() ObjectKey

Key is the object's ObjectKey.

type ShapeObservabilityPolicy

type ShapeObservabilityPolicy struct {
	Enabled  bool     `json:"enabled"`
	Disabled []string `json:"disabled,omitempty"`
	// OptIn lists the categories the env turned ON that default off (only
	// [ObsOptInReplay] today). Absent = none opted in.
	OptIn []string `json:"opt_in,omitempty"`
	// Retention is the browser-telemetry retention the env declared, present
	// only when it moves any of the defaults (see [ShapeRetention.IsDefault]).
	// A control plane that predates it refuses the shape, so it is part of
	// [BundleFeatureFrontendTelemetry].
	Retention *ShapeRetention `json:"retention,omitempty"`
}

ShapeObservabilityPolicy is the observability an env opted OUT of. Disabled lists categories, sorted; Enabled = false means everything is off (and Disabled then lists every category). Absent policy, or an empty Disabled with Enabled true, means everything is on.

func (*ShapeObservabilityPolicy) Off

func (p *ShapeObservabilityPolicy) Off(category string) bool

Off reports whether category is opted out under this policy.

func (*ShapeObservabilityPolicy) OptedIn

func (p *ShapeObservabilityPolicy) OptedIn(category string) bool

OptedIn reports whether the env turned an opt-in category on. Everything disabled is never opted in.

type ShapeOpenBao added in v1.0.0

type ShapeOpenBao struct {
	Name      string `json:"name"`
	Namespace string `json:"namespace"`
	// Cluster is the kube context it runs in; ConnectedCluster as for
	// [ShapeStoreSecret.ConnectedCluster].
	Cluster          string `json:"cluster"`
	ConnectedCluster string `json:"connected_cluster,omitempty"`
	// Image is the pinned image reference, so a promotion can tell a version
	// change from a configuration change.
	Image string `json:"image"`
}

ShapeOpenBao records that the env declares the self-run OpenBao component (Bundle.openbao), and where. One per env; the component is single-owner per cluster, which the control plane checks across envs.

type ShapeReleaseHealth added in v1.0.0

type ShapeReleaseHealth struct {
	// WindowSeconds is the length of each of the two windows compared: before
	// the promotion and after it.
	WindowSeconds int `json:"window_seconds"`
	// MinRequests is the after-window traffic below which a service is not
	// judged (the verdict is inconclusive, not a pass).
	MinRequests int `json:"min_requests"`
	// MaxErrorRateIncrease is the absolute error-rate rise, as a fraction
	// (0.01 = one percentage point), above which error_rate fires.
	MaxErrorRateIncrease float64 `json:"max_error_rate_increase"`
	// MaxP95Ratio is the after/before p95 ratio above which latency fires.
	MaxP95Ratio float64 `json:"max_p95_ratio"`
	// NewIssueMinEvents is the events a new issue needs before it counts.
	NewIssueMinEvents int `json:"new_issue_min_events"`
	// FailOn are the finding codes that fail a release; the rest warn.
	// Sorted, no duplicates.
	FailOn []string `json:"fail_on"`
}

ShapeReleaseHealth is the env's release-health policy (forge.ReleaseHealth): the thresholds a promotion is judged by, and which findings fail it rather than warn. The control plane's GetReleaseHealth reads it from the env's recorded shape, so the console, the CLI and alerting judge a release by the one declaration.

Shape.ReleaseHealth is nil when every value is the default (DefaultReleaseHealth), so every shape recorded before the field encodes and hashes as it did. A reader treats nil as the defaults.

func DefaultReleaseHealth added in v1.0.0

func DefaultReleaseHealth() ShapeReleaseHealth

DefaultReleaseHealth is the policy of an env that declares none. Latency and restarts warn: both are noisy on a fresh rollout.

It must agree with the defaults of forge.ReleaseHealth in kcl/schema.k; TestReleaseHealthDefaultsAgreeWithKCL fails if they drift.

func (ShapeReleaseHealth) Fails added in v1.0.0

func (h ShapeReleaseHealth) Fails(code string) bool

Fails reports whether finding code fails a release under h.

func (*ShapeReleaseHealth) IsDefault added in v1.0.0

func (h *ShapeReleaseHealth) IsDefault() bool

IsDefault reports whether h is the default policy, which a shape records as absent.

func (ShapeReleaseHealth) Window added in v1.0.0

func (h ShapeReleaseHealth) Window() time.Duration

Window is WindowSeconds as a duration.

type ShapeRetention added in v0.1.46

type ShapeRetention struct {
	ReplayDays        int `json:"replay_days"`
	ErrorsDays        int `json:"errors_days"`
	ProductEventsDays int `json:"product_events_days"`
}

ShapeRetention is how long the platform keeps each browser-telemetry table (forge.ObservabilityRetention). All three are always set: a retention is a decision, and a half-set one would leave a table's TTL to be guessed.

func (*ShapeRetention) IsDefault added in v0.1.46

func (r *ShapeRetention) IsDefault() bool

IsDefault reports whether r keeps every default. A nil r does.

type ShapeSecret

type ShapeSecret struct {
	Name string `json:"name"`
	// Provider is the env's secret provider for it: file | hosted |
	// external | … (forge's provider discriminator).
	Provider string `json:"provider"`
	// DeclaredBy are the workloads that read it.
	DeclaredBy []string `json:"declared_by,omitempty"`
}

ShapeSecret is one declared secret: a NAME, never a value.

type ShapeSigning added in v0.1.46

type ShapeSigning struct {
	Mode        DeliveryMode `json:"mode"`
	Key         string       `json:"key"`
	KeyRef      string       `json:"key_ref,omitempty"`
	OIDCIssuer  string       `json:"oidc_issuer,omitempty"`
	OIDCSubject string       `json:"oidc_subject,omitempty"`
}

ShapeSigning: images and the bundle are signed. Key is the key model; KeyRef is a customer key's PUBLIC reference (never key material); OIDCIssuer and OIDCSubject name the identity a keyless signature must come from.

type ShapeStorePin added in v1.0.0

type ShapeStorePin struct {
	Name    string `json:"name"`
	Version int    `json:"version"`
}

ShapeStorePin pins one store name to a KV version.

type ShapeStoreSecret added in v1.0.0

type ShapeStoreSecret struct {
	// Cluster is the kube context the namespace lives in, the same key the
	// shape's workloads and objects use.
	Cluster string `json:"cluster"`
	// ConnectedCluster is the name `forge cluster connect` registered, which
	// a control plane resolves to the cluster's auth mount. Empty for a
	// cluster no control plane reaches (a self-run store's converge Job
	// addresses the cluster by Cluster).
	ConnectedCluster string `json:"connected_cluster,omitempty"`
	Namespace        string `json:"namespace"`
	// Store is the store kind ([StoreHosted], [StoreOpenBao],
	// [StoreGCPSecretManager]).
	Store string `json:"store"`
	// Project is the Google project of a [StoreGCPSecretManager] binding, and
	// empty for the others.
	Project string `json:"project,omitempty"`
	// Names are the store names the namespace reads, sorted and distinct:
	// exactly the values of its ExternalSecrets' `data`. A name a source only
	// generates and no ExternalSecret reads is not here.
	Names []string `json:"names"`
	// Pins are the KV versions the namespace reads. A name with no pin
	// floats on the latest version.
	Pins []ShapeStorePin `json:"pins,omitempty"`
	// Immutable are names that are never rotated in place; a pin above 1 on
	// one is invalid.
	Immutable []string `json:"immutable,omitempty"`
}

ShapeStoreSecret is the set of store names one namespace of one cluster reads from one store: the unit secretaccess derives a role and a policy for. Several ExternalSecrets of the namespace merge into it.

func (ShapeStoreSecret) Key added in v1.0.0

func (s ShapeStoreSecret) Key() string

Key identifies the binding within a shape.

type ShapeTrustedBuilder added in v0.1.46

type ShapeTrustedBuilder struct {
	Mode     DeliveryMode `json:"mode"`
	Builders []string     `json:"builders"`
}

ShapeTrustedBuilder: only artifacts built by the platform's builder for the org, or by one of Builders (`<kind>:<id>`), are eligible.

type ShapeUnit added in v0.1.46

type ShapeUnit struct {
	Name    string `json:"name"`
	Applier string `json:"applier"`
}

ShapeUnit is one delivery unit and who applies it.

type ShapeUptimeCheck added in v1.0.0

type ShapeUptimeCheck struct {
	// Name is unique within the env. It tags every metric the prober writes.
	Name string `json:"name"`
	// URL is the absolute address to request. Empty exactly when Target is set.
	URL string `json:"url,omitempty"`
	// Target is a hosted frontend's or workload's name: the control plane
	// resolves its platform hostname, and requests Path on it.
	Target string `json:"target,omitempty"`
	// Path is requested on Target's hostname ("/" when empty). Not valid with URL.
	Path string `json:"path,omitempty"`
	// Method is GET or HEAD.
	Method string `json:"method"`
	// IntervalSeconds and TimeoutSeconds schedule the check.
	IntervalSeconds int `json:"interval_seconds"`
	TimeoutSeconds  int `json:"timeout_seconds"`
	// ExpectStatus lists the statuses that mean up: "200", or a class
	// ("2xx"). Empty means [DefaultUptimeExpectStatus].
	ExpectStatus []string `json:"expect_status,omitempty"`
	// ExpectBody is a substring the response body must contain. Empty means
	// the body is not read. Needs Method GET.
	ExpectBody string `json:"expect_body,omitempty"`
	// Source is [UptimeSourceUser] or [UptimeSourceDefault].
	Source string `json:"source"`
}

ShapeUptimeCheck is one outside-in HTTP check of an env: what to request, how often, and what counts as up. The control plane's prober reads it from the env's recorded shape, so the declaration is the only place a check lives.

A check has exactly one target: URL, an absolute http(s) address, or Target, the name of a platform-hosted frontend or workload whose hostname only the control plane knows (the platform allocates it). It carries no credential: the prober dials only public addresses, and a URL that names a private, loopback or cluster-internal host is refused here and again at dial time (pkg/netguard).

Regions are not recorded: v1 probes from the platform's own cluster.

func (ShapeUptimeCheck) Interval added in v1.0.0

func (c ShapeUptimeCheck) Interval() time.Duration

Interval is IntervalSeconds as a duration.

func (ShapeUptimeCheck) StatusOK added in v1.0.0

func (c ShapeUptimeCheck) StatusOK(status int) bool

StatusOK reports whether an HTTP status counts as up for this check.

func (ShapeUptimeCheck) Timeout added in v1.0.0

func (c ShapeUptimeCheck) Timeout() time.Duration

Timeout is TimeoutSeconds as a duration.

type ShapeVulnExempt added in v0.1.46

type ShapeVulnExempt struct {
	ID      string `json:"id"`
	Reason  string `json:"reason"`
	Expires string `json:"expires"`
}

ShapeVulnExempt is one accepted advisory: time-boxed, with its reason.

type ShapeVulnGate added in v0.1.46

type ShapeVulnGate struct {
	Mode          DeliveryMode      `json:"mode"`
	BlockSeverity string            `json:"block_severity"`
	WarnSeverity  string            `json:"warn_severity"`
	BlockKEV      bool              `json:"block_kev"`
	FixableOnly   bool              `json:"fixable_only"`
	Exemptions    []ShapeVulnExempt `json:"exemptions"`
}

ShapeVulnGate: a finding blocks when it is KEV-listed (BlockKEV), or when its severity is at or above BlockSeverity and, with FixableOnly, a fixed version exists. A non-blocking finding at or above WarnSeverity is warned.

type ShapeWorkload

type ShapeWorkload struct {
	Name string `json:"name"`
	// Runtime is forge's runtime discriminator: host | compose | cluster |
	// hosted (and the frontend runtimes for frontends).
	Runtime string `json:"runtime"`
	// Cluster is the kube context for a cluster workload.
	Cluster string `json:"cluster,omitempty"`
	// Artifact is the release artifact key its image comes from, if any.
	Artifact string `json:"artifact,omitempty"`
	Replicas *int   `json:"replicas,omitempty"`
}

ShapeWorkload is one declared workload and where it runs.

type Snapshot added in v0.1.46

type Snapshot struct {
	BootstrapFromPromotionID string               `json:"bootstrap_from_promotion_id,omitempty"`
	BuildEvents              []ArtifactBuildEvent `json:"build_events,omitempty"`
	// Declaration is the canonical declaration-only Shape (Objects empty).
	// Complete bundle indexes must derive from this recorded metadata, not the
	// current checkout. Partial compositions retain the base declaration.
	Declaration       *Shape                          `json:"declaration,omitempty"`
	StatefulWorkloads []string                        `json:"stateful_workloads,omitempty"`
	DeliveryUnits     []DeliveryUnit                  `json:"delivery_units,omitempty"`
	Assets            []SnapshotAsset                 `json:"assets,omitempty"`
	SecretRefs        []BundleSecretRef               `json:"secret_refs,omitempty"`
	Schema            string                          `json:"schema"`
	Project           string                          `json:"project"`
	Environment       string                          `json:"environment"`
	Base              *SnapshotBase                   `json:"base,omitempty"`
	Source            Source                          `json:"source"`
	PolicyDigest      string                          `json:"policy_digest"`
	Selected          []ConsumerKey                   `json:"selected"`
	Closure           []ConsumerKey                   `json:"closure"`
	Artifacts         map[ArtifactID]SnapshotArtifact `json:"artifacts"`
	Consumers         []Consumer                      `json:"consumers"`
	Bindings          []ConsumerBinding               `json:"bindings"`
	Resources         []SnapshotResource              `json:"resources"`
	Prerequisites     map[string]string               `json:"prerequisites,omitempty"`
	Evidence          []CompatibilityEvidence         `json:"evidence,omitempty"`
	Decisions         []CompositionDecision           `json:"decisions"`
}

func ComposeSnapshot added in v0.1.46

func ComposeSnapshot(in CompositionInput) (Snapshot, error)

Compose validates scope/base integrity, overlays selected inventory, preserves unselected spec bytes and provenance, and checks the complete dependency graph. Admission and compatibility-report authority are verified by the caller; this function checks exact subjects/policy and never treats absence as permission.

func (Snapshot) Digest added in v0.1.46

func (s Snapshot) Digest() (string, error)

Digest validates and hashes complete semantic state. Clock/actor/labels cannot affect it because they are not fields in the snapshot vocabulary.

func (Snapshot) Validate added in v0.1.46

func (s Snapshot) Validate() error

Validate rejects incomplete inventories and ambiguous ownership. It never guesses an absent binding from a repository name or live mutable tag.

type SnapshotArtifact added in v0.1.46

type SnapshotArtifact struct {
	Kind      Kind           `json:"kind"`
	Location  string         `json:"location"`
	Content   string         `json:"content"`
	Platforms []string       `json:"platforms,omitempty"`
	Origin    ArtifactOrigin `json:"origin"`
}

SnapshotArtifact is one immutable output. Content is an OCI sha256 digest, a resolved Git object ID, or a native integrity hash for other artifact kinds. Location is the repository/immutable download location, never an OCI tag. Platforms records verified OCI manifest platforms, not requested build flags.

type SnapshotAsset added in v0.1.46

type SnapshotAsset struct {
	Path   string `json:"path"`
	Data   []byte `json:"data"`
	Digest string `json:"digest"`
}

Snapshot is full desired state, even when Selected is a strict subset. Source describes composition; each artifact retains its original provenance. Prerequisites are immutable non-bundled input references (including Secret provider versions). Mutable/unverifiable prerequisites prohibit partial change. SnapshotAsset preserves non-applied bundle assets (dashboards/metrics schema).

func ReadSnapshotAssets added in v0.1.46

func ReadSnapshotAssets(layer []byte) ([]SnapshotAsset, error)

ReadSnapshotAssets validates and reads the non-applied layer inventory.

type SnapshotBase added in v0.1.46

type SnapshotBase struct {
	PromotionID    string `json:"promotion_id"`
	SnapshotDigest string `json:"snapshot_digest"`
	BundleDigest   string `json:"bundle_digest"`
}

SnapshotBase is captured before building. All three fields are required; promotion CAS must compare PromotionID under the environment lock.

type SnapshotResource added in v0.1.46

type SnapshotResource struct {
	// Order preserves the recorded render's apply order within its delivery tree.
	Order     int           `json:"order"`
	Key       ResourceKey   `json:"key"`
	Spec      []byte        `json:"spec"`
	Digest    string        `json:"digest"`
	Tree      string        `json:"tree"`
	Phase     string        `json:"phase"`
	Owners    []ConsumerKey `json:"owners"`
	DependsOn []ResourceKey `json:"depends_on,omitempty"`
}

SnapshotResource freezes the original serialized object bytes. []byte uses base64 in JSON; it intentionally does NOT canonicalize YAML/JSON or restamp pod templates. Secret values must never enter this inventory.

func ReadSnapshotResources added in v0.1.46

func ReadSnapshotResources(layer []byte) ([]SnapshotResource, error)

ReadSnapshotResources validates the archive with the existing shared ingest gate, then returns every desired object with its exact serialized bytes and delivery placement. It does not infer ownership; callers must declare it.

type SnapshotSeal added in v0.1.46

type SnapshotSeal struct {
	Release        string `json:"release"`
	Project        string `json:"project"`
	Environment    string `json:"environment"`
	SnapshotDigest string `json:"snapshot_digest"`
	BundleDigest   string `json:"bundle_digest"`
}

SnapshotSeal is the immutable (release, environment) association. Snapshot identity is computed before release labels/OCI packaging, avoiding hash cycles.

func (SnapshotSeal) Validate added in v0.1.46

func (s SnapshotSeal) Validate() error

Validate enforces complete immutable seal addressing. Matching to a cut and verified bundle and checking org scope happens inside the ledger transaction.

type Source

type Source struct {
	// Repo is the repository the source is fetched from.
	Repo string `json:"repo"`
	// Ref is the tag/branch/sha as DECLARED (what a human edits).
	Ref string `json:"ref"`
	// Subdir is the path within the repo the component builds from.
	Subdir string `json:"subdir,omitempty"`
	// Commit is what Ref resolved to when the release was cut — the actual
	// content address.
	Commit string `json:"commit,omitempty"`
}

Source is the content-addressed identity of a source-built artifact.

type Stage

type Stage struct {
	Kind StageKind `json:"kind"`
	// Name is which one: a check's name, an environment name, an artifact.
	Name   string      `json:"name,omitempty"`
	Status StageStatus `json:"status"`
	// Env is the environment a promote or rollout stage concerns. Empty on
	// a build, a cut, or a check that is not env-specific.
	Env string `json:"env,omitempty"`
	// PromotionID ties a promote or rollout stage to its ledger entry.
	PromotionID string     `json:"promotion_id,omitempty"`
	StartedAt   *time.Time `json:"started_at,omitempty"`
	FinishedAt  *time.Time `json:"finished_at,omitempty"`
	URL         string     `json:"url,omitempty"`
	Summary     string     `json:"summary,omitempty"`
}

Stage is one step of a run, as a reader sees it. It is a VIEW, assembled on read from the ledger — the release cut, the promotions, the gates carrying this run's id, and each promotion's derived rollout. Nothing constructs a Stage to store it.

func (Stage) Validate

func (s Stage) Validate() error

Validate checks a stage read back from a backend.

type StageKind

type StageKind string

StageKind says what part of a run a stage is. CLOSED.

The vocabulary covers the pipeline the primitives describe — build → cut → check → promote → rollout — where the narrative's "converge" and "verify" are not separate kinds:

  • CONVERGE is the rollout: StageRollout is one promotion's progress toward the digests it pinned, derived on read from the deployment rows. Naming it separately would imply a second stored thing.
  • VERIFY is a check: a post-deploy smoke or health probe is recorded as a Gate and appears as StageCheck, named "verify" or "smoke".

So every stage is either DERIVED from the ledger (cut, promote, rollout) or recorded as a gate (build, check). Nothing else is stored.

const (
	// StageBuild is an artifact build. Recorded as a gate.
	StageBuild StageKind = "build"
	// StageCut is the release cut. Derived: the release row.
	StageCut StageKind = "cut"
	// StageCheck is any check — lint, test, smoke, wait, verify, a manual
	// sign-off. Recorded as a gate; Name carries which.
	StageCheck StageKind = "check"
	// StagePromote is one environment binding. Derived: the promotion row.
	StagePromote StageKind = "promote"
	// StageRollout is one promotion converging. Derived on read from the
	// deployment rows against that promotion's frozen pins.
	StageRollout StageKind = "rollout"
)

func ParseStageKind

func ParseStageKind(s string) (StageKind, error)

ParseStageKind reads a stage kind and refuses anything outside the set.

func (*StageKind) UnmarshalJSON

func (k *StageKind) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes through ParseStageKind.

func (StageKind) Valid

func (k StageKind) Valid() bool

Valid reports whether k is one of StageKinds.

type StageStatus

type StageStatus string

StageStatus is a stage's verdict. CLOSED. It is GateStatus plus StageRunning, because a stage can be observed WHILE it happens — a rollout in progress, a check still running — and a gate cannot: a gate is recorded once its check has finished.

const (
	// StagePassed, StageFailed, StageSkipped and StageErrored mean exactly
	// what the matching [GateStatus] values mean.
	StagePassed  StageStatus = StageStatus(GateStatusPassed)
	StageFailed  StageStatus = StageStatus(GateStatusFailed)
	StageSkipped StageStatus = StageStatus(GateStatusSkipped)
	StageErrored StageStatus = StageStatus(GateStatusError)
	// StageRunning: the stage has started and has not reached a verdict.
	// A run with any running stage is not finished, which is why a "did
	// the whole run pass" check cannot treat this as either outcome.
	StageRunning StageStatus = "running"
)

func ParseStageStatus

func ParseStageStatus(s string) (StageStatus, error)

ParseStageStatus reads a stage status and refuses anything outside the set.

func StageStatusOfGate

func StageStatusOfGate(g Gate) StageStatus

StageStatusOfGate is the one mapping from a recorded gate to its stage status. A gate has always finished, so it never maps to StageRunning, and a gate whose stored status was unrecognised maps to StageErrored — the same refusal to read an uninterpretable claim as a pass.

func (*StageStatus) UnmarshalJSON

func (s *StageStatus) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes through ParseStageStatus.

func (StageStatus) Valid

func (s StageStatus) Valid() bool

Valid reports whether s is one of StageStatuses.

type StoreCluster added in v1.0.0

type StoreCluster struct {
	// Cluster is the kube context.
	Cluster string
	// ConnectedCluster is the name `forge cluster connect` registered; empty
	// when no control plane reaches the cluster.
	ConnectedCluster string
}

StoreCluster names a cluster the way the shape's other lists do.

type StoreProjection added in v1.0.0

type StoreProjection struct {
	StoreSecrets             []ShapeStoreSecret
	GeneratedSecrets         []ShapeGeneratedSecret
	OpenBao                  *ShapeOpenBao
	ExternalSecretsOperators []ShapeExternalSecretsOperator
}

StoreProjection is the four store-secret fields of a Shape, as one value a builder passes around and Shape.WithStoreSecrets applies.

func ProjectStoreSecrets added in v1.0.0

func ProjectStoreSecrets(in StoreRender) (StoreProjection, error)

ProjectStoreSecrets projects a render into the shape's store-secret fields.

It is deterministic: the same render gives the same projection, whatever order the records arrived in, and the result is canonical. A render with no store-backed ExternalSecret and no component projects to the zero StoreProjection, which encodes as nothing.

It refuses what would make the shape ambiguous rather than picking a winner: a store-backed Secret that reaches no cluster (there is no role to derive for it), two sources that disagree about a pin or about how a name is generated, and a value generated into Google Secret Manager.

type StoreRender added in v1.0.0

type StoreRender struct {
	// Secrets are the render's declared ExternalSecrets. Those without a
	// Source are out of band and project to nothing.
	Secrets                  []RenderedStoreSecret
	OpenBao                  *RenderedOpenBao
	ExternalSecretsOperators []RenderedExternalSecretsOperator
}

StoreRender is everything ProjectStoreSecrets reads.

type TreeObject added in v0.1.46

type TreeObject struct {
	Cluster   string
	Unit      string
	Phase     Phase
	Kind      string
	Namespace string
	Name      string
	// APIVersion identifies ESO objects; SecretSync is their
	// [AnnotationSecretSync] value (the shadow/deferred opt-outs), exactly
	// as the direct applier reads them. A string, not the annotation map, so
	// TreeObject stays comparable.
	APIVersion string
	SecretSync string
	// DeadlineSeconds is the object's own time allowance, when it states one: a
	// Deployment's progressDeadlineSeconds, a Job's activeDeadlineSeconds.
	// Zero means it states none and the default for its kind applies.
	DeadlineSeconds int
}

TreeObject is one object's placement, as PlanTrees needs it.

type Worktree

type Worktree struct {
	// Key is forge's devstack key: "" for the primary checkout, the
	// sanitized linked-worktree name otherwise. It is the same key the
	// checkout's dev-stack port block is held under.
	Key string `json:"key"`
	// Label is what a human calls the checkout: the branch, else the
	// directory's base name. Never a path.
	Label string `json:"label,omitempty"`
	// Host names the machine: a daemon id, or a hash of the hostname.
	// Never a raw hostname or path.
	Host string `json:"host,omitempty"`
	// Path is the checkout's absolute path, for DISPLAY on the machine that
	// built it. It is stripped from anything sent to a hosted ledger
	// ([Provenance.ForHosted]): a path names a user's home directory.
	Path string `json:"path,omitempty"`
}

Worktree identifies one checkout. None of it is authority.

Jump to

Keyboard shortcuts

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