The check says which credentials each stack needs, and which the workflow does not give it
Decision record 0099
Amends 0013 (its consequence that no secret manager, cluster or cloud is named in Sluiceway’s code: one fixed table of names now is, and still no value is read), 0042 (the check gains a part) and 0061 (the check reads the
env:names of a job and its other steps). Built as slice 5.34, for issue 225.
The hardest part of starting is not the workflow and not the config. It is knowing which credentials a stack needs and how they reach the tool. The owner, after days on a real repo, on 2026-09-24: “that secret stuff is so confusing”. It is confusing because three different things are all called secrets: what the tool needs to reach the cloud, decided by the program’s providers and different per stack; what the tool needs to read its state, a backend and a passphrase, which has nothing to do with the cloud; and what Sluiceway needs, which is nothing but the workflow token. And a fourth thing that is not a secret at all: how they reach the job. Sluiceway hands the job’s environment to the tool whole and never looks inside (0013, 0014), which is right, and it also meant Sluiceway had never helped with any of this.
The pieces were there. Discovery already reads the files that name providers and backends, and the check already reads the workflow files as text (0061). So the check can say, per stack, which environment variables the tool will want, and of those, which nothing in the workflow appears to provide. A reading with its reasons shown, never a guarantee, which turns a red first scan into a list before anything runs.
Decision
- Per stack, the check lists what its own files say the tool will want, as names with alternatives, each with the file that names it. A Pulumi stack: the backend (the project’s
backend.urlnames its cloud by its scheme, Pulumi Cloud the access token, and a project with no backend the token orPULUMI_BACKEND_URL), the passphrase when the stack file has anencryptionsalt, the cloud of asecretsprovider, and the providers, from the resource types of a YAML program, the packages a Node, Python, Go or .NET program’s own manifest names, and the namespaces of the stack’s config. An OpenTofu or Terraform root module: the providers from the lock file,required_providersandproviderblocks, the backend by its label or the token of acloudblock’s host, and every variable without a default that no var file the stack takes and no auto-loaded var file sets, asTF_VAR_<name>. A Terragrunt unit: itsremote_state, and the providers of the module its source names when that is a directory of the repo. A CDK for Terraform app: the providerscdktf.jsonlists, and that its backend is set in code. A Helm release and a Kubernetes manifests stack: the cluster. For the aws provider also a region, because the tool refuses to run without one, unless the stack’s config or the provider block sets it. - One fixed table turns a provider or a backend into names. A cloud’s credentials are the variable names its tools read, the official login action the docs name (0013’s own recipes), or a command that writes a kubeconfig, any one of which is enough. A provider that needs nothing, such as
random, is left out. A provider or a backend the table has no row for is said as such: “which the check has no table for”, never guessed. This amends 0013: Sluiceway’s code now names clouds and secret managers, in one table of names, and still never reads a value, not from a file and not from the environment of its own process. - Per job that runs the tool, the check reads what the file hands the step: the names
env:sets on the workflow, the job and the Sluiceway step (env:on another step reaches only that step), the names an env file of the repo lists when a run step before the Sluiceway step names it, the login actions before it, and the commands of the run steps before it. The value of anenv:line is never read:${{ secrets.X }}is a value. This amends 0061. - What nothing names is said as “nothing in this workflow provides”, one line per need, stacks with the same need together, and never a warning. A guess is not a failure (0042): a program can read any variable, and a step that writes to
GITHUB_ENV, loads an env file the check cannot read, is handed a secret, or is one of the secret loader actions the docs name, is opaque. Such a step is named as a maybe: “the step Load the environment may load it, and the check cannot see into it”. A job that names a way to everything gets one line that says so, and the job log lists which way for each need. A job that only checks gets nothing. - The reading lives in core and the files-only part of the adapters, so the hosted app can reuse it for the pull request it opens (step 2 of its onboarding):
core/credentials.tsholds the types, the table and the judgement, each adapter reads its own files, andadapters/files-only.tshands the check the same thing the check job and the command line get.npx sluiceway checkshows it too, with no change of its own. - Names only, and a test holds it. No config value, no URL, no salt, no var file value and no default leaves an adapter: a scheme, a key, a label and a package name are all that is read. The tests plant canary values in every file the reading touches and fail if one reaches the log or the summary.
Why the check does not say more
The check cannot know what a step writes to the environment, what a secret loader exports under which names, or what a program reads at run time. Asking the API or running the step would break the promise of 0042 that the check is safe on a pull request from a fork. So it says the half it can read for certain, names the steps it cannot see into, and never turns the other half into a failure. A stack that runs and fails for a missing credential still shows in the scan, as the closing line of the check has always said.
Considered
- Reading the environment of the check’s own process, to say which names a run of the check has. Rejected: promise 2 of 0014 is that no Sluiceway code reads a credential variable, and the check runs in a job that holds none anyway.
- A warning for a need nothing names. Rejected, for the reason 0061 gives: a warning on a correct workflow, such as one that loads an env file the check cannot read, teaches people to ignore the check.
- Following what a loader action exports from its
with:andenv:, such as the names a1password/load-secrets-actionstep maps. Left out (docs/later.md): each action has its own rule, and a step that loads secrets is named as a maybe, which is honest. - Resolving
${{ }}expressions, such as an env name built from a matrix. The text as written is what GitHub evaluates; a name that is an expression is a name the check does not know. - A guess from resource type prefixes in a root module without a lock file,
required_providersorproviderblocks. Left out:aws_instancenames a provider by convention, and the three sources the check reads are the ones the tool itself reads.
Consequences
- Record 0013 is amended: Sluiceway’s code names clouds, secret managers and login actions, in one table of names for the check, and still sets, defaults and reads none of them.
- Record 0042 is amended: the check has a Credentials part, after the Workflows part.
- Record 0061 is amended: the check reads the
env:names of a workflow, a job and the Sluiceway step, and the other steps of the job. CONTEXT.mdgains “credential need”.docs/credentials.mdgets a section on what the check says, and the check’s description in the workflow page, the reference and the README names it.docs/later.mdgains the lines about what a loader action exports, the providers and backends the table has no row for, the programs whose packages the check does not read, the repository of a Helm chart reference, and a per-job table in the summary.