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:
- 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,cargotasks,nox, ad-hoc shell scripts. - 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.pyfamily, 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 needscargo install justor a separate binary distribution. The ecosystem's cross-platform mandate (POSIX bash + PowerShell paired stubs atsrc/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 helpprovides; 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.pyshape (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 helpis the canonical discovery surface. Contributors readmake helpto find every entry point; the Makefile's first target ishelp, which lists every public target with a one-line description.- Pre-commit mirrors the same disciplines. A contributor running
pre-commit run --all-filesgets 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
descriptionfield fails thefrontmatter-grepvalidator at pre-commit; the contributor sees the schema-check error message naming the offending key. - The orchestrator at
src/apothem/conformity/conformity-gate.pyis the composite-gate boundary. Adding a new validator means: (a) author the per-discipline*-grep.py(or fresh-named validator) insrc/apothem/conformity/, (b) register it in the orchestrator's gate-list, (c) author its self-tests attests/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 testruns 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
justeven 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 helpdiscoverability. - 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.