- Config override (always wins):
AVATAR_TEST_COMMAND/AVATAR_LINT_COMMAND, per slot. The user’s stated contract is never overridden. - Deterministic detection (no LLM, no Python assumption): read repo artifacts —
CI workflows,
package.json,pyproject.toml, tox/nox,Cargo.toml,go.mod,.pre-commit-config.yaml, Makefile — and extract their declared test/lint invocations. CI-derived commands rank above arbitrary Makefile targets (CI is the gate the project actually trusts — least gameable). - LLM fallback (evidence-grounded only, opt-in via
AVATAR_PLANNER_MODEL): the model may propose a command for a slot detection left empty, but only citing the artifact it came from; the harness validates the citation before accepting. A proposal without valid provenance is rejected. No model configured → this tier simply does not exist (resolution stays deterministic and offline).
PlannedCheck (name, command, kind, provenance).
The runner freezes it onto TaskState at the investigating → editing boundary
and journals it; the Verifier then executes it with zero language knowledge.
Python-ecosystem tools are emitted as python -m <tool> so an installed-but-not-
on-PATH tool still resolves (the ADR’s robustness floor).
Classes
VerificationPlanner
Resolves the per-session verification plan (ADR-0007) — harness-owned.
Args:
config: Harness config; test_command/lint_command are the override tier
and planner_model (unset by default) opts into the LLM fallback.
client: An injected OpenAI-compatible client for the fallback, or None to
build one lazily on first use (the ModeClassifier precedent; the
fallback is never consulted unless planner_model is set).
VerificationPlanner.propose_smoke_check(self, ws: Workspace, files_modified: list[str]) -> PlannedCheck | None
Have the model author ONE executable smoke check for freshly-written code.
The greenfield floor: used only when tiers 1-3 resolved nothing and the run
wrote code, so there is genuinely no declared contract to discover. The model
chooses which command; the harness still runs it and reads the real exit code,
so this is author-and-run, never the self-certification §5 forbids. Resolved at
verification time (the artifact under test does not exist at the freeze boundary).
Runs on config.model (the main model) so the floor needs zero extra config; any
endpoint failure degrades to “no floor”, never blocks.
Args:
ws: The run-scoped workspace whose just-written files are excerpted.
files_modified: The repo-relative paths the run created or changed.
Returns:
The model-smoke check, or None when nothing runnable was proposed.
VerificationPlanner.resolve(self, ws: Workspace) -> list[PlannedCheck]
Resolve the verification plan for ws: override → detection → LLM fallback.
Args:
ws: The run-scoped workspace whose repo artifacts are read.
Returns:
The resolved checks, test slot first; empty when nothing is declared
anywhere (the verifier then fails legibly — never an invented default).
Functions
check_covers_content(command: str) -> bool
Whether a declared check covers a content change: anchored + falsifiable (ADR-0044).
The content rulebook replaces “must run what you build” (meaningless for a textual
deliverable) with two demands, judged together per stage (_stage_inspects_content):
anchored — some stage receives a content artifact (.md/.rst/.txt/.adoc,
behavior-bearing manifests excluded) as a real operand, so a plain pytest check
never silently “covers” the docs half of a mixed change and a quoted filename inside
a code string anchors nothing; falsifiable — that stage asserts (an assertive
inspector with an operand, or a real executor), and no trailing || alternative that
cannot fail (|| true, || echo fine) neutralizes the line to exit 0. The stage
split is quote-blind like vacuous_declared_check — live declared checks are single
argvs by the time they reach here (the ADR-0045 gate rejects/splits operators first),
so the multi-stage handling is defense-in-depth for direct callers, kept so the two
layers can be edited independently. The immutable floor beneath the contract is the
ultimate anchor.
Args:
command: The declared check command to classify.
Returns:
True when the command satisfies the content rulebook.
classify_change_paths(paths: Iterable[str]) -> set[Literal['code', 'content']]
The change kinds present in a set of changed paths (ADR-0044).
The verification-time half of the declared-kind audit: .md/.rst/.txt/.adoc
classify as content; every other suffix — and behavior-bearing .txt manifests
(requirements*.txt, CMakeLists.txt) — classifies as code, so ambiguity fails
toward the stricter rulebook.
Args:
paths: Workspace-relative changed paths (from the diff / files_modified).
Returns:
The subset of {"code", "content"} present; empty for no paths.
config_override_checks(config: HarnessConfig | None) -> list[PlannedCheck]
The override tier: checks declared explicitly in config (always win, per slot).
Shared by the planner (tier 1) and the verifier’s no-plan fallback, so the
two read the user’s stated contract identically.
Args:
config: The harness config, or None (no overrides).
Returns:
Up to one test and one lint check, for the non-empty config commands.
effective_invocation(command: str) -> tuple[str, list[str]]
The effective program + args of one command segment (program-position parse).
Strips leading env-var assignments (CI=1 pytest) and unwraps runner wrappers
(sudo, uv run, npx, python -m <module>) so classification keys on what
actually executes — never on a token appearing anywhere in the line.
Args:
command: One command segment (no &&/; chaining).
Returns:
(program, args) — program is the basename (empty when nothing remains).
vacuous_declared_check(command: str) -> bool
Whether a model-declared verification command is vacuous (proves nothing — ADR-0038).
A check is vacuous when it is empty or when every executed program on the line —
each &&/||/; segment and each | pipeline stage, after unwrapping
env/sudo/uv run/npx/python -m — is a no-op/inspector (true, echo, grep, …).
Such a line exits 0 regardless of the artifact, so it can’t be a real contract. One real
stage redeems the line: printf 'q' | python -m game drives the program under test via
stdin and must not be rejected for its printf head (dogfood 7e49b161 — the rejection
cost a turn plus a tier-3 amendment approval for an equivalent check). The split is
quote-blind, so an in-quote |/; can only mis-split toward accepting — e.g.
echo "a | b" now passes because its phantom second stage b" reads as an unknown
(real) program. That is the safe direction for a lower-bound guard: the immutable
floor beneath the declared contract is what ultimately anchors non-vacuity.
Args:
command: The declared check command to validate.
Returns:
True when the command is vacuous and must be rejected.