Not in v1
What it does not do yet
- Pulumi and OpenTofu only. OpenTofu stacks are declared in
sluiceway.yaml, there is no zero config for them (configuration). The Terraform binary, Helm and others can follow. - Only preview, drift check and deploy. Destroying a stack, a refresh that writes the state and repairing state stay with your own tooling. The drift check is opt-in, Pulumi only for now, and never changes the state (
drift.enabled). - A change to outputs alone is not shown, and deploys from somewhere else are not detected. Limits says what that means for you.
docs/later.md lists everything left out of this version, and why.
One list of everything that was considered and left out of v1, so planning for v2 starts from a complete picture. Each line says what it is, why it is not in v1, and where that was decided. The decision record holds the detail; this file only points.
Rule: whenever a decision leaves something out of v1, add a line here in the same change.
Deferred, door left open
Left out of v1 on purpose, and v1 was shaped so these can be added without a breaking change.
| What | Why not in v1 | Decided in |
|---|---|---|
| The Terraform binary behind the OpenTofu adapter | The OpenTofu adapter was built in slice 4.1. Terraform shares the plan format and is drifting from it (extra fields and actions), so it needs its own recordings and its own floor. | 0053, the adapter research |
| Zero-config discovery for OpenTofu (a directory with a backend block or a lock file as a stack) | A root module and a child module look the same on disk and a workspace lives in the backend. A heuristic would guess, and a wrong guess is a row that fails every scan. tool: opentofu entries declare stacks. |
0053 |
A backendConfig option for OpenTofu (tofu init -backend-config) |
Workspaces and var files cover the layouts the research found common, and backend settings go through the environment. It would also mean one init per stack, not per directory. | 0053 |
A hint in the check for a directory of .tf files that no entry declares |
The check lists those files as unclaimed today, which is true. A hint would have to tell a root module from a child module. | 0053, 0042 |
A Helm adapter (a release in a namespace as the stack, Chart.yaml and values files for discovery, helm diff upgrade for the preview, helm upgrade --install for the deploy) |
The closest fit after OpenTofu: a plan-and-apply cycle and a live release to compare against, credential-free fixtures, and the largest new audience (Kubernetes teams that have not wrapped Helm). First adapter after OpenTofu. | The adapter interface, 0006, 0007 |
A Kubernetes manifests adapter (kustomize or plain YAML, kubectl diff for the preview, kubectl apply for the deploy) |
Same shape as Helm, simpler. Shares its audience. After Helm. | 0006, 0007 |
| The Terraform family beyond OpenTofu (Terragrunt, CDK for Terraform) | They reduce to the same plan JSON, so they come nearly free once the OpenTofu adapter exists. | 0006, 0007 |
| An AWS CDK and CloudFormation adapter (change sets as the preview) | Fits the model, a different format from plan JSON, a large all-AWS audience. Wanted, waiting for a user who asks. | 0006, 0007 |
| A preview of a pull request’s branch before its merge, on the row of an update waiting to merge | It doubles the cost of a scan for those rows, and the diff that deploys is previewed and hashed after the merge anyway. The row shows the bump from the title. | 0054, issue 102 |
| Merging a pull request that two stacks claim, with one deploy each | One tick would approve a deploy of a stack nobody looked at. Such a pull request is never listed. | 0054, issue 102 |
| A narrowed scan after a merge from the dashboard | A merge made with the workflow token starts no push run, so resolve dispatches a scan, and a dispatched scan is full. It could be narrowed to the merged stack with a dispatch input. |
0054, 0010 |
Reading Renovate’s JSON5 config, its presets and its packageRules for the merge method |
Sluiceway has no JSON5 parser and a preset lives in another repo. The top level automergeStrategy of the JSON files is read, else the first allowed of squash, rebase and merge. |
0054 |
| A note on the row of an update waiting to merge whose tick was cleared without a comment | The row is one line. A tick cleared for a deploying stack, for deploys: false or because nobody could be named says why in the job log. |
0054 |
| Waiting, in the scan after a merge, for a dependency that became pending after the merge | resolve refuses to merge for a stack whose dependency has a change waiting or deploying. A change that lands on the dependency in the minutes between the merge and its scan is not waited for, and the merged change deploys first. |
0054, 0056 |
| Listing more than 10 updates waiting to merge, or pull requests beyond the oldest 100 open ones | Enough for a routine flow of bumps, and the section stays small inside the size budget. | 0054, 0028 |
| Drift detection, part 2 | Part 1 is built (slice 4.3, 0055): drift.enabled, the check in scheduled scans and in runs a person starts, the Drifted section, the drift state and marker key, one hash over both diffs, and a tick that repairs. Left for part 2: a drift check for OpenTofu (resource_drift of the plan, which 4.1 ignores); drift per stack (stacks[].drift stays reserved); values of drifted paths through dashboard.showValues; a drifted step output and a drifted count in the result file’s dashboard counts; a preview page for a drifted stack; the note about shortened rows counting drifted rows; a drift check in the scans that Sluiceway dispatches itself. |
Brief M3, 0009, 0055 |
| Stack dependencies, part 2 | Part 1 is built (slice 4.4, 0056): dependsOn, the refused tick, one layer per run, queued rows. Left for part 2: a queued stack that is ready while no resolve run is coming says so on its row instead of only waiting for the next run; transitive waiting (a stack waits only on the stacks it names); a deploy of several layers in one run. |
Brief M3, 0009, 0056 |
| Detecting dependencies from Pulumi stack references | Comes after manual dependsOn. |
Brief |
A named refresh option (preview, re-preview and deploy with refresh on every run of a stack) |
The drift design (0055) repairs drift without it: a deploy runs with refresh only when the approved hash covers drift that apply has just checked again. A stack that wants refresh on every preview would still need this, as one named option on all three calls so the hash stays honest. |
0015, 0055 |
| Showing property values on the dashboard or in the summary without a list | Property paths are in v1 (0046) and scan.logDiff shows values in the job log (0048). Built in slice 2.19: dashboard.showValues, an opt-in list of paths whose values appear (0052). Without a list, values stay out. |
0021, 0007, 0008, 0046, 0048, 0052 |
A finer dashboard.redact (per stack, or a middle level such as types without names) |
One boolean is enough to launch. | 0023 |
| Teams in the tick rule | The workflow token cannot read team membership, and a second stored token breaks the credential promises. The org/team syntax is reserved and fails with a clear message. |
0018, 0017 |
| A GitHub App token or PAT as the bot identity | One identity in v1. It would arrive as a new optional input, together with an identity input and a loop guard. The owner asked on 2026-09-21 for the dashboard to be written by a Sluiceway bot instead of github-actions[bot]. That is this item: a bot with its own name and picture is a GitHub App, and only a token minted from the app’s private key can act as it. With the user’s own app the key is the user’s secret and nothing else changes here. With one shared official app the key is ours, so a service has to mint the tokens, which is a backend and belongs to a hosted version. Either way edits made with an app token do start workflows, so the loop guard comes with it. |
0017 |
| Spreading one scan over several runners (sharding) | One job with a bounded pool is enough for now. A shard would be a narrowed scan, so no new merge rule is needed. | 0011, 0012 |
A full scan whenever sluiceway.yaml changes, also in a repo with a stack at the root |
The file lies outside every stack directory, so changing it is a full scan with no special case. A stack at the repo root claims every file, and there a push that turns dashboard.redact on rewrites only the rows it previews. The carried rows keep their names until the next full scan, which the rescan box gives at once. |
0010, 0023, slice 1.12 |
| Treating the files of an ignored stack as unrelated | An ignored stack claims nothing, so a change in its directory has no claimant and gives a full scan. Another stack may read those files, so dropping them by default could leave a row stale. scan.unrelated takes the directory when nothing reads it. |
0010, slice 1.12 |
| Paging past the 300 files of a comparison, or reading the files commit by commit | A push that large is rare, and a full scan is always right. | 0010, slice 1.12 |
| Links to the job, not the run, on the rows written from a deployment record (deploying rows, failure lines, Recently deployed) | The record holds its run and no job (0003). apply knows its own job’s id, and the log address on its deployment status could carry it, which a scan would then have to read. |
0044, slice 2.14 |
A link that lands on one stack’s group in the job log (#step:<n>:<line>) |
GitHub supports the address, but the step’s number and the line’s number are not the action’s to know, and a wrong one lands on another stack. | 0044, slice 2.14 |
| The carried rows in the summary of a narrowed scan | The summary shows the stacks its run previewed. A carried row links to the summary of the run that previewed it. | 0011, 0037, slice 1.12 |
An empty tickers list, as a way to get a dashboard that nobody can tick |
Config loading refuses it, because in a rule about who may deploy an empty list is far more likely a mistake than a wish. A read-only mode got its own key, dashboard.readOnly (0045). |
Slice 1.2 |
A scan that works out by itself that its workflow has no resolve job, so that dashboard.readOnly is not needed |
It would have to read and understand the workflow file, its triggers and its jobs, which the workflow token can do but which breaks the moment a repo splits the jobs over two files. | 0045, slice 2.17 |
dashboard.readOnly as a lock: resolve refusing a tick while it is on |
What keeps a read-only workflow from deploying is that it has no resolve or apply job. A second lock in the config would be one more thing to keep in step. |
0045, slice 2.17 |
Trying a failed permission lookup again in the same resolve run |
A lookup that fails is rare and the person is one tick away from a fresh try. Failing closed at once keeps the one job that an issue edit starts short. | 0018, slice 2.3 |
| Telling an account that was renamed or deleted after its tick apart from a failed lookup | GitHub answers 404 for a login it does not know. It is treated as a failed lookup: nothing deploys, the comment asks for a fresh tick and the job goes red. A refusal without the red job would need the lookup to read GitHub’s error text. | 0018, slice 2.3 |
| A cap on the names a refusal comment lists from a tick rule | A list of usernames is short in practice, and the comment states the rule in full. | 0018, slice 2.3 |
| Attribution in the result file (the pull requests and direct pushes a pending stack claims) | Record 0041 names what the file holds, and attribution is not in that list. A pull request title is free text from a person. It can join as a new field without raising the version. | 0041, 0026, slice 2.11 |
Timings in the result file of an apply, and the stacks a narrowed scan carried in the result file of a scan |
apply has no clock seam, and its summary shows no time. The scan’s file lists what its summary lists, the previewed stacks. Both can join as new fields. |
0041, slice 2.11 |
The JSON schema of the result file as a committed file next to schema/sluiceway.schema.json |
It is held by a snapshot test for now. Publishing it is a promise to readers that belongs with the first release. | 0041, slice 2.11 |
sluiceway.yml as a second spelling of the config file |
One name. A file under the other spelling is refused, not skipped, because skipping it would drop its tick rule without a word. | Slice 1.2 |
Logging deploys made outside the dashboard under recently deployed (a manual pulumi up from a laptop, a script, another pipeline), with when and from which commit |
The owner wants it, as an audit log of every deploy and not only the dashboard’s own. Not in v1: it is a new optional adapter method that reads the tool’s update history, one more backend call per stack (so full scans only), a new kind of line and a rule that matches history entries to deployment records. History entries hold config values, so they are dropped at the schema like preview values (0021). Who deployed is weak on a self-managed backend, which seems to record the checkout’s git metadata and not the person, and OpenTofu has only a state serial, so it can say that something changed and nothing more. Deploys outside stay legal (0016). Fits next to drift. Researched on 2026-09-21: the history lists every up, refresh and destroy with kind, result, times, change counts and the checked-out commit, reading it needs no passphrase and takes no lock, and an entry made inside GitHub Actions holds the run id, so Sluiceway’s own deploys are told apart by matching ci.build.id to the run id on the deployment record. Who deployed is not recorded on a self-managed backend at all. The entry’s config block holds plain values and has to be dropped whole. Findings |
0016 |
| A link to the scan that the rescan box started | The dispatch can answer with the run it started (return_run_details). resolve logs that it started a scan and the dashboard shows the scan when it is done. A line on the dashboard for a scan that is on its way would be a new fact outside the row blocks. |
0017, slice 2.4 |
Placing the orphan tick note in the order of record 0027 when resolve adds it |
resolve has no diff and never reads a row’s text, so it cannot tell an attribution line from a delete line. It puts the note right under the first line. The next scan renders the row in the fixed order. |
0025, 0027, slice 2.4 |
| Carrying on with the other records after one deployment record could not be written | A missing deployments: write would cost 256 failed requests. resolve stops at the first failure, hands on what exists and goes red. The ticks that are left stay ticked for the next run or the orphan sweep. |
0035, slice 2.4 |
A scan that carries a ticked rescan box while a resolve run is on its way |
A scan writes the rescan box unticked, as it always has. A narrowed scan that lands between the tick and its resolve run loses the request without a word, and the person ticks once more. Carrying it would be a change to the scan, with the run lookup of the orphan sweep. |
0025, slices 2.4 and 2.7 |
The scan clearing an orphan tick with clearTick in place of a preview |
Slice 2.4 built the way to clear a box on a block without a diff (src/render/clear-tick.ts). The scan still previews the stack, which also puts the note in the order of record 0027. Switching is a choice between one preview and the place of one line. |
0004, 0025, slices 2.4 and 2.7 |
A job summary for resolve |
The job log says what happened to every tick, and the dashboard and the comment say it to the people it concerns. | Slice 2.4 |
| Telling a full edit history that lost nothing from a capped one | GitHub keeps 100 entries and says nothing about what it dropped, so a history of exactly 100 entries is read as capped and its oldest entry is never used. That can cost one fresh tick on the one day a dashboard has exactly 99 edits, and never names a wrong ticker. | 0025, slice 2.2 |
Looking past the newest 100 runs that an issue edit started, when a scan asks whether a resolve run is on its way |
One request, newest first. A run that is not over and has 100 newer ones behind it started its resolve job long ago, and a waiting run is always among the newest. The worst case is one tick cleared that has to be made again. |
0025, slice 2.7 |
| Sweeping an orphan tick off a row without previewing its stack | Only the row renderer writes a row, and a row block is never patched inside. So an orphan tick on a row the scan carries costs one preview. If resolve gets a way to clear a box on a block it has no diff for, the scan can use the same. |
0004, 0025, slice 2.7 |
The orphan tick sweep in a repo that keeps scan and resolve in two workflow files |
The scan asks for the runs of its own workflow, as the record says. The README’s one workflow is the only supported shape. With two files the scan sees no run on its way and may clear a tick a moment before resolve reads it, which costs a fresh tick and never a wrong deploy. |
0025, slice 2.7 |
A real uses: step against the fake GitHub server in the e2e workflow |
A runner sets GITHUB_API_URL, GITHUB_WORKSPACE, GITHUB_SHA and GITHUB_EVENT_NAME itself, and neither a step nor a job can replace them (tried on a hosted runner). A real step could only reach the fake through a new input or variable for the API address, which would be a product feature that exists for a test. The e2e starts the committed bundle itself, with the environment a runner would build from action.yml, and the smoke job keeps a real uses: step that stops at a config error. |
Slice 1.13 |
| A row that says its stack was pending again right after a successful deploy of the same change (a value that differs on every run) | Wanted from the first real use, onboarding log hurdle 21. The scan has the deployment record and the hash to detect it. Not before 0.2.0. | 0003, 0008 |
An id: override for a stack in sluiceway.yaml |
A second source of identity that has to be kept unique. The id is always derived in v1. | 0006 |
| Starting a deploy without a person ticking (unattended deploys of chosen stacks) | If it comes, it is a real entry point such as workflow_dispatch with a stack input, where GitHub records who started it. Not a bot ticking boxes. |
0018 |
| Telling authors that their merge is waiting for a deploy | An @ mention in the issue body notifies and subscribes every author, with no way to opt out. If it comes, it is an opt-in setting or a comment on the pull request, not a mention on the row. |
0026 |
| Shipped pull requests under recently deployed | Needs the list stored on the deployment record at tick time, or a walk between two successful records that may lie outside the lookback. The record already carries the deployed commit. | 0026, 0003 |
| A configurable lookback, or number of pull requests named on a row | Fixed at 100 commits and five names to launch. The compare link and the summary page already give the full list. | 0026 |
| Naming changes outside a stack on its row (shared package, lockfile) | They are counted and one compare link away. Naming them puts the same long list on every row. Needs real inputs per stack first, which 0010 rejected. | 0026, 0010 |
| Paging through a pull request with more than 100 changed files, or a direct push with 300 or more | The walk is one query and one request per direct push. A change that large counts as outside every stack, which never hides it, and the compare link shows it. | 0026, slice 2.8 |
| The old path of a file that a pull request renamed | GraphQL lists only the new path of a pull request’s files. A direct push is read over REST and counts under both paths. | 0026, slice 2.8 |
| Looking past a failed record for an older success to start attribution from | The REST fall back of 0003 reads only the newest record of a stack. Such a row reads not deployed from this dashboard yet until the next success. |
0026, 0003, slice 2.8 |
| Attribution for a scan of a commit that is not on the default branch | The walk follows the scanned commit, so a branch’s own commits read as direct pushes. v1 scans the default branch. | 0026, slice 2.8 |
| Failed deploys in the recently deployed list | A failure already has its failure line on the stack’s own row. The list stays a log of what went out. | 0029 |
| A configurable length for recently deployed | Fixed at 10 to launch. | 0029 |
| The real time of a deploy that was later superseded, under recently deployed | GitHub keeps only the latest status of a record, and for a record it marked inactive that status is the moment it was superseded. Sluiceway always sends auto_inactive: false, so this only happens next to another writer in the same environment, such as a job level environment: key without deployment: false. The list then shows the later time. |
0003, slice 2.1 |
| Reading a deployment record whose payload has another version | This version reads v: 1 and leaves every other record alone, open ones too, and says so in the job log. One workflow pins one version of the action, so two versions writing records in one repo is not a case v1 plans for. |
0003, slice 2.1 |
Finding a stack’s records after its environment label changed |
The page is read per environment name that a stack uses now. A record under the old name is only found by the fall back, which looks for the task alone. For a stack that is in sync, a failure line from before the change can go missing. No deploy depends on it. | 0003, slice 2.1 |
settle writing the row of a stack whose record it ended |
settle runs no tool (0014), so it has no diff, and none of the four row forms of 0027 fits a stack whose change is unknown. It leaves the body alone and starts a full scan, which writes the row with the failure line. A row of its own would be a new row form, and the scan costs one runner per cancelled or rejected deploy. |
0003, 0004, 0014, slice 2.6 |
| A failure line on a row that a narrowed scan carries through | A carried row is never read inside, so it keeps the failure line it has, or has not. apply writes that row itself, settle starts a full scan that does (slice 2.6), and the next preview of the stack brings it in line. |
0004, slice 2.1 |
| Allowing for a runner clock that differs from GitHub’s | The scan compares the time of a deployment status, which is GitHub’s, with the start of its own preview, which is the runner’s. With a clock that is off, a scan can keep a live row it did not have to, or take its own row a moment too early. Both are repaired by the next scan, and no deploy depends on either. | 0004, slice 2.1 |
| A shortening level that also drops a row’s links, for more than about 100 stacks pending at once | Past what one issue can usefully show. The scan fails with a clear message instead. | 0028 |
| A link to the summary in the note about shortened rows | A row that a narrowed scan carried through links to the summary of an earlier run, so one link would be wrong for some rows. Every shortened row holds its own link. | 0028, slice 1.8 |
| One alert block above the pending list that names the stacks with a delete or replace | The open delete and replace lines under each row won. The destroys marker key keeps this possible for every writer. |
0027, 0029 |
| A finer personality switch (the header without the voice, or the reverse) | One boolean, dashboard.personality, is enough to launch. |
0034 |
| A custom header image, or a palette setting | The header is Sluiceway’s brand, and a user image brings back every file rule as something to validate. Turning it off is the v1 answer. | 0034, 0033 |
| A rotating set of good-news lines | One fixed line per state to launch. A set needs a stable pick rule so the same input still gives the same body. | 0032, 0004 |
| A header state for queued stacks | It arrives with dependencies. A new state is a new file pair and a new line in the precedence table, not a breaking change. Dependencies part 1 (0056) counts a queued row as deploying, so the deploying picture shows. The drift state was built in slice 4.3 (0055). | 0031, 0055, 0056 |
| A drift picture with crates, or drift above pending in the header | The drift picture (built in slice 4.3) is a closed gate with nothing waiting, so a pending row wins over it and the counts line says N drifted. A picture per crate count with a seep would be thirteen more file pairs. |
0055, 0038, 0031 |
| Levels for deploying or failing (how many are deploying, how much failed) | One deploy and five look the same from the quay, and one failure is already the whole message. A level is new files and a new line in the table. | 0039 |
| A sign of its own for a delete, next to the one for a replace | One destroy sign for both to launch. Two signs double the signed files, from 8 to 16, and the row marker has to learn to tell a delete from a replace, which it counts together as destroys today. Red belongs to the failing lamp, so such a sign needs its own shape. |
0043, 0027 |
| The destroy sign on the failing picture | The jam already says a person is needed, and the counts line under it carries the destroy warning. It is two more files if the sign is missed there. | 0043 |
| More than 12 exact crates | Above 12 the crates are too close to count on a phone. The overflow picture says more than 12, and the counts line has the number. A higher maximum is 4 more files per count. | 0047 |
| Deploying with the number of crates still waiting | Deploying is about what moves now, and crates behind an open gate read oddly. It would be about 52 more files. | 0047 |
| The destroy sign painted on the wall right of the wordmark, or on a pole at the far right | The pole right of the wordmark won. The far right crowds the reeds and the edge. | 0047 |
| The overflow as a pile running off the edge | A half crate at the edge says the same with less. | 0047 |
| Count dots as small images in the brand colours | GitHub wraps every image in a link, so each dot would be clickable, and they are more files that say nothing an emoji does not. | 0040 |
| A cap above 10 KB per header file | The fullest file, deploying-destroys-dark.svg, is 453 bytes under the cap. Final art that needs more asks the owner, with a reason. |
0039 |
| The header generator in this repo | The files are generated by prototype code that lives in the private lab repo. It moves here, tested, when final art is settled. | 0038 |
| Header images for a fork of the action, or for an action repo under another name | The image URLs name sluiceway/sluiceway. A fork that runs at its own commit SHA points at a commit the public repo may not hold, and shows a broken image. dashboard.personality: false is the v1 answer. If it comes, the glue hands the repo over from GITHUB_ACTION_REPOSITORY. |
Slice 1.7, 0033 |
| Final art for Penny | The files in assets/mascot/ are generated concept art after one polish round, good enough to ship a first release with. Final art may redraw every shape and keeps the character, the colour pair, the composition, the states, the crates and water steps, the destroy sign and the file rules. |
0030, 0033, 0038, 0039, 0043, 0047 |
| Showing a change that touches only a stack’s outputs | pulumi preview --json does not report it. It needs a second parser for the tool’s event stream. The shape is fixed already: one update change per stack with the output names as changed keys. Until then such a stack is in sync and is deployed from outside. |
0036, 0007 |
| Deploying the exact plan that was previewed, for Pulumi | Pulumi’s update plans are still experimental. OpenTofu deploys the plan file its fresh preview saved and hashed since slice 4.1 (0053). For Pulumi the hash check on a fresh preview is the answer. | 0001, 0008, 0053 |
| A strict mode input that turns the scan job red on any preview failure | A job that is red for one broken stack on every push teaches people to ignore red. It can be added without breaking anything. | 0012 |
Default globs for scan.unrelated (such as **/*.md) |
A program can read any file, and a wasted full scan costs far less than a stale row on a fresh install. | 0010 |
| A size budget level for the summary beyond its two, or the full diffs as a file to download | The job log already holds every diff in full. | 0037 |
The count of changes outside a stack, and earlier changes and the compare link in the summary |
The row carries them already. The summary adds only what a row has no room for: every claimed pull request and direct push, with its title. | 0026, slice 1.9 |
| A link from the summary’s note to the stack’s group in the job log | A job’s page has no address that can be worked out without an API call, and a group has none at all. The note names the run’s job log and the title of the group. | 0037, slice 1.9 |
| A live example dashboard in this repo | CI tests the loop against a fake GitHub, so nothing writes a real issue here. A public demo is launch work. | Build plan |
A setting for which directories discovery never enters, or reading .gitignore for it |
Discovery skips .git and node_modules and follows no symlink. ignore on the stack id covers every other case, since a false find costs a row and not a deploy. |
PR 42 |
| The check reading git’s own list of tracked files | The check walks the checkout, skipping .git and node_modules like discovery. Right after a checkout that is the tracked files, and asking git means a process or a parser for its index. A step that writes files before the check makes them show as unclaimed, and the README says to run it right after the checkout. |
0042, slice 2.12 |
The check reading the workflow files (triggers, permissions, a pinned SHA) or suggesting inputs |
Record 0042 lists what the check does, and the workflow is GitHub’s to validate. Which stack reads a shared file is the user’s to say, so the check lists unclaimed files and suggests only scan.unrelated for docs and tooling, from a fixed list of seven globs. |
0042, slice 2.12 |
Checking discovered stacks against the backend (pulumi stack ls) |
Discovery runs in jobs that hold no credentials. A stack with no stack file does not exist for Sluiceway, and a stack file with no backend stack is a preview failure on its row. | 0014, 0006 |
| A preview failure reason of its own for more of the tool’s documented exit codes (2 configuration, 3 authentication, 4 resource, 9 timeout) | Exit code 6, a stack that does not exist in the backend, is the one a real first scan showed misleading people. The others keep “the tool exited with an error” with the exit code on the row, and a reason is added when a real scan shows the need. | 0022, slice 2.13 |
The ignore glob on the row of a stack that does not exist in the backend, or one ready-to-paste ignore block for all such stacks |
The row keeps the constant reason of 0022 and stays short. The summary names the glob next to each such stack, one click from the row. | 0022, slice 2.13 |
Reading a Pulumi project file for more than stackConfigDir |
Discovery only needs to know where the stack files are. A project file that is wrong in another way is the tool’s to refuse, as a preview failure on the row. | PR 42 |
Showing a Pulumi resource that only moves to a new address through an alias (tracking: move) |
pulumi preview --json holds no step for it, on the minimum and on the newest CLI. Like an output change it needs the tool’s event stream. Until then such a stack is in sync, and the rename rides along with the next deploy, which leaves the real object alone. |
0007, 0036 |
Folding the Pulumi step ops that no recording shows (create-replacement, delete-replaced, read-replacement, import-replacement, discard, discard-replaced, remove-pending-replace) |
The table from step ops to changes is settled from recordings only. A step op outside it fails that stack’s preview and is never shown as in sync. Each needs a scenario that makes the tool print it. | 0007, build plan |
| Previewing a Pulumi stack whose state holds a resource twice at one URN, such as a copy left waiting for deletion by a deploy that failed half way | An address is unique within a diff, so two changes at one address are refused and the row is a preview failure until the stack is repaired from outside. Folding the two needs a recording of what the tool prints. | 0007, 0008 |
| Naming the step op that Sluiceway did not know, in the job log | It is a word the tool wrote at a place Sluiceway could not read, and a parse error never says what was found. The log names the place, steps[3].op. |
0021 |
| A limit on how much tool output a preview may hold in memory | 300 resources are 1.5 MB of JSON. Writing it to disk instead would put raw values in a file that has to be removed. | 0021 |
Stopping GitHub from linking a #123, an @name or a web address that sits inside a resource name or a stack id |
Names are escaped as HTML and as Markdown, which a test proves. Whether GitHub still links such text afterwards can only be seen on a real issue. A name like that is rare, and a stack id holding #123 is rarer. The live pass checks one. |
0009, 0027 |
Renaming an existing dashboard when dashboard.title changes |
The title is used when the dashboard is created. The dashboard is found by label, root marker and author, so a title a person changed by hand does no harm. | Slice 1.10 |
| Pinning a dashboard that already exists, on every scan | Only a new dashboard is pinned. Pinning again would undo a person’s unpin after every scan and cost a request each time. | Slice 1.10 |
| Trying a dashboard write again after an API error | The write loop tries again when a write did not stick. A call that GitHub answers with an error ends the job, and the next scan repairs the body. | Slice 1.10, 0004 |
| A bound on how many closed issues are read when looking for a closed dashboard | They are only read when no open dashboard exists, which is rare, and only issues with the dashboard label are listed. | Slice 1.10, 0017 |
| The tool’s output in the job log while a preview is still running | Previews run side by side, so live output would mix the stacks. Each stack’s group is printed when the pool is done. One line per finished preview shows progress until then. | Slice 1.11, 0022 |
| Links on a row to the attempt of a run that was run again | A row links to the run. GitHub shows the newest attempt there, which is the one that wrote the row. | Slice 1.11 |
| A preview failure row for a fault inside Sluiceway itself | The adapter turns everything the tool can do wrong into a preview failure. An error thrown past that is a bug, and a red job is how it gets seen. | Slice 1.11, 0012 |
| A time limit of Sluiceway’s on the deploy itself | Stopping a deploy half way leaves a stack half deployed and its state locked. The fresh preview before it has the limit of 0012. The job’s own timeout-minutes is the user’s limit for the deploy. |
0003, slice 2.5 |
| A preview after a deploy that went out, to check the row | The row is written in sync straight away: the tool deployed exactly the diff the tick approved. A program that gives another diff on its next run shows pending again after the next scan that previews it. A deploy that failed is previewed again, because it may have gone half way. | 0004, slice 2.5 |
A row of its own for a deploy that ended before any preview (the tool missing, the stack gone, a broken sluiceway.yaml) |
apply has no diff to make a row from. The record gets its result, and the next scan previews a deploying row whose record ended, a narrowed scan too, and puts the failure line on it. |
0004, slice 2.5 |
| Deploying a record from another run | apply deploys a record only in the run whose resolve created it, because the record lives as long as its run. A deploy started some other way would be a deploy without a tick, which is the later stack input. |
0003, 0035, slice 2.5 |
| A budget for the summary of an apply | It is about one stack, and 0037 says it needs none. A stack of many thousand changes could still pass 1 MiB. The job log holds the diff in full either way. | 0037, slice 2.5 |
scan.logDiff per stack, or as an action input |
One switch per repo is enough to launch, and it belongs in the reviewed config, not in each workflow. A stacks[] key could narrow it later without a breaking change. |
0048, slice 2.16 |
| The tool’s own diff for the preview after a failed deploy, and for a narrowed scan’s carried rows | apply prints it for its fresh preview, which is what went out or what moved. The row after a failed deploy links to the summary, and a carried row keeps the link it had. |
0048, slice 2.16 |
| Stopping workflow commands around the tool’s other words (its stderr and diagnostics) | Record 0012 gives them the standing of any other step of the user’s workflow. Only the tool’s own diff, which holds values on purpose, is printed with workflow commands stopped. | 0012, 0048, slice 2.16 |
| Values on a preview page from a list of its own | Built with slice 2.19: the page shows the values of dashboard.showValues, as the row does (0052). A second list for the page alone would be a second place to get a secret wrong. |
0050, 0052, slice 2.19 |
dashboard.showValues per stack |
One list per repo is enough to launch. A stacks[] key could narrow it later without a breaking change. |
0052, slice 2.19 |
A value at a path that only diffReasons or replaceReasons names |
The tool says there which property changed and not which side it compared, so the value shown could be the wrong one. Paths from detailedDiff carry that flag. |
0052, slice 2.19 |
A value that is an object, a list or several lines, and ** in the list |
An object or a list can hold a secret next to a harmless leaf, and several lines do not fit on a row. ** would show a whole subtree nobody listed. List the leaves instead. |
0052, slice 2.19 |
| Blanking or marking the preview page of a stack that is no longer pending | A page stays on its commit as it was. It shows no value, the row no longer links to it, and a scan of a new commit writes no page for a stack in sync. Blanking would cost a request per such stack on every scan of a commit that was scanned before. | 0050, slice 2.18 |
A preview page for the pending row that apply writes after a change that moved |
The row lives until the next scan, which writes the page. apply would need checks: write for it. |
0050, slice 2.18 |
| A link from the preview page to the dashboard’s own number | The page is written before the dashboard, whose number a first scan does not know. It links to the issue list with the dashboard’s label instead, which holds one open issue. Writing the page after the dashboard would cost a second write of every row. | 0050, slice 2.18 |
checks: write on the scan job alone |
A job’s permissions replace the workflow’s, so every user would repeat the whole block for one line. resolve and settle only run Sluiceway, and apply runs the same programs the scan does. A team that requires checks can still move it by hand. |
0050, slice 2.18 |
Rejected on principle
Not planned. Bringing one of these back means reopening the decision named here, not just scheduling work.
| What | Why | Decided in |
|---|---|---|
| A command wrapper or per-stack hooks around the tool | The workflow prepares the environment. A wrapper resolves secrets again for every call and makes Sluiceway responsible for the user’s tooling. | 0013 |
| Generic extra tool arguments | Arbitrary flags can make the preview and the deploy disagree, which breaks the hash. Only named adapter options. | 0015 |
| A pattern-based mask for things that look like tokens | No property value ever leaves the adapter, so there is nothing to mask. | 0021, 0022 |
| A built-in second approval (a second person who also has to tick) | GitHub’s required reviewers do this. Without locked-in credentials it would not be real protection. | 0020 |
| Holding or storing any credential | The five credential promises. | 0014 |
| The Pulumi Automation API as the driver | 13.9 MB bundle, still needs the CLI, and its preview result lacks per-resource steps. | 0001 |
| A partial list of a row’s deletes and replaces (“the first 20, and 431 more”) | Which ones show would be an accident of sort order, and a partial list can be taken for the whole. All or none, with the full count in the warning. | 0028, 0024 |
| Relative times on the dashboard (“2 hours ago”) | The body must be byte-identical for the same input, or every render is a write. | 0029, 0004 |
| A fenced diff block or a table for a row’s changes | Both make every list loose and scroll sideways on a phone. | 0027 |
| Playful wording anywhere but the good-news line and the first-run line (rows, comments, a line for a successful deploy, the pending and deploying states) | A comment is a notification in someone’s inbox, and rows are where people decide. Anything with a delete or replace is plain twice over. | 0032, 0024 |
| A header that shows what is happening now before bad news (deploying above failing) | A failure would vanish from the picture for the length of every deploy. | 0031 |
| A grey header with no face and no colour for any delete or replace (the plain state) | Routine replaces are everyday in some repos, so the header that sets Sluiceway apart almost never showed, and a warning that is always on is not read. The header always shows the real state, and a destroy adds a sign to the same picture. | 0043, 0031 |
| A header state of its own for a delete or replace, in colour, in place of the pending pictures (the careful state) | It hides the real state behind the warning. A sign inside the real picture hides nothing. | 0043 |
| The destroy sign as one stamped crate, or as a striped board with a beacon | The crate is small on a phone and drifts out of view while deploying. The board and beacon are loud and move, and a sign that shows most days and nags is tuned out. The still sign on the wall won. | 0043 |
| An Ansible adapter | Ansible has no state. --check --diff gives a dry run, but “what is pending since the last deploy” would need the whole playbook in check mode on every scan, and the dashboard could only promise “what this playbook would change now”. Decided 2026-09-22 with the owner: not a fit. |
The adapter interface |
| A Docker or Docker Compose adapter | No plan, no diff, no state to compare. “Pending” could only mean “the file changed since the last run”, which is a deploy button, and there are many. It would dilute what a row on the dashboard means. Decided 2026-09-22. | The adapter interface |
Per-resource selection (--target) |
Granularity is one checkbox per stack. | Brief non-goals |
| Plan comments on pull requests | Other tools do that well already. | Brief non-goals |
An env key, or an environment per stack, in sluiceway.yaml |
Programs may read any variable, so Sluiceway cannot know the names. The workflow prepares one environment for the job. | 0013 |
| The tool’s native diff text in the summary, or an excerpt of the tool’s error on a row | Native text prints unmarked values, and an error can quote a connection string. Only words Sluiceway wrote leave the job log. | 0021, 0022 |
| Values shown for deletes and replaces only | The one exception that brings back every risk, on the rows people read most closely. If values ever come, they come for every op by one rule. | 0024 |
| A redacted summary | The ticker would approve bare numbers with no way on GitHub to see what is behind them. Redact is about reach, not access. | 0023 |
| A digest of values, or the commit SHA, in the diff hash | A digest of a low-entropy value can be guessed offline. A commit SHA would void every outstanding tick on every merge. | 0008 |
| A retry through “Re-run failed jobs”, checked against the person who pressed it | Two ways to start a deploy, and a record that names the wrong ticker. A fresh tick is the only retry. | 0019 |
A tick level below write |
People with read or triage access cannot edit a bot’s issue, so the level would promise something it cannot do. | 0018 |
| A list in the tick rule that grants access on its own | A typo or a removed collaborator would keep deploy rights that the repo no longer gives. A list only narrows. | 0018 |
| A comment on the dashboard for a failed deploy | The failure line on the row says it, and a comment is a notification in every subscriber’s inbox. Sluiceway writes a comment for a refused tick and for a change that moved since the tick, both about what a person did, and none for a deploy that failed. | 0004, 0018, 0032, 0051 |
| A global write lock on the dashboard | Actions concurrency works per job, so a lock would hold ticks hostage for the length of a scan. Late read, write, verify instead. | 0004 |
| The last scan’s results kept as a workflow artifact | A second store with its own expiry. The live body already is the cache. | 0004, 0011 |
| One job per stack (a matrix) for scans | Every job pays the setup again, and a small runner pool starves. One job with a bounded pool. | 0012 |
| Working out a stack’s inputs from an import graph or workspace dependencies | Per-language knowledge in the adapter, and it still misses what programs read at run time. The claim rule fails open instead. | 0010 |
Bigger efforts, each its own plan
| What | Note |
|---|---|
| A hosted GitHub App with an org-wide dashboard | A control plane only. Previews and deploys always run in the user’s own runners. It reuses the open source core. |
| GitLab and Bitbucket | The UI is a GitHub issue, so this is a different product surface. |
| A notifier built into Sluiceway (Slack, Telegram, webhooks) and a metrics endpoint | It would hold a secret and call a third party. v1 gives step outputs and a result file, and the workflow sends (0041). Native notifications and history fit a hosted version. |
| A policy engine, cost estimation | Non-goals for v1 in the brief. |
| Docs site and Marketplace launch | Part of the public launch, after the core loop is proven. |
Launch material: screenshots, a note on merge queues, the JSON schema in SchemaStore, the 1.0.0 tag and the v1 moving tag |
Same. Until then the moving tag is v0. |