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.

LayerWhat it refuses
The state machineA transition to approved with no actor. There is no code path that produces an unattributed approval.
The API boundaryCLI 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 constraintAn 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".

.github/workflows/…
- 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 ;;
    esac

Running 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.