Purpose
Stage: Spec (pass 1, signed) + Plan (pass 2, appended). The autonomous Implement→Ship wave runs
AFK; the moment it hits a missing API key, an unprovisioned service, or a wrong runtime version, it fails
silently in the middle of a parallel wave. environment-manifest captures — declaratively, never executably —
every external thing the feature needs to run, so the value-blind preflight-readiness gate can refuse to start the
wave until each item is provisioned.
First principle: an environment need is data (a typed row), not code (a command). Encoding it as data
makes two whole classes of failure structurally impossible — a secret can't leak into a committed file
(there is nowhere to put a value), and an unreviewed shell string can't run unattended at the human→AFK
boundary (there is nowhere to put a command). You author the manifest; preflight-readiness checks it. Maker ≠ checker.
When to use / when to skip (depth: lite)
Use twice — two passes over one file:
- Pass 1 — Spec (interface-level, signed): after
to-prd lands prd.md, and on a UI feature after
frontend-design has explored the interface — it runs before this skill and acceptance-criteria both,
because an exploration turns up services a prd.md never named. Capture the keys / MCPs / services the
product needs. These rows are part of the single Spec gate sign-off.
- Pass 2 — Plan (implementation-level, appended): after
plan-breakdown produces slices. Append the
fixtures / version-pins / accounts the implementation needs; these re-surface at the Verify barrier.
Skip only when a pure-refactor feature introduces zero external dependency — but still emit a signed
manifest with an explicit (no external dependencies) note so the gate is explicit and preflight-readiness has
something to read. Never leave the file absent: absence is ambiguous, an empty-signed manifest is a decision.
Inputs
- Pass 1 consumes
docs/features/<slug>/prd.md — sections Implementation Decisions (names
services / MCPs / keys), Testing Decisions (names fixtures), Out of Scope (what NOT to provision).
Refuse to run pass 1 if prd.md is absent — without the PRD you'd be guessing at the feature's needs.
- Pass 2 consumes
docs/features/<slug>/plan.md + its slices — to pick up impl-level needs (test
fixtures, exact runtime versions, per-slice accounts) and tie each row to the slice id(s) that require it.
- Never consumes a
.env, a secret store, or any live value. This skill reads intent, not secrets.
Process
- Locate the file.
docs/features/<slug>/environment.md. Pass 1 creates it; pass 2 appends.
- Walk the source for external needs. From
prd.md (pass 1) / plan.md (pass 2), list everything the
run needs from outside the repo: keys, MCP servers, running services, language/tool versions, seed
data, third-party accounts. A row is a thing whose absence should stop the wave — that is what a row
means, because preflight-readiness refuses to dispatch over a missing one. Something the run merely
uses when it happens to be there, and runs unchanged without, is not a row: filing it turns an
optional convenience into a blocker. When in doubt ask whether you would want the wave held for it.
- Classify each into exactly one kind (closed enum — see Kind playbook). If a need doesn't fit a kind,
that's a signal you've mis-modeled it, not a license to invent a column.
- Write one row per need:
kind · name · purpose · required-by · pass · attest. No value. No command.
- Mark un-probeable items with
manual: <yes/no question> in attest — the single amber escape hatch
for things a value-blind prober can't check (paid quota remaining, a human-owned SaaS account). The
question is human-readable, never executable.
- Append-only after sign. Pass 2 never rewrites a row signed in pass 1; if a signed interface-level fact
genuinely changed, add a new row and flag the supersession for re-sign — don't silently mutate a signed row.
- Set status.
draft until the Spec gate signs pass-1 rows → signed.
- Do not probe. You author;
preflight-readiness checks. Running a check here would duplicate preflight-readiness and tempt
a command cell into existence.
The typed-kind manifest
Closed kind enum — these six, no others:
| kind |
covers |
env-var |
a named secret/config the process reads from the environment (e.g. POSTMARK_API_KEY) |
mcp |
an MCP server the agent/app connects to (e.g. supabase) |
service |
a running backing service (e.g. postgres, redis) |
runtime-dep |
a language/tool/version the build needs (e.g. node>=20, pnpm) |
fixture |
seed data / a test artifact (e.g. seed-users.json) |
account |
a third-party account/tenant (e.g. stripe-test) |
Row schema — exactly these columns: | kind | name | purpose | required-by | pass | attest |
name — the identifier only (the env-var name, the MCP name, the service name). Never the value.
purpose — one line: why the feature needs it.
required-by — the PRD-namespaced slice id(s) (e.g. PWR-2) or the feature slug; ties the row to STATE.md.
pass — spec or plan (which pass added the row).
attest — blank = auto-probed by preflight-readiness's per-kind value-blind prober; manual: <question> =
un-probeable amber escape hatch.
Two columns that must never exist (security fail-safe):
- No
value column. Secrets are structurally unrepresentable — a key can never leak into this committed
file because there is nowhere to put it.
- No
command/verify column. A free-text command cell is an unreviewed shell string that preflight-readiness
would run unattended at the AFK boundary. The reviewed prober-per-kind file in preflight-readiness replaces it.
A new service is a new row, never a new command.
Example:
feature: password-reset
status: signed
## Manifest
| kind | name | purpose | required-by | pass | attest |
|-------------|-------------------|----------------------------------|-------------|------|--------|
| env-var | POSTMARK_API_KEY | send reset emails | PWR-1 | spec | |
| service | redis | store reset tokens with TTL | PWR-2 | spec | |
| mcp | supabase | user-table reads | PWR-1 | spec | |
| runtime-dep | node>=20 | crypto.webcrypto for token gen | PWR-1 | plan | |
| fixture | seed-users.json | known users for reset E2E | PWR-2 | plan | |
| account | stripe-test | billing webhook on reset (paid) | PWR-3 | plan | manual: is the stripe-test tenant funded this month? |
Kind playbook
- Read from
process.env / the environment → env-var (name it; never paste it).
- Talked to over MCP → mcp.
- A daemon/container that must be up → service.
- A version/tool the build assumes → runtime-dep (encode the constraint in
name, e.g. python>=3.11).
- Data that must exist for a test to be meaningful → fixture.
- A tenant/login on a third-party platform → account (usually
manual: attested).
If two kinds fit, pick the one preflight-readiness can check value-blind. If none fit, you've mis-modeled the need —
re-read the PRD, don't add a column.
Rationalizations
- "I'll just drop the value in so
preflight-readiness can check it." → No. The value is structurally unrepresentable
on purpose; preflight-readiness probes value-blind. A value here is a committed secret.
- "A quick
curl/psql verify command in a cell makes it runnable." → No. That runs unattended at the AFK
boundary with no review. The prober-per-kind file is the reviewed replacement.
- "This dep is weird, I'll add a
notes/type column." → No. The enum is closed; a misfit is a modeling signal.
- "It's un-probeable, so I'll skip the row." → No. Add it with
manual: <question>; a missing row is an
invisible dependency that fails AFK. Un-probeable is not the same as optional: the test is whether the
wave should be held for it. Something the run uses when it happens to be there and runs unchanged
without — a design evidence source, say — is not a row at all, probeable or not.
- "I'll just rewrite the signed Spec row during planning." → No. Append + flag for re-sign; signed facts are
the Spec gate's contract.
- "Let me run the probe to be sure." → No. Maker ≠ checker; authoring and probing are separate skills.
Red flags — STOP
- A value, secret literal, token, password, or connection string appears in any cell.
- A column named
value, command, verify, cmd, or any shell string in a cell.
- A
kind outside {env-var, mcp, service, runtime-dep, fixture, account}.
- You are about to run a command to check an item (that's
preflight-readiness's job).
- Pass 2 edits/overwrites a row that pass 1 signed.
- A row for something the run works fine without — filing an optional convenience makes
preflight-readiness refuse a wave over a thing nobody needed.
- The manifest is absent for a feature heading into a wave.
Verification (ending criteria)
docs/features/<slug>/environment.md exists with a ## Manifest table and a status: field.
- Every row's
kind is one of the six enum values.
- Greppable security check (load-bearing done-predicate): no column header matches
value|command|verify|secret|cmd; no cell contains a secret-shaped literal (sk_, AKIA,
-----BEGIN, or a URL embedding credentials).
- Every row has a non-empty
purpose and required-by, and every row is something whose absence should
stop the wave — nothing the run merely uses when it happens to be there is filed as one.
- Un-probeable rows carry
manual: <question>; all others leave attest blank for preflight-readiness.
- Pass-1 (
pass: spec) rows are part of the Spec sign-off bundle; status: signed only after that gate.
Outputs & handoff contract
- Emits
docs/features/<slug>/environment.md.
- Stable sections consumers depend on: the
status: field and the ## Manifest table with the fixed
kind · name · purpose · required-by · pass · attest columns. Consumer: preflight-readiness reads the Manifest,
runs its per-kind value-blind prober per row, honors manual: attestations, and refuses the wave on any
red / un-attested amber.
- Contract rule: change the manifest's shape (a new kind, a renamed column) → update
preflight-readiness + its
prober registry in the same commit (a new kind needs a new prober file; OCP).
- STATE.md update: the feature sits in
feature: spec while pass-1 is unsigned; the plan-pass append
happens while feature: plan. This skill creates no slice rows.
- Per-stage handoff: the signed manifest is one of the 4–5 Spec-gate artifacts (intent + prd + acceptance
- environment [+ design contract when UI]); the next cold reader (
preflight-readiness) needs nothing but this file.
1---2name: environment-manifest3description: Capture every external thing the autonomous run needs — API keys, MCP servers, services, runtime deps, test fixtures, accounts — as a typed-kind manifest with NO values and NO commands. Use during Spec right after the PRD lands, and again during Plan once slices exist. ALWAYS run this before an AFK/autonomous wave so preflight-readiness can refuse to start on a missing dependency. If you are about to paste a secret value or a check command into a config file, STOP and use this instead.4---56## Purpose78Stage: **Spec** (pass 1, signed) + **Plan** (pass 2, appended). The autonomous Implement→Ship wave runs9AFK; the moment it hits a missing API key, an unprovisioned service, or a wrong runtime version, it fails10silently in the middle of a parallel wave. `environment-manifest` captures — **declaratively, never executably** —11every external thing the feature needs to run, so the value-blind `preflight-readiness` gate can refuse to start the12wave until each item is provisioned.1314First principle: an environment need is *data* (a typed row), not *code* (a command). Encoding it as data15makes two whole classes of failure structurally impossible — a secret can't leak into a committed file16(there is nowhere to put a value), and an unreviewed shell string can't run unattended at the human→AFK17boundary (there is nowhere to put a command). You author the manifest; `preflight-readiness` checks it. Maker ≠ checker.1819## When to use / when to skip (depth: lite)2021Use **twice** — two passes over one file:22- **Pass 1 — Spec (interface-level, signed):** after `to-prd` lands `prd.md`, and on a UI feature after23 `frontend-design` has explored the interface — it runs before this skill and `acceptance-criteria` both,24 because an exploration turns up services a `prd.md` never named. Capture the keys / MCPs / services the25 *product* needs. These rows are part of the single Spec gate sign-off.26- **Pass 2 — Plan (implementation-level, appended):** after `plan-breakdown` produces slices. Append the27 fixtures / version-pins / accounts the *implementation* needs; these re-surface at the Verify barrier.2829Skip only when a pure-refactor feature introduces zero external dependency — but still emit a signed30manifest with an explicit `(no external dependencies)` note so the gate is explicit and `preflight-readiness` has31something to read. Never leave the file absent: absence is ambiguous, an empty-signed manifest is a decision.3233## Inputs3435- **Pass 1 consumes** `docs/features/<slug>/prd.md` — sections **Implementation Decisions** (names36 services / MCPs / keys), **Testing Decisions** (names fixtures), **Out of Scope** (what NOT to provision).37 **Refuse to run pass 1 if `prd.md` is absent** — without the PRD you'd be guessing at the feature's needs.38- **Pass 2 consumes** `docs/features/<slug>/plan.md` + its slices — to pick up impl-level needs (test39 fixtures, exact runtime versions, per-slice accounts) and tie each row to the slice id(s) that require it.40- Never consumes a `.env`, a secret store, or any live value. This skill reads *intent*, not *secrets*.4142## Process43441. **Locate the file.** `docs/features/<slug>/environment.md`. Pass 1 creates it; pass 2 appends.452. **Walk the source for external needs.** From `prd.md` (pass 1) / `plan.md` (pass 2), list everything the46 run **needs** from outside the repo: keys, MCP servers, running services, language/tool versions, seed47 data, third-party accounts. A row is a thing whose absence should stop the wave — that is what a row48 means, because `preflight-readiness` refuses to dispatch over a missing one. Something the run merely49 *uses when it happens to be there*, and runs unchanged without, is not a row: filing it turns an50 optional convenience into a blocker. When in doubt ask whether you would want the wave held for it.513. **Classify each into exactly one kind** (closed enum — see Kind playbook). If a need doesn't fit a kind,52 that's a signal you've mis-modeled it, not a license to invent a column.534. **Write one row per need:** `kind · name · purpose · required-by · pass · attest`. **No value. No command.**545. **Mark un-probeable items** with `manual: <yes/no question>` in `attest` — the single amber escape hatch55 for things a value-blind prober can't check (paid quota remaining, a human-owned SaaS account). The56 question is human-readable, never executable.576. **Append-only after sign.** Pass 2 never rewrites a row signed in pass 1; if a signed interface-level fact58 genuinely changed, add a new row and flag the supersession for re-sign — don't silently mutate a signed row.597. **Set status.** `draft` until the Spec gate signs pass-1 rows → `signed`.608. **Do not probe.** You author; `preflight-readiness` checks. Running a check here would duplicate preflight-readiness and tempt61 a command cell into existence.6263## The typed-kind manifest6465Closed kind enum — **these six, no others**:6667| kind | covers |68|---|---|69| `env-var` | a named secret/config the process reads from the environment (e.g. `POSTMARK_API_KEY`) |70| `mcp` | an MCP server the agent/app connects to (e.g. `supabase`) |71| `service` | a running backing service (e.g. `postgres`, `redis`) |72| `runtime-dep` | a language/tool/version the build needs (e.g. `node>=20`, `pnpm`) |73| `fixture` | seed data / a test artifact (e.g. `seed-users.json`) |74| `account` | a third-party account/tenant (e.g. `stripe-test`) |7576Row schema — exactly these columns: `| kind | name | purpose | required-by | pass | attest |`77- `name` — the *identifier* only (the env-var name, the MCP name, the service name). **Never the value.**78- `purpose` — one line: why the feature needs it.79- `required-by` — the PRD-namespaced slice id(s) (e.g. `PWR-2`) or the feature slug; ties the row to `STATE.md`.80- `pass` — `spec` or `plan` (which pass added the row).81- `attest` — blank = auto-probed by `preflight-readiness`'s per-kind value-blind prober; `manual: <question>` =82 un-probeable amber escape hatch.8384**Two columns that must never exist** (security fail-safe):85- **No `value` column.** Secrets are structurally unrepresentable — a key can never leak into this committed86 file because there is nowhere to put it.87- **No `command`/`verify` column.** A free-text command cell is an unreviewed shell string that `preflight-readiness`88 would run unattended at the AFK boundary. The reviewed prober-per-kind file in `preflight-readiness` replaces it.89 A new service is a **new row**, never a new command.9091Example:92```93feature: password-reset94status: signed9596## Manifest97| kind | name | purpose | required-by | pass | attest |98|-------------|-------------------|----------------------------------|-------------|------|--------|99| env-var | POSTMARK_API_KEY | send reset emails | PWR-1 | spec | |100| service | redis | store reset tokens with TTL | PWR-2 | spec | |101| mcp | supabase | user-table reads | PWR-1 | spec | |102| runtime-dep | node>=20 | crypto.webcrypto for token gen | PWR-1 | plan | |103| fixture | seed-users.json | known users for reset E2E | PWR-2 | plan | |104| account | stripe-test | billing webhook on reset (paid) | PWR-3 | plan | manual: is the stripe-test tenant funded this month? |105```106107## Kind playbook108109- Read from `process.env` / the environment → **env-var** (name it; never paste it).110- Talked to over MCP → **mcp**.111- A daemon/container that must be *up* → **service**.112- A version/tool the build assumes → **runtime-dep** (encode the constraint in `name`, e.g. `python>=3.11`).113- Data that must *exist* for a test to be meaningful → **fixture**.114- A tenant/login on a third-party platform → **account** (usually `manual:` attested).115If two kinds fit, pick the one `preflight-readiness` can check value-blind. If none fit, you've mis-modeled the need —116re-read the PRD, don't add a column.117118## Rationalizations119120- "I'll just drop the value in so `preflight-readiness` can check it." → No. The value is structurally unrepresentable121 on purpose; `preflight-readiness` probes value-blind. A value here is a committed secret.122- "A quick `curl`/`psql` verify command in a cell makes it runnable." → No. That runs unattended at the AFK123 boundary with no review. The prober-per-kind file is the reviewed replacement.124- "This dep is weird, I'll add a `notes`/`type` column." → No. The enum is closed; a misfit is a modeling signal.125- "It's un-probeable, so I'll skip the row." → No. Add it with `manual: <question>`; a missing row is an126 invisible dependency that fails AFK. Un-probeable is not the same as optional: the test is whether the127 wave should be held for it. Something the run uses when it happens to be there and runs unchanged128 without — a design evidence source, say — is not a row at all, probeable or not.129- "I'll just rewrite the signed Spec row during planning." → No. Append + flag for re-sign; signed facts are130 the Spec gate's contract.131- "Let me run the probe to be sure." → No. Maker ≠ checker; authoring and probing are separate skills.132133## Red flags — STOP134135- A value, secret literal, token, password, or connection string appears in **any** cell.136- A column named `value`, `command`, `verify`, `cmd`, or any shell string in a cell.137- A `kind` outside `{env-var, mcp, service, runtime-dep, fixture, account}`.138- You are about to **run** a command to check an item (that's `preflight-readiness`'s job).139- Pass 2 edits/overwrites a row that pass 1 signed.140- A row for something the run works fine without — filing an optional convenience makes141 `preflight-readiness` refuse a wave over a thing nobody needed.142- The manifest is absent for a feature heading into a wave.143144## Verification (ending criteria)145146- `docs/features/<slug>/environment.md` exists with a `## Manifest` table and a `status:` field.147- Every row's `kind` is one of the six enum values.148- **Greppable security check (load-bearing done-predicate):** no column header matches149 `value|command|verify|secret|cmd`; no cell contains a secret-shaped literal (`sk_`, `AKIA`,150 `-----BEGIN`, or a URL embedding credentials).151- Every row has a non-empty `purpose` and `required-by`, and every row is something whose absence should152 stop the wave — nothing the run merely uses when it happens to be there is filed as one.153- Un-probeable rows carry `manual: <question>`; all others leave `attest` blank for `preflight-readiness`.154- Pass-1 (`pass: spec`) rows are part of the Spec sign-off bundle; `status: signed` only after that gate.155156## Outputs & handoff contract157158- **Emits** `docs/features/<slug>/environment.md`.159- **Stable sections** consumers depend on: the `status:` field and the `## Manifest` table with the fixed160 `kind · name · purpose · required-by · pass · attest` columns. **Consumer:** `preflight-readiness` reads the Manifest,161 runs its per-kind value-blind prober per row, honors `manual:` attestations, and refuses the wave on any162 red / un-attested amber.163- **Contract rule:** change the manifest's shape (a new kind, a renamed column) → update `preflight-readiness` + its164 prober registry **in the same commit** (a new kind needs a new prober file; OCP).165- **STATE.md update:** the feature sits in `feature: spec` while pass-1 is unsigned; the plan-pass append166 happens while `feature: plan`. This skill creates no slice rows.167- **Per-stage handoff:** the signed manifest is one of the 4–5 Spec-gate artifacts (intent + prd + acceptance168 + environment [+ design contract when UI]); the next cold reader (`preflight-readiness`) needs nothing but this file.