Skip to content
Go to console
Go to console

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 runs resolve before the scan) and 0003 (the payload key window). 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

  • deployWindows names 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 are HH:MM on a 24 hour clock, and 24:00 is the end of the day. The start is inside and the end is outside, so a window to 17:00 closes as the clock turns 17:00. A window is written in dashboard.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 to 24:00 and one from 00:00 on the next day. Empty, the default, is any time, and a repo without the key is byte for byte what it was.
  • stacks[].deployWindows sets 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 for tickers.
  • 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: true and is not handed on. It is a queued stack (0056) that waits for a time and not for a stack: its row says queued 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 resolve that 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, onMerge and the fingerprint of the waiting one, and no window; the waiting record ends as inactive, “started in a later run”; the new record goes in matrix, and apply previews 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 runs resolve before the scan, as a dispatch does, so the scheduled scan inside the window is what starts it. In the split workflow the resolve job 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. planDeploys decides the layers first (0056). A stack in the first layer whose window is closed gets window: true. A stack behind another gets behind and no window, 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 says queued behind **network:prod**, and for the deploy window, which opens ... while the window is closed, from the record and the config of that moment. settle dispatches 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 onMerge and window, and hands nothing on. The run inside the window starts it, and the started record carries onMerge, so the row and the trail still say merged 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.
  • apply never deploys a record with window. If one is handed on by hand, apply leaves it alone and the job goes red, as for a queued record (0056). settle and 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 window flag 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 resolve job runs on the schedule too, so its if: names schedule. Without that, a window opens only when a dispatch happens: the rescan box, settle after a layer, or a merge from the dashboard. The check does not warn about a resolve job 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.md gains Deploy window, and the queued stack and auto mode say what changed.