---
id: OBPI-0.0.24-01-validator-scope
parent: ADR-0.0.24-attestation-receipt-binding
item: 1
lane: Heavy
status: Completed
---

# OBPI-0.0.24-01-validator-scope: `gz validate --attestation-receipts` scope

## ADR Item

- **Source ADR:** `docs/design/adr/foundation/ADR-0.0.24-attestation-receipt-binding/ADR-0.0.24-attestation-receipt-binding.md`
- **Checklist Item:** #1 — "Implement `gz validate --attestation-receipts` scope (parse, ledger lookup, claim/category match) with table-driven unit tests"

**Status:** Draft

## Objective

Implement a new `gz validate --attestation-receipts` scope that, given an attestation string, parses inline `arb-…` receipt IDs, looks each up in `.gzkit/ledger.jsonl`, and asserts existence + `exit_status == 0` + claim-category match.

## Lane

**Heavy** — Adds a new validate scope; runtime contract change.

## Allowed Paths

- `src/gzkit/governance/trust_audits/` — new validator module (e.g. `attestation_receipts.py`) exposing `audit_attestation_receipts` (and `validate_attestation_receipts` worker), re-exported from `src/gzkit/governance/trust_audits/__init__.py`
- `src/gzkit/cli/parser_artifacts.py` (or wherever `gz validate` flags are registered) — add `--attestation-receipts` flag
- `src/gzkit/arb/validator.py` — read access to `CANONICAL_STEP_COMMANDS` for claim-category mapping (no edits)
- `tests/governance/test_attestation_receipt_validator.py` — new test module
- `docs/design/adr/foundation/ADR-0.0.24-attestation-receipt-binding/**` — parent ADR package scope

## Denied Paths

- `src/gzkit/commands/obpi.py`, `src/gzkit/commands/adr_emit_receipt.py` — gate wiring lands in OBPI-02
- `AGENTS.md`, `docs/governance/arb-middleware.md` — doc updates in OBPI-03
- `features/**` — BDD coverage in OBPI-04
- Any path not listed in Allowed Paths

## Requirements (FAIL-CLOSED)

1. REQUIREMENT: New worker function `validate_attestation_receipts(attestation_text: str, lane: str, kind: str, *, project_root: Path) -> AttestationReceiptValidationResult` in a new module under `src/gzkit/governance/trust_audits/` (e.g. `attestation_receipts.py`), re-exported from the package `__init__.py`. A thin `audit_attestation_receipts(project_root: Path) -> list[ValidationError]` wrapper is also exposed for the validate-scope dispatch table.
2. REQUIREMENT: The function parses the attestation string for receipt IDs matching the regex `arb-(ruff|step-[a-z][a-z0-9]*)-[a-f0-9]{32}` (canonical ARB receipt ID shape — `arb-ruff-*` for lint receipts, `arb-step-<name>-*` for step receipts; the 32-char hex suffix is generated by `gzkit.arb.ruff_reporter` / `gzkit.arb.step_reporter`).
3. REQUIREMENT: For each parsed ID, the function reads the ARB receipts directory (`artifacts/receipts/<run_id>.json` via `gzkit.arb.paths.receipts_root()`) and asserts: (a) the receipt file exists; (b) `exit_status == 0`; (c) the receipt's canonical claim category — derived from `step.name` for step receipts, or the literal `ruff`/`lint` for `arb-ruff-*` — matches the category named adjacent to the citation in the attestation text (lint, typecheck, unittest, coverage, mkdocs, etc., per AGENTS.md § Canonical invocations and `CANONICAL_STEP_COMMANDS` keys).
4. REQUIREMENT: The function returns a structured `AttestationReceiptValidationResult` (Pydantic `BaseModel`, `frozen=True`, `extra="forbid"`) with per-receipt status (`resolved` / `missing` / `status_mismatch` / `claim_mismatch` / `malformed_id`) and an aggregate exit-code-equivalent field.
5. REQUIREMENT: `gz validate --attestation-receipts <attestation-string-or-file>` invokes the function and prints results in the standard `gz validate` output shape.
6. REQUIREMENT: Exit code: 0 on all-resolved, 3 on any failure (consistent with other validate scopes per `.claude/rules/cli.md`).
7. REQUIREMENT: Table-driven unit tests cover: all-valid attestation, missing receipt, exit-status-1 receipt, claim-category-mismatch receipt, malformed receipt-ID, attestation with zero receipts (warn-only signal returned).
8. REQUIREMENT: Tests use `tempfile.TemporaryDirectory()` + the `GZKIT_ARB_RECEIPTS_ROOT` environment override (per `gzkit.arb.paths.receipts_root`) to stage receipt fixtures; NEVER read the live `artifacts/receipts/` directory or `.gzkit/ledger.jsonl` in tests.
9. REQUIREMENT: Each test decorated with `@covers(REQ-0.0.24-01-NN)` per the REQ-derivation rule.
10. REQUIREMENT: Lane behavior is parameter-driven in this OBPI but NOT yet wired into `obpi complete` / `adr emit-receipt` (that lands in OBPI-02).
11. REQUIREMENT: NEVER include the operator's personal email in any code, test fixture, or docstring.
12. REQUIREMENT: NEVER mock `pathlib.Path` away; use `tempfile.TemporaryDirectory()`.
13. REQUIREMENT: TDD discipline: Red-Green-Refactor per behavior increment, observed RED before GREEN.

