# Plan — OBPI-0.0.27-05-citation-contract

**Brief:** `docs/design/adr/foundation/ADR-0.0.27-exemplar-corpus-doctrine/obpis/OBPI-0.0.27-05-citation-contract.md`
**Parent ADR:** ADR-0.0.27-exemplar-corpus-doctrine (Heavy + Foundation)
**Canonical plan twin:** `.claude/plans/OBPI-0.0.27-05-citation-contract-plan.md` (audit PASS — receipt at `.claude/plans/.plan-audit-receipt-OBPI-0.0.27-05-citation-contract.json`)

## Context

Why this change: the prior distillation-pass increment has landed the first
distilled-characteristics document at
`docs/governance/complexity/distilled-characteristics-2026-05-04.md` with
frontmatter `corpus_revision: 1`. Three downstream foundation ADRs
(0.0.28 / 0.0.29 / 0.0.30) and OBPI-07's link-integrity validator must cite
that document mechanically. Today the canonical complexity rule
(`.gzkit/rules/complexity-doctrine.md`) names the citation contract in prose
but does not formalize: (a) the canonical citation tuple, (b) the
percentile + absolute-number pairing rule that keeps boundaries portable
across corpus refresh, or (c) the refresh-portability rule the link
validator consumes. This OBPI extends the rule with those three
formalizations and lands the parser surface OBPI-07 mechanically validates.

Intended outcome: a frozen Pydantic `Citation` model + JSON Schema +
`is_portable` predicate consumed by OBPI-07, plus a rule body section
formalizing the tuple, the pairing rule, and the portability rule, with
rule-version bumped to `0.2.0` and vendor mirrors regenerated cleanly.

## Files

| Path | Action | Why |
|---|---|---|
| `.gzkit/rules/complexity-doctrine.md` | Extend "Citation Contract (binding)" section; bump rule-version `0.1.0 → 0.2.0` (body comment + visible block quote) | Formalize tuple, percentile + absolute pairing, refresh-portability per REQ-01/02/03/07 |
| `src/gzkit/complexity/citation.py` | New | `Citation` Pydantic model (`frozen=True, extra="forbid"`), `parse_citation`, `is_portable`. REQ-04. |
| `src/gzkit/schemas/complexity_citation.json` | New | JSON Schema mirroring the Pydantic model (`additionalProperties: false`). REQ-05. |
| `tests/complexity/test_citation.py` | New | unittest module decorated `@covers("REQ-0.0.27-05-NN")` for all 9 REQs. REQ-06/08. |
| Brief evidence sections | Update | Implementation Summary / Key Proof / Closing Argument / Acceptance Criteria. |

Vendor mirrors at `.claude/rules/complexity-doctrine.md`,
`.agents/rules/complexity-doctrine.md`, `.github/instructions/complexity-doctrine.md`
are regenerated by `gz agent sync control-surfaces` — never edited directly.

## Steps

1. **Red phase — REQ-derived unittest module first.** Author
   `tests/complexity/test_citation.py` decorating each test with
   `@covers("REQ-0.0.27-05-NN")` covering all 9 REQs (parser canonical
   shape, parser missing-fields rejection, portability within window,
   portability outside window, rule-body section parsing, rule-version
   marker agreement, sync idempotence, TDD hygiene, no-PII guard). Use
   `tempfile`-backed fixtures for any rule-body parsing assertions. Run
   unittest — confirm RED.

2. **Green phase — citation module.** `src/gzkit/complexity/citation.py`:
   `Citation` Pydantic frozen model with `extra="forbid"` over fields
   `(distilled_characteristics_path, section_anchor, corpus_revision)`;
   `parse_citation(text: str) -> Citation` accepting the canonical string
   form (raises `ValidationError` on missing fields); `is_portable(citation,
   current_revision: int, supported_window: int = 2) -> bool` (true within
   window, false outside).

3. **JSON Schema co-generation.** `src/gzkit/schemas/complexity_citation.json`
   with `additionalProperties: false`, `distilled_characteristics_path` regex
   `"^docs/governance/complexity/.+"`, slugified `section_anchor`, positive-int
   `corpus_revision`. Add schema-shape test asserting Draft 2020-12 validity
   and semantic equivalence to `Citation.model_json_schema()`.

4. **Rule body extension.** Replace existing "Citation Contract (binding)"
   prose section in `.gzkit/rules/complexity-doctrine.md` with formalized
   subsections: tuple definition (citing
   `src/gzkit/complexity/citation.py`); percentile + absolute pairing rule
   with worked example sourced from the landed `radon_cc` p90 = 7 boundary;
   refresh-portability rule naming the supported-window default and OBPI-07's
   flag-not-rewrite behavior. Bump `<!-- rule-version: 0.1.0 -->` →
   `<!-- rule-version: 0.2.0 -->` and visible block quote with one-sentence
   rationale per `.gzkit/rules/skill-surface-sync.md`.

5. **Vendor-mirror sync + idempotence.** `uv run gz agent sync
   control-surfaces` propagates canonical to mirrors. Re-run — diff empty.

6. **Refactor pass.** Tighten duplication; ARB ruff/typecheck clean.

7. **REQ → @covers parity gate.** `uv run gz covers
   OBPI-0.0.27-05-citation-contract --json` — `summary.uncovered_reqs == 0`.

8. **Heavy-lane verification.** ARB-wrap unittest, mkdocs strict, `gz
   validate --documents --surfaces`, `gz validate --advisory-scorecard`.

9. **Stage 4 evidence assembly + Gate 5.** Foundation + heavy walkthrough
   at brief level. Operator attestation enriched per AGENTS.md § Attestation
   (verbatim phrase + concrete session evidence + ARB receipt IDs).

## Verification

```bash
uv run gz arb ruff
uv run gz arb typecheck
uv run gz arb step --name unittest -- uv run -m unittest tests/complexity/test_citation.py -v
uv run gz arb step --name mkdocs -- uv run mkdocs build --strict
uv run gz validate --documents --surfaces
uv run gz validate --advisory-scorecard
uv run gz agent sync control-surfaces  # diff empty
uv run gz covers OBPI-0.0.27-05-citation-contract --json
```

## Notes

- `is_portable` default `supported_window=2`: revision N is portable when
  current revision is N or N+1; non-portable at N+2 or later. Operator may
  amend at attestation if practitioner-eye observation suggests a different
  window.
- BDD waiver registered at `data/behave_coverage_waivers.json` for OBPI-05
  per brief Gate 4 — downstream-citation BDD lands at OBPI-07.
- Rejected alternatives: free-form regex parser without Pydantic (loses
  schema/model isomorphism); implicit corpus-revision derivation from cited
  file frontmatter (citation must be self-contained); percentile-OR-absolute
  pairing (brief mandates AND).
- Sibling-ADR scope-collision advisories (202 overlaps) are advisory per
  audit code — speculative pending OBPIs in pool/pending ADRs do not block.
- No operator-PII tokens permitted anywhere per AGENTS.md § Local Agent
  Rules.
