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:

  1. 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.

  2. 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.

  3. Identity-neutral — JWT/JWKS only (OIDC-standard). No IdP admin API clients. CRAIG maintains its own worker_identities for runtime lookups (ADR-026).

  4. 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.

  5. Don’t reinvent — use OIDC for auth; AMQP for messaging; S3 for blobs; JDM (zen-engine) for decision logic; RFC standards over custom protocols.

  6. 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.

  7. Independent services — each service has its own DB + queue + API. No cross-service direct DB access. Inter-service flow is API + events.

  8. Pre-1.0 destructive migrations are acceptable — until production data exists, expand-contract dances are over-engineering. First-production deployments are per-jurisdiction concerns.

  9. 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.

  • ADR-014 — Pluggable HTTP client (pluggability example)

  • ADR-022 — Async-first foundation

  • ADR-023 — Configuration-as-data applied to authz

  • ADR-026 — Identity-neutral applied

Edit this page · latest