Proposal: Milestone 0 — the walking skeleton (implementation plan)
The implementation plan for Vord's first vertical slice. It records how M0 is built; the why lives in the ADRs it references. M0 is the first slice of the whole implementation arc.
Motivation
Milestone 0 proves the differentiator — stateless edge verification independent of the strongly-consistent core (ADR-0001 rule 1) — end to end in a single process: one auth-code+PKCE login issued by Modgud and verified by Vör, with the verification shown correct while the core is unavailable, under fault injection in simulation. Per the build strategy (architecture §4.3; ADR-0010) this is a vertical slice, not bottom-up: the layers that matter for one real operation, wired thinly, then widened by risk.
Building this slice first de-risks the project's central claim before any breadth (revocation, the authz engine, multi-tenancy, SAML) is added, and it establishes the trait seams and the DST harness everything after it attaches to.
Design
Scope boundary
In scope: minimal OAuth/OIDC authorize + token endpoints (Modgud) → password login
ceremony → minimal SSO session → core issuance (the irreducible write) → a signed
OIDC JWT → stateless verification (Vör) against published JWKS + a clock, plus the
testkit DST harness and the edge-independence property test. Around that spine,
the review-hardened day-one seats: the client object of
ADR-0038 (M0
registers its one client through the real model — type, hashed secret,
exact-match redirect), the budget registry and startup gate of
ADR-0039 (the dev profile ships a
consistent budget set), EventSink emit points with versioned event schemas
(ADR-0022), the interim static
admin authorizer behind the Authorizer seam
(ADR-0021), a minimal hosted login page
(the ceremony half of proposal 0007) with the accessibility gate (proposal 0020),
and the observability spine (proposal 0012 — tracing plus the staleness/lag
gauges the M2 gate will need).
Explicitly out of M0 (deferred to the risk-ordered widening): production
KeyCustody/KMS and rotation, the status-list/NATS revocation propagation path
(the status-list format ships at M0 — trivially empty, but parsed and
freshness-checked so the verifier contract is final), the Forseti authorization
engine, multi-tenancy/realm, SAML, SCIM, MFA, the event-stream projections, and
multi-region. Their hooks are respected where retrofit is expensive (see
Backward compatibility).
Deliverable
A proof, not a product: a single binary that runs auth-code+PKCE → core issuance → a signed OIDC JWT → stateless edge verification in one process, plus a seed-reproducible DST proof that verification completes correctly with the core unavailable (the property test below), the trait and test scaffold every later milestone attaches to, and the token format (ADR-0003) implemented (v0/unstable) with a conformance vector. It is explicitly not deployable (one key, no revocation propagation, no production core); a usable multi-tenant product is M4.
Workspace and crate scaffold (coarse start, ADR-0013)
A Cargo workspace, coarse, splitting later. Trait boundaries exist from day one even
where crate boundaries do not. Cargo.lock committed.
| Crate | M0 contents |
|---|---|
vord-format |
OIDC-JWT and status-list encode/sign/verify against the specs (specs/vord-token-v0, specs/vord-status-list-v0); dependency-light, spec-first |
vord-traits |
KeyCustody, CredentialVerifier, CredentialStore, SessionStore, Authorizer, EventSink, StatusListPublisher definitions (the keystone — consumers/impls depend here; all seven seams of ADR-0013 even where the M0 concrete is trivial) |
vord-core |
Modgud auth-code+PKCE + token endpoints, the client object (ADR-0038), the Vör verifier, the budget registry + startup gate (ADR-0039), the embedded directory/keystore/session backends, the static admin authorizer |
vord-server |
The binary; wires concretes; hosts the HTTP surface and the minimal hosted login page |
testkit |
Abstract clock; deterministic seed-driven runner; fault injection (core-unavailable); grows the in-sim fakes (ADR-0028) at later milestones |
xtask |
Codegen, conformance-vector run, cargo xtask ci |
CI adds cargo build/test/fmt/clippy; the adr-immutability, dco, docs-check,
and require-issue checks already exist.
Logical view
M0 collapses the planes into a single process behind the real trait seams: a
vertical slice through Modgud (issuance), the embedded core, and Vör (verification),
with the CredentialVerifier carrying no core handle so rule 1 is a compile-time
fact. The auth-code+PKCE flow is the architecture's §6.1 scenario.
Issuance — the auth-code + PKCE flow (ADR-0014)
- Authorize — Modgud validates the request against the registered client
(exact-match redirect, allowed grant — ADR-0038)
with a PKCE challenge, runs the password login ceremony behind the
multi-step-shaped
AuthenticationMethodseam (ADR-0015 — challenge/response-shaped now so WebAuthn and federation widen it without a trait break), establishes a minimal session with asid,auth_time, and assurance context (ADR-0016), and returns a code. - Issue — on the token request (PKCE verifier), Modgud performs the irreducible
core write and mints a signed OIDC JWT (EdDSA, embedded keystore,
kid-addressed from the first token — ADR-0023), stampingvfmt,sid,amr(password),acr, and the status-list reference. This write is the issuance point, and it emits the login event through theEventSink(ADR-0022) — rich (IP, client, method), versioned, projectionless for now. - Verify — Vör checks the JWT against the published JWKS, the clock, and the (trivially-empty at M0, but signed, counter-carrying, freshness-checked) status list, returning a decision without any core read.
Directory and issuance model (ADR-0011)
A principal record (immutable internal id, mutable identifiers, the password hash
— Argon2id with the cost-before-auth posture of proposal 0006, and the
per-principal data-key reference of ADR-0034)
plus a unique realm-scoped, blind-indexed identifier entry
(HMAC(realm-index-key, identifier) — ADR-0011 decision 3), written atomically;
single-tenant/single-realm at M0, but the realm column and the index key shape
are final so multi-tenant (M4) is a widening, not a reshape. The meta:version
consistency fence (ADR-0004)
is reserved (returned, not yet enforced).
Token and status-list formats (ADR-0003)
The external OIDC JWT (EdDSA primary) implements
specs/vord-token-v0: required kid with per-kid
algorithm binding, vfmt, sid, amr/acr, and the status-list reference — the
full v0 register, so no later milestone adds a claim verifiers must already know.
The status list implements specs/vord-status-list-v0
(counter + iat + the revocation sections, all empty at M0). M0 lands the first
conformance vectors in specs/conformance/; it does not redesign the formats.
Both stay v0/unstable until a second independent verifier validates them.
DST harness + property tests — attach at M0 (ADR-0010)
testkit provides an abstract clock and a single-threaded, seed-reproducible runner.
The headline property:
- Edge-independence (bounded autonomy) — with the core made unavailable, verification of an issued, unexpired token completes correctly for as long as the cached projections are within their freshness budgets; an expired token correctly fails; and past the status-list max-age the edge fails closed (ADR-0010, ADR-0032) — the DST scenario advances simulated time across the boundary and asserts both phases. A seed that finds a verification path reaching for the core is committed as a permanent regression.
Supporting assertions: a token verifies against the published JWKS; an unknown format
version is rejected; a replayed older status list is rejected while an equal-counter
re-fetch is accepted (ADR-0032); the CredentialVerifier signature carries no core
handle (asserted structurally at the trait seam — it graduates to a crate-graph
compile-time fact at the M4 Vör split, ADR-0013).
Test tiers
The project-wide testing strategy is ADR-0028; M0 stands up its first tier.
- Tier 0 — DST correctness (
testkit), from M0, no containers. Production logic is written against abstract time and the trait seams, run in a seed-reproducible simulator (madsim). The core-unavailable fault is injected here. - Tier 1 — integration against real backends, from M1+. Real KMS, the Zanzibar engine, a Wyrd metadata deployment, and NATS run under containers for (non-deterministic) integration tests. None of this exists in M0.
CI
cargo fmt --check, cargo clippy -D warnings, cargo build, cargo test (the DST
property tests run here on madsim), and cargo xtask conformance (the token-format
reader against specs/conformance/). DCO sign-off and cargo-deny (the
permissive-license allowlist + RUSTSEC advisories) enforce
ADR-0008 in CI from the
first commit.
Alternatives considered
- Bottom-up (directory and protocols first): rejected — a vertical slice proves the differentiator sooner.
- Fine-grained crates from day one: rejected (ADR-0013) — a combined
vord-corenow, split later. - Full OIDC surface in M0: deferred — auth-code+PKCE is enough to drive the slice end to end; client-credentials/refresh/the rest widen later.
Graduation criteria (definition of done)
- A relying party completes auth-code+PKCE against a registered client and receives
an OIDC JWT carrying the full v0 claim register (
kid,vfmt,sid,amr/acr, status reference). - The JWT verifies at the edge against the published JWKS + a clock + the (empty, freshness-checked) status list.
- Verification completes correctly under DST with the core made unavailable — within the projection freshness window, failing closed past it — reproducible from a seed (the bounded edge-independence property).
- The budget startup gate rejects an inconsistent dev profile (ADR-0039); the abuse-case suite passes; the login page passes the axe-core gate (proposal 0020).
fmt/clippyclean;Cargo.lockcommitted; thevord-formatreader accepts the conformance vectors inspecs/conformance/(token and status list, accept and reject).
Suggested PR sequence
- Workspace scaffold —
vord-traits(all seven seams, includingAuthorizer,EventSink,StatusListPublisheras definitions),testkitskeleton (abstract clock, seed-driven runner),xtask, CI (cargo xtask ci, cargo-deny, DCO; Actions pinned per proposal 0005). vord-format, token side — OIDC-JWT encode/sign/verify perspecs/vord-token-v0(kid/alg binding,vfmt, claim register) + first accept/reject conformance vectors.vord-format, status-list side — container encode/verify perspecs/vord-status-list-v0(counter,iat, revocation sections; persistent high-water-mark check) + vectors.- Embedded stores —
CredentialStore(principal record with data-key reference + realm-scoped blind identifier index, atomic write) andKeyCustody(software keystore,kiddiscipline) behind the traits. - Budget registry + startup gate (ADR-0039) with the self-consistent dev profile; an inconsistent profile refuses to start (tested).
- Client object + authorize endpoint — ADR-0038 registration (type, hashed
secret, exact-match redirect), PKCE, the password ceremony behind the
multi-step
AuthenticationMethodseam, minimalSessionStore(sid, auth_time, assurance context). - Token endpoint — issuance (the irreducible core write) + signed JWT with
the full claim set;
EventSinkemit points (versioned login/issuance events); the interim static adminAuthorizerfor the two management calls M0 needs (create tenant, register client). - Vör
CredentialVerifier— verify against JWKS + clock + status list with freshness state; no core handle. vord-serverwiring — minimal HTTP surface + the hosted login page (ceremony half of proposal 0007, axe-core gate per proposal 0020) + end-to-end auth-code+PKCE test.- DST bounded edge-independence — core-unavailable fault injection asserting both phases (correct within freshness window, fail-closed past it).
- Abuse-case tests (ADR-0028, §13): forged
alg:none/ wrong-algorithm / missing-kidtokens rejected; unknownvfmtrejected; PKCE-downgrade and redirect-mismatch requests refused (ADR-0038); replayed older status list rejected, equal-counter re-fetch accepted (ADR-0032); theCredentialVerifiercarries no core handle (structural assertion). The security spine starts at M0. - Observability spine (proposal 0012, M0 slice) — tracing through the issuance and verification paths plus the projection staleness/lag gauges the M2 gate will read.
Backward compatibility
- Token and status-list formats: fixed by the v0 specs (ADR-0003; v0/unstable until validated); M0 implements them. No tokens to migrate.
- Reserved seats honored now because retrofit is expensive: the full v0 claim
register —
kid/alg-binding,vfmt,sid,amr/acr, the status-list reference (ADR-0003 decision 6, ADR-0044) — plus themeta:versionfence (ADR-0004), the versionedEventSinkemit-seam (ADR-0022), the realm-scoped blind-index key shape and per-principal data-key reference (ADR-0011, ADR-0034), the session-record fields (ADR-0016), and the trait seams for KMS/Forseti/Wyrd-metadata. - API / deployments: none yet, so nothing to stay compatible with.
Open questions
- redb-or-equivalent embedded directory encoding (identifier-index key shape) — settled during M0 as an implementation detail, recorded in-code and in the architecture doc.
- Whether the minimal HTTP surface lives in
vord-serveror warrants avord-gatewaycrate at M0 — combinedvord-serverat M0, split later under the crate-evolution rule (ADR-0013). - No open question blocks M0.