---
id: OBPI-0.0.29-01-advisor-diagnosis-schema
parent: ADR-0.0.29
item: 1
lane: Heavy
status: Completed
---

# OBPI-0.0.29-01-advisor-diagnosis-schema: Advisor Diagnosis Schema

## ADR Item

- **Source ADR:** `docs/design/adr/foundation/ADR-0.0.29-complexity-advisor/ADR-0.0.29-complexity-advisor.md`
- **Checklist Item:** #1 — "Advisor diagnosis schema (frozen Pydantic AdvisorDiagnosis, RefactorArchetype enum, DoctrinalFrame, ProofRange; JSON Schema mirror)"

**Status:** Draft

## Objective

Implement frozen Pydantic models `AdvisorDiagnosis`, `DoctrinalFrame`, `ProofRange`, and a `RefactorArchetype` enum at `src/gzkit/complexity/advisor/diagnosis.py`, with a JSON Schema mirror at `src/gzkit/schemas/advisor_diagnosis.json`. This is the data contract every downstream OBPI in ADR-0.0.29 binds against.

## Lane

**Heavy** — New runtime data contract consumed by OBPI-02 engine, OBPI-03 CLI, OBPI-08 proof binding, and downstream ADR-0.0.30 authoring-guidance. Foundation-kind brief-level Gate 5 attestation.

## Allowed Paths

- `src/gzkit/complexity/advisor/__init__.py`
- `src/gzkit/complexity/advisor/diagnosis.py`
- `src/gzkit/schemas/advisor_diagnosis.json`
- `tests/complexity/advisor/test_diagnosis.py`
- `docs/design/adr/foundation/ADR-0.0.29-complexity-advisor/obpis/OBPI-0.0.29-01-advisor-diagnosis-schema.md` — this brief's evidence section only

## Denied Paths

- `src/gzkit/complexity/advisor/engine.py` — engine is OBPI-02
- `src/gzkit/complexity/advisor/intrinsic.py` — attestation is OBPI-07
- `src/gzkit/complexity/advisor/timeout.py` — timeout is OBPI-09
- `src/gzkit/commands/complexity_advise.py` — CLI is OBPI-03
- Any path not listed in Allowed Paths

## Requirements (FAIL-CLOSED)

1. REQUIREMENT: `RefactorArchetype` is a `StrEnum` with exactly ten initial values: `long_parameter_list`, `arrowhead`, `switch_on_type`, `feature_envy`, `large_class`, `divergent_change`, `shotgun_surgery`, `primitive_obsession`, `data_clumps`, `message_chain`. Amendments require ADR-0.0.29 ceremony per § Decision rationale #2.
2. REQUIREMENT: `DoctrinalFrame` is a frozen Pydantic `BaseModel` with `ConfigDict(frozen=True, extra="forbid")`. Fields: `authority: Literal["fowler", "martin", "page_jones", "constantine"]`, `citation: str`, `excerpt: str`.
3. REQUIREMENT: `ProofRange` is a frozen Pydantic `BaseModel`. Fields: `file_path: str` (relative to repo root), `start_line: int` (≥ 1), `end_line: int` (≥ `start_line`), `ast_node_kind: str`.
4. REQUIREMENT: `AdvisorDiagnosis` is a frozen Pydantic `BaseModel`. Fields: `metric: str`, `crossing_band: Literal["block", "warn", "advise"]`, `crossing_value: float`, `archetype: RefactorArchetype`, `doctrinal_frame: DoctrinalFrame`, `proof: tuple[ProofRange, ...]` (non-empty), `recommended_move: str`, `intrinsic_attestation: IntrinsicAttestationRef | None` (optional). `IntrinsicAttestationRef` is defined here as a forward stub model with `attestation_id: str`; full implementation lands at OBPI-07.
5. REQUIREMENT: The JSON Schema at `src/gzkit/schemas/advisor_diagnosis.json` enforces: `proof` is a non-empty array; `archetype` is in the enum; `crossing_band` is one of the three trigger-semantic values; `authority` is one of the four canonical authorities.
6. REQUIREMENT: The `proof` field MUST be non-empty; `AdvisorDiagnosis` instantiation with `proof=()` raises `ValidationError`. This codifies ADR § Decision rationale #5 at the model layer.
7. REQUIREMENT: All four model classes use `ConfigDict(frozen=True, extra="forbid")` per `.claude/rules/models.md`; mutation attempts raise.
8. REQUIREMENT: Tests cover: valid instantiation; rejection of empty `proof`; rejection of `authority` outside enum; rejection of `archetype` outside enum; rejection of `crossing_band` outside enum; rejection of `end_line < start_line`; mutation raises; JSON Schema validates a serialized model dict; JSON Schema rejects empty proof / unknown enum. Each test decorated with `@covers(REQ-0.0.29-01-NN)`.
9. REQUIREMENT: TDD discipline; `tempfile`-backed fixtures.
10. REQUIREMENT: NEVER include the operator's personal email in code, fixtures, or docstrings.

