DOCS · REFERENCE
Security and invariants
The nine properties specd enforces in code rather than by convention, and where each one is enforced.
Each of these is enforced in code, and each has a test. A property that depends on everyone remembering it is not a property.
The invariants
- Only a named human can approve
- The state machine refuses
approvedwithout an actor, and a database CHECK constraint rejects an approved row with no approver — a direct write cannot record an unattributed approval. - The gate cannot be routed around
specd spec pullis refused server-side for anything unapproved, and CLI tokens are audience-scoped and rejected on every route that authors or approves.- Approval is append-only
approved → draftis refused. v2 supersedes v1 while v1 keeps its stamp exactly as recorded.- A citation means someone can check it
- Citations are validated against what was actually retrieved; invented paths are demoted to
UNVERIFIED, because a citation that cannot be followed is worse than none. - The loop closes
- The last task of every spec files the as-built copy. If the model omits it, specd appends it.
- Spend cannot run away
- Caps are checked before a run starts. Money is integer EUR cents — floats never touch it.
- Agents never push
- Editing tools only, the spec's own branch only, never a default branch. The build agent has no shell.
- Webhooks cannot be impersonated
- GitHub: HMAC over raw bytes, constant time, before parsing. GitLab: token echo, constant time. Both fail closed on an unset secret, dedupe by delivery id, and act only for a registered repository.
- Leaving is free
- Git holds the knowledge; the platform holds a derived index. Delete a project and nothing you would miss is gone.
Credentials
- Every connection secret is held with envelope encryption under a project-bound key (
VAULT_MASTER_KEY), and never logged. - Credentials are verified against the provider before being stored, so a bad one fails in front of you rather than inside an agent run.
- A subscription credential is never seen, stored or proxied — specd shells out to a CLI that is already signed in. That is also why a hosted specd could not offer that mode.
- A runner token lives in a separate keychain slot from a person's own CLI token, so pairing cannot clobber a session. Revoking a runner takes effect on its very next request, with no grace period.
Tokens are audience-scoped
A CLI token, a runner token and a web session are different audiences, and the API checks which one is presenting before it checks what is being asked. This is what makes "the MCP server is read-only" a structural statement rather than a promise about the MCP server's code.
The audit trail
Every webhook delivery is recorded with what specd decided and why — including the ones it ignored. "The webhook arrived and specd chose not to act" and "the webhook never arrived" are different problems, and the delivery log is what tells you which one you have. Rows are retained for 30 days by default.