ADR-027: Architectural Principles
On this page
Status
Accepted (2026-05-09). The principles guided every plan in the application-layer
hardening track — Plan A (multi-jurisdictional authz, archived) and Plan E
(service-identity, archived 2026-05-11), and continue to apply to Plans B/C/D.
The forward-referenced gitfs-jurisdiction-config plan remains a future follow-up.
Context
CRAIG’s design choices accumulate into a coherent architectural posture, but until now no single document has named the principles. As the codebase grows (10+ shared crates, 8 services, 4+ planned hardening tracks), future contributors and adopters benefit from explicit principle-naming so reflexes stay consistent.
The architectural ethos draws from SaltStack + OpenStack: pluggable backends, async-first messaging, public APIs, configuration as data, no vendor lock-in. CRAIG inherits the principles without inheriting either system’s well-known scaling pain points (Salt master-as-bottleneck, OpenStack microservice sprawl).
Decision
CRAIG commits to the following architectural principles:
-
Pluggability everywhere — storage (object_store), identity (any OIDC IdP), message bus (AMQP-conformant), rule engine (zen-engine consumed via abstract interface where practical). Don’t bake in implementation choices the deployer should own.
-
Async-first + event-driven — the spine is RabbitMQ + outbox/inbox per ADR-022. Side-effects are events, not synchronous calls. Cache invalidation, audit, replay, and orchestration all flow through the event bus.
-
Identity-neutral — JWT/JWKS only (OIDC-standard). No IdP admin API clients. CRAIG maintains its own
worker_identitiesfor runtime lookups (ADR-026). -
Configuration as data — jurisdiction-specific behavior lives in data (rulesets, policies, admin units, branding) not code. Adopters customize via files + env vars + admin endpoints, not by forking.
-
Don’t reinvent — use OIDC for auth; AMQP for messaging; S3 for blobs; JDM (zen-engine) for decision logic; RFC standards over custom protocols.
-
Public APIs — every service exposes OpenAPI; CLI + Web are clients on top, not privileged paths. CCWIS adopters can integrate with CRAIG’s APIs the same way external partners do.
-
Independent services — each service has its own DB + queue + API. No cross-service direct DB access. Inter-service flow is API + events.
-
Pre-1.0 destructive migrations are acceptable — until production data exists, expand-contract dances are over-engineering. First-production deployments are per-jurisdiction concerns.
-
Maintainability + code quality is king — prefer the proper refactor over fig-leaf seams.
Consequences
-
New contributors have a clear architectural-direction North Star
-
Reflex anti-patterns (reaching for a Keycloak admin client; hardcoding Georgia as default; introducing a custom DSL when a standard exists) get caught at design review
-
The gitfs-jurisdiction-config follow-up plan (filed by Plan A’s Step 14 archive) extends "configuration as data" further: each jurisdiction’s full config (policies, rulesets, branding, locales, admin units) lives in a deployer-owned git repo; CRAIG fetches it at runtime. SaltStack-pillar mental model.