# Validation Receipt Schema

**Status:** Active
**Last reviewed:** 2026-02-11
**Schema version:** `govzero.ledger.v1`
**Parent ADR:** ADR-0.0.24 (Validation Receipt Temporal Anchoring)

---

## Overview

Validation receipts anchor ADR/OBPI validation events to specific git commits, enabling
"valid as of" semantics. When an ADR is validated, a receipt records the exact commit SHA,
so later drift detection can determine whether code has changed since the last audit.

### Purpose

- **Temporal anchoring**: Every validation event is tied to a git commit
- **Drift detection**: Compare current HEAD against the last validation anchor to detect changes
- **Audit trail**: Machine-readable evidence of when and by whom validation occurred
- **Separation of concerns**: Validation receipts live in their own ledger, complementing the OBPI audit ledger

### Relationship to OBPI Audit Ledger

| Aspect | OBPI Audit Ledger | Validation Receipt Ledger |
|--------|-------------------|---------------------------|
| **File** | `obpi-audit.jsonl` | `adr-validation.jsonl` |
| **Scope** | Per-brief status audits | Per-ADR validation events |
| **Purpose** | Track OBPI status transitions | Anchor validation to git state |
| **Schema** | `obpi-audit`, `covers-map`, etc. | `validation` |

Both ledgers share the `govzero.ledger.v1` base schema (`LedgerEntryBase`) for consistency.

---

## Entry Schema

### validation

Records a validation event tied to a specific git commit.

```json
{
  "type": "validation",
  "timestamp": "2026-02-10T14:30:00Z",
  "schema_version": "govzero.ledger.v1",
  "agent": "claude-code",
  "session_id": "2026-02-10-adr-024-validation",
  "adr_id": "ADR-0.0.24",
  "event": "validated",
  "anchor": {
    "commit": "de80292",
    "tag": "v0.0.24",
    "semver": "0.0.24"
  },
  "evidence": {
    "tests_passed": true,
    "test_count": 15,
    "coverage_percent": 48.5,
    "gate2_pass": true
  },
  "attestor": "human:jeff"
}
```

### Common Fields (inherited from LedgerEntryBase)

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `type` | `"validation"` | Yes | Entry type discriminator |
| `timestamp` | string | Yes | ISO 8601 UTC timestamp (Z-suffixed) |
| `schema_version` | string | No | Schema version (default: `govzero.ledger.v1`) |
| `agent` | string | No | Agent that created the entry |
| `session_id` | string | No | Session identifier for grouping |

### Validation-Specific Fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `adr_id` | string | Yes | ADR identifier (format: `ADR-X.Y.Z`) |
| `event` | string | Yes | One of: `validated`, `completed`, `compliance_check` |
| `anchor` | object | Yes | Temporal anchor (see below) |
| `evidence` | object | No | Evidence payload (test results, coverage, etc.) |
| `attestor` | string | Yes | Human attestor (must be `human:<name>`, e.g., `human:jeff`). Only humans can attest — agents record evidence via the `agent` field. |

For OBPI-scoped completion receipts (`obpi_receipt_emitted` with `receipt_event=completed`),
runtime evidence must include value narrative + key proof semantics, and Heavy/Foundation
parents require explicit human-attestation evidence fields.
REQ-proof inputs follow the
[OBPI Runtime Contract](obpi-runtime-contract.md), including required
`name`/`kind`/`source`/`status` fields and optional `scope` / `gap_reason`.
Completed receipts also preserve the transaction snapshot needed for later
reconciliation:

- `scope_audit.allowlist`
- `scope_audit.changed_files`
- `scope_audit.out_of_scope_files`
- `git_sync_state` (branch, remote, dirty, ahead/behind, divergence, actions, warnings, blockers)
- `recorder_source`
- `recorder_warnings`

If git anchoring degrades after completion is already allowed, gzkit records the
warning in `recorder_warnings` and keeps the completion decision intact rather
than retroactively rewriting the brief state.

Operationally, gzkit expects the final completed OBPI receipt to be emitted only
after the guarded `uv run gz git-sync --apply --lint --test` ritual has
succeeded. Later brief/ADR sync edits may still occur, but the completion
receipt itself should anchor the already-synced implementation state.

### ValidationAnchor Fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `commit` | string | Yes | Git SHA, 7-40 lowercase hex characters |
| `tag` | string | No | Git tag if HEAD is tagged at validation time |
| `semver` | string | Yes | ADR semver version (e.g., `0.0.24`) |

---

## Event Types

| Event | When Used |
|-------|-----------|
| `validated` | ADR passes Gate 5 audit and is marked Validated |
| `completed` | ADR passes Gates 1-4 and is marked Completed |
| `compliance_check` | Periodic or on-demand compliance verification |

---

## Ledger File Location

Validation receipts are stored per-ADR alongside the existing OBPI audit ledger:

```text
docs/design/adr/{series}/ADR-{id}-{slug}/logs/adr-validation.jsonl
```

Example:

```text
docs/design/adr/adr-0.0.x/ADR-0.0.24-validation-receipt-temporal-anchoring/logs/adr-validation.jsonl
```

---

## Edge Case Behavior

| Scenario | Behavior |
|----------|----------|
| Ledger file does not exist | `read_receipts()` returns empty list |
| Ledger file is empty | `read_receipts()` returns empty list |
| Malformed JSONL line | Skipped with `warnings.warn()`; valid entries still returned |
| Duplicate receipts for same commit | All are preserved (idempotency is caller's responsibility) |

---

## Python API

### Creating and Writing Receipts

```python
from pathlib import Path

from gzkit.ledger import Ledger, audit_receipt_emitted_event

ledger = Ledger(Path(".gzkit/ledger.jsonl"))
event = audit_receipt_emitted_event(
    adr_id="ADR-0.3.0",
    receipt_event="validated",
    attestor="Jeffry Babb",
    evidence={"scope": "ADR", "adr_completion": "completed"},
)
ledger.append(event)
```

### Reading Receipts

```python
from pathlib import Path

from gzkit.ledger import Ledger

ledger = Ledger(Path(".gzkit/ledger.jsonl"))

# Read all audit receipt events
all_receipts = ledger.query(event_type="audit_receipt_emitted")

# Filter by ADR
adr_receipts = ledger.query(event_type="audit_receipt_emitted", artifact_id="ADR-0.3.0")
```

---

## See Also

- [Ledger Schema](ledger-schema.md) — Base schema and OBPI audit entry types
- [Layered Trust Architecture](layered-trust.md) — Tool layer model
- [ADR Lifecycle](adr-lifecycle.md) — Status transitions
- Python module: `src/gzkit/ledger.py`
