A tick outside the deploy window waits for it instead of going out
Decision record 0104
Amends 0056 (a queued record may wait for a time and not for a stack, and the record that starts one carries no
window), 0091 (the list of payload keys), 0095 (a deploy on merge waits for the window too), 0077 (in auto mode the schedule runsresolvebefore the scan) and 0003 (the payload keywindow). Built as slice 5.39.
Every tool Sluiceway is compared with has a way to say when a deploy may go out: Argo CD has sync windows, Pulumi sells scheduled operations, Scalr and env0 have run schedules. The need behind them is the same (issue 230): nobody wants a deploy going out at 17:00 on a Friday, during a sale, or while the on-call is asleep. Sluiceway already has the shape for it. A tick is a request, and a deployment record is how a request waits (0056).
Decision
deployWindowsnames when the stacks of a repo may go out, in the dashboard zone. A window is days of the week, a start and an end:{ days: [monday, tuesday, wednesday, thursday], from: "09:00", to: "17:00" }. The days are full names in lower case. The times areHH:MMon a 24 hour clock, and24:00is the end of the day. The start is inside and the end is outside, so a window to17:00closes as the clock turns 17:00. A window is written indashboard.timeZone(0089), the zone the people who tick live in, and daylight saving is the zone’s: 09:00 is 09:00 on the wall in January and in July. The end comes after the start, so a window over midnight is two windows, one to24:00and one from00:00on the next day. Empty, the default, is any time, and a repo without the key is byte for byte what it was.stacks[].deployWindowssets a stack’s own windows, in place of the top level’s. An empty list lets its stacks go out at any time while the repo has windows, for the stack that is boring or urgent. An entry with a name wins over one without, as fortickers.- A tick outside the window is not refused. Its record waits for the window. The tick is judged now, by the tick rule, the dependencies and the cap as always. The record is opened now, on the commit the run checked out, with everything the tick approved: the hash, the ticker, drift and the value fingerprint. It carries the added payload key
window: trueand is not handed on. It is a queued stack (0056) that waits for a time and not for a stack: its row saysqueued for the deploy window, which opens 2026-09-28 09:00 UTC+2 · ticked by alice, has no box, and counts as deploying. The time is in the dashboard zone with its offset, as every time that stands alone (0089). The version of the payload stays 1: a reader that does not know the key reads an open deployment, which is what it is. - The run that falls inside the window starts it, through the fresh preview and the hash check. A
resolvethat no issue edit started, the one a dispatch or a schedule starts, starts every waiting record whose window is open now, exactly as it starts a queued stack whose dependencies went out (0056): a new record of its own run with the hash, the ticker, drift,onMergeand the fingerprint of the waiting one, and nowindow; the waiting record ends asinactive, “started in a later run”; the new record goes inmatrix, andapplypreviews again and deploys only on the same hash. So what goes out is what the person ticked, or nothing, and a change that moved in the meantime is refused with the comment of 0051. In auto mode the schedule now runsresolvebefore the scan, as a dispatch does, so the scheduled scan inside the window is what starts it. In the split workflow theresolvejob runs on the schedule as well as on a dispatch. A window is judged against the config on the default branch at that moment, as the tick rule is (0018): a repo that widens or removes a window moves what waits with it. - A record behind a stack is held to the window when it is started.
planDeploysdecides the layers first (0056). A stack in the first layer whose window is closed getswindow: true. A stack behind another getsbehindand nowindow, whatever the clock said when it was ticked: when the stacks it waited behind went out, the run that would start it checks the window live and starts it only inside one. Its row saysqueued behind **network:prod**, and for the deploy window, which opens ...while the window is closed, from the record and the config of that moment.settledispatches the next layer only inside the window, because a run started outside could not start it. - A deploy on merge waits for the window too (0095). The scan of the merge opens the record now, attributed to whoever merged, with
onMergeandwindow, and hands nothing on. The run inside the window starts it, and the started record carriesonMerge, so the row and the trail still saymerged by. The deploy after a merge from the dashboard (0054) is the ticker’s, and waits the same way. A destroy on a stack set to on-merge still waits for a tick, window or not: the window opening deploys nothing that no person asked for. - A drift repair waits like any tick. The record carries
drift, and the run inside the window checks drift again before it compares (0055, 0091). - A ticked destroy waits for the window like any tick, and goes out when it opens. The person looked at the destroy when they ticked it, and the hash check refuses a change that moved. What must not go out unattended is a destroy nobody asked for, which is 0095’s rule for on-merge and is unchanged.
applynever deploys a record withwindow. If one is handed on by hand,applyleaves it alone and the job goes red, as for a queued record (0056).settleand every late read leave such a record open past its run, as they leave a queued record.- What this is not. A window is what this repo’s file says about this repo’s stacks, in the open, in a reviewed file. It is not a change freeze for a company, and nothing enforces it beyond the dashboard’s own deploys: an outside deploy (0016) is as allowed as ever.
Payload keys, one by one
The record that starts a waiting stack carries, by the rule of 0091: hash, ticker, drift, onMerge and fingerprint; not run, attempt, behind or window. window is not carried because the started record waits for nothing: it is opened inside the window, in the run that deploys it.
Considered
- Refusing a tick outside the window, with a note. The person would have to come back inside the window to tick again, which is the problem a window is meant to solve. Waiting keeps the tick as the request and keeps the hash check as the guard.
- Putting the opening time on the record. The row would then keep a time from a config that may have changed. The record says only that it waits, and the row and the run read the config of the moment, as the tick rule is read live.
- A cron expression, as Argo CD takes. Days and clock times read as a person would say them, and a cron needs a reference to read. Two windows cover what a cron with two ranges covers.
- A break-glass tick that deploys outside the window. Needed one day, and it has to be visible on the trail and probably needs a permission of its own. Left for later: today a repo widens a stack’s window with
stacks[].deployWindows: []in a reviewed change, or waits. - A freeze with an end date, for a sale or a release. The same mechanism with a date instead of a weekday. Left for later; a repo freezes today by writing no window that falls in the freeze and changing the file back after.
- The
windowflag on a record behind a stack too. It would say something true but decide nothing: the window is checked when the stack starts, whatever the record says, and the row reads the window live. One rule is easier to hold than two. - Starting a waiting record from the scan itself, at its late read. A scan never starts a deploy for a tick (0025); the hand-off of a merge is the exception, and this is not one. The schedule’s run gets
resolve, which already starts queued stacks.
Consequences
- The schedule’s run in auto mode loads the config and, when a stack has dependencies, a phase or a window, reads the deployment records. A repo without any of them is one config load and no request more.
- The split workflow’s
resolvejob runs on the schedule too, so itsif:namesschedule. Without that, a window opens only when a dispatch happens: the rescan box,settleafter a layer, or a merge from the dashboard. The check does not warn about aresolvejob that lacks it yet (docs/later.md). - A waiting record is open until the window opens and the scheduled run starts it, which may be days. That is on purpose, as with a queued record (0056), and the row says when.
CONTEXT.mdgains Deploy window, and the queued stack and auto mode say what changed.