Skip to content

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_INTEGRATED switch in §4/§5 no longer apply.
  • The Veltro repo is the only supported install (veltro install or 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.Z release; product images, the design system, suite-auth and ai keep 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 (issuer veltro-suite, svc claim, 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 + one include: 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.

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 their ghcr.io/veltrosecurity/* packages are still private, and pulls need a read:packages token. 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.

Every new module (<module>, e.g. warden) MUST satisfy:

  1. 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.
  2. Images. Semver v* git tags publish flat ghcr.io/veltrosecurity/<module>-backend and -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.
  3. Path zone. A /<module>/ location behind veltro-nginx, served at the single public origin so the session cookie stays host-only (ADR-001). No published port of its own in the full profile.
  4. Delegated auth. In the suite (full profile) the module authenticates browser requests by decoding VF’s Auth.js JWE session cookie with the shared VELTRO_SESSION_SECRET (VF’s image reads it as its native NEXTAUTH_SECRET; HKDF-SHA256 → JWE dir/A256CBC-HS512). CHAD’s backend/app/core/suite_service_token.py + its config.py VELTRO_INTEGRATED flag are the reference implementation — copy that pattern. Standalone mode (VELTRO_INTEGRATED unset, the container default) keeps the module’s own login working unchanged.
  5. Env contract. All module config uses a <MODULE>_* prefix and is wired through veltro’s .env.example (+ the SSO fan-out: the full profile sets VELTRO_INTEGRATED=true and fans VELTRO_SESSION_SECRET to the module, exactly as CHAD is wired).
  6. Version pin. A <MODULE>_VERSION line in versions.env — the only file that moves on a routine upgrade.
  7. Compose wiring. One compose/<module>.yml (service definitions + volumes, shared env deduped with an x-<module>-env anchor) pulled in via one include: line in docker-compose.yml, plus one nginx zone in nginx/veltro.conf. Each block carries the module’s own profile (<module>) and a full-profile single-origin variant (network alias for the nginx upstream, no published port).
  8. 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.
  9. Design system. The frontend consumes @veltrosecurity/{tokens,ui,shell} so rail widths, header, theme sync (shared theme localStorage key), and the AppSwitcher read as one product across zones. Policy: a design-system minor publish (e.g. @veltrosecurity/shell 0.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.

  • 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).

  • 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 existing cases workspace with Warden once Warden ships. Done: Warden shipped and CHAD’s legacy cases surface was deleted (#135; CHAD migration 20260807b_drop_legacy_cases.py).
  • The next implementation plan is “scaffold veltrosecurity/warden against this contract,” not a design exercise — the contract is the spec.
  • CHAD’s cases feature now has a defined future: it becomes a deep-link surface into Warden in the full profile and stays as-is standalone (docs/prd/warden.md).

This checklist applies to any future module. Prowl is withdrawn by ADR-012.

  1. Repo + license — new veltrosecurity/<module> repo, AGPL-3.0, standalone-capable.
  2. Images — docker-build.yml publishing flat ghcr.io/veltrosecurity/<module>-{backend,frontend}, amd64-only on the self-hosted runner.
  3. Delegated auth — decode the Veltro session cookie with VELTRO_SESSION_SECRET, gated on VELTRO_INTEGRATED (copy CHAD’s suite_service_token.py + config flag).
  4. Env contract — <MODULE>_*-prefixed config in veltro’s .env.example; the full profile sets VELTRO_INTEGRATED=true + fans VELTRO_SESSION_SECRET.
  5. Version pin — one <MODULE>_VERSION line in versions.env.
  6. Compose — one compose/<module>.yml + one include: line + one nginx zone (profile <module> + a full single-origin variant).
  7. Suite chrome — register in @veltrosecurity/shell DEFAULT_VELTRO_APPS; pin the current shell minor.
  8. Verify — extend the scripts/verify-sso.sh checks: / routing, delegated login into /<module>/, single logout, and standalone still boots.