A stack waits only on the stacks it names, and a chain deploys one layer per run
Decision record 0056
Record 0009 reserved the row state queued and said the fact behind it is a deployment record with status queued and the blocking stack ids in its payload. The brief’s rule for dependencies was: refuse a tick whose upstream has pending changes that are not ticked, and when both are ticked, deploy the first layer and re-trigger resolve after it. Build plan slice 4.4 adds the lesson of the earlier internal dashboard that inspired Sluiceway (onboarding log, hurdle 23): only refuse on things that can hold state, and a gate that never applies must not stay green and silent. This record fixes part 1.
Decision
stacks[].dependsOnis a list of stack ids, exact, no globs. An entry without a name gives its list to every stack in its path, and entries add up likeinputs. A stack waits only on the stacks it names, not on theirs.- Every id is checked against discovery at config load. A stack that discovery did not find, one that
ignoreleaves out, the stack itself and a circle are config errors, socheck,scanand every other mode say so. This is the second half of the lesson: a dependency that could never hold anything back would be a gate that stays green and says nothing, so it is refused before it exists. A circle is named once, from its smallest stack id. - A tick is refused only while a dependency’s row is pending and nobody ticked it in the same run. That is the first half of the lesson, read for Sluiceway: the only thing that can change what a stack reads from its dependency is a change of the dependency that has not gone out yet. A dependency that is in sync, whose preview failed, or whose row is of a state this version does not know holds nothing back. A refusal travels down a chain in one run: a stack whose dependency was refused waits on a change that is not going out either.
- A refused tick gets a note, not a comment. The box is cleared, and a line under the row names the dependencies: “this tick started nothing: it depends on network:prod, which has a change waiting. Tick both to deploy them in order, or deploy network:prod first.” No record is made, the job stays green, and the log says it. It is a fact about the stacks, like
deploys: false(0051), not about the person, so nobody is mentioned (0018 keeps its comment for the tick rule). - Ticks in one chain deploy one layer at a time. Every allowed tick gets its deployment record in the same
resolverun, in stack id order, first the layer that has nothing to wait for and then the rest. The first layer goes intomatrixas always. Every other stack gets a record with statusqueuedandbehindin its payload: the stack ids of its dependencies that were ticked in this run or have an open deployment of any run. It is not handed on. The same goes for a single tick whose dependency is deploying: it is queued behind that deploy. - The payload gains
behind, and its version stays 1. A key is added only to a queued record, as a non-empty list of stack ids. A version that does not know it reads the record as an open deployment, which is what it is. - A queued row is a deploying row that waits. Its marker state is
queued, its text isqueued behind **app:prod** · ticked by alice · [run](...), and it has no box. It is placed in the Deploying section and counted as deploying on the counts line, in the header state, in the destroy sign and in the result file. No header picture of its own in part 1 (docs/later.md). A scan makes the row from the queued record, as it makes a deploying row from an open one, so a queued row survives a scan. settlestarts the next layer by starting the workflow again. A job cannot hand a later job of its own run a new matrix, andapplydeploys only a record of its own run (0035). So when a queued record anywhere is ready,settledispatches this same workflow on this same ref, the way it starts a full scan (0035, slice 2.6). The workflow’sresolvejob runs onworkflow_dispatchas well as onissuesfor this. Aresolvethat no issue edit started does one thing: it starts every ready queued stack.- Ready means that every stack in
behindwent out. The newest record of each is read. Open: the queued record waits.successorinactive(a rehearsal included, so a rehearsed chain rehearses on): it goes on.failure,error, or no record at all: it can never start. - A queued stack starts under a record of the run that starts it.
resolvecreates a new record with the hash and the ticker of the queued one,runof its own run and nobehind, hands it on inmatrix, and then ends the queued record asinactivewith the words “started in a later run”. Like a rehearsal, that status is told apart by its words, and unlike a rehearsal it is not even a line of the trail: the new record says everything. The ticker is not looked up again. The tick rule was checked when the tick was made, and every recordapplytakes is trusted the same way. - A queued record outlives its run on purpose. The rule of 0003, that an open record whose run is over gets
error, does not apply to it, because between thesettleof one run and theresolveof the next it is the only thing that says the stack is taken. It is ended instead, bysettlefor its own run and by the late read of any writer for any run, asfailurewith the new reason “a stack it depends on did not deploy” (0022), as soon as a stack it waits behind did not go out. The rows then show the failure line, so a chain that stops is never silent. applynever deploys a queued record.resolvenever hands one on. If one is handed on by hand,applyleaves it alone and the job goes red, as for a record of another run.
Consequences
- A tick is now also a request to deploy after the dependencies. The hash it approved is the one checked when the stack finally deploys. When a dependency’s deploy changes what the stack reads, the fresh preview gives another hash,
applyrefuses it as moved, and the ticker gets the comment of 0051. That is right: the tick approved the old diff. - A layer costs a workflow run of its own. On a hosted runner the
resolveandsettlejobs take seconds. - Every dispatch of the workflow now starts a
resolvejob as well. WithoutdependsOnin the config it loads the config, finds nothing to do and asks GitHub nothing. - A queued stack whose dependencies went out while nobody was going to start it waits for the next
resolverun. That happens when the workflow’sresolvejob does not run onworkflow_dispatch, when a dispatch failed, and in the short moment where a dependency ends betweenresolvereading it as open and writing the queued record. Ticking the rescan box starts aresolverun and so starts it. The row keeps saying what it waits behind. Detecting it and saying more is left for part 2. - The row state is still a cache, with one exception that can only hold a deploy back:
resolvereads a dependency’s row state to see whether it is pending. It never previews, so the live body is the one source it has. A wrong state can at worst refuse a tick that could have gone out, or let one out that a person could have ticked by hand anyway.
This record amends 0003 (the payload key behind, the inactive status “started in a later run”, and the exception to the run rule), 0009 (the queued state is now used, and the exception to “nothing is decided from the row state”), 0019 and 0035 (resolve on a dispatch, and apply refusing a queued record) and 0022 (a new deploy failure reason).