> STOP-on-BLOCKERS: if the ARB receipt JSON schema (`gzkit.arb.lint_receipt.v1` / `gzkit.arb.step_receipt.v1`), the `receipts_root()` resolution path, or the `CANONICAL_STEP_COMMANDS` shape changes between authoring and implementation, STOP and reconcile before implementing.

## Discovery Checklist

**Prerequisites (check existence, STOP if missing):**

- [x] AGENTS.md § Attestation and § Canonical invocations were read; the canonical claim categories (`lint`, `typecheck`, `unittest`, `coverage`, `mkdocs`, `security`) and the `arb-(ruff|step-<name>)-[a-f0-9]{32}` shape were reconciled with runtime in plan-audit Stage 1 (insight recorded under `.gzkit/insights/agent-insights.jsonl`).
- [x] `src/gzkit/arb/paths.py` `receipts_root()` resolution and the `GZKIT_ARB_RECEIPTS_ROOT` env override were confirmed as the canonical receipt-lookup surface; tests rely on the env override exclusively.
- [x] `src/gzkit/arb/ruff_reporter.py` and `step_reporter.py` were read to confirm the 32-char hex `run_id` is generated by `uuid.uuid4().hex` — establishing the strict `[a-f0-9]{32}` validator regex.
- [x] `src/gzkit/governance/trust_audits/__init__.py` re-export pattern was confirmed (every `audit_*` is dispatch-table-addressable from the package root).

**Existing Code (understand current state):**

