Supersedes in part 0168
(decisions 4 and 5) and
0169 (decision 3), and
amends 0289
(where the resource kinds of decision 4 and the groups of decision 5 are declared). Builds on
0173 (secret names and values),
0227 (deferred engines),
0294 (the member shape)
and 0295 (the build
shape). Parts of the superseded pages that this record does not name stand.
Context
ADR 0168 decision 4 gives each repository one rootrunway.yaml, and its decision 5 lists the sections that file holds: deployables and providers, resources and scaling, the release policy, domains and the secrets map. Topology, which packs logical services into deployables, is decided in ADR 0169. The JSON Schemas in tools/schemas/ already go further. runway-root.schema.json, runway-app.schema.json and runway-service.schema.json describe runway format version 2, which splits the root from member runways. In version 2 both member schemas require a member key that names the member, and the service schema also holds facts about where a service runs: for each environment, its deployable, its isolate flag and its resources. The app schema has none of those three fields. A service that needs a dedicated cache or search index has no field that says so or why, and the facts about placement are split between the member and the root.
Those are two different facts. What a member needs changes with its code. Where a member runs, and which processes it shares, changes with the environment and the account. Splitting them lets a member state its requirements beside its code, and lets the root decide placement once for each environment.
Decision
-
A member runway states WHAT. A member that needs runtime resources has a
runway.yamlat its member root. The member is the one whose folder holds the file, so the file does not name it. It declaresversion: 3and three things:requires: one entry per resource the member needs beyond what gen derives fromstackra.manifest.json. An entry names anengineand whether the resource isdedicated. A dedicated resource also has areason, one line that says why the member cannot share a resource.secrets: the names of the secrets the member reads. The file holds names only, never values (ADR 0173).environments.<name>: the member’s sizes and values for each environment. These are a port, the database storage and extensions, the cache size, replicas and eviction policy, queue settings, and theconfigvalues that set the member’s config classes for that environment. Those values are the last layer of configuration.
member,deployable,isolate,group,cpu,memoryorinstances. Those are root facts. -
The root runway states WHERE and HOW PACKED. The repository’s root
runway.yamlholds the providers, destinations, shared pools, domains, defaults and the topology of each environment. A topology has:mode:monolith,groupedorsplit;isolate: the members that keep their own database server and network policy;default: the group for members that no group lists;groups: for each group, itsmembers, itscpu,memoryandinstances(minandmax), andworkers.splitwhere its workers run apart.
- Groups are process allocations. Each group is a process allocation inside the one generated Encore application (ADR 0091). This is the one-app rule of 0289 decision 5: the groups are not separate Encore applications. Whether a group scales on its own is not decided here.
- A dependency with one user belongs to that user. A dependency that only one member needs is declared by that member, not by the root. The identity provider (ADR 0206) belongs to the identity member, and a development container belongs to the driver package that needs it.
-
Engines follow ADR 0227.
requires.<resource>.engineaccepts only an engine that ADR 0227 neither defers nor rejects. A dedicated search resource takespostgres, and the schema refuses the deferred engines. A dedicated search engine returns only through a new record, once the revival trigger of ADR 0227 has fired. - Both files are block-style YAML. They follow the repository’s YAML rule: no flow mappings, and flow sequences only for short scalar lists on one line.
Consequences
- A requirement sits beside the code that needs it, and a dedicated resource cannot be declared without a reason.
- The root is the one file that decides where members run and how they are packed, so it is the file a reviewer checks for placement.
- Adding a member to an environment is one line in one group of the root file. The validator fails until the member is placed, so nothing runs unplaced. This is the cost of having the root pack.
- A group’s size has one source, the root, because member runways carry no sizes for groups.
Enforcement
Today the only check that reaches these files ischeckYamlFlowMaps in tools/scripts/validate-repository.mjs, which covers the block-style rule for repository YAML. No runway file is committed yet. The schemas on main describe format version 2: tools/schemas/runway-root.schema.json, tools/schemas/runway-app.schema.json and tools/schemas/runway-service.schema.json fix version at 2, and both member schemas require member, so a version 3 member runway fails them. Editors read these schemas, and the static Worker serves them; pnpm validate does not check runway files against them yet.
Planned, tracked in STA-339:
- planned — STA-339: the three schemas are replaced by version 3 schemas. The member schemas fix
versionat 3, have nomemberkey and refuse every root key; the root schema gains the topology groups of decision 2.pnpm validatechecks every runway file against them. It fails when a member runway holds a root key, when a grouped environment does not place each member exactly once, when an engine is not allowed by ADR 0227, and whendedicatedhas noreason. - planned — STA-339: gen fails when a
requiresentry has no driver.