The proof ladder
From a stated requirement to fresh end-to-end evidence: the stages a requirement climbs to count as proven.
Consumer-facing reference for proofStatus, proofStages, proofGaps, and
proofAdvisories as reported by kibi coverage --by req and kb_coverage.
The authoritative implementation is packages/core/src/requirement_proof.pl
(proof version kibi.requirement-proof.v3); this document explains what each
stage means and what to do when it does not pass.
Applicability: is the requirement in scope at all?
The ladder only evaluates current requirements. A requirement is current
when its status is one of:
- Canonical:
open,in_progress,closed - Legacy (accepted for backwards compatibility):
active,approved
Everything else is out of scope by design. Two things can take a requirement out of scope:
- Supersession — a newer requirement links to it with
supersedes. - Status outside the vocabulary — e.g. the ADR vocabulary
accepted. Since that silently excludes the requirement from proof,kibi checkreports it as a blockingreq-status-vocabularyviolation instead of letting it hide.
Out-of-scope requirements report:
{
"proofStatus": "not_applicable",
"proofGaps": [],
"proofStages": {
"applicability": {
"status": "not_applicable",
"reason": "status 'accepted' is not a current requirement status"
}
}
}The typed reason is what not_applicable used to hide: superseded
requirements say so; status-vocabulary mismatches name the offending status.
Proof exemption: current but intentionally not E2E-provable
Some current requirements cannot be proven by end-to-end evidence — toolchain-currency gates, architectural boundaries, quality-gate policy. Parking them with an ADR status is exactly the trap above. Instead, mark them explicitly in the requirement document:
---
id: REQ-TOOLCHAIN-001
status: open
priority: must
proof_exempt: true
proof_exempt_reason: architectural boundary — verified by toolchain CI, not product E2E
---proof_exempt requires a non-empty proof_exempt_reason (upsert validation
enforces the pairing, and the ladder ignores an exemption without a reason).
Exempt requirements report not_applicable with the author's reason attached,
so coverage can distinguish "cannot be proven" from "not proven yet".
To re-enter the proof ladder, remove both properties.
The stages
For in-scope requirements the ladder evaluates nine stages, each reporting
status (and, for productionSymbols, a typed reason when it does not
pass):
| Stage | Meaning | Statuses |
|---|---|---|
semanticInventory |
Every assertive proposition in the requirement's prose is inventoried and classified (modeled / unresolved / missing / nonlogical). | passed, unresolved, missing |
logicGrounding |
Modeled claims are grounded by strict property, predicate, or safe rule facts, one-to-one with the declared manifest. | passed, missing, unresolved |
contradictions |
No other current requirement contradicts this one over shared facts. Check completeness itself is visible. | passed, blocked, unresolved |
scenarios |
At least one scenario specifies the requirement (specified_by). |
passed, missing |
scenarioTests |
Each scenario is validated by at least one test (verified_by/validates). |
passed, missing |
passingE2E |
Every linked scenario has at least one end-to-end test, and every linked E2E proof-bearing test carries a valid, fresh, passing kibi.proof-receipt.v1 bound to the current snapshot, contract hash, and fingerprint. Per-scenario results are exposed in scenarioObligations; unit/integration-only ancillary tests remain nonblocking. |
passed, missing, unresolved |
executableSymbols |
Each qualifying E2E test is linked to executable test code via executable_for. |
passed, missing |
productionSymbols |
Production symbols implementing the requirement are covered by those passing E2E tests (covered_by). Additive explanations[] (still kibi.requirement-proof.v3) give each symbol a rollup reason and per-candidate primary plus optional secondary rejection codes. |
passed, missing, blocked |
sourceCoordinates |
The requirement source and all linked symbols carry exact published coordinates. | passed, missing, blocked |
Stage statuses, precisely
passed— the stage's obligations hold.missing— the evidence is absent. The stage names what is missing (missingTests,missingReceiptTests,uncoveredSymbols,missingSymbols, ...), andproofGapscarries the blocking gap code.blocked— the stage cannot be evaluated because an upstream input is unavailable. Only two stages use it:contradictions: logical grounding is incomplete, so absence of a found conflict is not evidence of safety.productionSymbols: there is no passing E2E evidence at all in the current snapshot (typically stale or missing receipts). The stage now says so withreasoninstead of an opaque word. ExistinguncoveredSymbolsstill lists every remaining production symbol; each explanation usesstage_blocked_no_passing_e2erather than implying the symbol independently lackscovered_by.- Additive
explanations[]never changestatus,reason,uncoveredSymbols, orproofStatus. Rejectedcovered_bycandidates carry the earliest disqualifyingreasonand optionalsecondaryReasonsalready known from cheaper gates. Out-of-chain tests do not walk receipts.
unresolved— evidence exists but is not conclusive (e.g. unresolved propositions in the inventory).
A blocked stage never silently downgrades a requirement's proof status: it
maps to proofStatus: unresolved, not proven.
Gaps, advisories, and status
proofGaps[]— blocking only. Every entry carries a code (e.g.missing_proof_receipt,missing_production_symbol_coverage), a priority, a stage name, and a suggested repair action.proofAdvisories[]— explicitly non-blocking context. Receipt-completeness codes (missing_proof_receipt,stale_proof_receipt,failed_proof_receipt,invalid_proof_receipt, andproof_contract_mismatch) remain blocking for every linked E2E obligation; they are never downgraded because another scenario has a passing receipt.proofStatus— the headline:proven— all stages passed (proofGapsis empty by invariant).missing— at least one stage ismissing(evidence absent).unresolved— evidence is inconclusive or a stage isblocked.not_applicable— out of scope; see applicability above.
proofRepairs[]— ranked concrete recovery actions derived from blocking gaps.
Common failure patterns
| Symptom | Usual cause | Fix |
|---|---|---|
productionSymbols: blocked |
No passing E2E receipts in the current snapshot at all | Run kibi prove to refresh receipts, then re-check |
missing_symbol_coordinates gap |
Symbol has no coordinate entry; often granularity_reason missing so the coarse fallback was gated off |
kibi sync --refresh-symbol-coordinates reports failed ids and reasons; add symbol_role/granularity_reason where the reason says to |
| Requirement missing from proof reports entirely | Non-current status or supersession | Coverage --status not_applicable --by req lists it with the typed applicability reason |
passingE2e.scenarioObligations has a non-passed status |
A linked scenario has no E2E test or one of its E2E proof-bearing tests lacks qualifying evidence | Inspect the scenario's gaps, repair every listed receipt, and run kibi prove |
Related surfaces
kibi coverage --by req --status missing(also:proven,unresolved,not_applicable) — enumerate exactly one proof-status slice; N/A rows include their applicability reason. The summary always reflects the whole KB.kibi proof inspect— detect test infrastructure and pick a proof integration.kibi proof explain REQ-*/kibi proof explain SYM-*— project the same Proof, labelingrequired_proofs,executable_for, andcovered_byseparately.kibi proof impact— compare current proof state toHEAD:proof/baseline.json(diagnostic; exits 0 after a successful report). The ratchet remainsscripts/check-proof-baseline.mjs.bun run proof:baseline:semantic(this repository) — compare the committed baseline without re-proving: gaps that only reflect stale evidence (stale_proof_receipt,proof_contract_mismatch, and production coverage whose symbols all have a qualifying candidate or a scenario-backed E2E candidate rejected solely for stale evidence) are set aside. Unit/integration scope, missing receipts, invalid evidence, and scenario-chain failures remain blocking gaps.bun run proof:replay(this repository) — replay the CI proof job's gate steps from.github/workflows/proof.ymlin a clean clone of the committed HEAD, outside the repository tree, and stop at the first failing step.kibi proof prune --keep 1— drop superseded receipts (re-proving the same snapshot appends duplicates).kibi proof migrate-legacy— remove legacyverification_receiptsblocks from tests that already carry aproof_contract.docs/proving-requirements.md— the runner-neutral proof pipeline: contracts, integrations, artifacts, and howkibi proveproduces receipts.
Before pushing, run kibi sync --refresh-symbol-coordinates with the project-local
CLI, review and commit the generated changes, and run bun run proof:prepush.
If committing leaves the KB stale, sync it again before the check. This checks the
committed manifests, publish metadata (including exact GitHub owner casing),
and semantic baseline using the project-local CLI. When
proof contracts, coverage links, or proof tooling change, also run
bun run proof:replay to reproduce the full CI gate on committed HEAD.
The repository's pre-commit framework configuration includes a pre-push stage
for the fast gate. Maintainers using that framework enable it with
pre-commit install --hook-type pre-push; tracking the configuration alone does
not install the hook. The fast gate checks the current clean checkout; use the
corresponding checkout when pushing another branch or an explicit ref.