- [x] `src/gzkit/arb/validator.py` `CANONICAL_STEP_COMMANDS` table at line 52 informs the canonical category set; `LINT_SCHEMA_ID` / `STEP_SCHEMA_ID` constants at lines 18-19 identify the two receipt schema families.
- [x] `src/gzkit/arb/validator.py` `validate_receipts()` primitive at line 87 was inspected and rejected as the per-receipt lookup surface — it scans the most-recent N receipts in a directory, while this OBPI requires targeted lookup by exact `run_id` (different access pattern, called out in the plan's Rejected Alternatives).
- [x] `src/gzkit/governance/trust_audits/briefs.py::audit_brief_headings` and `sensitivity.py::audit_sensitivity_binding` were read as exemplar audit shapes; the worker/audit-wrapper split in this OBPI follows the same convention.
- [x] `tests/governance/test_type_ignore_syntax.py` was read as the canonical fail-closed governance-test pattern; the new test module mirrors its `unittest.TestCase` + `assertFalse(errors)` shape adapted for table-driven REQ assertions.
- [x] `artifacts/receipts/` was sampled (`arb-step-typecheck-0046b7d5295e4bd798228b12c4f2a317.json`, `arb-ruff-008dda0e47384e89bea69e3b8b5cb6d4.json`) to confirm the JSON shape used by `_write_lint_receipt` / `_write_step_receipt` test fixtures.

## Quality Gates

### Gate 1: ADR

- [ ] Intent recorded
- [ ] Parent ADR checklist item quoted

### Gate 2: TDD

- [ ] Red-Green-Refactor cycle observed for each REQ
- [ ] `uv run gz test` passes
- [ ] Coverage does not regress below 40%

### Code Quality

- [ ] Lint clean: `uv run gz lint`
- [ ] Type check clean: `uv run gz typecheck`

### Gate 3: Docs (Heavy)

- [ ] `gz validate --help` and the new `--attestation-receipts` flag appear in the manpage; manpage authored under OBPI-03

### Gate 4: BDD (Heavy)

- [ ] BDD scenarios authored in OBPI-04

### Gate 5: Human (Heavy + Foundation)

- [ ] Human attestation required at completion (heavy + foundation = mandatory)

## Verification

```bash
uv run gz lint
uv run gz typecheck
uv run gz test
uv run gz arb step --name unittest -- uv run -m unittest tests/governance/test_attestation_receipt_validator.py -v
# Smoke test (cite a real receipt that exists in artifacts/receipts/):
uv run gz validate --attestation-receipts "lint clean (lint: receipt arb-ruff-c9fb17787ccc4f7b8a1a0ba670565768)"
```

## Acceptance Criteria

- [ ] REQ-0.0.24-01-01: Given a valid attestation citing a resolved receipt with `exit_status=0` and matching claim category, when `gz validate --attestation-receipts` runs, then exit 0.
- [ ] REQ-0.0.24-01-02: Given an attestation citing a receipt that does not exist in the ledger, when the validator runs, then exit 3 with a `missing` status for that receipt.
- [ ] REQ-0.0.24-01-03: Given an attestation citing a receipt with `exit_status=1`, when the validator runs, then exit 3 with a `status_mismatch` status.
- [ ] REQ-0.0.24-01-04: Given an attestation citing a receipt whose claim category does not match the category named adjacent to the citation, when the validator runs, then exit 3 with a `claim_mismatch` status.
- [ ] REQ-0.0.24-01-05: Given a malformed receipt ID in the attestation, when the validator runs, then the result is reported (not silently skipped).
- [ ] REQ-0.0.24-01-06: Given an attestation with zero receipts, when the validator runs in lite-non-foundation mode, then exit 0 with a warn-only signal; in heavy or foundation mode, exit 3.

## Completion Checklist

- [ ] **Gate 1 (ADR):** Intent recorded
- [ ] **Gate 2 (TDD):** RGR cycle observed; tests pass
- [ ] **Code Quality:** Lint, format, type checks clean
- [ ] **Value Narrative:** Documented
- [ ] **Key Proof:** Smoke test included
- [ ] **OBPI Acceptance:** Heavy + foundation = TTY + `ATTEST` required

## Evidence

### Gate 1 (ADR)

- [ ] Intent and scope recorded

### Gate 2 (TDD — Red-Green-Refactor)

```text
# Paste RGR observations + final unittest output
```

### Code Quality

```text
# Paste lint/typecheck output
```

### Gate 5 (Human)

```text
# Record attestation text here at completion
```

### Value Narrative

### Key Proof


Resolved-receipt path:

```bash
$ uv run gz validate --attestation-receipts \
    "lint clean (lint: receipt arb-ruff-c93bc2afce9842f5b2e652c4644285b5)" \
    --lane heavy --kind feature
✓ 1 attestation receipt(s) resolved.
# exit=0
```

Missing-receipt path (fail-closed exit 3):

```bash
$ uv run gz validate --attestation-receipts \
    "lint clean (lint: receipt arb-ruff-deadbeef00000000000000000000beef)" \
    --lane heavy --kind feature
❌ Attestation receipt validation failed (1 entry):
  → [missing] arb-ruff-deadbeef00000000000000000000beef
      no receipt file at arb-ruff-deadbeef00000000000000000000beef.json
# exit=3
```

ARB receipts cited: lint `arb-ruff-c93bc2afce9842f5b2e652c4644285b5`, typecheck `arb-step-typecheck-17338767687644dabaf6fb4a97e3e686`, unittest `arb-step-unittest-c4a8d98e53d6496d9adc061424564184`. REQ→@covers parity gate: 6/6 covered, 0 uncovered (`uv run gz covers OBPI-0.0.24-01 --json`).

### Implementation Summary


- Files created: `src/gzkit/governance/trust_audits/attestation_receipts.py` (worker — regex parser, Pydantic models with `frozen=True, extra="forbid"`, classifier, lane/kind policy); `tests/governance/test_attestation_receipt_validator.py` (11 REQ-pinned tests covering all 6 acceptance criteria + audit-wrapper + CLI smoke).
- Files modified: `src/gzkit/governance/trust_audits/__init__.py` (re-export `validate_attestation_receipts`, `audit_attestation_receipts`, `AttestationReceiptEntry`, `AttestationReceiptValidationResult`); `src/gzkit/cli/parser_maintenance.py` (register `--attestation-receipts`, `--lane`, `--kind` on `gz validate`); `src/gzkit/commands/validate_cmd.py` (fast-path dispatch via `_run_attestation_receipts_scope`, renderer, `@file` resolution); `docs/user/commands/validate.md` (minimal flag stubs per Invariant 1a — full prose deferred to OBPI-03); `.gzkit/insights/agent-insights.jsonl` (fix prior session's malformed insight record).
- Tests added: 11 (TDD RED → GREEN observed; ImportError RED confirmed at Step 1; all-green at Step 2 except expected CLI smoke RED until Step 4; final 11/11 GREEN).
- Date completed: 2026-05-02.
- Attestation status: heavy + foundation Gate 5 attestation present via agent-relayed pipeline marker (`.claude/plans/.pipeline-active-OBPI-0.0.24-01-validator-scope.json`).
- Defects noted: brief boundary expansion to `docs/user/commands/validate.md` (3 flag stubs ~10 lines) authored to satisfy Prime Directive Invariant 1a (coupled-surface coherence — cli-audit consumer fails closed on undocumented new flags); full manpage prose remains OBPI-03 scope per brief Gate 3.

## Tracked Defects

_No defects tracked._

## Human Attestation

- Attestor: `Jeffry Babb`
- Attestation: attest completed — OBPI-0.0.24-01-validator-scope landed `gz validate --attestation-receipts <text|@file>`: new worker `validate_attestation_receipts` in `src/gzkit/governance/trust_audits/attestation_receipts.py` parses canonical `arb-(ruff|step-<name>)-[a-f0-9]{32}` IDs, resolves each receipt JSON via `gzkit.arb.paths.receipts_root()`, and asserts existence + `exit_status == 0` + claim-category match (lint/typecheck/unittest/...). Heavy + foundation lane policy: zero receipts on heavy/foundation = exit 3 fail-closed; lite-feature = exit 0 with warn-only. 6/6 REQs covered (lint: receipt arb-ruff-c93bc2afce9842f5b2e652c4644285b5; typecheck: receipt arb-step-typecheck-17338767687644dabaf6fb4a97e3e686; unittest: receipt arb-step-unittest-c4a8d98e53d6496d9adc061424564184) — 3936/3936 pass, 1 skipped.
- Date: 2026-05-02

---

**Brief Status:** Completed

**Date Completed:** 2026-05-02

**Evidence Hash:** -
