# ADR-0014: The `runward characterize` command contract **Date**: 2026-07-09 **Status**: accepted **Deciders**: Thibault Souris (maintainer) **Method**: decision-loop — first build piece of the retro-documentation surface scoped in ADR-0013. Contract decided against the never-a-runtime and zero-LLM-gate invariants. Decision only; no code in this ADR. > **Amendment (2026-07-10, before implementing `--mine`).** Reconciled `--mine` with the zero-LLM-tool invariant and [ADR-0007](ADR-0007-advisory-llm-conformance-verification.md): runward's `--mine` is **deterministic** (git archaeology) and **makes no model call** — the original "may use an agent/LLM" wording is corrected below. The LLM refinement of a candidate into an argued hypothesis lives in the **harness** (the operating agent), exactly like the advisory verify workflow; a model behind a port remains possible but runward bundles none. > > **Amendment (2026-07-20, resume-existing audit).** The Decision's inventory list described three capabilities ahead of the code; reconciled here rather than left as drift. **Pinned versions** and **git churn** are now implemented ([ADR-0035](ADR-0035-characterize-extracts-pinned-versions-from-lockfiles.md), [ADR-0036](ADR-0036-characterize-reports-git-churn-hotspots-and-bus-factor.md)) — those claims are now true. **"reported coverage" is withdrawn**: `characterize` deliberately reads test *presence* and a file *count*, never a coverage figure — reading a coverage report would mean trusting a generated artifact the command never produced, against the "parse at rest" line. In the **Surface**, `--yes` and `--dry-run` are **global** flags (`runward --dry-run characterize`), not command options; the command's own options are `--path` and `--mine`. Separately, the "two runs agree" promise now rests on deterministic ordering (every filesystem scan is sorted — [ADR-0034](ADR-0034-characterize-sees-the-whole-tree-monorepos-and-deterministic-ordering.md), which also adds monorepo/workspace coverage), and `--mine` now mines across languages and groups by family ([ADR-0038](ADR-0038-mine-across-languages-groups-by-family-and-never-resurrects-a-ratified-draft.md)). None of these loosen the read-only / zero-LLM / operator-owns-the-gate contract; they fulfil it. ## Context ADR-0013 adopts retro-documentation as the transmission phase pointed backward and names `runward characterize` as the one genuinely new primitive: the read-only entry point that turns an undocumented system into the start of a governed mission. Today the brownfield entry mode is a workflow the agent reads (`brownfield.md`) with no CLI verb; "characterize before touching" is a manual instruction, not a tool. Before writing code, the command's contract must be locked, because `characterize` is the highest-temptation surface in runward: it is the one place an agent reads a real codebase, and the pull is to let it *conclude* — to emit "here is your architecture / here are your decisions / here is your compliance posture" as fact. That would put an LLM's judgment on the path to the gate and make runward an oracle. The contract exists to make that impossible by construction, not by good intentions. ## Decision `runward characterize` is a **read-only** command that produces the raw material of a brownfield mission: a factual inventory plus *candidate* decisions, all explicitly unvalidated. It reads artifacts at rest and writes only into `runward/` — never into `.git/`, never the user's source, and it never executes the user's system. **Two layers, sharply separated:** - **Deterministic inventory (default, zero-LLM).** Parses artifacts at rest: repo tree, dependency manifests and lockfiles (pinned versions), entrypoints, CI/workflow config, container/deploy files, test presence and reported coverage, and the *shape* of `git log` (age, churn, authors — counts, not interpretation). Emits `runward/characterization.md`: a factual inventory with a `confidence: high` header, no opinions, no *why*. Reproducible — two runs on the same commit agree. - **Candidate ADR-mining (opt-in, `--mine`).** Only under an explicit flag, and **deterministic — runward makes no model call.** It reads git history and the inventory at rest to propose *candidate* structural decisions (the stack/language choice, containerization, the CI pipeline, notable dependency families) and writes **only** `runward/adr/DRAFT-*.md` files, each with `status: hypothesis`, the evidence pointers (file paths, first-seen dates) that suggest a decision, and an explicit `why: UNKNOWN — needs operator` field. Turning a candidate into an argued hypothesis, and supplying the *why*, is the **harness/agent's** job downstream (per ADR-0007), never runward's. It writes nothing else and touches no manifest. **The contract's hard lines:** - **Read-only, never a runtime.** `characterize` opens files; it does not install, build, run, or instrument the target. No network to the target, no execution of its code. - **Zero-LLM, no model call.** The whole command is deterministic: the inventory is pure parsing, and `--mine` is git archaeology. runward calls no model and bundles none — the LLM refinement of candidates lives in the harness (ADR-0007's advisory posture), strictly upstream of any gate, and even there its only output is DRAFT hypotheses the operator must ratify. - **Nothing it emits can pass the gate.** DRAFT ADRs carry `status: hypothesis`; `check --strict` (per ADR-0013's lifecycle) requires `status: accepted` + a human-written *why* + a trigger. `characterize` produces material for the operator to ratify, never a conformance verdict. - **Facts are labelled facts; guesses are labelled guesses.** `characterization.md` is `confidence: high` (mechanical). DRAFT ADRs are hypotheses with provenance. No output is ever presented as original design intent or as compliance. - **Idempotent and non-destructive.** Re-running refreshes `characterization.md` and adds/updates DRAFTs; it never overwrites an operator-ratified ADR (`status: accepted`) or any mission state. **Surface:** `runward characterize [--path