Appendix B: The contract, end to end
The resolution contract in one file.
Appendix A made the whole shape runnable. This one narrows to a single mechanism: the contract from Resolve by contract, enforced the way Fail before apply describes. One file: a producer publishes a typed value, a consumer names an address, and a resolver returns the bound value or refuses.
It is placeholder and self-contained. No provider SDKs, state backend, or network. The catalogue is an in-memory array standing in for stack outputs, a registry, or a generated manifest; that source is the implementation detail the contract hides.
The type
The address locates a value; the type decides whether that value is the capability the consumer asked for. Names alone create accidental compatibility, so NetworkRef names the network identity and its attachable subnets, and a bare string[] cannot pass as one.
type Context = 'staging' | 'prod'
// A logical infrastructure type: the network identity and the
// subnets a workload may attach to, not the raw values behind them.
type NetworkRef = {
readonly kind: 'NetworkRef'
readonly vpcId: string
readonly privateSubnetIds: readonly string[]
}
// The check that stops a string[] from resolving as a network.
function isNetworkRef(v: unknown): v is NetworkRef {
return (
typeof v === 'object' &&
v !== null &&
(v as NetworkRef).kind === 'NetworkRef' &&
typeof (v as NetworkRef).vpcId === 'string' &&
Array.isArray((v as NetworkRef).privateSubnetIds)
)
}What the producer publishes
The producer owns the published values; where they live is not the consumer's concern. Each entry is keyed and scoped to a context, so the same producer and key can resolve in one context and be absent in another. It also records its source: the stack output, registry entry, or manifest key behind the address. The resolver names those sources when two collide on one address. requires records what a producer must resolve before it can publish — the edge the resolver walks to catch a cycle.
type Addr = { producer: string; key: string }
type Entry = Addr & {
context: Context
source: string // the stack output, registry entry, or manifest key
requires?: Addr[] // must resolve before this entry can publish
value: unknown // the backing value; its type is checked at resolve
}
// The environment producer publishes network.primary in two contexts.
// This array stands in for stack outputs, a registry, or a manifest.
const catalogue: Entry[] = [
{
producer: 'environment',
key: 'network.primary',
context: 'staging',
source: 'env-stack',
value: {
kind: 'NetworkRef',
vpcId: 'vpc-123',
privateSubnetIds: ['subnet-a', 'subnet-b'],
},
},
{
producer: 'environment',
key: 'network.primary',
context: 'prod',
source: 'env-stack',
value: {
kind: 'NetworkRef',
vpcId: 'vpc-789',
privateSubnetIds: ['subnet-x', 'subnet-y'],
},
},
]The consumer request
The consumer selects the address (a producer namespace and a key), the context it resolves in, and the type it expects back — not the backing value. It carries the type check with it, so a value that resolves as the wrong shape is refused before the consumer can use it.
type Request<T> = {
producer: string
key: string
context: Context
expects: string // type name, for the refusal message
is: (v: unknown) => v is T // the type check itself
}
// workload-api asks the environment for its primary network in prod.
const request: Request<NetworkRef> = {
producer: 'environment',
key: 'network.primary',
context: 'prod',
expects: 'NetworkRef',
is: isNetworkRef,
}The resolver
The resolver is the release-path refusal point. locate walks the address and the publish graph, and fails on the four that live there. resolve adds the type check. Every refusal names the part of the contract that failed before any provider call runs: the producer, plus the context on address failures, the key once the producer is known, and the expected type on the wrong-type refusal.
class ContractError extends Error {}
// Address and publish-graph failures: unknown producer, missing key,
// ambiguous binding, cycle.
function locate(
cat: Entry[],
producer: string,
key: string,
context: Context,
path: string[],
): Entry {
const at = `${producer}/${key}`
if (path.includes(at)) {
throw new ContractError(`cycle: ${[...path, at].join(' -> ')} in ${context}`)
}
const byProducer = cat.filter((e) => e.producer === producer)
if (byProducer.length === 0) {
throw new ContractError(
`unknown producer: '${producer}' is not published in ${context}`,
)
}
const matches = byProducer.filter(
(e) => e.key === key && e.context === context,
)
if (matches.length === 0) {
const elsewhere = byProducer.some((e) => e.key === key)
throw new ContractError(
elsewhere
? `missing key: '${at}' exists, but not in ${context}`
: `missing key: '${at}' is not published`,
)
}
if (matches.length > 1) {
const sources = matches.map((e) => e.source).join(', ')
throw new ContractError(
`ambiguous binding: '${at}' in ${context} is published by ${matches.length} sources: ${sources}`,
)
}
const [entry] = matches
// The producer publishes only once its own dependencies resolve.
for (const dep of entry.requires ?? []) {
locate(cat, dep.producer, dep.key, context, [...path, at])
}
return entry
}
// Address failures first, then the type check.
function resolve<T>(cat: Entry[], req: Request<T>): T {
const entry = locate(cat, req.producer, req.key, req.context, [])
if (!req.is(entry.value)) {
throw new ContractError(
`wrong type: '${req.producer}/${req.key}' resolved, but not as ${req.expects}`,
)
}
return entry.value
}Resolve, or refuse
A good composition resolves and the consumer proceeds. Then five compositions are wrong on purpose, and each stops at a named refusal before anything is applied.
// Resolves: apply may proceed.
const net = resolve(catalogue, request)
console.log(net.vpcId, net.privateSubnetIds)
// vpc-789 [ 'subnet-x', 'subnet-y' ]
// refuse runs one bad composition and prints its refusal, so the file
// runs top to bottom instead of stopping at the first thrown error.
const refuse = (cat: Entry[], req: Request<NetworkRef>) => {
try {
resolve(cat, req)
} catch (e) {
console.log((e as Error).message)
}
}
// Each of the following is refused before any provider call.
// unknown producer
refuse(catalogue, { ...request, producer: 'enviroment' })
// unknown producer: 'enviroment' is not published in prod
// missing key: published in staging, not in prod
refuse([catalogue[0]], request)
// missing key: 'environment/network.primary' exists, but not in prod
// wrong type: the value resolves, but it is a bare string[]
refuse(
[
{
producer: 'environment',
key: 'network.primary',
context: 'prod',
source: 'env-stack',
value: ['subnet-x', 'subnet-y'],
},
],
request,
)
// wrong type: 'environment/network.primary' resolved, but not as NetworkRef
// ambiguous binding: a copied stack kept the original producer identity,
// so two sources publish the same address with different values
refuse(
[
...catalogue,
{
producer: 'environment',
key: 'network.primary',
context: 'prod',
source: 'env-stack-copy',
value: {
kind: 'NetworkRef',
vpcId: 'vpc-999',
privateSubnetIds: ['subnet-p', 'subnet-q'],
},
},
],
request,
)
// ambiguous binding: 'environment/network.primary' in prod is published
// by 2 sources: env-stack, env-stack-copy
// cycle: the environment cannot publish until a workload value resolves
refuse(
[
{
producer: 'environment',
key: 'network.primary',
context: 'prod',
source: 'env-stack',
requires: [{ producer: 'workload-api', key: 'network-config' }],
value: catalogue[1].value,
},
{
producer: 'workload-api',
key: 'network-config',
context: 'prod',
source: 'workload-api-stack',
requires: [{ producer: 'environment', key: 'network.primary' }],
value: null,
},
],
request,
)
// cycle: environment/network.primary -> workload-api/network-config
// -> environment/network.primary in prodMap it back to the series
NetworkRefandisNetworkRefare the typed capability a producer publishes, from Resolve by contract. The type carries the semantic promise; the subnet IDs are only the backing values.Requestnames the address (a producer and a key), the context, and the expected type. The consumer selects the address, and resolution returns the value bound to it in the current context.locateandresolveare the resolver gate from Fail before apply, and the five refusals are its five contract failures.- The
cataloguearray is the transport, not the public contract: stack outputs, a registry, or a manifest, kept behind the address so the consumer never reads it directly.
This leaves out, on purpose, everything that is not the contract: the tier cut from Start with the shape, lifecycle membership from Define tier membership, the binding rules that govern shared-surface mutation in Fail before apply, wave ordering, and the deletion order from Re-cut the system. Appendix A runs those together in the companion repository. This is only the seam where a consumer asks and the resolver answers or refuses.