> STOP-on-BLOCKERS: pydantic ≥ 2 is required (already runtime dep); if the constraint changes, reconcile before drafting.

## Discovery Checklist

**Prerequisites**

- [x] Parent ADR-0.0.29 § Decision rationale #1, #2, #5 — schema field shapes, four-authority canon (Fowler / Martin / Page-Jones / Constantine), ten-archetype canon (long_parameter_list, arrowhead, switch_on_type, feature_envy, large_class, divergent_change, shotgun_surgery, primitive_obsession, data_clumps, message_chain), proof-binding rationale. Amendments to either canon require ADR-0.0.29 ceremony.
- [x] AGENTS.md § Lane & Kind & Sensitivity Attestation Matrix — `kind=foundation` + `lane=heavy` cell triggers brief-level Gate 5 walkthrough; `_requires_human_obpi_attestation` returns True via the foundation branch and the heavy branch independently.
- [x] AGENTS.md § STDLIB-FIRST DOCTRINE — pydantic is the canonical named departure (validation semantics genuinely cannot be supplied by stdlib); `jsonschema` is already a runtime dep used elsewhere in `src/gzkit/` for Draft 2020-12 schema validation.
- [x] `.claude/rules/models.md` — `ConfigDict(frozen=True, extra="forbid")` is the binding immutable-model pattern; `Field(...)` with description for required fields; `str | None` not `Optional[str]`; mutation on frozen instances raises.
- [x] `.claude/rules/pythonic.md` — function ≤ 50 lines, module ≤ 600 lines; top-level imports only; explicit exceptions; ty type-ignore syntax (bare `# type: ignore` or `# ty: ignore[<code>]`, never mypy-style `# type: ignore[<code>]`).
- [x] OBPI-0.0.29-07 brief — confirms `IntrinsicAttestationRef` is a forward stub here with `attestation_id: str` only; full implementation (reason, attestor, attested_at, ledger linkage) lands at OBPI-07 without breaking the optional-field shape on `AdvisorDiagnosis`.

**Existing Code**

- [x] `src/gzkit/complexity/citation.py` — `Citation` Pydantic model (`frozen=True`, `extra="forbid"`) + companion JSON Schema at `src/gzkit/schemas/complexity_citation.json` (`additionalProperties: false`); used as the structural template for `AdvisorDiagnosis` ↔ `advisor_diagnosis.json` mirror pair.
- [x] `src/gzkit/schemas/complexity_citation.json` — Draft 2020-12 schema shape (`$schema`, `$id: gzkit.complexity_citation.v1`, `additionalProperties: false`); the new schema follows the same envelope conventions and adds `$defs` for `RefactorArchetype`, `DoctrinalFrame`, `ProofRange`, `IntrinsicAttestationRef`.
- [x] `src/gzkit/traceability.covers` — `@covers("REQ-X.Y.Z-NN-MM")` decorator for REQ→test parity (consumed by `gz covers OBPI-0.0.29-01 --json` parity gate at Stage 3 Phase 1b).
- [x] `pydantic` (≥ 2, runtime dep) — `BaseModel`, `ConfigDict`, `Field(min_length=...)`, `model_validator(mode="after")`. Belt-and-braces non-empty `proof` enforcement uses both `Field(min_length=1)` and a `_check_proof_nonempty` model validator because pydantic-core 2.10–2.18 had intermittent `min_length` looseness on tuple fields.
- [x] `jsonschema` (runtime dep) — `Draft202012Validator` is the test surface for the JSON Schema mirror's positive/negative validation cases (REQ-06).
- [x] `data/behave_coverage_waivers.json` — existing waiver shape (rationale-key + per-OBPI entry); precedent at OBPI-0.0.27-01 / OBPI-0.0.28-01 for the same foundation-bdd-deferred pattern. OBPI-0.0.29-01 entry under `adr-0.0.29-foundation-bdd-deferred` rationale defers BDD to OBPI-03's CLI surface (no operator-runnable verb at this schema-only OBPI).

