Enforcement check
v7 unifies enforcement into one report. The CLI haft check is the
CI-friendly entry point; haft_query(action="check") is the
plugin-mode parity for embedded host agents. Both consume the same Go helpers and
produce JSON with identical fields — a contract test asserts the shape.
What the report covers
- Stale — decisions and artifacts whose
valid_untilis past, or whose evidence has decayed (R_eff < 0.5; AT-RISK at < 0.3). - Drifted — decisions with file drift on baselined paths.
- Unassessed — active decisions with no measurement yet.
- Coverage gaps — claims with no satisfying evidence.
- Spec health — drift on approved SpecSections
(
spec_section_drifted), missing baselines on active sections (spec_section_needs_baseline), time-based staleness (spec_section_stalefor active sections whosevalid_untilis past today), and L0/L1/L1.5 structural carrier findings fromhaft spec check.
CLI: haft check
cd path/to/your/project
haft check # human-readable summary
haft check --json # structured JSON for CI Exit code:
- 0 — clean. No stale, no drift, no coverage gaps, no spec issues.
- 1 — at least one finding. CI gates can branch on this.
Sample human output on a project with a stale section:
haft check: governance debt found (2 finding(s))
stale: 0
drifted: 0
unassessed: 0
coverage gaps: 0
spec health: 2
Spec Health
- [error/spec_section_needs_baseline] TS.role.001 — section "TS.role.001" is active
but has no baseline; the operator has not yet approved it through the onboarding
method
next_action: haft_spec_section(action="approve", section_id="TS.role.001") to
record a baseline
- [error/spec_section_stale] TS.role.001 — section "TS.role.001" is active but
valid_until 2025-01-01 expired 482 day(s) ago
next_action: triage staleness on "TS.role.001": rebaseline if the claim is still
current (extend valid_until in the carrier and run haft_spec_section
action=rebaseline), reopen if the claim needs review, or deprecate the section MCP: haft_query(action="check")
Embedded host agents in plugin mode call the same machinery through MCP:
haft_query(action="check")
Returns a JSON object with the same field layout as haft check --json.
The host agent reads spec_health, sees a typed
spec_section_drifted finding, and proposes the next action directly:
haft_spec_section(action="rebaseline",
section_id="TS.role.001",
reason="role narrowed after stakeholder review") Without typed access the host agent would be parsing CLI prose. With it, the triage loop is fully structured.
When to use which
- CI pipelines —
haft checkin a pre-merge check. Exit-code gates the merge. - Pre-commit hook — same command. Optionally narrow to
haft spec checkfor spec-only feedback. - /h-verify in plugin mode — automatically calls
haft_query(action="check")as discovery, then walks the operator through triage.
Distinguishing status from check
haft_query(action="status") is the at-a-glance overview — counts,
recent activity, readiness state. haft_query(action="check") is the
CI-actionable enforcement view — every individual finding with code, level, and
next-action hint. Use status when reporting; use check
when acting.
Schema parity guarantee
The contract is enforced by tests:
TestHandleQuintQuery_CheckMatchesCLIJSONdecodes both MCP and CLI--jsoninto the same Go struct and asserts field-by-field equality.TestHandleToolsList_NoToolDeclaresTopLevelCompositorsiterates every advertised tool and rejects top-levelallOf/oneOf/anyOfin input schemas (a regression that previously took the whole haft MCP server offline at the host LLM API boundary).