---
name: Decision Verification
slug: decision-verification
category: Quality
description: Decision Verification checks whether a recorded DecisionRecord still holds by running baseline vs measure evidence loops and drift detection. Use it when a shipped decision needs a post-implementation reality-check before further work relies on it.
github: "https://github.com/m0n0x41d/haft/tree/main/internal/cli/skill/h-verify"
language: Go
stars: 1383
forks: 102
install: "npx degit https://github.com/m0n0x41d/haft/tree/main/internal/cli/skill/h-verify ~/.claude/skills/h-verify"
installs_to: ~/.claude/skills/h-verify
source_path: internal/cli/skill/h-verify/SKILL.md
collection_size: 24
category_size: 1354
collection_url: "https://dirskills.com/collections/m0n0x41d/haft"
added: 2026-08-19T07:27:48.136Z
last_synced: 2026-08-19T07:27:48.136Z
canonical_url: "https://dirskills.com/skills/decision-verification"
---

# Decision Verification

Decision Verification checks whether a recorded DecisionRecord still holds by running baseline vs measure evidence loops and drift detection. Use it when a shipped decision needs a post-implementation reality-check before further work relies on it.

**Install:**

```bash
npx degit https://github.com/m0n0x41d/haft/tree/main/internal/cli/skill/h-verify ~/.claude/skills/h-verify
```

## README

<!-- haft-contract-source: kernel_interface_catalog source_digest=sha256:26e174fdd87993d53721c925be9727239d77e8a425b7c52d28fd9f833b6d1153 -->

# h-verify — Verify a decision still holds

You are running the FPF verification loop: baseline → measure → evidence → record. Drift detection compares current state against baselined affected_files; evidence decay reports surface when valid_until passes; measure verdict is recorded for the predictions the decide step declared.

Verification already has a selected object and a named evidence use. Do not run
a generic pattern router first. When an unfamiliar evidence, causal-use, or
temporal distinction becomes material, query the exact concern with
`mcp__haft__haft_query(action="fpf", mode="concern", query="...")` and inspect the full direct
pattern body before applying it.

## Conditional project-memory orientation

When verification is context-heavy, multi-session, or otherwise
reliance-bearing and the exact EntityOfConcern is not already current, resolve
its identity with `haft_query(action="memory",
memory_request={"mode":"resolve","contract_version":"haft.memory.v1",
"basis":{"kind":"project_current"},"query":"...","max_candidates":5})`. Select
the exact candidate by the current use rather than rank, then use the closed
`memory_request` neighborhood branch advertised by the tool schema with
`projection_profile_ref="agent_orientation.v2"`.

Inspect `result_kind` before relying on content. `project_basis_unavailable`,
known absence, or explicit abstention is visible but non-blocking: continue
with the exact artifact/evidence surfaces available to this verification and
do not invent a profile, entity, artifact, or human gate. This read does not
replace code-graph preflight before a later code edit. Never persist typed
memory merely because a read failed; persistence requires an explicit operator
save request or a concrete operator-named or agent-inferred receiving use
supplied by current Work, with request provenance.

## Step 1 — Identify the decision

If `decision_ref` is given, use it. Otherwise:
- `mcp__haft__haft_query(action="status")` — surfaces stale/refresh-due decisions
- `mcp__haft__haft_refresh(action="drain", dry_run=true)` — preview machine-safe maintenance closures and needs_operator groups
- `mcp__haft__haft_query(action="contract_generation")` — read-only generated-fragment carrier hints for host/skill/plugin/Pi sync
- `mcp__haft__haft_query(action="drift_events", limit=5)`, `mcp__haft__haft_query(action="decision_reconcile", limit=5)`, and `mcp__haft__haft_query(action="governing_set", limit=5)` — compact drift fanout, reconciliation, and current-authority drill-downs
- add `full=true` to those drill-down calls only when you need the full audit payload
- `mcp__haft__haft_query(action="list", kind="DecisionRecord")` — full list
- Ask the operator which decision to verify

read-only/generated text is discovery only; it is not evidence truth, gate passage, global approval, or operator authorization.

When the operator asks to verify/drain the project maintenance backlog, do not
stop after status and do not ask for extra approval to apply closures that the
kernel has already classified as machine-safe. The operator's request to run
`h-verify` on stale/refresh-due status is sufficient authority for rung-1
deterministic auto-baselines and rung-2 allowlisted machine evidence/revalidation.
Use the explicit drain loop:

1. `mcp__haft__haft_refresh(action="drain", dry_run=true)` to preview closed /
   failed / needs_operator classes.
