Why this is written before the sweep, not during it
rank: decision outranks: [] doc_id: PTV-DEC-0018 title: What counts as a credential store, for the purposes of N8N-27 date: 2026-08-17 status: open — awaiting human countersign owner: petrova-codes scaffold: PTV-SCF-0001 phase: P4 supersedes: none
Section titled “rank: decision outranks: [] doc_id: PTV-DEC-0018 title: What counts as a credential store, for the purposes of N8N-27 date: 2026-08-17 status: open — awaiting human countersign owner: petrova-codes scaffold: PTV-SCF-0001 phase: P4 supersedes: none”Why this is written before the sweep, not during it
Section titled “Why this is written before the sweep, not during it”N8N-27 exists because three stores were swept and a fourth was not, and the
fourth was invisible to every coverage figure computed inside the three
(PTV-FND-0038). The obvious repair is to enumerate stores rather than values.
The non-obvious failure mode is that “store” has no fixed meaning. VAULT qualified because it holds a credential the platform environment does not. So does a config file with a key in it, a database column, a keychain call, a hardcoded literal, and a secret fetched from another service at request time. If that boundary is settled repo by repo while reading, the resulting list is shaped by what the reader happened to notice — which is the same sweep-blindness in a new population, arrived at by a new route.
So the boundary is fixed here, in advance, and the sweep is measured against
it. Where the definition turns out to be wrong, it is superseded by a new
decision doc (MR-7), not widened mid-pass.
The definition
Section titled “The definition”A credential store is any location, reachable by a running deployment, from which that deployment obtains a secret it did not receive through its platform environment variables.
Three clauses, each load-bearing:
- reachable by a running deployment — excludes a developer’s laptop, a
password manager, a
.env.example. Those are exposure surfaces and matter elsewhere; they are not places a production call gets its key from. - obtains — the deployment reads it at runtime or build time. A file that exists in the repo and is never read is not a store, it is litter.
- did not receive through its platform environment variables — the three swept stores are, by construction, the platform-environment path. This clause is what makes the definition additive to the existing sweep rather than overlapping it.
In scope
Section titled “In scope”| Class | Example | Why it qualifies |
|---|---|---|
| Application-managed vault | rocky-console VAULT (vault-secrets.json, AES under ROCKY_VAULT_MASTER_KEY) | The instance that raised N8N-27 |
| Secret in a committed or deployed file | a key literal in config.json, settings.yaml, a seed script | Read at runtime; invisible to platform enumeration |
| Hardcoded literal in source | const KEY = "n8n_api_..." | The limiting case; still a place a call gets a key |
| Database-resident credential | a secrets/credentials/connections table | Read at runtime; no platform surface shows it |
| Runtime fetch from another service | a call to a secrets manager, another app’s API, an OIDC exchange | The credential exists at call time and never appears at rest |
| Sidecar or mounted secret file | a Docker/Railway mounted volume, /run/secrets/... | Reachable, read, not an env var |
Out of scope, and why — so the exclusions are auditable
Section titled “Out of scope, and why — so the exclusions are auditable”| Class | Why excluded |
|---|---|
| Platform environment variables (Railway, Vercel, GitHub Actions) | Already swept. Including them double-counts and hides whether N8N-27 added anything |
Local developer files (~/code/env/**, shell profiles) | Not reachable by a running deployment. Covered by N8N-04 |
.env.example / templates with empty or placeholder values | No secret present |
| Third-party services holding their own copy (e.g. an n8n credential record) | Not a store PETROVA can enumerate; a separate problem |
| CI caches, build logs, error trackers | Leak surfaces, not sources. Real, and a different item |
The last two are deliberately excluded rather than forgotten. Recording an exclusion is what makes the negative result mean something later.
The pattern that defeats single-sweep reasoning
Section titled “The pattern that defeats single-sweep reasoning”rocky-console reads its key from VAULT with a process.env fallback
(N8N_BOOTSTRAP_KEY, undeclared on that project):
try { return await getSecretValue("n8n_api_key", "relay"); } // storecatch { const b = process.env.N8N_BOOTSTRAP_KEY; if (b) return b; } // platformA repo with that shape appears in a platform sweep and holds a store, and neither sweep alone establishes which path is live. The platform sweep sees a variable and concludes the key comes from there; the store sweep sees a vault and concludes the same about it. Both can be wrong simultaneously — here, both paths are dead and the service cannot authenticate at all.
Any repo exhibiting vault-primary-with-env-fallback, or any other multi-source resolution, gets both questions asked and the resolution order recorded. This is a required output of the sweep, not a note.
What the sweep must record
Section titled “What the sweep must record”- The repo denominator, measured, with skips itemised and reasoned. The store-denominator error came from three known stores with no record of how “known” was established; reproducing that at the repo layer is the one outcome that would make this whole item pointless.
- Per store found: repo, path, class from the table above, what credential it holds if determinable, and whether it is reachable in a live deployment or only in principle.
- Multi-source resolutions, per the section above.
- What the instrument cannot see, stated as a limit rather than a clean negative. Reading code finds stores that are declared in code. It does not find a credential written into a database by a human through a UI, and it does not find one fetched from a service whose name is itself in a secret.
The denominator is already unstable
Section titled “The denominator is already unstable”Two enumerations of “the estate” disagree, measured today:
PTV-FND-0032 (2026-08-17, GitHub Actions sweep) : 30 orgs, 465 reposthis enumeration (2026-08-17) : 40 orgs, 458 repos in orgs 524 repos accessible in totalForty organisations, not thirty. Fewer repos inside them than the earlier count of repos, while the total accessible is higher than both. These cannot all be the same population, and no reconciliation is offered here because none has been measured.
This is recorded before the sweep runs so that its coverage figure is read against a denominator known to be contested, rather than presented as a fraction of a number nobody has checked. Establishing which population is correct is part of N8N-27, not a precondition someone waves through.
Countersign
Section titled “Countersign”- Human: FLIGHT<alex@devarno.com:2026-09-22> — the boundary above is the one N8N-27 is measured against
Unchecked, this decision is open and the sweep’s results are provisional
against it.