Keyed by what you see. Each row: what the signal means, and the one command that fixes it. If a sentence here stops matching the code, the code is right and this doc is a bug. For the model see how-ctrl-ai-works.md; for the full agent contract see agent-guide.md.
The three checks and where they run:
ctrlai guard— local pre-commit hook. Blocks the commit.ctrlai audit— CI. Boundary + imports + attestations (+ heuristic warns).ctrlai conform— CI. Did you build what you configured (ctrl.spec.json)?
Only mechanical checks FAIL the build; heuristics only ever WARN. See the severity tables in trust-and-security.md.
audit / guard failures (these FAIL the build)
parts/<name> was modified (content hash no longer matches parts.lock) — BOUNDARY / INTEGRITY
Something edited a file inside parts/<name>/. Interiors are locked.
git checkout HEAD -- parts/ # undo the interior edit
Then make your change on your side of the seam (parts/<name>/seams.md). If
the part genuinely needs to change, use a verb instead of an edit:
ctrlai upgrade <name> [--adapter=<vendor>] # new version / vendor swap
ctrlai eject <name> # take ownership (loud, reviewable)
A type error pointing inside
parts/**is still fixed on your side — restore the part and re-read itsseams.md. The fix is never to edit the interior.
imports part interior "…" — only parts/<name>/src/index is the legal surface — IMPORTS
App code reached past the seam. Import only the part's index:
import { sendEmail } from "@parts/email.transactional"; // ✅ the seam
// not "parts/email.transactional/src/internal/…" ❌ interior
(The @parts/* alias maps to parts/*/src, so a deep @parts/<name>/internal/…
won't even resolve — TypeScript fails it as "cannot find module".)
ATTESTATION_MISMATCH / SIGNATURE_PIN / NO_ATTESTATION
The ATTESTATION.json and parts.lock disagree, or the attestation is gone —
usually a half-applied manual edit. Reinstall the part so it lands clean:
ctrlai add <name> # re-vendors a verified part + re-pins the lock
npm dependency <pkg>@<range> is not installed / is outside the contract range — NPM_DEP_MISSING / NPM_DEP_RANGE
A part's declared dependency isn't installed in range.
npm install # (or your package manager) — installs the contract's deps
audit warnings (these do NOT fail the build)
These guide; they never redden CI (even under --strict for the heuristics).
| Warning | Meaning | Fix |
|---|---|---|
UNSIGNED (dev-tier) |
the attestation is dev:unsigned |
expected today — fine locally; real signing is the team tier |
STALE |
the attestation's expires passed |
ctrlai upgrade <name> to pull a freshly re-attested version |
ROUTES |
a declared route isn't confirmed mounted | re-export the part's handler in your route file (see seams.md) |
ENV |
a required env key isn't in .env.example |
add the key (and set it); or ctrlai provision <name> |
SPRAWL |
app imports an SDK a verified part already covers | wire the part instead of the SDK |
conform failures — "not built to spec" (these FAIL the build)
conform compares ctrl.spec.json (what you configured) to what's installed.
| Check | What it means | Fix |
|---|---|---|
INSTALLED |
a capability you configured isn't installed (agent skipped it) | ctrlai add <capability> |
ATTESTED |
an installed, specified part has no attestation | ctrlai add <capability> (re-install clean) |
VENDORS |
a capability is on the wrong vendor | ctrlai upgrade <capability> --adapter=<the one you chose> |
HAND_ROLLED |
app code imports a vendor SDK for a capability you specified | delete the hand-rolled code; wire the part via its seams.md |
BEYOND_SPEC |
a part is installed beyond your spec | informational only — never fails |
N verified parts but no ctrl.spec.json(WARN; FAIL under--strict): the spec-loop has nothing to check against. If you configured this stack, the spec should exist — re-runctrlai add <…>(it writesctrl.spec.json). A brand-new empty repo with no parts is benign.
Blind spot:
conform's hand-roll detection is import-based. A capability re-implemented with rawfetch()and no SDK import is not flagged. Don't hand-roll a capability a part already covers, even viafetch.
Other common moments
The pre-commit hook blocked my commit
That's ctrlai guard. Don't git commit --no-verify — it only skips the local
hook; audit/conform still run in CI and the change still can't land. Use a
sanctioned verb (add / upgrade / eject / remove); they move the lock and
spec for you, faster than a CI round-trip.
ctrlai doctor says a route/part isn't wired
doctor checks the wired app, not just the lock. Run ctrlai wire <part> to
scaffold the route/seam wiring, then follow parts/<name>/seams.md for the code
only you can write.
ctrlai provision db / provision <part> / deploy failed
These need real credentials on your accounts and touch the network:
provision db→ needsNEON_API_KEY(Neon is the implemented provider today).provision <part>→ prints the vendor setup; set the env it names.push→ needsGITHUB_TOKEN(scopes:repo+workflow).deploy→ needs your Vercel token.
A "working ≠ on-spec" gap after a vendor flip is normal: the new vendor needs its
credentials before it actually works. Run ctrlai provision <part>.
The /ctrl panel won't let me change anything (403 / empty catalog)
The panel's write route (realize) and registry-fetch route (catalog) are
loopback-gated: they only work on a local next dev. In production they're
read-only by design. On a remote dev box, set CTRLAI_PANEL_ALLOW_REMOTE=1 only if
you accept the risk. See trust-and-security.md.