<!-- markdownlint-configure-file { "MD013": { "line_length": 9999 } } -->

# State Doctrine — Three-Layer Model and Authority Rules

**Source ADR:** [ADR-0.0.9 — State Doctrine and Source-of-Truth Hierarchy](../design/adr/foundation/ADR-0.0.9-state-doctrine-source-of-truth/ADR-0.0.9-state-doctrine-source-of-truth.md)

**Purpose:** Define the canonical storage layers and authority rules that every `gz` command must follow when reading or writing state. This document is the single reference for "which layer wins?" questions.

---

## Three-Layer Model

gzkit stores governance state across three layers. Each layer has a distinct role, authority level, and durability guarantee.

### Layer 1: Governance Canon (L1)

**What it is:** The versioned markdown and YAML files checked into git that define ADRs, OBPI briefs, configuration, and governance policy.

**Authority:** Defines *what should exist* — the intent, scope, and structure of governance artifacts.

**Examples in gzkit:**

| Artifact | Path |
|----------|------|
| ADR documents | `docs/design/adr/**/ADR-*.md` |
| OBPI briefs | `docs/design/adr/**/obpis/OBPI-*.md` |
| Governance config | `.gzkit/manifest.json` |
| Governance runbook | `docs/governance/governance_runbook.md` |

**Durability:** Git-versioned. Survives clone, fork, and archive. Rebuildable from any git checkout.

### Layer 2: Event Log (L2)

**What it is:** The append-only JSONL ledger that records every governance event — completions, attestations, receipts, audits, and reconciliations.

**Authority:** Defines *what has happened* — the runtime truth for status and completion.

**Examples in gzkit:**

| Artifact | Path |
|----------|------|
| Global ledger | `.gzkit/ledger.jsonl` |
| ADR-level audit log | `docs/design/adr/**/logs/obpi-audit.jsonl` |
| Ceremony log | `.gzkit/ceremonies/*.jsonl` |

**Durability:** Git-versioned (append-only). Each line is a timestamped, immutable event. Lines are never edited or deleted.

### Layer 3: Derived State (L3)

**What it is:** Caches, indexes, markers, and computed artifacts that are derived from L1 and L2. Deletable and rebuildable.

**Authority:** Defines *what is convenient* — accelerates queries and runtime operations but is never the source of truth.

**Examples in gzkit:**