2. If the preview contains only kernel-classified machine-safe closures for an
   item, apply them by default with
   `mcp__haft__haft_refresh(action="drain", dry_run=false)`.
   If MCP times out on a large backlog, use the equivalent CLI drain command and
   save the JSON evidence in `.context`.
3. Report every applied automatic closure, including undo commands when present,
   and every needs_operator item. The drain may
   close only rung-1 deterministic drift and rung-2 allowlisted machine
   evidence/revalidation. Material drift, semantic uncertainty, reopen/supersede
   choices, public/authority/security-sensitive cases, and weak waivers stay
   operator-facing.

## Step 2 — Read the decision's claims and predictions

Recover the selected DecisionRecord through the exact artifact surface:

```
mcp__haft__haft_query(action="related", artifact_ref="<decision_ref>")
```

Read the full payload, especially `status`, `valid_until`, `body`, and
`structured_data`. The canonical structured payload exposes `claims` (and
legacy-compatible `predictions`) with:
- `claim` — the falsifiable statement
- `observable` — what to measure
- `threshold` — pass/fail boundary
- `verify_after` — when async evidence should be available (if any)

`search` is discovery only. A compact search hit or miss does not establish
whether claims, predictions, observables, or thresholds exist. Do not read the
project SQLite database directly while kernel exact recovery is available.

If predictions are empty (the decision was recorded tactical with `_skips: ["predictions"]`), there's nothing to measure — report that to operator and recommend either:
- ask the operator for a direct choice of a replacement with testable claims,
  route it through `/h-decide`, then supersede the old record with
  `haft decision supersede <old-dec-...> --new <new-dec-...> --reason "..."`
- Just attach evidence directly via `haft_decision(action="evidence", ...)`

## Step 3 — Baseline (if drift detection wanted)

If the decision has `affected_files` and you want drift comparison:

```
mcp__haft__haft_decision(
  action="baseline",
  decision_ref="<dec-...>"
  // affected_files optional — kernel uses the decision's list
)
```

The kernel snapshots file content hashes. Subsequent comparisons detect drift. Call once after each commit cycle if you want continuous drift signal.

## Step 4 — Gather evidence per prediction

For each prediction:
- Run the observable (test, metric query, log scan, code grep)
- Compare to threshold
- Capture the actual measurement value

Tools available depending on the observable:
- `Bash` for test runners, metric queries, log scans
- `Read` / `Grep` / `Glob` for code-level invariant checks
- For external metrics: kernel has no special integration; agent describes the source

## Step 5 — Attach evidence to the artifact

For each material evidence item:

```
mcp__haft__haft_decision(
  action="evidence",
  artifact_ref="<dec-...>",
  evidence_type="measurement | test | research | benchmark | audit",
  evidence_content="<what you observed, with concrete numbers>",
  evidence_verdict="supports | weakens | refutes",
  carrier_ref="<file path or URL where the evidence lives>",
  claim_refs=["<prediction id or scope label>"],
  congruence_level=3,  // 3=same context, 2=similar, 1=different, 0=opposed
  valid_until="<RFC3339 or YYYY-MM-DD — when this evidence expires>",
  causal_support_basis="observational | interventional | realized_counterfactual | identified_estimate | simulation_only"
)
```

**congruence_level** (CL) defaults per FPF B.3.5:
- 3: same-context evidence (own production system, own tests)
- 2: similar-context (related project, similar load)
- 1: different-context (external docs, vendor benchmarks)
- 0: opposed-context (rare; conflicting framework)

CL impacts R_eff per FPF B.3:3 — never average across CL.

## Step 6 — Record the measurement verdict

**What-got-worse check (FPF E.13).** Before recording an `accepted` or
`partial` verdict, ask: the measured proxy improved — what got worse? Check
the protected qualities the decision did NOT optimize (usability, footprint,
maintainability, signal honesty, adjacent modules). Put the answer in
`findings`: either the concrete regression you found, or a reasoned
"none worsened: checked X, Y, Z" — a reflexive "nothing worsened" without
named loci is the Goodhart failure mode this check exists to catch.

After all evidence is attached, record the overall verdict:

```
mcp__haft__haft_decision(
  action="measure",
  decision_ref="<dec-...>",
  verdict="accepted | partial | failed",
  findings="<what actually happened compared to predictions>",
  measurements=["p99 latency: 42ms (predicted <50ms — accepted)", "..."],
  criteria_met=["<criterion that was met>"],
  criteria_not_met=["<criterion that was NOT met>"]
)
```

