ADR-002: Module roadmap and the Veltro module contract
Source of truth:
docs/adr/002-module-roadmap-and-contract.md(synced at build time — edit the original, not this copy). Status: Accepted (2026-07-03)
Amended by ADR-016: the design system packages move from vectorflow/packages to the veltro repository.
Current state (refreshed 2026-09-30). Read this block first; the amendment notes below are kept as history.
- Standalone products with their own login are gone. Every product runs behind Veltro identity, and a “standalone” install is the Veltro core plus that one product (ADR-014 Decision 1). Each product has one authentication path: the gateway’s signed browser assertion. The shared-secret JWE session decode in Decision 2 §4 and the
VELTRO_INTEGRATEDswitch in §4/§5 no longer apply.- The Veltro repo is the only supported install (
veltro installor the umbrella chart). Product repos publish images and Helm subcharts; their Compose files are for product CI and development (ADR-014 Decision 3).- Veltro is one app behind one chrome, branded by what is installed (ADR-016). The suite cuts one
vX.Y.Zrelease; product images, the design system,suite-authandaikeep their own semver as internal artifacts (ADR-016 D30-D34).- Public Veltro is fixed to realm
default(ADR-010).- Repo and image visibility: see “Visibility” under Context.
ADR-010 partial supersession (2026-07-30): the single-origin integration requirement remains authoritative. The shared symmetric delegated-auth mechanism described below was shipped compatibility, not the target trust boundary. (Its “standalone products use local identity” clause was later superseded by ADR-014; see above.)
Amended by ADR-003: the delegated issuer is Veltro (the standalone identity authority), not VectorFlow. The Decision 2 §4 mechanism below still holds; a new module points its
<MODULE>_DELEGATED_AUTH+ session secret at Veltro. Everything else in the contract stands.Amended by ADR-004 (2026-07-04): §4/§5 flag + secret names are now
VELTRO_INTEGRATED/VELTRO_SESSION_SECRET, and the suite-service token plane (issuerveltro-suite,svcclaim,external:*scopes) is generalized to all three products.Amended by ADR-012 (2026-09-06): Decision 1’s Prowl module and later TIP evaluation are withdrawn. Hunting lives in CHAD. Threat intelligence stays inside CHAD through MISP, OpenCTI, and TAXII 2.1 integrations. The Backlog’s separate OSCAR module is also withdrawn. OpenSearch storage lifecycle and cluster health live in CHAD. Warden remains the case and response authority. The general module contract below does not change.
Amended by the suite-simplification wave (2026-07): §2 (server images are amd64-only on the shared self-hosted runner; the VF agent stays multi-arch), §7 (compose wiring is now one
compose/<module>.yml+ oneinclude:line + one nginx zone), and §9 (design-system minor bumps are taken each release). A “new module in N steps” checklist is appended below.Superseded in part by ADR-014 (accepted 2026-09-28): Decision 2 §1’s standalone module with its own login, §4’s standalone mode, and the “standalone-mode still boots with its own login” acceptance check stop applying. Every module runs behind Veltro identity, and a standalone install is the Veltro core plus that module.
Context
Section titled “Context”Veltro is now three artifacts: the platform repo veltrosecurity/veltro
(compose profiles, single-origin nginx, env contract, ADRs) and two module
repos, veltrosecurity/vectorflow (VF) and veltrosecurity/chad (CHAD). The
org migration + reference cutover are done (see the repin in versions.env /
docker-compose.yml); ADR-001 records the topology and the delegated-SSO
mechanism, verified end-to-end (docs/sso-verification.md).
Two questions were open: which module comes next, and what exactly a new module must satisfy to slot into the suite. This ADR decides both, extracting the contract from what VF and CHAD already do rather than inventing it.
Visibility (2026-09-30): the four repos (
veltro,vectorflow,chad,warden) and theirghcr.io/veltrosecurity/*packages are still private, and pulls need aread:packagestoken. The flip to public is the 1.0 launch step tracked in #925, after the full-history secret scan (#1297). Licensing stays AGPL-3.0. The owner updates this note to “public” when the flip happens. (The 2026-07-03 note called this a temporary closed-source hold; that hold ends at the flip.)
Decision 1 — module order (superseded by ADR-012)
Section titled “Decision 1 — module order (superseded by ADR-012)”Warden first. CHAD is a detection engine: it emits alerts but has no
first-class response home. It ships a lightweight “investigation workspace”
(cases: open→investigating→contained→closed, owner, alert links, timeline,
comments — see chad/backend/app/api/cases.py), but there is no case-centric
SOAR surface, no response playbooks, and the detection→response loop is open.
Warden closes that loop and gives the suite its case + response substrate.
Prowl second. A hunting console produces leads that need somewhere to escalate into — a case. Building Prowl before a case substrate exists means Prowl would either reinvent cases or dead-end its findings. With Warden landed, Prowl’s hunts escalate into Warden cases; it also reuses CHAD’s OpenSearch and VF’s Lake (ClickHouse) as query backends.
TIP is an evaluation, not a build (for now). CHAD already integrates MISP
(frontend/src/pages/MISP.tsx, TISettings.tsx) and other TI sources. A
dedicated threat-intelligence platform is therefore an evaluate-vs-adopt
exercise deferred until Warden + Prowl exist and the intel consumers are clear.
If the owner prefers Prowl-first, the only change is which PRD lands first
(docs/prd/prowl.md vs docs/prd/warden.md); the contract below is identical
for either module.
Decision 2 — the Veltro module contract
Section titled “Decision 2 — the Veltro module contract”Every new module (<module>, e.g. warden) MUST satisfy:
- Repo & license. Own repo under
veltrosecurity/, AGPL-3.0 (matching VF and CHAD). Standalone-capable — the module runs on its own compose with its own login, independent of the suite. - Images. Semver
v*git tags publish flatghcr.io/veltrosecurity/<module>-backendand-frontend(never nested…/<module>/<module>-*), built amd64-only on the shared self-hosted runner — the suite host is x86, so a multi-arch matrix is wasted work. Only the VectorFlow agent stays multi-arch (edge devices are legitimately arm64); revert a server image to multi-arch only if arm64 server deploys become a target. Derive image owner from${{ github.repository_owner }}in CI. - Path zone. A
/<module>/location behindveltro-nginx, served at the single public origin so the session cookie stays host-only (ADR-001). No published port of its own in thefullprofile. - Delegated auth. In the suite (
fullprofile) the module authenticates browser requests by decoding VF’s Auth.js JWE session cookie with the sharedVELTRO_SESSION_SECRET(VF’s image reads it as its nativeNEXTAUTH_SECRET; HKDF-SHA256 → JWE dir/A256CBC-HS512). CHAD’sbackend/app/core/suite_service_token.py+ itsconfig.pyVELTRO_INTEGRATEDflag are the reference implementation — copy that pattern. Standalone mode (VELTRO_INTEGRATEDunset, the container default) keeps the module’s own login working unchanged. - Env contract. All module config uses a
<MODULE>_*prefix and is wired through veltro’s.env.example(+ the SSO fan-out: thefullprofile setsVELTRO_INTEGRATED=trueand fansVELTRO_SESSION_SECRETto the module, exactly as CHAD is wired). - Version pin. A
<MODULE>_VERSIONline inversions.env— the only file that moves on a routine upgrade. - Compose wiring. One
compose/<module>.yml(service definitions + volumes, shared env deduped with anx-<module>-envanchor) pulled in via oneinclude:line indocker-compose.yml, plus one nginx zone innginx/veltro.conf. Each block carries the module’s own profile (<module>) and afull-profile single-origin variant (network alias for the nginx upstream, no published port). - Suite chrome. An entry in the shared app registry
packages/shell/src/config.ts(DEFAULT_VELTRO_APPS) so the module appears in the AppSwitcher / SuiteTopBar / SuiteLanding.buildSuiteApps({include})already makes standalone deploys hide absent siblings — no extra work. - Design system. The frontend consumes
@veltrosecurity/{tokens,ui,shell}so rail widths, header, theme sync (sharedthemelocalStorage key), and the AppSwitcher read as one product across zones. Policy: a design-system minor publish (e.g.@veltrosecurity/shell0.2→0.3) is taken by every product in its next release PR, so suite chrome never silently diverges between zones (a caret range on 0.x does not cross a minor).
Acceptance for “a module is suite-ready” is the same shape as
scripts/verify-sso.sh: / routing, delegated login into /<module>/, single
logout, and standalone-mode still boots with its own login.
Decision 3 — stack rule
Section titled “Decision 3 — stack rule”- UI-and-data modules (query-heavy, dashboard-first, read-mostly) copy VF’s stack: Next.js / React / Tailwind / Prisma / Auth.js.
- Workflow / backend-heavy modules (state machines, background workers,
webhooks, audit) copy CHAD’s stack: FastAPI + SQLAlchemy/Alembic + Postgres,
with a React/Vite/pnpm/Tailwind SPA frontend and
@veltrosecurity/*.
Warden is workflow/backend-heavy → CHAD-pattern (see docs/prd/warden.md).
Backlog (tracked, not scheduled here)
Section titled “Backlog (tracked, not scheduled here)”- OpenSearch storage lifecycle and cluster health belong to CHAD (ADR-012). They are not a separate OSCAR module.
- CHAD Playwright E2E (deferred from the consistency pass).
- Formatter/Prettier unification across VF + CHAD.
Reconcile CHAD’s existingDone: Warden shipped and CHAD’s legacy cases surface was deleted (#135; CHAD migrationcasesworkspace with Warden once Warden ships.20260807b_drop_legacy_cases.py).
Consequences
Section titled “Consequences”- The next implementation plan is “scaffold
veltrosecurity/wardenagainst this contract,” not a design exercise — the contract is the spec. - CHAD’s
casesfeature now has a defined future: it becomes a deep-link surface into Warden in thefullprofile and stays as-is standalone (docs/prd/warden.md).
Appendix — new module in N steps
Section titled “Appendix — new module in N steps”This checklist applies to any future module. Prowl is withdrawn by ADR-012.
- Repo + license — new
veltrosecurity/<module>repo, AGPL-3.0, standalone-capable. - Images —
docker-build.ymlpublishing flatghcr.io/veltrosecurity/<module>-{backend,frontend}, amd64-only on the self-hosted runner. - Delegated auth — decode the Veltro session cookie with
VELTRO_SESSION_SECRET, gated onVELTRO_INTEGRATED(copy CHAD’ssuite_service_token.py+ config flag). - Env contract —
<MODULE>_*-prefixed config in veltro’s.env.example; thefullprofile setsVELTRO_INTEGRATED=true+ fansVELTRO_SESSION_SECRET. - Version pin — one
<MODULE>_VERSIONline inversions.env. - Compose — one
compose/<module>.yml+ oneinclude:line + one nginx zone (profile<module>+ afullsingle-origin variant). - Suite chrome — register in
@veltrosecurity/shellDEFAULT_VELTRO_APPS; pin the current shell minor. - Verify — extend the
scripts/verify-sso.shchecks:/routing, delegated login into/<module>/, single logout, and standalone still boots.