What v1 is and the names it fixes
What v1 is
v1 is the core loop and what real use asked for around it. The core loop: a scan previews stacks and writes the dashboard, a person ticks a box, exactly that stack deploys, and the row returns to in sync or shows why it failed.
Names fixed for v1
Every name, default and fixed value of v1 in one place. The record in the last column is the authority.
Action inputs and outputs
| Name | Kind | Modes | Default | Meaning | Record |
|---|---|---|---|---|---|
mode |
input | all | auto |
auto, scan, resolve, apply, settle, check or init. auto runs the modes the event asks for in one step; the schedule and a dispatch run resolve before the scan, and a dispatched run that deployed outside records alone skips its scan |
0003, 0042, 0065, 0077, 0104, 0109 |
concurrency |
input | scan |
one per core, 1 to 8 | Size of the preview pool. Without it the pool is the number of cores of the machine, at most 8, and the job log says which | 0012, 0085 |
preview-timeout |
input | scan, apply |
10 |
Time limit for one preview, whole minutes, from when the preview starts | 0012, 0035, 0085 |
strict |
input | scan, auto |
false |
Any preview failure turns the job red, after the dashboard is written | 0012 |
github-token |
input | all | the workflow token | Always the workflow’s own GITHUB_TOKEN |
0017, 0035 |
deployment-id |
input | apply |
required there | The deployment record to deploy | 0035 |
dry-run |
input | apply, auto |
false |
A rehearsal: everything up to the hash check, then no deploy. The record ends as inactive, “rehearsed, nothing was deployed” |
0051 |
backend |
input | check, auto |
false |
Also ask the backend which of the discovered stacks it holds, with the credentials of the job, and give one ignore block for the ones it does not hold. Refused in every other mode but auto |
0074, 0077 |
pull-request-preview |
input | check, auto |
false |
Preview the stacks the pull request claims as they would be after the merge, with the credentials of the job, and write a check run per stack on its head commit. Never deploys, never opens a record, leaves no row. A fork is refused and pull_request_target is never used. Refused in every other mode but auto |
0101 |
deploy-timeout |
input | apply, auto |
none | A time limit on the deploy itself, whole minutes. The tool is interrupted and gets two minutes to stop | 0003 |
env-file |
input | scan, apply, auto, check with backend: true or pull-request-preview: true |
none | A file of NAME=value lines, relative to the checkout or absolute, read once for the tool’s process. Every value is masked first, the file wins over the job environment, and the job log names what it loaded. A warning, and never read, on a step that runs no tool |
0100 |
slack-webhook-url |
input | scan, resolve, apply, auto |
none | A Slack incoming webhook address, from a secret. A warning in any other mode | 0078, 0077 |
telegram-bot-token |
input | scan, resolve, apply, auto |
none | A Telegram bot token, from a secret. Needs telegram-chat-id |
0078 |
telegram-chat-id |
input | scan, resolve, apply, auto |
none | The chat the bot posts to: an id or an @ name |
0078 |
telegram-thread-id |
input | scan, resolve, apply, auto |
none | The topic of a forum group the bot posts to, as message_thread_id. Needs the token and the chat id |
0078 |
webhook-url |
input | scan, resolve, apply, auto |
none | An http or https address, from a secret, that gets { version, event, repository, stacks, dashboard, run, text } |
0078 |
job-id |
input | scan, apply |
${{ job.check_run_id }} |
The id of the running job, for links to its log. Never set by hand | 0044 |
matrix |
output | resolve, scan |
[] |
[{ stack, environment, deployment }]. A scan sets one entry after a merge from the dashboard, and one per stack set to on-merge that the scan of a push hands on (slice 5.31). A resolve that no issue edit started sets one per outside record it hands on (slice 5.44) |
0035, 0054, 0095, 0109 |
dashboard-url |
output | scan, apply, settle |
none | Web address of the dashboard issue | 0041 |
pending |
output | scan |
0 |
Number of pending stacks after this scan | 0041 |
preview-failed |
output | scan |
0 |
Number of stacks whose preview failed | 0041 |
in-sync |
output | scan |
0 |
Number of stacks in sync | 0041 |
dashboard-changed |
output | scan |
false |
true when this scan wrote a different body, so a notify step can stay quiet otherwise |
0041 |
outcome |
output | apply |
none | deployed, in-sync (nothing to deploy), rehearsed (dry-run), refused (the change moved, the record was not open, or deploys: false) or failed |
0041, 0051 |
stack |
output | apply |
none | The stack id this job handled | 0041 |
result-file |
output | scan, apply |
none | Path under RUNNER_TEMP of a JSON file with what the summary holds: no values, none of the tool’s words |
0041 |
sluiceway.yaml
The file is optional and sits at the repo root. Since slice 5.9 sluiceway.yml is read the same way, and both at once is an error. Unknown keys are an error, because a typo in tickers would change who can deploy.
| Key | Default | Meaning | Record |
|---|---|---|---|
dashboard.title |
Sluiceway dashboard |
Issue title, set again by every scan when it differs | brief |
dashboard.label |
sluiceway |
Label the dashboard is found by | 0009, 0017 |
dashboard.pin |
true |
Pin the issue on every scan when it is not pinned, best effort | — |
dashboard.redact |
false |
Keep names out of the issue | 0023 |
dashboard.personality |
true |
Header image and the voice | 0034 |
dashboard.readOnly |
false |
No boxes: pending rows have none, there is no rescan box, and the line under the Pending heading says so. For a workflow that only scans (onboarding log, hurdle 16) | 0045 |
dashboard.showValues |
[] |
Property paths whose old and new value may appear, as old → new after the path. Exact paths, * for part of one name. Never a value the tool marks secret, none with redact on. The hash covers a shown value (0008) |
0052 |
dashboard.recentlyDeployed |
10 |
How many lines Recently deployed lists, newest first, failed deploys included. 0 to 50, and 0 leaves the list out | 0062 |
dashboard.timeZone |
UTC |
The IANA zone every time on the dashboard is shown in. A time that stands alone ends in its offset from UTC (UTC+2), the line under Recently deployed names the zone, and the markers keep UTC. A name that is not a zone fails the config |
0089 |
dashboard.sections |
the order of 0063 | The order of the sections: deploying, updates, pending, drifted, previewFailed, inSync, recentlyDeployed. The ones named come first, a section left out follows in the default order and is never hidden, a name twice fails the config |
0114 |
dashboard.deployingSection |
true |
false moves the deploying and queued rows to one closed fold at the end of the sections, shared by every section that is off. The rows stay in the body |
0114 |
dashboard.driftedSection |
true |
false moves the drifted rows to that fold, with no repair all box. A row with a failure line or a resource gone stays open under the heading |
0114 |
dashboard.inSyncSection |
fold |
fold, list (every row open) or off (the rows to the fold at the end, no fold of ignored stacks). A row with a failure line stays open |
0114 |
dashboard.zeroCounts |
true |
false leaves a count of 0 out of the counts line, except pending |
0114 |
dashboard.destroyAlert |
destroys |
always draws a note in the alert’s place when nothing is destroyed. There is no off |
0114 |
dashboard.pendingDetail |
full |
compact: the first line, the failure line, the lines of a failed policy, the notes about a tick, every delete and replace line. names: the first line without the preview link, the failure line, the policy’s lead line and every delete and replace line. The marker is the same |
0024, 0114 |
dashboard.deployAll |
true |
false draws no deploy all box |
0083, 0114 |
dashboard.repairAll |
true |
false draws no repair all box |
0083, 0114 |
dashboard.rescanBox |
true |
false draws no rescan box |
0114 |
dashboard.footer |
true |
false leaves out the version line, and the rule above it when there is no rescan box either |
0114 |
tickers |
write |
Default tick rule: write, maintain, admin or a list of usernames |
0018 |
deploys |
true |
false stops every deploy: resolve clears every ticked box with a note, apply ends before the tool runs |
0051 |
recordWriters |
[] |
Logins, an app as name[bot], whose open deployment records that name a dispatched or scheduled run are deployed by that run: outside records. The writer is who GitHub records as the creator of the record, never a name in the payload. Empty hands none on |
0109 |
deployWindows |
[] |
When the stacks may go out, in the dashboard zone: windows of days (full names, lower case), from and to (HH:MM, 24:00 the end of the day, the end after the start). A tick, a deploy on merge and the deploy after a merge outside every window open their record now and wait for the window; the resolve of a scheduled or dispatched run inside it starts them. Empty is any time |
0104 |
freezes |
[] |
Periods when nothing goes out, in the dashboard zone: from and to as YYYY-MM-DDTHH:MM (the end after the start, the end outside), and an optional reason. A tick, a destroy, a deploy on merge and the deploy after a merge during a freeze open their record now, with window, and wait for the end, and for a window after it; no stack entry lifts one. The dashboard names each freeze under the scan line while it holds and for the week before it starts, and the check warns about one that ended |
0115 |
ignore |
[] |
Globs matched against the stack id. An entry is a glob, or { glob, reason }, and a stack left out with a reason is listed with it under In sync |
0010, 0051 |
scan.unrelated |
[] |
Globs for files that claim nothing and force nothing | 0010 |
scan.logDiff |
false |
Print the tool’s own diff of every pending stack, values included, in that stack’s group of the job log and nowhere else | 0048 |
drift.enabled |
false |
Check every stack for drift in each scan that a schedule starts or a person starts with Run workflow, and in a push’s scan only for the stacks whose row showed drift. There is no drift.schedule: the loader says the cron goes in the workflow |
0055 |
valueFingerprint |
true |
Every pending and drifted row carries a value fingerprint, a hash of the values its diff holds that the row does not show, and apply compares it after the diff hash: a value that changed since the tick stops the deploy. A value the tool marks secret enters as its mark. Off for a repo whose programs make values that differ on every run |
0102 |
policies |
[] |
Directories or files of Rego policies, relative to the repo root, that the preview document of every pending stack is tested against with Conftest, which the workflow installs. A policy that fails takes the box off the row until it passes and stops a deploy on merge. Empty runs nothing | 0106 |
phases |
[] |
Names of phases in deploy order. A stack in a phase depends on every stack in every earlier phase | 0067 |
cost.enabled |
false |
Estimate what each pending change of an OpenTofu or Terraform stack costs a month with the Infracost CLI the workflow installs, over the plan’s JSON the preview made, and show it on the row as a change to the monthly bill. The CLI sends resource types, regions and quantities to its pricing API, never a value or a credential. Other tools get no line, a failed estimate is a missing line and never a red scan, and the cost is not in the diff hash | 0105 |
cost.threshold |
none | The change to the monthly bill above which a stack set to on-merge waits for a tick, in the currency of the estimate, and its row says why. A failed estimate waits too. Needs cost.enabled |
0105 |
stacks[].path |
required per entry | Directory of the stack, relative to the repo root | 0006 |
stacks[].name |
none | Name of the stack. Without it the entry covers every stack in path |
0006 |
stacks[].tool |
none | opentofu, terraform, helm or kubectl: the entry declares a stack of that tool at path, because files alone cannot name one |
0053, 0058, 0060, 0068 |
stacks[].id |
the derived id | The id of the one stack the entry covers, in place of the one derived from its path and name, so a stack that moved keeps its row and its deploys. Unique among every stack id | 0006 |
stacks[].environment |
sluiceway |
Label on the deployment record, and the GitHub Environment where one is used | 0003 |
stacks[].tickers |
the top level value | Tick rule for this stack | 0018 |
stacks[].inputs |
[] |
Extra globs this stack claims | 0010 |
stacks[].previewTimeout |
the input | Time limit for this stack, whole minutes | 0012, 0035 |
stacks[].dependsOn |
none | Stack ids this stack depends on, or auto: the stacks its Pulumi program reads through stack references, read at each preview and carried on the row. A tick waits while one of them is pending and not ticked, ticks in one chain deploy one layer per run |
0056, 0059 |
stacks[].phase |
none | One of phases, or { from: <key> }: the text under that key of the stack’s Pulumi project file, under config or at the top level. The stack depends on every stack in every earlier phase, and dependsOn adds to that |
0067 |
stacks[].deployWindows |
the top level value | The windows of the stacks of this entry, in place of the top level ones. [] lets them go out at any time |
0104 |
stacks[].deploy |
on-tick |
on-tick: the stack deploys when a person ticks its row. on-merge: it deploys by itself after the scan of a push to the default branch found it pending, through the path of a tick, attributed to whoever pushed. A delete or replace, drift, deploys: false, a read-only dashboard, any other scan and a dependency that waits for a tick keep it waiting for a tick, and its row says why |
0095 |
stacks[].drift.enabled |
the top level | The drift check on or off for the stacks of this entry, in the same scans as drift.enabled |
0059 |
stacks[].valueFingerprint |
the top level | The value fingerprint on or off for the stacks of this entry | 0102 |
stacks[].envFile |
none | A file of NAME=value lines, relative to the checkout or absolute, that the tool gets for the stacks of this entry alone, on top of the job environment and the step’s env-file. Every value is masked first, the file is read once per job, and a file that cannot be loaded fails the preview of these stacks and no other |
0103 |
stacks[].policies |
[] |
Policy paths for the stacks of this entry, on top of the top level policies |
0106 |
stacks[].createInBackend |
false |
true: a scan creates each Pulumi stack of the entry that the backend lacks, with pulumi stack init right before its first preview, and previews it as all creates. A deploy never creates a stack. Refused on an entry with tool |
0107 |
stacks[].cost.enabled |
the top level | The cost estimate on or off for the stacks of this entry | 0105 |
stacks[].cost.threshold |
the top level | The threshold of the stacks of this entry, in place of the top level one. Needs the estimate on for them | 0105 |
stacks[].options |
{} |
Named adapter options, only with tool. OpenTofu and Terraform: workspace, varFiles and wrapper (terragrunt or cdktf). Helm: release, namespace, chart, version (a chart reference only), valuesFiles and createNamespace. kubectl: context, namespace, recursive, prune, forceConflicts and fieldManager |
0006, 0015, 0053, 0058, 0060, 0068, 0069, 0070 |
discovery.rootModules |
true |
Find OpenTofu and Terraform root modules from their files: not a local module source of another directory, not under modules/, a backend or cloud block, one workspace, no var file that chooses, and a lock file or .tofu files that name the tool. Each is one stack in the default workspace with its path as its id. A directory a stacks entry with a tool names stays the entry’s. discovery is a mapping of switches that the adapters name, and an unknown one fails the config |
0092 |
attribution.lookback |
100 |
How many of the newest commits a job walks for attribution, 1 to 1,000, one GraphQL page per 100 | 0026, 0072 |
attribution.names |
5 |
How many pull requests and direct pushes a row and a shipped line name before the rest is a count, 0 to 20 | 0026, 0072 |
mergeAndDeploy.authors |
[] |
Logins whose green pull requests are listed to merge and deploy with one tick, one deploy per stack that claims them. Empty turns it off | 0054, 0064, 0071 |
notify.events |
[pending, drift, failed, refused] |
The events a notification is sent on, to each channel the step’s inputs name: pending (stacks newly pending after a scan), drift (newly drifted), deployed, failed, refused (a tick that deployed nothing). A channel is never a key here |
0078 |
mergeAndDeploy.preview |
false |
Preview each listed update as it would be after the merge and show the counts on its row: one extra preview per stack of each of the oldest 30 updates, never for a fork | 0071 |
Rules for config loading:
- A
stacks[]entry adds settings to stacks that discovery found. It never creates a stack, except an entry withtool, which declares one (0053) and takes its directory over from root module discovery (0092). An entry that matches no discovered stack is a config error. - A
tickersentry with a slash fails with the message that teams are not supported yet (0018). - No key is reserved now. A reserved key fails with a message that says it is not in this version yet, and is never ignored. (
dependsOnwas one until slice 4.4, 0056, a top leveldriftuntil slice 4.3, 0055, andstacks[].driftuntil slice 4.7, 0059.) - The JSON schema is generated from the Zod schema into
schema/sluiceway.schema.json, committed, and checked in CI the waydist/is.
Fixed strings and numbers
| What | Value | Record |
|---|---|---|
| Bot | github-actions[bot], type Bot |
0017 |
Deployment task |
sluiceway:<stack id> |
0003 |
| Deployment payload | { "v": 1, "hash", "ticker", "run" }, plus "drift": true when the approved hash covers drift, and since slice 5.9 "attempt", the attempt of the run that created the record. behind on a queued record (0056), merge in place of hash on a merge record (0054), onMerge (0095), fingerprint, the value fingerprint the tick approved (0102), window on a record that waits for the deploy window (0104) or the end of a deploy freeze (0115). Since slice 5.33 the schema is schema/deployment-payload.schema.json, and every payload is checked against it before it is written |
0003, 0055, 0044, 0096 |
| Default environment label | sluiceway |
0003 |
| Concurrency groups | The one-step workflow: sluiceway-${{ github.event.issue.number }} with queue: max. The split workflow: sluiceway-scan, and sluiceway-resolve and sluiceway-apply-<stack id> with queue: max |
0004, 0025, 0035, 0077, 0123 |
| Dispatch input of the scan after a merge | sluiceway-merged, the merged pull request numbers joined with commas, sent only to a workflow that declares it |
0064 |
| Marker version | 1 |
0009 |
| Row states | pending, deploying, in-sync, preview-failed, queued since slice 4.4, and drift since slice 4.3, with the marker key drift="true" on a row whose hash covers drift, and since slice 4.7 depends-on="<ids>" on a row of a stack with dependsOn: auto, and since slice 5.37 fingerprint="<16 hex>", the value fingerprint, on a pending or drifted row whose diff holds values the row does not show, and since slice 5.54 busy="true" on a preview-failed row of a stack whose lock another update held, and since slice 5.56 drift-check="failed" on a row whose drift check failed |
0009, 0055, 0056, 0059, 0102, 0117, 0121 |
| Diff hash | SHA-256 of the canonical document, first 16 hex characters | 0008 |
| Value fingerprint | SHA-256 of the hidden values of a change, and of the change fingerprints of a row, first 16 hex characters each; a secret enters as {"path":p,"secret":true} |
0102 |
| Body target, hard limits | 58,000 characters, 65,536 characters, 262,144 bytes | 0028 |
| Summary budget | 1,000,000 bytes | 0037 |
| Preview page | A check run named sluiceway / <stack id>, completed, neutral, its text cut on a line at 65,535 bytes. Needs checks: write |
0050 |
| Write loop | at most 3 tries | 0004 |
| Second try of a failed preview | once, after one pause of 10 seconds a scan. The job log shows the tool’s last 20 lines under a preview that did not work | 0117 |
| Lookback, names on a row, recently deployed | 100 commits, 5, 10, the defaults of attribution.lookback, attribution.names and dashboard.recentlyDeployed |
0026, 0029, 0062, 0072 |
| Minimum Pulumi CLI | v3.229.0 | 0001 |
| Minimum OpenTofu CLI | v1.11.0 | 0053 |
| Minimum Terraform, Terragrunt and cdktf CLIs | terraform v1.14.0, terragrunt v1.0.0, cdktf v0.21.0 | 0068 |
| Minimum Helm CLI and diff plugin | helm v3.18.0, helm-diff v3.15.11 | 0058 |
| Minimum kubectl CLI | v1.34.0 | 0060 |
Minimum Conftest, for a repo with policies |
v0.50.0 | 0106 |
Infracost CLI, for a repo with cost.enabled |
the open source 0.10 line, recorded with v0.10.45. No floor is checked: a failed estimate is a missing line, never a red scan | 0105 |
| Minimum self-hosted runner | v2.328.0, no ARM32 | — |
| API budget | 1,000 requests per hour per repo | 0017 |
| GitHub API version | 2026-03-10, in X-GitHub-Api-Version on every request, set once in src/github/client.ts (slice 5.47) |
0017 |