Kernel ties the verdict back to the predictions and surfaces:
- Accepted → decision health remains good
- Partial → some predictions held, some didn't → consider reopen or supersede
- Failed → decision invalidated → consider supersede or rollback per the decision's rollback spec

## Step 6.5 — Loop verdict (FPF E.23)

After the measure verdict, classify what the improvement loop does next. The
admissible verdict set is exactly:

- **stop** — further movement is dominated, unavailable, or out of scope.
  "Good enough" is a legitimate terminal state: a decision measured adequate
  does NOT owe an endless refresh cycle. Name WHY movement is dominated.
- **continue** — evidence gathering or improvement continues on the same frame.
- **switch-method** — same problem, different solution method → supersede.
- **open-new-frame** — the problem itself shifted → reopen with a new ProblemCard.
- **hold** — defer with an explicit revisit condition → waive with new_valid_until.

**Narrowing is NOT a verdict.** Shrinking scan scope, suppressing signals, or
restricting the evaluation window to make the picture look better is false
improvement — any scope reduction must carry an explicit dominance
justification ("movement on X is dominated because Y"), or it doesn't happen.

If any verified prediction carried a `probability` forecast (set at `/h-decide`),
the measure response also appends a **Calibration** read: the decomposed-Brier
profile (Brier = reliability − resolution + uncertainty) over all verified
forecasts, plus a directional over/under-confidence bias. Below ~15 accumulated
forecasts it reports cold-start and is not yet actionable — surface it to the
operator but do not over-read a sparse profile.

## Step 7 — Handle stale or drifted decisions

If verification reveals:
- **Evidence decayed** (valid_until passed): `mcp__haft__haft_refresh(action="waive", artifact_ref=..., evidence="<new evidence>", new_valid_until="...")` to extend validity, OR `action=reopen` to start a new problem cycle
- **Drift detected** (affected_files changed since baseline): classify drift as cosmetic / incidental / material via `haft_query(action="status")` and decide whether to re-baseline or reopen
- **Verdict failed**: `mcp__haft__haft_refresh(action="supersede", artifact_ref=<old>, new_artifact_ref=<replacement>)` after the operator directly requests and `/h-decide` routes the replacement decision

## Step 8 — Present to operator

Surface:
- Predictions vs actual measurements
- Verdict (accepted / partial / failed)
- Evidence attached with CL
- Drift status if baseline existed
- Loop verdict (stop / continue / switch-method / open-new-frame / hold) with
  its kernel mapping: stop → leave active with rationale recorded; continue →
  schedule next verify; switch-method → supersede; open-new-frame → reopen;
  hold → waive with new_valid_until

**Re-grounding discipline (FPF A.7).** When you reference decision IDs
(`dec-20260525-...`), prediction labels, or evidence refs in the verdict
summary and recommendation paragraphs, pair each with its
human-readable title or claim — `dec-20260525-abc (NATS over Kafka for
ops simplicity) — verdict accepted` not bare `dec-20260525-abc verdict
accepted`. Bare IDs accumulate cognitive debt across long sessions. Keep
IDs for traceability but never let them stand alone in summaries. See
CLAUDE.md Critical Reminders for the project-wide rule.

## What NOT to do

- Do NOT call `action="measure"` without first gathering evidence — kernel rejects measure-from-memory (the protocol requires evidence before verdict).
- Do NOT use congruence_level=3 for evidence that came from a different context (vendor benchmark, external doc). Misclassifying CL inflates R_eff and corrupts trust signal.
- Do NOT skip baseline if the decision has affected_files and drift matters — without baseline, drift is invisible.
- Do NOT silently change a decision's verify_after to "kick the can". Use `action="waive"` with `evidence` parameter so the extension is auditable.
- Do NOT mark verdict=accepted when predictions were skipped — verify what was declared, not what was hoped.
- Do NOT use raw SQLite as a fallback for DecisionRecord recovery while `related(artifact_ref=...)` is available from the kernel.

## FPF spec references

- B.3 — Trust & Assurance Calculus (F-G-R + CL)
- B.3.3 — Assurance Subtypes & Levels (L0/L1/L2 + TA/VA/LA)
- B.3.4 — Evidence Decay & Epistemic Debt
- A.10 — Evidence Graph Referring
- VER-01 (evidence graph), VER-02 (decay), VER-03 (R_eff), VER-07 (refresh triggers)
- C.27 — Temporal Claim Adequacy (state reading vs trend vs intervention claim)

Inspect via `mcp__haft__haft_query(action="fpf", mode="inspect", identifier="B.3")`.
