Virtualization · Platform Engineering

What a Golden Path Guarantees: The Platform Contract as a Versioned Interface

A golden path is a contract only when it declares its inputs, the runtime facts it provides, and the platform versions it tracks. What that declaration contains, and what breaks when a piece of it is missing.

A repository template is easy to produce and almost worthless on its own. Copy it once, and the copy is a fork: it diverges, and nothing in the copy remembers where it came from. What separates a golden path from a scaffold is that the path keeps a live relationship with the platform that issued it, and that relationship is a contract with named parts. The parts are the input schema, the runtime guarantees, the compatibility set, and the support lifetime. Drop any one of them and the artifact degrades into a starting point rather than a path.

The four clauses a path has to declare

The input schema is the easiest clause to get right and the one most often over-specified. It lists every value the platform will consume when instantiating the path: a workload name, an environment class, a container image reference, a replica range, a set of port numbers. Each input carries a type, a default, and a statement of whether it is required. The schema is a contract in the strict sense because the platform validates against it and refuses to render otherwise.

The runtime guarantees are harder and more valuable. They are the facts the tenant may rely on after instantiation: a stable DNS name inside the platform’s service zone, an identity issued to the workload so it can reach the platform’s secret store without a static credential, a scrape target registered with the platform’s metrics collector, and a log stream routed to the shared aggregation endpoint. These are the promises that make a path worth taking, because they are exactly the things a team would otherwise wire by hand and wire inconsistently. They must be enumerated, because an unwritten guarantee is not a guarantee: a platform that emits metrics for some paths and not others has no observability contract, only an observability habit.

The compatibility set is what the version number on the path actually names. It declares which platform API versions, which base image digest, and which runtime version the path has been validated against. It is not the same as the version of the template repository. A template at version 4.2.0 might target platform API v1beta1 and a specific base image digest, and the fact that the path renders successfully against a newer platform is untested until the compatibility set says so.

The support lifetime closes the contract. It states how long the version will receive fixes, what the platform does when the lifetime ends, and what the tenant is expected to do. Without a lifetime, “supported” means “supported until someone changes their mind”, and tenants plan accordingly.

Why instantiation must record provenance

The single mechanism that keeps a path from becoming a fork is a recorded provenance stamp written at instantiation time and never edited by hand. The stamp carries the path identifier, the path version, and a digest over the rendered output.

The digest is the part that earns its keep. Comparing the current working tree against the digest tells the platform whether a tenant has modified generated files, which is the normal case: generated configuration is rarely left alone. The distinction matters because the two upgrade strategies have different costs. A path whose generated files are untouched can be re-rendered from the new version and diffed mechanically. A path whose generated files have drifted requires a three-way merge, and three-way merges of generated YAML are where upgrades stall.

A minimal input schema makes the contract concrete:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "additionalProperties": false,
  "required": ["name", "environment", "image"],
  "properties": {
    "name": { "type": "string", "pattern": "^[a-z][a-z0-9-]{2,30}$" },
    "environment": { "enum": ["development", "staging", "production"] },
    "image": { "type": "string", "pattern": "^[^@]+@sha256:[0-9a-f]{64}$" },
    "replicas": { "type": "integer", "minimum": 1, "maximum": 20, "default": 2 },
    "expose": { "type": "boolean", "default": false }
  }
}

Three details in that schema do real work. additionalProperties: false makes the schema closed, so a tenant cannot smuggle a parameter the platform has not tested. The image pattern requires a digest rather than a tag, because a mutable tag makes every instantiation non-reproducible and every rollback ambiguous. The environment enumeration is a closed set, which lets the platform derive resource defaults, network policy, and retention rules from a single value instead of exposing each of them as a separate knob.

That last point is the central design move in a platform contract: parameters that can be derived should not be exposed. Every exposed parameter multiplies the number of configurations the platform team must keep working, and configurations grow combinatorially while the team does not.

The trade between expressiveness and the test matrix

The obvious objection to a closed schema is that it cannot express every workload. That objection is correct, and the response is to treat expressiveness as a budget rather than a goal.

Consider a path with ten independent boolean inputs. The configuration space has 1024 points. Testing all of them is not feasible for a small platform team, so some fraction is untested, and the untested fraction is where the guarantees silently fail: the combination that renders a service without the scrape annotation, or with a network policy that blocks its own health check. A path with three enumerated classes and two booleans has twelve points, all of which can be exercised in a pipeline that instantiates each one and asserts the runtime facts.

The cost of the narrower schema is real: workloads that do not fit must either be reshaped to fit or be taken off the path and supported as an exception. Reshaping is often cheap when the path is well designed, because most apparent variety in internal services is variety in naming rather than in structure. When it is not cheap, the escape hatch has to be explicit, and an explicit escape hatch with a name and an owner is better than a hundred untested parameters.

There is a second axis: derivation depth. A path can compute more from less, but each derivation rule is itself code that must be correct for every combination of the inputs it reads. Deriving the replica floor from the environment class is one rule with three cases. Deriving it from environment class, request rate, and a criticality flag is a function with a much larger domain, and the platform now owns the semantics of that function forever.

Where the guarantees actually break

Contract failures are rarely dramatic. They appear as a path that renders and runs but does not deliver a clause it claimed.

The most common is a guarantee that is documented but not wired. The path’s description says the workload receives an identity for the secret store, and the rendered output contains no binding for it, or contains one that references a role the platform never creates. Nothing fails at instantiation. The failure appears later, when a tenant’s first attempt to fetch a secret returns an authorization error, and the diagnosis starts from the assumption that the guarantee holds.

The second is a floating reference. A base image referenced by tag, a platform module referenced by a branch name, a pipeline step referenced by a moving alias. Each of these makes the path’s output depend on the time of instantiation, so two tenants on the same path version can end up with different runtimes, and reproducing an old environment becomes impossible. Pinning by digest is the only defense, and it must be enforced by the schema rather than by convention.

The third is default drift. Changing a default value in the schema changes the output for every future instantiation while leaving existing instantiations untouched, which is usually the intended behaviour but is invisible unless the platform can report the distribution of values in use. Without that report, a default change looks safe and later turns out to have moved a majority of tenants.

The fourth is the untested combination, described above, which surfaces as a guarantee that holds for the environment class the platform team uses internally and fails for the one it does not.

What the contract deliberately excludes

A platform contract covers the boundary between the platform and the workload. It does not cover the workload’s internal architecture, its data model, or its choice of language. Extending the path to constrain those things produces a contract that the platform cannot test and the tenant cannot renegotiate, and it is the point where a golden path turns into an application framework.

The same boundary logic applies to enforcement. A declared contract is a claim; whether tenants actually follow the path is a separate mechanism, handled by the admission layer described in admission control as the enforcement point. Keeping the declaration and the enforcement in separate systems is what allows the declaration to be read as documentation and the enforcement to be read as policy, with different change rates and different audiences. The versioning discipline that keeps the declaration honest over time is taken up in propagating template changes across an instantiated fleet.

References

  1. JSON Schema Core Specification, draft 2020-12 — JSON Schema Organization
  2. Semantic Versioning 2.0.0 — SemVer
  3. Team Topologies: Organizing Business and Technology Teams for Fast Flow — Skelton and Pais, IT Revolution Press, 2019