## Quality Gates

### Gate 1: ADR
- [ ] Intent recorded; parent checklist item quoted

### Gate 2: TDD
- [ ] RGR cycle; tests pass with `@covers`

### Code Quality
- [ ] Lint/type clean

### Gate 3: Docs (Heavy)
- [ ] mkdocs --strict clean (no doc surface here; docs at OBPI-03/04)

### Gate 4: BDD (Heavy)
- [ ] BDD waiver registered: schema-only; behavior coverage in OBPI-02/03

### Gate 5: Human (Heavy + Foundation)
- [ ] TTY + `ATTEST`

## Verification

```bash
uv run gz lint
uv run gz typecheck
uv run gz test
uv run gz validate --documents
uv run gz arb step --name unittest -- uv run -m unittest tests/complexity/advisor/test_diagnosis.py -v
```

## Acceptance Criteria

- [ ] REQ-0.0.29-01-01: Given a valid input dict, when `AdvisorDiagnosis(**data)` runs, then a frozen instance is returned.
- [ ] REQ-0.0.29-01-02: Given `proof=()`, when `AdvisorDiagnosis` is instantiated, then `ValidationError` is raised.
- [ ] REQ-0.0.29-01-03: Given `archetype` outside the ten-value enum, when instantiated, then `ValidationError`.
- [ ] REQ-0.0.29-01-04: Given `authority` outside the four-value enum, when `DoctrinalFrame` is instantiated, then `ValidationError`.
- [ ] REQ-0.0.29-01-05: Given a frozen instance, when mutation is attempted, then `ValidationError`.
- [ ] REQ-0.0.29-01-06: Given a serialized `AdvisorDiagnosis` dict, when validated against the JSON Schema, then validation passes; given a dict with empty proof or unknown enum, validation fails.

## Completion Checklist

- [ ] Gate 1: Intent recorded
- [ ] Gate 2: RGR cycle; tests pass with `@covers`
- [ ] Code Quality: lint/type clean
- [ ] Gate 3: docs clean
- [ ] Gate 4: BDD waiver registered
- [ ] Gate 5: TTY + `ATTEST`

## 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 3 (Docs)
```text
# n/a — schema-only OBPI
```

### Gate 4 (BDD)
```text
# Waiver: data/behave_coverage_waivers.json — OBPI-0.0.29-01
```

### Gate 5 (Human)
```text
# Record attestation text + receipt IDs
```

### Value Narrative

### Key Proof


End-to-end frozen-instance construction:

```bash
uv run python -c "from gzkit.complexity.advisor import AdvisorDiagnosis, RefactorArchetype, DoctrinalFrame, ProofRange; \
    d = AdvisorDiagnosis(metric='radon_cc', crossing_band='warn', crossing_value=8.5, \
        archetype=RefactorArchetype.LONG_PARAMETER_LIST, \
        doctrinal_frame=DoctrinalFrame(authority='fowler', citation='Refactoring 2e p.78', excerpt='...'), \
        proof=(ProofRange(file_path='x.py', start_line=10, end_line=20, ast_node_kind='FunctionDef'),), \
        recommended_move='Introduce Parameter Object'); \
    print(d.metric, d.archetype, len(d.proof))"
# Output: radon_cc RefactorArchetype.LONG_PARAMETER_LIST 1
```

`AdvisorDiagnosis(proof=())` raises `pydantic.ValidationError` (defense-in-depth verified across `Field(min_length=1)` and `_check_proof_nonempty` model validator). REQ-coverage parity gate `gz covers OBPI-0.0.29-01 --json` reports `summary.uncovered_reqs == 0` (6/6 acceptance-criteria REQs covered).

