ADR-151: Extract ways-core crate and ways-audit sibling binary¶
Context¶
ADR-200 reframes the compliance subsystem into an operator-invoked claim/finding
formation layer, backed by a purpose-built toolkit rather than the core ways binary.
This ADR decides how that toolkit is packaged.
The relevant facts about the current code:
tools/is a Cargo workspace (a set of crates built together), and shared library crates are already the house style here —sensor-trait,agent-fmt, andagent-identityare exactly that pattern: small libraries several binaries depend on.ways-cliis a binary-only crate (it produces thewaysexecutable and has no[lib]target). The compliance logic — thegovernance/provenancemodules, ~1,100 lines — lives inside that binary crate and reaches into its private siblings (crate::util,crate::cmd). It is not a reusable library; it is trapped in the executable.- That ~1,100-line engine has zero tests, including the integrity linter that is supposed to audit the claims. Being buried in a binary with no library boundary is part of why: there is no clean seam to unit-test against.
- ADR-111 consolidated a sprawl of shell scripts (
governance.sh,provenance-scan.py, …) into the singlewaysbinary. That was the right call for script sprawl. The question here is different: the compliance concern is a distinct operator surface, invoked deliberately (like/shipor/wrap), which the subsystem's owngovernance/README.mdalready sketches as separable (its "Making This Its Own Repo" section — a "perforated pop-out"). It should not be re-scattered — but it also should not bloat the core tool everyone runs.
So the packaging question: how does ways-audit reuse the engine without rewriting it,
keep the core binary lean, and give the untested engine a home worth testing?
Decision¶
1. Extract a ways-core library crate¶
Pull the reusable engine out of the ways binary into a new ways-core library
crate in the workspace: way discovery and scanning, frontmatter parsing, path and
projection resolution, the firing-event log reader, and the claim (provenance.yaml)
sidecar model and manifest builder. Both ways (binary) and ways-audit (binary)
depend on it. This is the established house pattern (sensor-trait et al.), not a new
paradigm.
The extraction pays for itself independently of the compliance work: a library boundary
is exactly what makes the previously-untested engine unit-testable. ways-core ships
with tests; the core tool gets healthier whether or not anyone ever runs ways-audit.
2. Add ways-audit as a sibling binary¶
A new ways-audit binary crate, depending on ways-core, owns the whole compliance
surface:
- The reporting commands presently under
ways governance(report, trace, control, gaps, matrix, lint), migrated and renamed to the compliance vocabulary of ADR-200 (claim / finding / POA&M). - The finding pipeline: read a claim + its firing events + the session transcript, produce an assessment finding with a determination, and append it to the finding ledger.
- The claim-authoring assist: propose candidate control mappings for a way (agent-assisted, human-grounded per ADR-200 §1).
The core ways binary sheds the governance subcommand. It goes back to being the
runtime — matching, hooks, session lifecycle — with the compliance concern living next
to it, not inside it.
3. The claim schema lives in ways-core, built for scale and assessability¶
ways-core defines the claim type stored in the provenance.yaml sidecar (ADR-110).
Per ADR-200 §1 the schema carries a determination criterion — the observable
behavior that would let an assessor mark the claim satisfied / other than satisfied
— so a claim is assessable rather than a bare assertion. ways-audit consumes that
type both to assess (produce findings) and to suggest (candidate mappings). Defining
it in the shared crate keeps the format single-sourced as claims are seeded across the
corpus at scale (ADR-200 §7).
4. Distribution reuses the per-component release machinery¶
ways-audit slots into the existing component-parameterized release flow: a
ways-audit-v* tag series, its own CI build job, and a download entry, exactly as
ways has. The marginal cost is one CI job and one download path — the same
prebuilt-binary machinery already planned for the other app binaries (ADR-142/ADR-146).
ways-audit is a first-class member of the suite, not an opt-in add-on.
ways update manages the entire agent-ways collection; we do not ship partial
tool collections, and no binary has a separate lifecycle. So make install /
make setup build and link ways-audit alongside ways/attend, and
ways update refreshes it through the same download-first refresh_component
path as the rest. "Deliberately-invoked" (§2) describes how the tool is used —
you run ways-audit assemble when you want it, the way you run /ship — not
whether it is installed: it is always present, like ways itself. (An earlier
implementation kept it out of the default install "to stay lean"; that produced a
published-but-unfetchable binary and is corrected here.)
Relationship to ADR-111¶
ADR-111 folded shell-script sprawl into one binary because the abstraction layer — one
tool, consistent surface — was the value. This ADR does not reverse that: it introduces
one additional binary for one cohesive, separable concern, and the two binaries
share a real library (ways-core) rather than duplicating logic. The distinction is
concern-separation with a shared abstraction, not a return to sprawl. ADR-111's own
"the abstraction layer is the value" argument is what ways-core embodies.
Consequences¶
Positive¶
- The core
waysbinary stays lean — the compliance concern is isolated and optional. ways-coregives the previously-untested engine a testable boundary; the extraction improves the main tool on its own merits.- The claim schema is single-sourced and scale-ready, with assessability built in.
- Extraction to a standalone repo later (already sketched in
governance/README.md) becomes a small move — the library seam is already drawn — without paying that cost now. - Distribution is nearly free: the release/CI machinery already handles N components.
Negative¶
- A real refactor: extract
ways-core, move the modules offcrate::util/crate::cmdonto the library API, and fix imports across the workspace. - A second binary to build, ship, and version.
ways-corenow has a public API surface to keep stable for two consumers.
Neutral¶
ways-auditcan version independently ofways(different cadence, own tag series).- The
ways governancecommand path is retired from the core binary; a one-release deprecation pointer toways-auditcan be kept if any operator scripted against it, though it was never a consumed surface. - Repo extraction remains an available future option, deliberately not taken now.
Alternatives Considered¶
- Keep compliance inside the
waysbinary (status quo / strict ADR-111). Rejected: it bloats the core runtime with an optional, deliberately-invoked concern, offers no separation seam, and couples the compliance release cadence to the core tool. - A new binary that copies the engine (no shared crate). Rejected: duplication and drift. The workspace already solves this with shared library crates; not using one here would be the anomaly.
- Extract the whole subsystem to a separate repository now. Rejected as premature:
a same-workspace sibling is cheaper, keeps the
ways-corerefactor in one place, and still leaves repo extraction open once the toolkit proves itself. Drawing the library seam now is the reversible half of that decision.
References¶
- ADR-200 — the compliance claim/finding model this toolkit backs.
- ADR-110 — the
provenance.yamlsidecar storage the claim schema extends. - ADR-111 — the single-binary consolidation this refines (one sibling, shared lib).
- ADR-142 / ADR-146 — XDG application distribution and installer binary handling the new binary plugs into.
- The Cargo Book — Workspaces — the shared-crate mechanism used here. https://doc.rust-lang.org/book/ch14-03-cargo-workspaces.html