ADR-001: Three-context architecture (worlds, platform, core)
Status: Accepted (2026-04-20) — partially superseded by ADR-031 on import-package naming (the three-context topology claim stands) Supersedes: ADR-011 (one bounded context with feature modules)
Context
Spectral 0.3.0 is a greenfield rewrite. The previous codebase (spectral-sef/) treated the whole
system as a single bounded context organized into feature modules — a decision captured in the
prior ADR-011 (retired 2026-04-28; historical context preserved in the addendum below). That
framing served the prior architecture but has aged out:
- Spectral has grown two genuinely distinct domains with different ubiquitous languages. Scanning talks about scans, evaluations, candidate fixes, verdicts, tournaments; its primary abstractions are observations and statistical comparisons. World modeling talks about worlds, mechanics, entities, invariants, simulation rules; its primary abstractions are state spaces and transition systems.
- When both domains live inside one context, the shared vocabulary quietly fractures: the same word gets two meanings (“entity” in worlds vs. “entity” as a generic DB record), and the same meaning gets two words (“Observation” in scanning, “Event” in worlds). That is the textbook signal that the team is carrying two bounded contexts dressed up as one.
- We also want a durable discipline gate between the domains so one context cannot casually import internals from the other. The legacy validator could only enforce layer rules inside a single context; it had no inter-context rule to enforce because the project had no seam between contexts.
Without an explicit decision, the default would be to carry the single-context framing forward and watch the vocabulary problem repeat. The 0.3.0 scaffolding is the right moment to make the split explicit.
Decision
Adopt a three bounded-context topology for the Python monorepo:
spectral.core. Owns substrate transport + cross-cutting plumbing shared across contexts. Admission discipline + the contract surface live in ADR-065.spectral.worlds. Owns the world-modeling bounded context — worlds, mechanics, entities, invariants, and the application + infrastructure layers that serve them.spectral.platform. Owns the customer-facing platform bounded context — workspaces, evaluation frameworks, change sets, scans, optimizations, verdicts, and their application + infrastructure layers.
Naming + layout superseded by ADR-031 (single-library layout under
src/spectral/) and ADR-041 D11 (context renamedscanning→platform). The original ADR-001 usedpackages/<context>/directories andspectral_<context>import-package names; the topology decision (three contexts with isolation between them) carries forward unchanged under the new layout + names.
apps/test-agents/ (under apps/ per ADR-031) hosts subject agents used to exercise the optimization loop; it is not a context and is excluded from default pytest runs.
Inter-context discipline. spectral.worlds and spectral.platform cannot import each other without an ADR explicitly authorising the exception (per ADR-065 D7). The architecture validator (tools/quality/validate_architecture.py) runs with STRICT=True and blocks CI on violations. A self-test in tools/quality/tests/test_validate_architecture.py runs the validator against a fixture tree containing a deliberate import between contexts; the test fails if the validator does not reject it.
Clean Architecture inside each context. The existing domain → application → infrastructure rule continues to apply within each context. The validator enforces both rules simultaneously: inter-context isolation at the package level and layer discipline inside each context.
This ADR explicitly departs from the prior ADR-011 (single context with feature modules). ADR-011’s reasoning — “one ubiquitous language, so these are Evans-style modules, not contexts” — no longer holds for the 0.3.0 architecture: scanning/platform and world modeling have measurably different vocabularies and will diverge further. ADR-001 supersedes that framing in full; ADR-011 has been retired and its historical context lives in the addendum below.
Alternatives considered
Keep the single-context / feature-modules framing from legacy ADR-011 (rejected). Least disruptive option. Rejected because it defers the split rather than making it; the vocabulary problem gets worse as world-modeling grows, and the project pays for the failed abstraction in every cross-module import that really wanted to be an event between contexts.
Collapse everything into a single packages/spectral (rejected). Simplest possible
layout. Rejected for the same vocabulary reason as above, plus it forecloses ever splitting
the contexts later without a large rename.
Split into four or more contexts (rejected for now). Tempting to carve out the inference engine, the agent orchestration layer, or the operations tooling as separate contexts. Rejected because none of those currently have a distinct ubiquitous language from scanning — they are scanning-domain concerns. Revisit if one of them develops its own vocabulary.
Shared library (packages/shared or packages/common) instead of packages/core as
the contract surface (rejected). Any package named “shared” or “common” becomes a dumping
ground. Naming it core and giving it an explicit governance rule (contract-requirement
tests + founder veto) is the discipline mechanism, not the name alone — but the name
reinforces that it is the contract layer, not a grab-bag.
Consequences
- The architecture validator is the primary enforcement mechanism for this decision. Every PR runs it; CI blocks on violations; a self-test fixture guarantees the validator itself stays honest.
spectral.corecarries elevated review weight per ADR-065 admission discipline. Adding an item to the kernel is a doctrine event, not a convenience.spectral.worldsandspectral.platformdo not directly import each other. Coordination between them is mediated by events declared in producer-owned<context>.contracts.events.*per ADR-065 D2 + D4. The two contexts can ship at different cadences and be tested independently.- Apps (
apps/api,apps/workers,apps/dashboard,apps/operations) may import from any context and fromcore; they cannot import each other. Deployment separation is preserved. - Future ADRs build on this one. Any decision that touches contracts between contexts, event shapes, or the
coresurface should link back here.
Historical context — ADR-011 (Spectral is one bounded context organized into feature modules)
ADR-011 was retired and folded here on 2026-04-28 per the doctrine in docs/decisions/overview.md. The Context and Decision sections above describe why the 0.3.0 architecture warrants three contexts; this addendum preserves what a future reader needs to know about the prior framing it replaced.
- ADR-011 (Accepted 2026-04-13) declared Spectral a single bounded context with
workspaces,intelligence,agent,inference, andoperationsas Evans-style feature modules (per Domain-Driven Design chapter 5). The argument: at v0.2.0 scale, Spectral had one ubiquitous language —Workspace,ChangeSet,Sample,EvaluationFramework,Scan,Observation,InterventionLog,Agent,Verdictall meant exactly one thing throughout the codebase. There was no Sales/Support-style dual meaning that bounded contexts exist to manage. Cross-module imports were normal module-to-module coupling within one bounded context, not a doctrine violation. - ADR-011 was load-bearing in clearing up a recurring source of friction: the architecture audit had flagged cross-module imports as smells, and ADR-010 had proposed re-homing repositories (
EvaluationFrameworkRepo,SampleRepository,AutonomySettingsRepo) to resolve them. Pressure-testing the single-context framing against DDD canon revealed the underlying boundary did not exist — the modules shared a ubiquitous language. ADR-011 therefore also superseded ADR-010, and ADR-010 was deleted in the same commit ADR-011 was created. ADR-010 is missing from the index for that reason. - The v0.2.0 framing aged out when
packages/worldsintroduced a genuinely second context — Rules, Provenance tiers, World Agent, EvalSet,WorldModelCard— that did not collapse into Spectral’s scanning vocabulary. The vocabulary problem ADR-011 had argued did not exist started existing for real. ADR-001 (this ADR) is the response. - The Clean Architecture layer rules (
domain → application → infrastructure) that ADR-011 preserved continue to apply unchanged inside each of the three contexts in the 0.3.0 architecture. - The full ADR-011 text is preserved in git history at the commit prior to its retirement.