ARB receipts (cited inline per AGENTS.md § Attestation):

- lint clean: `arb-ruff-e5a645d8abb94b7fb9e7d52b5a1697cc`
- typecheck clean: `arb-step-typecheck-61f8cc905920408b870c6fd9342b3a00`
- OBPI-scoped tests pass (12/12): `arb-step-unittest-f39f0ed8b54e491c9532940fe33ffe18`
- mkdocs --strict clean: `arb-step-mkdocs-2fa975da1edd4185b8265fb084b88523`

### Implementation Summary


- Files created: `src/gzkit/complexity/advisor/__init__.py` (23 lines, package re-exports), `src/gzkit/complexity/advisor/diagnosis.py` (183 lines, `RefactorArchetype` StrEnum + 4 frozen Pydantic models), `src/gzkit/schemas/advisor_diagnosis.json` (115 lines, Draft 2020-12 mirror with `additionalProperties: false`), `tests/complexity/advisor/__init__.py` (empty marker), `tests/complexity/advisor/test_diagnosis.py` (275 lines, 12 tests — 11 `@covers`-decorated)
- Files modified: `data/behave_coverage_waivers.json` (new `adr-0.0.29-foundation-bdd-deferred` rationale + waiver entry; brief Allowed-Paths gap noted, sibling precedent OBPI-0.0.28-01 followed)
- Tests added: 12 — `TestAdvisorDiagnosisSchema` covering valid instantiation, empty-proof rejection (defense-in-depth via `Field(min_length=1)` + `_check_proof_nonempty` model validator), three enum-rejection cases (archetype, authority, crossing_band), `end_line < start_line` cross-field rejection, frozen-mutation rejection, JSON Schema positive validation, three JSON Schema negative cases (empty proof, unknown archetype enum, unknown authority enum), and parameterized `extra="forbid"` walk across all four model classes
- Date completed: 2026-05-06
- Attestation status: agent-relayed-operator-attestation (TTY proxy via `--attestor-present`; pipeline marker active at Stage 1)
- Defects noted: brief Allowed-Paths gap on `data/behave_coverage_waivers.json` (cluster brief-coherence pattern; tracked at `ADR-pool.brief-authoring-evidence-checks`); pre-flight remediated two cluster defects in flight — Discovery Checklist Prerequisites + Existing Code subsections added (GHI #406-class), ADR-0.0.28 frontmatter drift reconciled (Draft → Validated to match ledger)

### Closing Argument

## Tracked Defects

_No defects tracked._

## Human Attestation

- Attestor: `Jeffry Babb`
- Attestation: attest completed — OBPI-0.0.29-01 advisor diagnosis schema lands frozen Pydantic data contract (RefactorArchetype StrEnum + DoctrinalFrame + ProofRange + IntrinsicAttestationRef forward stub + AdvisorDiagnosis) plus Draft 2020-12 JSON Schema mirror at additionalProperties: false; 12 unittests pass; 6/6 acceptance-criteria REQs covered (uncovered_reqs == 0 via gz covers parity gate); belt-and-braces non-empty proof enforcement (Field(min_length=1) + _check_proof_nonempty validator) addresses pydantic-core 2.10-2.18 looseness; cross-field end_line >= start_line enforced via _check_line_range Pydantic validator (JSON Schema Draft 2020-12 has no portable cross-field comparator); BDD waiver registered under adr-0.0.29-foundation-bdd-deferred rationale (BDD lands at OBPI-03 CLI verb); two cluster brief-defects remediated in flight (Discovery Checklist subsections + ADR-0.0.28 frontmatter drift reconciled); ARB receipts arb-ruff-e5a645d8abb94b7fb9e7d52b5a1697cc, arb-step-typecheck-61f8cc905920408b870c6fd9342b3a00, arb-step-unittest-f39f0ed8b54e491c9532940fe33ffe18, arb-step-mkdocs-2fa975da1edd4185b8265fb084b88523.
- Date: 2026-05-06

---

**Brief Status:** Completed

**Date Completed:** 2026-05-06

**Evidence Hash:** -