| Artifact | Path |
|----------|------|
| Pipeline markers | `.claude/plans/.pipeline-active*.json` |
| OBPI work locks | `.gzkit/locks/obpi/*.lock.json` |
| Frontmatter `status:` fields | YAML frontmatter in brief files |
| ADR status table `Status` column | OBPI Decomposition tables in ADR docs |
| **ADR status index** | `docs/governance/GovZero/adr-status.md` (regenerated by `uv run gz register-adrs`; drift fail-closed by `uv run gz validate --adr-status-fresh` — GHI #322) |

**Durability:** Ephemeral. Delete all L3 artifacts, run `uv run gz state`, and everything reconstructs from L1 + L2.

**Worked example — `docs/governance/GovZero/adr-status.md` (GHI #322).**
The ADR status index is the canonical worked example of *"Layer 3 derived
views never become source-of-truth."* It drifted across ~5 ADRs in early
2026-04 because no maintained regenerator existed; the table was hand-edited
between recovery sessions. The closure mechanizes Rule 3: regeneration is
owned by `gz register-adrs` (the same ceremony that reconciles ledger
truth), and Rule 5 is upheld by `gz validate --adr-status-fresh` — drift
surfaces as a fail-closed advisory in `gz check`, never as a silently
trusted view.

---

## Five Authority Rules

These rules are drawn verbatim from the ADR-0.0.9 Decision section. They govern every `gz` command that reads or writes state.

### Rule 1: Ledger Is Authoritative for Runtime Status

> The ledger (`.gzkit/ledger.jsonl`) is authoritative for all runtime status. If frontmatter says `status: Completed` but no corresponding ledger event exists, the entity is not complete.

### Rule 2: Frontmatter Is a Lazy Mirror

> Frontmatter status is a lazy mirror. It is auto-fixed at lifecycle moments (`gz closeout`, `gz attest`, `gz obpi reconcile`) but allowed to lag during active execution. Frontmatter is never read as source-of-truth for completion.

### Rule 3: Layer 3 Is Always Rebuildable

> Layer 3 artifacts (pipeline markers, caches, derived indexes) are always rebuildable. Delete them all, run `gz state`, and everything reconstructs from L1 + L2.

### Rule 4: Reconciliation Is a Core Operation

> Reconciliation is a core architectural operation, not a maintenance chore. It is tested, gated, and optionally run as part of the pipeline.

### Rule 5: Layer 3 Cannot Block Gates

> Layer 3 artifacts cannot block gates. Only L1 (canon) and L2 (events) can be gate evidence. L3 can surface warnings but never fail-close a gate.

---

## Decision Table: When Layers Disagree

When state across layers is inconsistent, use this table to determine which layer wins.

| Conflict | Winner | Action |
|----------|--------|--------|
| Frontmatter says `Completed`, ledger has no completion event | **L2 (Ledger)** — entity is NOT complete | Run `gz obpi reconcile` to fix frontmatter |
| Ledger says completed, frontmatter says `Draft` | **L2 (Ledger)** — entity IS complete | Run `gz obpi reconcile` to update frontmatter |
| Pipeline marker exists but ledger has no pipeline event | **L2 (Ledger)** — marker is stale | Delete the marker; it will be recreated if needed |
| ADR table shows `Pending` but brief frontmatter says `Completed` | **L1 (Brief)** over L3 (table) | Run `gz obpi sync` to update the ADR table from brief source |
| Brief frontmatter says `Completed` but ledger disagrees | **L2 (Ledger)** over L3 (frontmatter) | Run `gz obpi reconcile` to align frontmatter with ledger |
| Lock file exists but process is dead | **L3 is ephemeral** — lock is stale | Re-claim the lock; stale locks auto-release past TTL |
| Config manifest lists a path that no longer exists on disk | **L1 (Disk)** — manifest is stale | Update manifest or run `gz check-config-paths` |

### General Precedence

```text
L2 (Ledger events)  >  L1 (Canon files)  >  L3 (Derived state)
```

- **L2 beats L1** for runtime status questions ("is this done?"). The ledger records what actually happened; canon records what was intended.
- **L1 beats L3** for structural questions ("what briefs exist?"). Canon is the source of truth for artifact identity and scope.
- **L3 loses to everything.** Derived state is always rebuildable and never authoritative.

### Recovery Command

When drift is detected between layers, the canonical repair sequence is:

```bash
uv run gz state --repair       # Force-reconcile all frontmatter from ledger
uv run gz obpi reconcile <ID>  # Reconcile one OBPI
uv run gz adr audit-check <ADR-ID>  # Verify no evidence gaps remain
```

---

## Anti-Patterns

| Anti-Pattern | Why It Fails | Correct Approach |
|-------------|-------------|------------------|
| Reading `status: Completed` from YAML frontmatter as proof of completion | Frontmatter is L3 — it can lag or be manually edited | Read the ledger for completion events |
| Using pipeline marker existence as gate evidence | Markers are L3 — they can be stale or orphaned | Use ledger events for gate evidence |
| Manually editing `.gzkit/ledger.jsonl` | Ledger is append-only by policy | Use `gz` commands to emit events |
| Treating reconciliation as optional maintenance | Drift accumulates and gates produce false results | Run reconciliation as part of lifecycle |
| Blocking a gate check on a missing L3 artifact | L3 cannot fail-close gates (Rule 5) | Only L1 and L2 evidence can block gates |

---

## Canonical Status Vocabulary (ADR-0.0.16 addendum)

**Source:** [ADR-0.0.16 — Frontmatter-Ledger Coherence Guard](../design/adr/foundation/ADR-0.0.16-frontmatter-ledger-coherence-guard/ADR-0.0.16-frontmatter-ledger-coherence-guard.md)

**Canonical constant:** `gzkit.governance.status_vocab.STATUS_VOCAB_MAPPING`

Frontmatter `status:` terms in circulation across gzkit (as of ADR-0.0.16 authoring) do not all match the ledger state-machine terms defined in ADR-0.0.9. This addendum records the canonical mapping that downstream surfaces — `gz gates` error output (OBPI-0.0.16-02) and the `frontmatter-ledger-coherence` chore (OBPI-0.0.16-03) — consume. There is exactly one canonical mapping; consumers import the constant and never inline duplicates.

| Frontmatter term | Canonical ledger term | Rationale |
|------------------|-----------------------|-----------|
| Draft | pending | Authoring state; no ledger events yet |
| Proposed | pending | ADR authored but not validated |
| Pool | pending | Backlog item; no lifecycle events |
| Promoted | pending | Recently promoted from pool; no events yet |
| Pending | pending | Direct mirror |
| Active | in_progress | Operator-facing OBPI active-work term |
| in_progress | in_progress | Canonical ledger term written verbatim in frontmatter |
| In-Progress | in_progress | Operator-facing hyphenated variant |
| In Progress | in_progress | Operator-facing space-separated variant |
| Accepted | validated | Historical term for Gate-1 validated |
| Validated | validated | Direct mirror |
| Pending-Attestation | completed | Work done, awaiting human attestation |
| Completed | completed | Direct mirror (accepted as a valid specialization when ledger is `attested_completed`) |
| attested_completed | attested_completed | Canonical ledger term written verbatim in frontmatter post-attestation |
| Attested | attested_completed | Operator-facing post-attestation term |
| Superseded | abandoned | Replaced by later decision |
| Deprecated | abandoned | Retired lifecycle term |
| Abandoned | abandoned | Public mirror for ledger-withdrawn OBPIs |
| Withdrawn | abandoned | Operator-pulled before completion |
| archived | abandoned | Retired; no longer active |
| drift | drift | Canonical runtime drift term written verbatim when needed |

Canonical targets are drawn from `OBPI_RUNTIME_STATES` (at `src/gzkit/ledger.py`) and ADR lifecycle states (`pending`/`validated`/`completed`/`abandoned`). Lookups are case-insensitive; consumers that encounter a frontmatter term not in this mapping MUST block with a clear error naming the unmapped term — they never silently skip.

---

## See Also

- [Historical Self-Close Waivers](historical-self-close-waivers.md) — closed waiver list for pre-doctrine receipts (ADR-0.0.36, OBPI-0.0.36-04).
