Skip to content

ADR-0005: Build Runner (Make) and Validator Stack (src/apothem/conformity/*-grep.py Family + Per-Class JSON Schemas)

Status

Accepted

Context

Two related ratifications, bundled here because their tradeoffs entangle:

  1. Build runner. The published ecosystem needs a single canonical entry point for lint, validate, test, format, audit, release, plans-doctor, headers-doctor, headers-fix, ai-surfaces-doctor, and the dual-distribution release entry points. Candidates: Makefile, justfile, npm scripts, tox, cargo tasks, nox, ad-hoc shell scripts.
  2. Validator stack. The custom validators (per Spec §2.4) need a consistent shape — a per-discipline gate that returns pass/fail with structured findings, integrates into a CI orchestrator, and runs the same way locally and in CI. Candidates: extend the existing src/apothem/conformity/*-grep.py family, author a single mega-validator, lift validators into a third-party gate framework, or compose ad-hoc per-discipline scripts with no unifying orchestrator.

The two decisions interlock because the build runner exposes the validator stack's CI-green entry point. A Makefile target like make validate invokes the orchestrator at src/apothem/conformity/conformity-gate.py, which in turn runs every per-discipline grep family member. The build runner's idioms shape the validator stack's invocation API, and vice versa.

The build-runner candidates fail in characteristic ways:

  • just. Less ubiquitous on Windows runners — the Windows path needs cargo install just or a separate binary distribution. The ecosystem's cross-platform mandate (POSIX bash + PowerShell paired stubs at src/apothem/hooks/lib/bootstrap.{sh,ps1} etc.) extends to the build runner; a runner that requires per-OS install friction breaks the parity.
  • tox. Python-only; the ecosystem is Python-primary but ships shell scripts, PowerShell scripts, Markdown linters, schema validators, and link checkers — many of which are not Python. tox's envlist abstraction does not naturally cover the heterogeneity.
  • npm scripts. Adds a Node dependency to a Python-primary repo; downstream consumers cloning to install would carry a Node footprint they don't otherwise need.
  • nox. Better polyglot story than tox but still Python-side; Windows install friction remains.
  • Ad-hoc shell scripts (no runner). Loses the discoverability make help provides; every contributor has to read the scripts directory to find the entry points.

The validator-stack candidates fail in characteristic ways:

  • Single-file mega-validator. Violates clean-architecture single-responsibility per CM-27 (src/apothem/rules/clean-architecture-layers.md); a single file enforcing every discipline becomes the place every gate change lands, fragmenting the per-discipline ownership the existing per-grep family demonstrates.
  • Third-party gate framework (Snyk, custom-rule platforms, etc.). Foreign dependency; opaque rule semantics; vendor-lock surface; runs against a different evaluation model than the ecosystem's own src/apothem/conformity/*-grep.py shape (which is a discoverable, edit-locally, audit-the-source pattern).
  • Per-discipline ad-hoc scripts with no orchestrator. Loses the composite gate semantics — the airtightness composite at the pre-emission gate (per Spec §M4 / §9) needs a single entry point that returns a single overall verdict; ad-hoc per-script invocation cannot cleanly express the composite.

The D4 mix-strategy decision (recorded at Phase 04A) further constrains the validator-stack choice: some disciplines extend the existing grep family (the matcher pattern is already proven), others need fresh-authored validators (the discipline does not reduce to a grep-shaped match). The validator-stack ratification must accommodate both shapes.

Decision

Build runner: Makefile.

Rationale: idiomatic for cross-platform dotfiles; matches existing CI/release-engineering patterns; ubiquitous on macOS + Linux + WSL2 + Termux + ChromeOS Linux container; available natively on Windows via make from MSYS2 / GnuWin / Chocolatey. The Windows path is the tightest; the install-friction is one choco install make (or a documented WSL2 flow) per fresh Windows host, which is acceptable for a dotfiles-targeted publication. The published Makefile follows the canonical-target convention (make help lists every entry point with a one-line description).

Validator stack: src/apothem/conformity/*-grep.py family extended + fresh-authored validators per the D4 mix-strategy split.

Rationale: every existing src/apothem/conformity/*-grep.py member follows the proven shape — a per-file scanner with a structured-findings JSON output, an --findings <out.json> flag for CI capture, a --check-only mode for fast invocation, and an exit code that signals pass/fail. The orchestrator at src/apothem/conformity/conformity-gate.py aggregates the per-grep verdicts into a composite verdict suitable for the airtightness composite gate. New disciplines that reduce to a grep-shaped match extend the family; new disciplines that need richer logic (e.g., a graph-shape coherence check that walks AST-level structure rather than line-level patterns) author fresh validators in the same src/apothem/conformity/ directory under the same composite-orchestrator integration.

Per-class JSON Schemas at schemas/*.schema.json.

Each artifact class with a structured frontmatter contract (agents, skills, commands, output-styles, plan files, etc.) carries a JSON Schema at schemas/<class>.schema.json. The frontmatter-grep.py validator parses each file's frontmatter and validates against the class's schema. Schema authorship is the per-discipline ownership boundary; the orchestrator's role is invocation aggregation, not schema authorship.

Canonical CI-green entry: make lint && make validate && make test.

These three targets gate every publication; pre-commit hooks mirror the same chain via .pre-commit-config.yaml. The publication phase (Phase 09B) verifies CI green across the chain before tagging.

Consequences

  • make help is the canonical discovery surface. Contributors read make help to find every entry point; the Makefile's first target is help, which lists every public target with a one-line description.
  • Pre-commit mirrors the same disciplines. A contributor running pre-commit run --all-files gets the same gate verdict as CI; surprises at PR-review time are rare.
  • Per-class JSON Schemas validate frontmatter. A new agent file with a malformed description field fails the frontmatter-grep validator at pre-commit; the contributor sees the schema-check error message naming the offending key.
  • The orchestrator at src/apothem/conformity/conformity-gate.py is the composite-gate boundary. Adding a new validator means: (a) author the per-discipline *-grep.py (or fresh-named validator) in src/apothem/conformity/, (b) register it in the orchestrator's gate-list, (c) author its self-tests at tests/conformity/<discipline>/, (d) update CHANGELOG. The four steps are mechanical; no architectural decision is made per-validator.
  • Cross-platform parity is preserved. make lint && make validate && make test runs identically on macOS, Linux, WSL2, and Windows-with-make-installed; no per-OS branching in the Makefile.
  • The validator-stack-vs-mega-validator decision is closed. The ecosystem will not collapse the per-discipline files into a single mega-validator; the per-discipline ownership boundary is the architectural invariant.
  • The build-runner-vs-just decision is closed. The ecosystem will not migrate to just even when its DSL is more expressive; the ubiquity argument for Make outweighs the DSL-ergonomics argument for just.

Alternatives Considered

  • Build runner: just. Rejected: less ubiquitous on Windows runners; install friction breaks the cross-platform parity mandate.
  • Build runner: tox. Rejected: Python-only; the ecosystem is heterogeneous (shell, PowerShell, Markdown, JSON Schema, link-check).
  • Build runner: npm scripts. Rejected: adds a Node dependency to a Python-primary repo.
  • Build runner: ad-hoc shell scripts with no runner. Rejected: loses make help discoverability.
  • Validator stack: single-file mega-validator. Rejected: violates CM-27 single-responsibility; per-discipline ownership boundary is the architectural value.
  • Validator stack: third-party gate framework. Rejected: foreign dependency; opaque rule semantics; vendor-lock surface; foreign evaluation model.
  • Validator stack: per-discipline ad-hoc scripts with no orchestrator. Rejected: loses composite-gate semantics needed by the airtightness composite.

Cross-references: src/apothem/conformity/conformity-gate.py, schemas/, src/apothem/rules/clean-architecture-layers.md, Spec §2.4, Spec §5.10.