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:
- The WRITE path is strict. ParseGateStatus, ParseStageKind and ParseStageStatus refuse anything outside the set, so a typo in a --gate flag fails before anything is recorded.
- The READ path is lenient where history demands it. GateStatusFromStored maps a status written before the set closed to GateStatusError and keeps the original on Gate.RawStatus. Old entries stay renderable, an uninterpretable claim never renders as a pass, and nothing is silently discarded.
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
- Variables
- func BindingReference(a SnapshotArtifact, b ConsumerBinding) string
- func BuildInputDigest(in BuildInputs) (string, error)
- func BundleClusterPath(cluster string) string
- func BundleDocFeatures() []string
- func BundlePathSegment(s string) string
- func BundleTreePath(cluster, unit string, phase Phase) string
- func CanonicalRepo(url string) string
- func ChartMarker(chart string) string
- func CheckRecut(existing, requested Release) error
- func CheckSnapshotSeal(r Release, doc BundleDoc, seal SnapshotSeal) error
- func CommitText(s string, max int) string
- func ConfigDigest(shape Shape) (string, error)
- func ConsumerDigest(s Snapshot, key ConsumerKey) (string, error)
- func DescribeObjectChange(from, to ShapeObject, parts []PartChange) string
- func FlatCluster(cluster string) bool
- func ForceRecreateKind(kind string) bool
- func HashDocument(doc any) (string, error)
- func HostID() string
- func IdentityIssuer(slug string) string
- func IdentitySlug(envID, project, env string, hashChars int) string
- func ImagesIn(renderedDoc string, pins map[string]string) map[string]string
- func IsESOKind(apiVersion, kind string) bool
- func IsIdentityKind(kind string) bool
- func IsNestedCheckout(dir string) bool
- func MigrationVersion(file string) (uint64, bool)
- func NormalizeImages(doc any, pins map[string]string) any
- func NormalizeTargets(targets []string) []string
- func ObjectExternalIdentity(kind string, body any) map[string]string
- func ObjectHashes(doc any, pins map[string]string) (hash, configHash string, err error)
- func PlacementPassiveKind(kind string) bool
- func PlacementRuntimeKind(kind string) bool
- func RecreatableKind(kind string) bool
- func RedactSecrets(doc any) any
- func RolloutWorkloadKind(kind string) bool
- func SecretPrerequisiteKey(ref BundleSecretRef) string
- func SecretSyncAwaited(annotations map[string]string) bool
- func SetExplicitRollout(doc map[string]any) bool
- func SnapshotDelivery(s Snapshot) ([]BundleClusterTree, []BundleTree, error)
- func SourcemapsArtifactName(frontend string) string
- func SourcemapsFrontend(artifact string) (string, bool)
- func SourcemapsRepository(registryBase, frontend string) (string, error)
- func SourcemapsTag(buildID string) (string, error)
- func SpecDigest(b []byte) string
- func StatefulKind(kind string) bool
- func SummarizeObject(kind string, body any) (summary string, parts map[string]string)
- func TreeBudget(phase Phase, objects []TreeObject) int
- func ValidDigest(d string) bool
- func ValidIdentitySlug(s string) bool
- func ValidImageTag(tag string) bool
- func ValidObjectID(s string) bool
- func ValidateObjectSummaries(shape Shape, summaries []ObjectSummary) error
- func ValidateSnapshotBindings(s Snapshot) error
- func ValidateSnapshotLayer(s Snapshot, layer []byte) error
- func ValidateSnapshotTransition(base *Snapshot, candidate Snapshot) error
- func ValidateUptimeURL(raw string) error
- type Actor
- type AppliedManifest
- type Apply
- type ApplyOutcome
- type ApplyState
- type ApplyStatus
- type ApplyWorkload
- type Artifact
- type ArtifactBuildEvent
- type ArtifactChange
- type ArtifactChangeKind
- type ArtifactID
- type ArtifactOrigin
- type Attest
- type BuildInputs
- type BuildReceipt
- type BuildSource
- type BundleClusterTree
- type BundleDoc
- type BundleHealthCheck
- type BundlePins
- type BundleReader
- type BundleRecord
- type BundleSecretRef
- type BundleTree
- type CaptureOptions
- type Changes
- type ChartChange
- type ChartOp
- type ChartPlan
- type ChartUnplanned
- type ClosureError
- type Commit
- type CommitComparison
- type CommitComparisonState
- type CommitRange
- type CompatibilityEvidence
- type CompositionDecision
- type CompositionInput
- type Consumer
- type ConsumerBinding
- type ConsumerDependency
- type ConsumerKey
- type DBQueryPolicy
- type DeliveryMode
- type DeliveryUnit
- type DriftObservation
- type EnvKind
- type EnvRecord
- type ExternalArtifact
- type FindingClass
- type FluxFinding
- type Gate
- type GateStatus
- type Git
- type ImageChange
- type Kind
- type KindChangedeprecated
- type LayerObject
- type LayerViolation
- type LocalSession
- type ManifestsLayerError
- type ManifestsLayerSummary
- type MigrationManifest
- type MigrationSet
- type Mode
- type ObjectChange
- type ObjectChangeKind
- type ObjectComparison
- type ObjectDelta
- type ObjectKey
- type ObjectMove
- type ObjectSummary
- type OwnedKept
- type OwnedRemoval
- type PartChange
- type PassedMigration
- type Phase
- type PlacementBinding
- type PlacementEdge
- type PlacementItem
- type PlacementKey
- type PlacementObject
- type PlacementPrerequisites
- type Plan
- type PlanBasis
- type PlanFinding
- type PlanInput
- type Promotion
- type PromotionKind
- type Provenance
- type Release
- type RenderedExternalSecretsOperator
- type RenderedGeneratedSecret
- type RenderedOpenBao
- type RenderedStore
- type RenderedStoreSecret
- type RenderedStoreSource
- type ResourceKey
- type Run
- type RunTimeline
- type RuntimeChange
- type SecretStage
- type Shape
- func (s Shape) Canonical() Shape
- func (s Shape) DashboardsFor(database string) ShapeDatabaseDashboards
- func (s Shape) Encode() ([]byte, error)
- func (s Shape) HasFrontendTelemetry() bool
- func (s Shape) HasOptionalParts() bool
- func (s Shape) SecretNamed(name string) (ShapeSecret, bool)
- func (s Shape) StoreBindings(store string) []secretaccess.Binding
- func (s Shape) Validate() error
- func (s Shape) WithStoreSecrets(p StoreProjection) Shape
- type ShapeAlert
- type ShapeAlertRoute
- type ShapeApprovals
- type ShapeBundleKinds
- type ShapeControl
- type ShapeControlPlaneSecret
- type ShapeDataAccess
- type ShapeDatabase
- type ShapeDatabaseDashboards
- type ShapeDeliveryPolicy
- type ShapeDiff
- type ShapeExternalSecretsOperator
- type ShapeFrontendTelemetry
- type ShapeFunnel
- type ShapeGeneratedSecret
- type ShapeHealthGate
- type ShapeIdentity
- type ShapeIdentityApplication
- type ShapeIdentityConsumer
- type ShapeIdentityDelivery
- type ShapeIdentityKeySecret
- type ShapeIdentityLogin
- type ShapeIdentityMachineUser
- type ShapeIdentityRole
- type ShapeIdentitySMTP
- type ShapeIdentitySeedUser
- type ShapeObject
- type ShapeObservabilityPolicy
- type ShapeOpenBao
- type ShapeReleaseHealth
- type ShapeRetention
- type ShapeSecret
- type ShapeSigning
- type ShapeStorePin
- type ShapeStoreSecret
- type ShapeTrustedBuilder
- type ShapeUnit
- type ShapeUptimeCheck
- type ShapeVulnExempt
- type ShapeVulnGate
- type ShapeWorkload
- type Snapshot
- type SnapshotArtifact
- type SnapshotAsset
- type SnapshotBase
- type SnapshotResource
- type SnapshotSeal
- type Source
- type Stage
- type StageKind
- type StageStatus
- type StoreCluster
- type StoreProjection
- type StoreRender
- type TreeObject
- type Worktree
Constants ¶
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.
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.
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).
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.
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.
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.
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).
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.
const ( SourcemapsPublish = "publish" SourcemapsPublic = "public" SourcemapsOff = "off" )
Source map handling, ShapeFrontendTelemetry.Sourcemaps.
const ( IdentityType = "type" IdentityLoadBalancerIP = "load_balancer_ip" IdentityLoadBalancerClass = "load_balancer_class" IdentityStaticIP = "static_ip" )
Identity keys. Closed.
const ( // BundleFeaturePlacementPrerequisites gates required mixed dependency evidence. // Unlike browse-only additions, Limit must NEVER discard this safety contract. // // v2 (graph version 2) is v1 with the shared passive rule // (PlacementPassiveKind): every kind but the runtime kinds, ConfigMap and // Secret is applied inventory, where a v1 reader refused any kind outside // a closed list. v1 was never advertised by a control // plane and no v1 graph was ever recorded, so it is retired rather than // kept: a reader that names only v1 is refused before composition, never // handed a graph it would reject or under-check. BundleFeaturePlacementPrerequisites = "placement-prerequisites-v2" // 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" // BundleFeatureDatabaseDashboards is Shape.Databases: whether live // dashboard panels may query each managed database, and the row cap, // timeout and concurrency they run under. A control plane that predates it // cannot enforce the limits, so a shape carrying them is written only to // one that names the feature; one that does not reads every database as // dashboards-disabled, which is the fail-closed reading. BundleFeatureDatabaseDashboards = "shape-database-dashboards" // 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" // BundleFeatureHealthGate is ShapeDeliveryPolicy.HealthGate: the envs a // release must be proven good in before this env takes it // (forge.HealthGate). A control plane that predates it gates nothing, so // a shape carrying it is written only to one that names the feature, // which is the control plane that enforces it. BundleFeatureHealthGate = "shape-health-gate" )
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.
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).
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.
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 left unhashed rather than making a developer wait, and CaptureProvenance says so with ErrUnhashed: 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.
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 )
const ( DefaultDashboardRowCap = 1000 MaxDashboardRowCap = 10000 DefaultDashboardTimeoutMs = 5000 MinDashboardTimeoutMs = 100 MaxDashboardTimeoutMs = 10000 DefaultDashboardMaxConcurrency = 2 MaxDashboardMaxConcurrency = 4 )
Bounds of the limits. KCL (kcl/schema.k DatabaseDashboards) and the ManagedDatabase spec (pkg/deploy/v1alpha1) hold the same numbers; TestDatabaseDashboardsDefaultsAgreeWithKCL keeps them from drifting.
const ( SigningKeyOrgKMS = "org_kms" SigningKeyCustomer = "customer" SigningKeyKeyless = "keyless" )
Signing key models (ShapeSigning.Key).
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.
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.
const ( RuleJobTTL = "flux-job-ttl" RuleJobUnhashed = "flux-job-unhashed" RuleForceData = "flux-force-on-data" )
Rule IDs. Stable: lint output, suppressions and tests name them.
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).
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).
const ( IdentityAppNativeBroker = "native_broker" IdentityAppOIDCWeb = "oidc_web" )
Application kinds (ShapeIdentityApplication.Kind).
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).
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.
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.
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.
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 )
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" )
const ( DeployPhasePreRollout = "pre-rollout" DeployPhasePostRollout = "post-rollout" )
The values AnnotationDeployPhase accepts.
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" // FindingDeliveryGrant: a self-hosted delivery (`applier = "flux"`) // grants one of its apply identities a permission it lacked, because // this release needs it. The deploy re-runs the bootstrap with the // caller's own credential to make the grant, so the grant is reviewed // and approved with the rest of the plan. FindingDeliveryGrant = "delivery_grant" )
Finding codes. Closed: a consumer switches on them, and a stop-class code is what an operator names to acknowledge it.
const ( SectionObjects = "objects" SectionStateful = "stateful_deletions" SectionLB = "lb_identity" SectionTrees = "tree_moves" SectionImages = "images" SectionConfig = "config" SectionSecrets = "secrets" SectionDrift = "drift" // SectionDeliveryGrants is the apply-identity grants a self-hosted // delivery's deploy makes (FindingDeliveryGrant). SectionDeliveryGrants = "delivery_grants" )
Plan sections, in display order (§8.6).
const ( MaxTargets = 64 MaxTargetBytes = 253 )
MaxTargets bounds a promotion's Targets, and MaxTargetBytes each name.
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.
const ( MinHealthWindow = time.Minute MaxHealthWindow = 6 * time.Hour )
Release-health window bounds.
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.
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).
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.
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.
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.
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.
const ApplierFlux = "flux"
ApplierFlux is Shape.Applier for an env with no control plane that a Flux forge installs in its own cluster reconciles from the recorded bundle (Bundle.applier = "flux"). Like an undeclared env, its whole bundle is applied by a reconciler and pruned; unlike one, the reconciler is that in-cluster Flux and never a hub.
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).
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>`.
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.
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`.
const BundleSchema = "forge.dev/bundle/v1"
BundleSchema is the BundleDoc schema this package reads and writes.
const ComponentOpenBao = "openbao"
ComponentOpenBao is the LabelComponent value of every object the OpenBao component renders.
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.
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.
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.
const ESOGroup = "external-secrets.io"
ESOGroup is the API group of ExternalSecret and SecretStore.
const KubernetesJobCompleteV1 = "kubernetes-job-complete-v1"
KubernetesJobCompleteV1 names ordinary completion of the exact sealed Job.
const LabelComponent = "forge.dev/component"
LabelComponent names the platform component an object belongs to.
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.
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).
const MaxComparedCommits = 250
MaxComparedCommits caps the commits a comparison lists. Total still counts them all when every composed range could.
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.
const MinAlertInterval = time.Minute
MinAlertInterval is the evaluation-interval floor a shape may record.
const ObsOptInReplay = "replay"
ObsOptInReplay is session replay: the browser SDK's recorded DOM events, dropped by the platform unless the env opts in.
const PlacementPrerequisitesVersion = 2
PlacementPrerequisitesVersion is the graph version BundleFeaturePlacementPrerequisites names (placement-prerequisites-v2).
const PruneMarker = "[prune]"
PruneMarker is the prefix an owned-object prune finding's detail carries.
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.
SharedVariant is the sole Digests key of a Mode ModeShared artifact: one digest serves every environment.
const SnapshotBundleSchema = "forge.dev/bundle/v2"
SnapshotBundleSchema embeds a full immutable desired-state snapshot.
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.
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).
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 ¶
var ApplyStatuses = []ApplyStatus{ApplySucceeded, ApplyFailed, ApplyTimedOut}
ApplyStatuses is the closed set.
var ControlPlaneSecretKinds = []string{ ControlPlaneSecretIdentity, ControlPlaneSecretIdentityJWKS, ControlPlaneSecretIdentityMachineKey, ControlPlaneSecretTelemetryToken, }
ControlPlaneSecretKinds is every kind a control-plane secret can have.
var DBQueryPolicies = []DBQueryPolicy{DBQueryOpen, DBQueryAudited, DBQueryDeny}
DBQueryPolicies is the closed set, in a stable order.
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.
var EnvKinds = []EnvKind{EnvPersistent, EnvPreview, EnvSelfManaged, EnvLocal}
EnvKinds is the closed set, in a stable order.
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.
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.
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.
var ErrUnhashed = errors.New("working tree not hashed")
ErrUnhashed is returned by CaptureProvenance, alongside the provenance it did capture, when the checkout names a commit but the built tree was not hashed: the dirty content exceeded CaptureMaxDirtySize, or hashing it exceeded CaptureTreeTimeout. The provenance is still usable — its commit, branch and dirty flag are recorded and Provenance.Unhashed is true — so a best-effort caller tests for this with errors.Is and carries on. It is an error rather than a quiet empty Tree so that no caller mistakes "not hashed" for a hash: an empty tree is never a silent success.
var FindingClasses = []FindingClass{ClassInfo, ClassWarn, ClassStop}
FindingClasses is the closed set, least to most severe.
var FindingCodes = []string{ FindingObjectAdded, FindingObjectChanged, FindingObjectRemoved, FindingImageChanged, FindingConfigChanged, FindingSecretNeeded, FindingStatefulDeletion, FindingLBIdentityChange, FindingTreeMoved, FindingDrift, FindingUnknown, FindingDeliveryGrant, }
FindingCodes is the closed set of codes.
var GateStatuses = []GateStatus{GateStatusPassed, GateStatusFailed, GateStatusSkipped, GateStatusError}
GateStatuses is the closed set, in a stable order (for error messages and for the hosted schema's CHECK, which must list exactly these).
var HealthFindings = []string{HealthErrorRate, HealthLatency, HealthNewIssue, HealthRegressedIssue, HealthAlertFiring, HealthWorkloadRestarts}
HealthFindings is every finding code, in a stable order.
var IdentityKeys = []string{IdentityType, IdentityLoadBalancerIP, IdentityLoadBalancerClass, IdentityStaticIP}
IdentityKeys is the closed set of ShapeObject.Identity keys.
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.
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.
var Kinds = []Kind{KindOCI, KindNPM, KindGoModule, KindFile, KindGit}
Kinds is the closed set, in a stable order (for error messages and for the hosted schema's CHECK, which must list exactly these).
var Modes = []Mode{ModeShared, ModeVariant, ModeSource}
Modes is the closed set.
var ObsCategories = []string{ ObsCategoryBrowser, ObsCategoryLogs, ObsCategoryMetadata, ObsCategoryMetrics, ObsCategoryProfiles, ObsCategoryTraces, }
ObsCategories is every opt-out category, sorted.
var ObsOptIns = []string{ObsOptInReplay}
ObsOptIns is every opt-in category, sorted.
var Phases = []Phase{PhasePre, PhaseRollout, PhasePost}
Phases is the closed set, in apply order.
var PlanSections = []string{SectionStateful, SectionLB, SectionTrees, SectionDeliveryGrants, SectionObjects, SectionImages, SectionConfig, SectionSecrets, SectionDrift}
PlanSections is the closed set, in display order.
var SecretStages = []SecretStage{SecretStageBootstrap, SecretStageStorePre, SecretStageStore, SecretStageSecrets}
SecretStages is the closed set, in apply order.
var StageKinds = []StageKind{StageBuild, StageCut, StageCheck, StagePromote, StageRollout}
StageKinds is the closed set, in PIPELINE order: a run's stages sort by time, but this is the order the kinds happen in.
var StageStatuses = []StageStatus{StagePassed, StageFailed, StageRunning, StageSkipped, StageErrored}
StageStatuses is the closed set, in a stable order.
var StoreKinds = []string{StoreHosted, StoreOpenBao, StoreGCPSecretManager}
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 ¶
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 ¶
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
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 ¶
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
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 ¶
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
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 ¶
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
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
ForceRecreateKind reports whether the bundle build stamps kind with the per-object force annotation.
func HashDocument ¶
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
IdentityIssuer is the issuer of a slug: https://<slug>.reliantidentity.com.
func IdentitySlug ¶ added in v0.1.46
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 ¶
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
IsESOKind reports whether apiVersion/kind is an External Secrets Operator SecretStore or ExternalSecret.
func IsIdentityKind ¶ added in v0.1.46
IsIdentityKind reports whether a control-plane secret kind is one a managed identity derives.
func IsNestedCheckout ¶
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
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 ¶
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
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
ObjectExternalIdentity is the shared planning projection of destructive external identity fields. Both normal bundle producers and ingest use it.
func ObjectHashes ¶
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 PlacementPassiveKind ¶ added in v1.0.9
PlacementPassiveKind reports whether a cluster object of kind is applied inventory in a mixed bundle (a hosted record is always an item): it has no readiness item of its own. That is every kind except the runtime kinds, ConfigMap (bound to the items of its namespace) and Secret (never in the bundle). A Service, a Role or RoleBinding, a route or a policy attachment affects ordinary rollout readiness only through the healthy Pods of the component that uses it; a CRD, an admission policy or a scheduling class changes the cluster's API; a CronJob's spec is applied and starts nothing; a custom resource is its controller's input, and the controller is an item of its own. No kind is refused for being unknown.
forge's producer and a control plane's reader share this rule, at the forge the control plane pins, so the two can never disagree about one bundle.
func PlacementRuntimeKind ¶ added in v1.0.9
PlacementRuntimeKind reports whether kind runs something on a cluster. A mixed bundle observes each such object (Deployment, StatefulSet, DaemonSet, Job) or refuses it (ReplicaSet, Pod: owned by a controller in a real render).
func RecreatableKind ¶
RecreatableKind reports whether kind may be deleted and re-applied by the immutable-field recovery.
func RedactSecrets ¶
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
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
SecretSyncAwaited reports whether a deploy waits for an ExternalSecret with these annotations.
func SetExplicitRollout ¶ added in v0.1.46
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
SourcemapsArtifactName is the release artifact name for a frontend's maps.
func SourcemapsFrontend ¶ added in v0.1.46
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
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
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
SpecDigest addresses the exact original serialized bytes, not canonical YAML.
func StatefulKind ¶
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
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 ¶
ValidDigest reports whether d is a canonical `sha256:<64 hex>` digest.
func ValidIdentitySlug ¶ added in v0.1.46
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
ValidImageTag is the OCI distribution-spec tag grammar.
func ValidObjectID ¶
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
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
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
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
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 ¶
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.
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 ¶
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.
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"`
// PlacementPrerequisites is sealed dependency evidence; absent is unknown.
PlacementPrerequisites *PlacementPrerequisites `json:"placement_prerequisites,omitempty"`
// 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 ¶
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.
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 removes optional browse-only parts the reader does not decode. PlacementPrerequisites is a safety contract and is NEVER removed: producers must refuse unsupported readers before effects rather than drop dependencies. Omitted browse-only fields use omitempty; empty unknown fields still fail.
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
// contains filtered or unexported fields
}
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 ¶
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 ¶
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.
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.
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
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
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 ¶
ParseEnvKind reads a stored or wire kind, refusing anything outside the set.
func (EnvKind) Placed ¶
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 ¶
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 ¶
UnmarshalJSON refuses an empty or unknown kind.
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 ¶
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 ¶
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.
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 ¶
UnmarshalJSON refuses an empty or unknown kind.
type KindChange
deprecated
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 ( // 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 ¶
UnmarshalJSON refuses an empty or unknown mode.
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.
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
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
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.
type PlacementBinding ¶ added in v1.0.5
type PlacementBinding struct {
Item PlacementKey `json:"item"`
Object PlacementObject `json:"object"`
ManifestHash string `json:"manifest_hash"`
JobContract string `json:"job_contract,omitempty"`
}
PlacementBinding binds an item to a same-parse ShapeObject.Hash, not a live spec hash. JobContract proves only ordinary Kubernetes successful completion, never SQL effects.
type PlacementEdge ¶ added in v1.0.5
type PlacementEdge struct {
Predecessor PlacementKey `json:"predecessor"`
Dependent PlacementKey `json:"dependent"`
}
PlacementEdge requires Predecessor readiness before Dependent activation.
type PlacementItem ¶ added in v1.0.5
type PlacementItem struct {
Key PlacementKey `json:"key"`
Workload string `json:"workload"`
}
PlacementItem associates an actual placement with its release component.
type PlacementKey ¶ added in v1.0.5
type PlacementKey struct {
Runtime string `json:"runtime"`
Actuator string `json:"actuator"`
Cluster string `json:"cluster"`
Namespace string `json:"namespace"`
Kind string `json:"kind"`
Name string `json:"name"`
}
PlacementKey identifies an actual placement independently of its logical workload.
type PlacementObject ¶ added in v1.0.5
type PlacementObject struct {
Cluster string `json:"cluster"`
APIVersion string `json:"api_version"`
Kind string `json:"kind"`
Namespace string `json:"namespace"`
Name string `json:"name"`
}
PlacementObject locates an object in the verified sealed manifest inventory.
type PlacementPrerequisites ¶ added in v1.0.5
type PlacementPrerequisites struct {
Version int `json:"version"`
Items []PlacementItem `json:"items"`
Edges []PlacementEdge `json:"edges"`
Bindings []PlacementBinding `json:"bindings"`
}
PlacementPrerequisites is sealed dependency evidence, not an assertion of readiness. Absence is unknown; a present empty graph declares independence.
func (*PlacementPrerequisites) Validate ¶ added in v1.0.5
func (p *PlacementPrerequisites) Validate() error
Validate checks the complete typed graph. The reader must additionally match every descriptor/hash and the complete inventory against admitted sealed bytes.
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 ¶
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) Unacknowledged ¶
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 ¶
VerifyDigest recomputes the digest and reports whether it matches Digest.
func (Plan) WithCharts ¶ added in v0.1.46
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.
func (Plan) WithFindings ¶ added in v1.0.14
func (p Plan) WithFindings(fs ...PlanFinding) (Plan, error)
WithFindings is p with fs added — findings a caller learns after the plan was built (a self-hosted delivery's grants) — sorted, and the digest recomputed over them, so an approval of the returned plan approves them too.
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 ¶
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 ¶
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
Moved reports whether this promotion moved component: every component for a whole-release promotion, only its Targets for a scoped one.
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, which
// CaptureProvenance reports as [ErrUnhashed]. 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.
A checkout with a commit always comes back with its Tree hashed or with an error: ErrUnhashed when a capture bound stopped the hash (the returned provenance is still complete otherwise), any other error when git failed or ctx ended. An empty Tree with a nil error is never returned for a commit.
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; CaptureProvenance returned ErrUnhashed). 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 ¶
ArtifactNames returns the artifact names, sorted.
func (Release) SameContent ¶
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 ¶
SharedDigests is the image-name → digest pin set a promotion freezes: every shared OCI artifact, and nothing else.
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
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.
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"`
// Databases is what the env declares about its managed databases beyond
// their workload entries: today whether live dashboard panels may query
// each one, and under which limits (ManagedDatabase.dashboards). One entry
// per database that ENABLES something, sorted by name. Omitted when none
// does, so every shape recorded before the field encodes and hashes exactly
// as it did; a reader treats a database with no entry as dashboards
// DISABLED, never open ([Shape.DashboardsFor]). A control plane that
// predates it refuses the whole shape, so it is a bundle-document feature
// ([BundleFeatureDatabaseDashboards]).
Databases []ShapeDatabase `json:"databases,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 (a
// transitional state, see [ApplierForge]), or "flux" for one an in-cluster
// Flux reconciles ([ApplierFlux]). 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
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 ¶
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) DashboardsFor ¶ added in v1.0.9
func (s Shape) DashboardsFor(database string) ShapeDatabaseDashboards
DashboardsFor is the dashboards declaration in force for the named database: the zero value (disabled) when the shape names no such database or it declares none. This is the one question a consumer asks of the field, and the one that cannot be answered open by accident.
func (Shape) Encode ¶
Encode is the canonical JSON of the shape, refused when it exceeds MaxShapeBytes.
func (Shape) HasFrontendTelemetry ¶ added in v0.1.46
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
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 ¶
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 ¶
func (s ShapeControlPlaneSecret) Key() string
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 ShapeDatabase ¶ added in v1.0.9
type ShapeDatabase struct {
Name string `json:"name"`
// Dashboards is the database's live-dashboard declaration
// (ManagedDatabase.dashboards in KCL). Nil means disabled.
Dashboards *ShapeDatabaseDashboards `json:"dashboards,omitempty"`
}
ShapeDatabase is what the env's shape records about one managed database, beyond the workload entry every database already has. It exists only for a database that DECLARES something the control plane enforces; a database that declares nothing has no entry, so a shape recorded before the field encodes and hashes exactly as it did.
type ShapeDatabaseDashboards ¶ added in v1.0.9
type ShapeDatabaseDashboards struct {
Enabled bool `json:"enabled"`
RowCap int `json:"row_cap,omitempty"`
TimeoutMs int `json:"timeout_ms,omitempty"`
MaxConcurrency int `json:"max_concurrency,omitempty"`
}
ShapeDatabaseDashboards is the limits a dashboard panel query runs under on one database, and whether panels may query it at all.
THE CONTROL PLANE ENFORCES FROM THIS RECORDED SHAPE, not from a client's request, so a client cannot choose a looser cap or a longer timeout than the env declared. FAIL CLOSED: a database with no entry, an entry with no dashboards, and a dashboards record whose enabled is false (or absent from the JSON) all read as disabled (Shape.DashboardsFor).
func DefaultDatabaseDashboards ¶ added in v1.0.9
func DefaultDatabaseDashboards() ShapeDatabaseDashboards
DefaultDatabaseDashboards is what `forge.DatabaseDashboards {enabled = True}` declares: the limits a database that opted in and said nothing else gets. DISABLED is not this value: a disabled database records nothing.
func (*ShapeDatabaseDashboards) Resolved ¶ added in v1.0.9
func (d *ShapeDatabaseDashboards) Resolved() ShapeDatabaseDashboards
Resolved returns d with every unset limit filled. A nil or disabled d resolves to the zero value: disabled, no limits.
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"`
// HealthGate is the promotion health gate (forge.HealthGate). Nil is its
// default — mode enforce, no environments named, so no gate — and is how
// a policy that declares none encodes, so every shape recorded before
// the field still hashes as it did. A reader that predates it never sees
// it: it is the bundle feature [BundleFeatureHealthGate].
HealthGate *ShapeHealthGate `json:"health_gate,omitempty"`
}
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 for an env that delivers through a control plane (the only reader of a recorded policy, so it is what a nil policy in a shape means). A control that cannot block a deploy and costs the customer nothing is enforced (provenance/SBOM); a control that can refuse a deploy is audited until the env turns it up.
Signing and flux_verify are audit: the platform signs every recorded bundle with the org's key and verifies it server-side, recording the verdict, and enforce (spec.verify, and Promote refusing an unsigned bundle) is the env's opt-in once every rollback target is signed.
It must agree with the defaults of forge.DeliveryPolicy in kcl/schema.k for an env with a control plane; TestDeliveryPolicyDefaultsAgreeWithKCL fails if they drift.
func DefaultSelfHostedDeliveryPolicy ¶ added in v1.0.14
func DefaultSelfHostedDeliveryPolicy() ShapeDeliveryPolicy
DefaultSelfHostedDeliveryPolicy is the default of an env with no control plane: as DefaultDeliveryPolicy, except that signing and flux_verify are off, because nothing signs a self-hosted env's bundles yet. Such an env that declares forge.DeliveryPolicy {} records this, which is not the shape-level default, so the shape says plainly that nothing is signed.
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
func (p *ShapeDeliveryPolicy) Resolved() ShapeDeliveryPolicy
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 ¶
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.
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
func (g ShapeGeneratedSecret) Spec() secretgen.Spec
Spec is the secretgen.Spec this entry stands for.
type ShapeHealthGate ¶ added in v1.0.14
type ShapeHealthGate struct {
Mode DeliveryMode `json:"mode"`
After []string `json:"after"`
}
ShapeHealthGate: a promotion of release R to this env is admitted only once every env in After has proved R good — the control plane holds a release-health verdict of PASS for its newest promotion of R there (the verdict `forge obs check` reports as exit 0). The order is declared, never inferred: an empty After gates nothing, whatever the mode. `enforce` refuses an unproven promotion unless it carries the recorded override; `audit` admits it and records what enforce would have refused.
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"`
// 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.
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
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 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.
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.
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 ¶
ParseStageKind reads a stage kind and refuses anything outside the set.
func (*StageKind) UnmarshalJSON ¶
UnmarshalJSON decodes through ParseStageKind.
func (StageKind) Valid ¶
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.
Source Files
¶
- apply.go
- build_inputs.go
- build_receipt.go
- bundle.go
- bundle_features.go
- bundle_trees.go
- capture.go
- commits.go
- compare.go
- composition.go
- composition_contract.go
- data_access.go
- database_dashboards.go
- delivery_policy.go
- diff.go
- doc.go
- env.go
- fluxsafe.go
- gate.go
- hash.go
- identity.go
- manifests_layer.go
- migrations.go
- normalize.go
- object_summary.go
- phase.go
- placement_prerequisites.go
- plan.go
- plan_charts.go
- promotion.go
- provenance.go
- release.go
- release_health.go
- run.go
- secret_stage.go
- snapshot.go
- snapshot_bundle.go
- snapshot_identity.go
- snapshot_layer.go
- snapshot_projection.go
- snapshot_transition.go
- snapshot_validate.go
- sourcemaps.go
- store_secrets.go
- store_secrets_render.go
- uptime.go