DOCS · CORE CONCEPTS
The human gate
The approval step at station 04: what it records, where it is enforced, and why no agent — including specd's own — can open it.
On this page
One named person approves one version of one spec. Nothing downstream runs without that record, and the record cannot be forged, backdated or produced by an agent.
What approving records
- Who
- A named, signed-in human. Not a service account, not an agent, not "the system".
- What
- A specific version of a specific spec — v1, not "the spec".
- When
- A timestamp, kept exactly as recorded even after the spec is superseded.
Where it is enforced
Three independent layers, so no single mistake opens the gate. This is the difference between a rule and an invariant.
| Layer | What it refuses |
|---|---|
| The state machine | A transition to approved with no actor. There is no code path that produces an unattributed approval. |
| The API boundary | CLI tokens are audience-scoped and rejected on every route that authors or approves. specd spec pull is refused server-side for anything unapproved. |
| A database CHECK constraint | An approved row with no approver. Even a direct UPDATE against the database cannot record one. |
Why no agent can open it
An agent that could approve its own spec would make the gate decorative — the system would be reviewing itself and reporting that it agreed. So the capability does not exist anywhere an agent can reach:
- The CLI fetches, registers and reports. It never authors, reviews or approves — the server refuses those for CLI tokens regardless of what the binary asks.
- The MCP server is read-only by construction rather than by convention. It carries the same CLI-audience token, so approving through it is not blocked — it is impossible.
- The Claude Code plugin's hooks can block an edit; neither can approve anything.
A plugin that could open the gate would defeat the product.
knowledge/decisions/0018-working-agreements-ship-as-a-plugin.md
Approval is append-only
approved → draft is refused. A change means a new version: v2 supersedes v1, and v1 keeps its approval stamp exactly as recorded. Nothing rewrites history, so the question "what did we approve, and who approved it?" always has an answer.
Gating your own CI on it
The gate is available to your pipeline too. specd spec status exits 3 when a spec exists but is not approved — deliberately distinct from a generic failure, so a script can tell "not approved yet" from "could not reach specd".
- name: Require an approved spec
run: |
specd spec status "$SPEC_ID"
case $? in
0) echo "approved — building" ;;
3) echo "::error::$SPEC_ID is not approved yet"; exit 1 ;;
*) echo "::error::could not reach specd"; exit 1 ;;
esacRunning the gate well
- Name an owner per project. An unowned approval queue is a stalled queue, and the failure mode is that people route around it.
- Approve versions, not intentions. If the spec is nearly right, ask for v2. Approving "the idea" and fixing it in review puts you back where you started.
- Do not batch-approve. Four specs stamped in ninety seconds is the exact failure this station exists to prevent.
A reviewer's checklist is at Reviewing and approving a spec.