Architecture Decision Record

0013. Crate and workspace structure

Context

Vord is Rust (ADR-0002) and has not yet written a line of it. Before the walking skeleton, the workspace shape must be fixed enough that trait seams exist from day one — they are cheap to define and expensive to retrofit — while crate boundaries stay coarse and split later, because premature fine-grained crates buy nothing but Cargo plumbing and visibility friction. This is precisely the tension Wyrd settled in its ADR-0016 (monorepo, evolving crate structure, the keystone trait crate, xtask for CI logic), and Vord adopts the same shape — it is the sibling project's proven answer, and consistency between the two lowers the cost of moving between them.

The seams Vord must reserve are dictated by the prior ADRs: KeyCustody (ADR-0003), Authorizer (ADR-0006), and the CredentialVerifier/Vör contract whose whole point is that the core is absent from its signature (ADR-0010). Several more fall out of this plan and must exist as seams before the concretes behind them are built.

Decision

  1. A single Cargo workspace (monorepo), vord-* crates, starting coarse. The keystone rule (Wyrd ADR-0016): implementations and consumers depend on the trait crate, never on concrete backends; only the top-level binary crate wires concretes together. Cargo.lock is committed (Vord is an application, not a library). CI logic lives in an xtask crate (cargo xtask ci), not in YAML or a Makefile, so it runs identically on a laptop and in CI — a claim scoped to the cargo-native gates (fmt, clippy, build, test, conformance, cargo-deny); the GitHub-side gates (DCO, CodeQL, secret scanning, OSS-Fuzz, ADR-immutability) are platform services xtask neither runs nor replaces (ADR-0028 §6).

  2. Trait seams fixed from day one, even where the M0 concrete behind them is trivial:

    • KeyCustody (ADR-0003) — signing-key custody; embedded keystore in dev, KMS/HSM in prod.
    • CredentialVerifier (Vör, ADR-0010) — the verification seam with no core handle.
    • Authorizer (Forseti, ADR-0006) — the policy-decision seam.
    • CredentialStore / directory (ADR-0011) — principal and credential records on Wyrd's metadata tier.
    • SessionStore (ADR-0016) — the eventually-consistent SSO session.
    • StatusListPublisher (ADR-0005) — the revocation status-list projection.
    • EventSink (ADR-0022) — the identity-event emit point (projections built later; the seam reserved now).
  3. The M0 crate set (coarse; split per the Wyrd ADR-0016 evolution rule as boundaries firm up):

    Crate Role
    vord-format (+ specs/) The token/credential format encode/decode against the normative spec (ADR-0003); dependency-light, spec-first.
    vord-traits The keystone — every trait above, definitions only. Consumers/impls depend here.
    vord-proto gRPC/protobuf message shapes for the admin and internal surfaces (transport deferred; shapes defined as needed).
    vord-core Modgud issuance, the OIDC flow logic, the Vör verifier, the embedded backends; combined now, split later — the Vör split is not optional: vord-vor (or equivalent) must be its own crate by the M4 release, because that is when the "no core dependency" claim graduates from trait-signature discipline to a crate-graph fact (see consequences), and when per-plane binaries (ADR-0024) need a verifier crate that never links the core client.
    vord-server The binary; the only crate that knows concrete backends and wires them. Per-plane binary crates (a vord-edge that links only the verifier side) are reserved: the deployment topology (ADR-0024) runs Vör and Modgud as separate node types, and an edge deployable that carries issuance/core-client code would weaken the blast-radius story (ADR-0027 §2) from "no capability" to "no credentials".
    testkit The DST harness (abstract time/clock, seed-reproducible runner, fault injection) — first-class, not a helper (ADR-0002, Wyrd ADR-0009).
    xtask Codegen, conformance-vector runs, cargo xtask ci.
  4. Vord-only accommodations stay in Vord's crates (ADR-0001 rule 4). Anything that would otherwise be pushed into Wyrd to serve Vord lives here instead, behind Vord's own traits that consume Wyrd's client surface.

Consequences

  • The dependency graph makes the constitution visible — in two stages, stated honestly: while Vör and Modgud share vord-core (M0–M3), the "no core handle" guarantee is enforced at the trait signature (the CredentialVerifier inputs carry no core client, ADR-0010) and asserted structurally in tests, but the crate does link the core client for issuance's sake. It becomes a crate-graph, compile-time fact at the Vör split (M4 gate, decision 3): from then on the verifier crate cannot import what it must not read, and cargo itself is the reviewer.
  • The single-binary dev profile is dependency-free (embedded keystore, embedded authz, in-memory everything behind the seams), exactly as Wyrd's redb/in-memory profile is, satisfying the scale-range goal at the small end.
  • Swapping a production backend (KMS, SpiceDB/OpenFGA, the Wyrd metadata core) is a vord-server composition change, not a refactor — the pluggability that the ADR-0008 selection criteria assume and that lets the deferred engine picks stay cheap.
  • Extension is composition, never dynamic loading: operators add backends and authentication methods by building vord-server with additional crates (rung 3 of the extension ladder, ADR-0029; the method contract is ADR-0015 decision 5) — so the executing code of any deployment is enumerable from its Cargo.lock, which is the auditability property a runtime plugin loader would forfeit.
  • Crate boundaries will move as the design firms up; that is expected and governed by the same evolution rule Wyrd uses, not a re-decision each time.