Architecture Decision Records — Index & Process
An ADR is the permanent record of one consequential architecture choice: the problem, the decision, the alternatives that lost, and the consequences — including the incidents that motivated or followed it. This index lists every ADR for the VegaDūta platform in the format of Michael Nygard's Documenting Architecture Decisions.
ADR index
| ADR | Title | Status | Decided | One-line decision |
|---|---|---|---|---|
| ADR-0001 | Modular monolith over microservices | accepted | 2026-07 | One Spring Boot deployable (core/app) assembled from a ~40-module Maven reactor; ports-and-adapters modules, scaled by shape, pool and replica; satellite services only for async, untrusted or other-runtime work (amended 2026-10-15). |
| ADR-0002 | Schema-per-tenant Postgres isolation | accepted | 2026-07 | Tenant data isolated in a per-tenant Postgres schema via Hibernate multi-tenancy; SET search_path per checked-out connection. |
| ADR-0003 | Spring AI dynamic per-request ChatClients | accepted | 2026-07 | All Spring AI provider auto-configurations excluded; ChatClients built per request from decrypted BYOK credentials. |
| ADR-0004 | Container runtime for the original hosted deployment | superseded for self-hosting | 2026-09 | Historical: the hosted instance used a managed container platform plus a VM. This repository ships Compose and Helm; the per-service runtime needs still apply. |
| ADR-0005 | Dual-issuer JWT validation | accepted | 2026-09 | Core validates the issuer Keycloak stamps and accepts extra issuers via KEYCLOAK_ADDITIONAL_ISSUERS. |
| ADR-0006 | Managed Redis for the original hosted deployment | superseded for self-hosting | 2026-09 | Historical: managed Redis on a private network. Self-hosters use the bundled Valkey or any Redis-API service on a private network. |
| ADR-0007 | Frontend public configuration | accepted | 2026-08 | Public frontend settings (NEXT_PUBLIC_*) must never bake a localhost URL into a deployed image; build args are optional and the runtime environment decides. |
| ADR-0008 | Immutable, verified, reversible deploys | accepted | 2026-09 | Pinned versions, core before frontends, verify health, keep the previous version to roll back to. The hosted single-revision pipeline is historical. |
| ADR-0009 | Untrusted code never runs inside core | accepted | 2026-07-27 | Tenant-authored tools and third-party MCP STDIO packages execute in dedicated sandbox services (tool-sandbox, mcp-stdio-runner), never in core. |
| ADR-0010 | Secret injection model | accepted | 2026-09 | Secrets reach services as environment from one source of truth per deployment: deploy/compose/.env or Kubernetes Secrets. |
When to write an ADR
Write one when a decision meets any of these tests:
- Hard to reverse. Data models, tenancy isolation, provider/hoster choice, secret stores — anything whose undo cost is a migration, not a config edit.
- Crosses module or service boundaries. Contracts other modules or satellite services depend on (e.g. "ChatClients are built per request", "secrets resolve at revision creation").
- A real incident taught it. Most ADRs here exist because something broke: the PgBouncer wrong-tenant routing (ADR-0002), the 401-everything issuer mismatch (ADR-0005), the "Cookie not found" domain-cutover outage (ADR-0007). The record exists so the next contributor doesn't have to re-learn it.
- A competent engineer would ask "why is it like this?" If the answer is non-obvious and lives only in someone's memory or a closed chat, it is an ADR.
Do not write an ADR for reversible implementation detail, library minor-version bumps, or anything already fully expressed by code + tests. Ops events belong in runbooks and postmortems; the ADR cites them, it does not replace them.
Lifecycle
| State | Meaning | Badge on this site |
|---|---|---|
| Proposed | Written for review; not yet binding. An ADR should not sit here long — decide it or withdraw it. | proposed |
| Accepted | Binding. Code, deploy files and docs that contradict it are bugs. | accepted |
| Deprecated | No longer holds; kept for history with the reason recorded. | deprecated |
| Superseded | Replaced by a newer ADR, which must be linked in both directions. | superseded |
An accepted ADR is edited only to add dated clarifications or a supersession link — never silently rewritten. If the world changed, write the new ADR and mark the old one superseded; the diff between them is the history.
Format
Each ADR page follows the Nygard structure, extended with two sections this platform found it needs:
- Status line
- Lifecycle badge, decision date, deciders, supersession links.
- Context
- The problem and the forces: team size, cost, operational constraints, the incident at hand.
- Decision
- The choice, stated as a commitment in active voice.
- Alternatives considered
- A table of the options that lost and why — this is what makes the record a decision rather than a description.
- Consequences
- Positive and negative, honestly split — including consequences that only showed up later in real use.
- Evidence & references
- Repository paths and commits the claims can be checked against.
Using the template
There is no separate skeleton file — the newest ADR page is the template. To add one:
- Copy
docs/platform-documentation/decisions/adr-0009-untrusted-code-sandboxing.htmltoadr-NNNN-short-slug.htmlwith the next free number. - Update
<body>'sdata-doc(nextVD-ADR-0NN),data-pageanddata-updated; fill the Status/Context/Decision/ Alternatives/Consequences/Evidence sections. - Add a row to the index table above and a nav entry in
assets/js/site.jsunder thedecisionssection. - Number sequentially — ADR numbers are never reused, even if a record is withdrawn.