The constitution. Before you add a file, move a module, or define a contract, it is governed by something here. Every principle names the established practice it comes from and the CI gate that enforces it — so no rule lives by convention alone. If a rule is not gated, it is aspirational; treat closing that gap as work. Scope note:crmandretrievalare deferred, and there is nomemorydomain. A workspace is a user-owned git repo (data, not platform code);crmis an application — one entity schema over a workspace (see P11) — not a platform domain. This doc governs what we build now.
0. The shape, in one sentence
Vexa is contract-bounded at two scales — a handful of microservices coupled only by published schemas, each internally a modular monolith of modules coupled only by ports — all over a shared runtime kernel. The construction discipline is modular-monolith; the deployment shape is microservices, carved where a real force requires it (runtime, scale, data, ephemerality).1. Concepts — the vocabulary (use these words precisely)
2. Principles — the rules (each has a why, a source, and a gate)
3. The structure — render the chart, don’t snapshot it
The module / service / contract inventory and the runtime data-flow ARE the chart —architecture.calm.json, the single source of truth, gated against drift
(P23, gate:dataflow). A hand-written tree here would drift (it already had), so structure lives nowhere
but the chart. Render any slice instead of restating it:
gate:graph / gate:graph-py (spec in
.dependency-cruiser.cjs + scripts/check-isolation-py.mjs), so it is doctrine, not a snapshot: a
domain’s internals (services/, modules/) may import only its own code · another domain’s
contracts/ · core/runtime/contracts — never another domain’s internals. meetings ⊥ agent at the
internals level: agent may reference core/meetings/contracts/transcript.v1 (that IS the seam), never
core/meetings/services / modules. Contracts nest with their owner domain as JSON Schema (P4); the
chart lists which contract each domain exposes.
4. The gates (CI — the teeth)
Each gate enforces one or more principles. An artifact “exists” only when it is gate-green — “verified-compliant” = passing this suite. Admit nothing on trust. The rows below are kept in sync withscripts/gates.mjs’s GATES map — every gate the runner exposes appears here (node scripts/gates.mjs all
runs them). (“ADD” = gap still open; “retire” = scheduled for removal.)
Two-layer enforcement. Locally, a
pre-push hook (.githooks/pre-push, wired by core.hooksPath via the root prepare script — zero-dependency, per P17) runs pnpm gates and blocks any push that isn’t green. In CI, gates.yml re-runs each gate as its own step so a failure is unambiguous. git commit itself runs nothing — the bar is at push, not every commit.
The architecture chart. architecture.calm.json (FINOS CALM) is the runtime data-flow + ownership model and the single inventory of every node — the index for AI, the mental model for humans (P23). pnpm arch:viz <cluster:|domain:|flow:|path:> [--lod=0..3] [--scale] renders deterministic perspectives to docs/views/*.svg — you read one cluster or path at a time, never the whole graph. gate:dataflow keeps it true (completeness · single-writer · render-only · seal); pnpm seal:arch re-stamps the asserted-true baseline after a reviewed change.
5. How we prove a change — the validation pyramid
Build downward from the cheapest, most-isolated proof. The bot is the worked example (71 checks).
Rule: a port (P5) is what lets L2 exist. If you can’t unit-test the core without a browser/redis,
you’re missing an adapter seam. The eval harness (L4 quality) is first-class — it’s how a domain
proves its output is correct, not merely that it ran.
6. Reference shelf (read these to pressure-test a decision)
7. When the rules bend (be honest, not dogmatic)
- An adapter is ceremony if it does no vocabulary translation, no dispatch, and unlocks no test seam, over a stable leaf — inline it (P5 has a cost).
- Default to a module. A new service must justify its distribution tax against P10’s forces.
- A README boundary is acceptable for a leaf with no consumers. The moment it has two, gate it (P9).
shared/-style kernels are allowed but strict — smallest, most stable, most reviewed code; never a junk drawer. If you can’t name the one concern it hides, it’s not a module.
8. Development process — how we build (and contribute)
The sections above say what the system is. This says how you change it. Same discipline — every step names its practice and ends at a gate. The inner loop (one change):- Contract first — define/change the port or
contracts/*.v1before the code; the contract is the unit of agreement. (API-first / consumer-driven contracts) - Implement behind a port — transports are adapters; the core stays offline-provable. (hexagonal)
- Prove down the pyramid — L1 golden → L2 unit (mock ports) → L3 integration → L4 live + eval. Cheapest proof first. (test pyramid)
- Green under the gates is “done” — isolation · graph · exports · schema-conformance · unit. Green or it didn’t happen. (fitness functions)
- Small PR on trunk — short-lived branch, small diff, gates required to merge. (trunk-based dev / DORA)
Planning mode ⇄ execution mode — never build without a current plan:
- A current plan always exists — the full path from where we are to the goal: the staged route to “done”, each stage with a runnable proof, plus the critical path and what’s parallelizable. It is a maintained doc (kept in the maintainer workspace), not a memory. The plan is the macro expectation; the expectation–reality loop below executes against it.
- Goal vs objective. The goal is the destination — the end of the plan (the release). An objective is the current go — the one waypoint you execute toward now; the plan is the ordered path of objectives to the goal. The objective is the unit of execution and assessment: you are always executing toward exactly one open objective (never drifting), and it closes either expected (result met it → continue with the next planned objective autonomously — do not stop to ask) or unexpected (result diverged → stop, interpret it with the human as learning, root-cause + codify, and update the plan — an unexpected result is exactly what triggers a re-plan). Expected closure flows on its own through the plan; unexpected closure pulls in the human and revises the plan (ADR-0017).
- Planning mode produces or revises that plan before building — decompose the objective into staged proofs, mark the parallel workstreams, set each stage’s definition-of-done. Output = an approved plan. (design-before-build; staged migration)
- Execution mode runs the plan one stage at a time under the expectation–reality loop — instrument-validated, stop-on-surprise.
- The modes interlock. A surprise that root-causes to a principle gap — or a changed objective — loops back to planning: revise the plan, then resume. The plan is living and always current; you are always in one mode or the other, never improvising without a path.
Collapsed: Plan the full path (planning mode) → execute a stage under the loop (execution mode) → on a gap/surprise, re-plan. Never build without a current plan.The expectation–reality loop — how execution mode runs, and how the principle-set grows:
- State the expected behaviour first. Before acting, name what the system should do and what “done” looks like for the current objective — the contract for the work in front of you. You can’t detect a divergence you never defined. (expectation-first; P19/P21 applied to the work itself)
- Validate cheaply by instrument — but know it’s approximate. Gates, unit/integration tests, the
eval/replay·analyze·benchmarkpath are fast, reproducible, and run with no human — so they do the broad, cheap filtering. But their pass/fail is a proxy for “actually working”: the proxy can mis-define success (green while broken, red while fine) or mis-interpret the signal (thecapturetool once called a healthy gmeet “unhealthy”). Cheap, not definitive — green is necessary, never sufficient (P19). - The human is the ground-truth oracle — scarce, and fallible. At the end of the day only a human can finally tell if the thing actually works — deploy the human precisely where cheap tests can’t correctly define success or interpret the signal (real browser behaviour, real-meeting quality). Because the human is the scarcest resource and errs: spend it last and least, hand a minimal, fully-instructed surface (the exact
🧑step), and cross-validate both directions — a green instrument is provisional until it correlates with real success; a human “it works” / “I topped up the balance” is checked against an instrument (ping the service, census the tape). A human↔instrument disagreement is a signal: usually the instrument’s success-definition is the gap — fix it.- Instrumentalise the human’s verdict. Each human judgement is captured as ground truth — a golden (P8), an eval baseline, a recorded expected-vs-actual — so the cheap test is calibrated to the human and the human is needed less next time. (The
eval/completeness·leakage·attribution scores are “is the transcript right?” turned reproducible.)
- Instrumentalise the human’s verdict. Each human judgement is captured as ground truth — a golden (P8), an eval baseline, a recorded expected-vs-actual — so the cheap test is calibrated to the human and the human is needed less next time. (The
- An unexpected error is a STOP. Reality ≠ expectation ⇒ stop. Don’t paper over it, blind-retry, or push past — an unpredicted behaviour is a signal, not a nuisance.
- Root-cause every surprise — earn the learning with the human, and promote it twice. Each unexpected error is a symptom of a missing/violated principle. Interpret it with the human (the ground-truth interpreter — a learning is never minted from an instrument alone), trace it to the gap, fix the instance, and promote the learning to BOTH (a) the architecture — a principle + its gate + an ADR, so it bites (P9) — and (b) the learnings log (the running ledger, kept in the maintainer workspace of surprise → root-cause → promotion; the log always, even for a practice/candidate with no P-number). This is how the principle-set grows — P18–P21 were each born from one such surprise. (blameless root-cause; evolutionary architecture; ADR-0018)
Collapsed: Expect → instrument → (human: minimal, cross-validated) → stop on surprise → root-cause to a principle → codify. Report facts, not evaluations.The brick lifecycle (how a module is born): scaffold (one template, incl. its
README.md: what · surface · deps) → define its contract (a nested
port, or contracts/*.v1 if it crosses a boundary) → implement behind the port → pass the gate suite →
admit it (consumers may now import its index). A brick that isn’t gate-green doesn’t exist yet.
Collapsed: Contract → pyramid → gates → small PR. Contracts (lane:contract) are human-gated. Fix
in the owning brick. Decisions get an ADR. Big work is staged to runnable proofs.
This file is the source of truth for “how we build.” Changes to a principle or a gate ride a
lane:contract, human-reviewed PR and are recorded as an ADR under docs/adr/.