← All plugins

Plugin I — Architecture Enforcement

Puritan

Architectural heresy ends here.

Architectural doctrine enforcement through composable lenses. Define your architecture as auditable rules, then let the Inquisition hold the codebase to them — commit by commit, or across the whole sanctum.

/puritan:covenant /puritan:inquisition /puritan:scriptorium

How it works

Three skills. One cycle.

Puritan's three skills work together as a continuous loop — plan, enforce, extend.

/puritan:covenant Decide patterns, generate config
/puritan:inquisition Audit code against doctrines
/puritan:scriptorium Author new or updated doctrines
↑   loops back

The skills

/puritan:covenant

Covenant

Architecture Planning

Covenant helps you choose architectural patterns before you build, or assess what patterns your existing codebase is already using. Discover mode is the fastest way to get started on an existing codebase — it reads directory structure only, infers likely patterns, confirms with you, then writes .architecture/config.yml ready for Inquisition.

Invocation modes

/puritan:covenant Full analysis — pattern recommendations + phased implementation roadmap
/puritan:covenant discover Lightweight scan → detects patterns → generates .architecture/config.yml
/puritan:covenant assess Gap analysis of current architecture against stated patterns
/puritan:covenant roadmap Phased implementation plan (assumes patterns already chosen)
/puritan:covenant risks Architecture risk analysis and mitigation strategies
/puritan:inquisition

Inquisition

Code Audit

Inquisition audits your codebase against the doctrines in .architecture/config.yml. It dispatches parallel subagents per doctrine and collates a structured violation report. Violations are classified as error (blocks commit) or warning (advisory). A large-codebase guard pauses at 100+ files and lets you narrow scope before proceeding.

Invocation modes

/puritan:inquisition Changed files only (git diff against base branch)
/puritan:inquisition full Entire codebase
/puritan:inquisition interactive Full codebase, interactive — fix violations one by one
/puritan:inquisition <doctrine> Changed files, single doctrine only
error Correctness violation — blocks commit
warning Quality concern — advisory
/puritan:scriptorium

Scriptorium

Doctrine Authoring

Scriptorium writes new architecture doctrine files, or updates existing ones. It researches the pattern from authoritative sources, structures the doctrine to the required format, and places it in the doctrines/ directory ready for Inquisition to use. Each doctrine is a markdown file with a structured violation catalog — typically 20–50 rules across 5–8 categories — each with a concrete detection pattern.

Invoke when you want to codify an informal team rule, add a doctrine for a pattern not yet covered, update an existing doctrine, or convert an ADR into doctrine format.

Doctrine catalog

The Articles of Judgment.

Expand any doctrine to see its full rule set — when to use it, why it matters, and the exact patterns Inquisition scans for.

11 doctrines 471 rules
DDD

DDD Pureness

This doctrine audits Domain-Driven Design implementation for isolation, consistency boundaries, and ubiquitous language compliance.

45 rules 10 categories
When to Use
Use DDD when your domain has significant complexity — multiple aggregates with non-trivial business rules, invariants that span state transitions, and terminology that matters to the business. DDD is overkill for CRUD-heavy applications with simple validation, but essential when the cost of getting domain logic wrong is high (financial systems, regulatory compliance, multi-party workflows).
Why Use It
DDD isolates business logic from infrastructure concerns, making the domain model the single source of truth for business rules. This means: - Business rules are testable without databases, HTTP, or message brokers - Domain experts can read the code and validate correctness - Infrastructure can change (swap Postgres for DynamoDB) without touching business logic - New team members understand the business by reading the domain layer
Sources and Authority

Foundational Works:

Aggregate Design Rules:

Value Objects and Entities:

Anti-Patterns:

Modern Practices (2020-2025):

Violation Catalog
ID Category Rule Severity Scan For
DDD-001 layer-boundary Domain must not import from infrastructure error from <pkg>.infrastructure or import <pkg>.infrastructure in domain/ files
DDD-002 layer-boundary Domain must not import from API layer error from <pkg>.api or import <pkg>.api in domain/ files
DDD-003 layer-boundary Domain must not import from application layer error from <pkg>.application or import <pkg>.application in domain/ files
DDD-004 layer-boundary Domain must not import framework libraries error import sqlalchemy, fastapi, celery, redis, aiohttp, httpx, requests in domain/ files
DDD-005 layer-boundary Domain must not perform I/O error session.execute, await fetch, open(, publish(, HTTP calls, DB queries in domain/ files
DDD-006 layer-boundary Application must not import from API layer warning from <pkg>.api in application/ files
DDD-010 aggregate-design Aggregates must extend BaseAggregate error Classes in aggregates/ not inheriting BaseAggregate (excluding base.py)
DDD-011 aggregate-design Aggregates must not hold other aggregates by instance error Aggregate attributes typed as another aggregate class. References by ID (uuid.UUID) are correct
DDD-012 aggregate-design State changes must only happen via events error self.<field> = assignments outside _when_* handlers in aggregate files
DDD-013 aggregate-design Event handlers must be private _when_* methods warning Public methods mutating state from event data without following the _when_<snake_case> naming convention
DDD-014 aggregate-design Aggregates must implement snapshot methods warning Missing to_snapshot() or from_snapshot() on aggregate classes
DDD-015 aggregate-design Aggregate size should be manageable warning Aggregate class >500 LOC (Vernon: 300-400 LOC max), or >20 distinct event types, or >15 command methods
DDD-016 aggregate-design One aggregate per transaction error Service methods modifying multiple aggregates in one transaction (Vernon: consistency boundary rule)
DDD-017 aggregate-design Constructor must establish valid state warning Aggregate __init__ or create() methods with >5 parameters that perform no validation before emitting the creation event
DDD-020 value-object Value objects should use __slots__ warning Classes in value_objects/ without __slots__
DDD-021 value-object Value objects must be immutable error Public setter methods, self.<field> = outside __init__, properties returning mutable references without copying
DDD-022 value-object Equality must be by value warning Value object classes without __eq__ (relying on identity comparison)
DDD-023 value-object Must provide serialization warning Missing to_dict() or from_dict() methods
DDD-024 value-object Arithmetic must return new instances error __add__/__sub__/etc. that mutate self instead of returning type(self)(...)
DDD-025 value-object Immutable VOs should be hashable warning Value objects with __eq__ but missing __hash__ (prevents use in sets/dict keys)
DDD-030 entity Entities must have identity error Classes in entities/ without an *_id attribute
DDD-031 entity Entities should use __slots__ warning Entity classes without __slots__
DDD-032 entity Must provide serialization warning Missing to_dict() or from_dict() (needed for aggregate snapshots)
DDD-033 entity Entity equality must be by identity warning Entity __eq__ comparing attributes instead of just self.<id_field> == other.<id_field>
DDD-040 event-design Events must be immutable error Event classes without model_config = {"frozen": True} or Pydantic frozen inheritance
DDD-041 event-design Events must extend DomainEvent error Event classes in events/ not inheriting DomainEvent
DDD-042 event-design Events should not contain business logic warning Event classes with methods beyond serialization, properties, or model validators
DDD-043 event-design Event names must be past tense warning Event class names not past tense (e.g., CreateLoan instead of LoanCreated)
DDD-044 event-design Events must support schema evolution warning New event fields without default values, or handlers that don't use .get() for optional fields
DDD-045 event-design Events must be dispatched after state change error Aggregate methods that publish directly to an event bus instead of adding to uncommitted events list
DDD-050 command-design Commands must extend BaseCommand error Command classes in commands/ not inheriting BaseCommand
DDD-051 command-design Commands must use imperative naming warning Command names not imperative (e.g., LoanApproved instead of ApproveLoan)
DDD-052 command-design Validation must use Pydantic warning Manual if/raise validation in __init__ instead of Pydantic validators
DDD-053 command-design Commands must not contain business logic warning Commands with methods beyond Pydantic validators that perform domain calculations or call services
DDD-060 domain-service Domain services must be stateless error self.<field> = assignments in methods other than __init__ in domain services/
DDD-061 domain-service Domain services must not perform I/O error Same as DDD-005 scoped to domain services/
DDD-062 domain-service Domain services must not orchestrate warning Domain services that load aggregates from repositories/event stores, manage transactions, or coordinate multi-aggregate workflows (that belongs in application services)
DDD-070 anti-pattern Anemic domain model warning Aggregate with >5 properties but <3 business methods (Fowler: AnemicDomainModel)
DDD-071 anti-pattern Primitive obsession warning Aggregate fields typed as raw str/Decimal/int for domain concepts that warrant value objects (e.g., email addresses, currency amounts not using Money, status codes as raw strings)
DDD-072 anti-pattern Invariant enforcement outside aggregate warning Application/service layer performing business rule validation (if amount > limit: raise) that should live in the aggregate's command method
DDD-073 anti-pattern Missing anti-corruption layer warning Domain layer importing external SDK types or using external system terminology directly without a translation adapter
DDD-080 naming Domain objects should use business language warning Technical jargon in domain names (DBLoan, HttpPayment, CacheAccount, DataManager, EntityProcessor)
DDD-081 naming Avoid CRUD naming in domain warning Methods named create_record, update_row, delete_entry, insert_*, select_*. Prefer domain verbs: submit, approve, disburse, accrue
DDD-082 naming Avoid abbreviations in domain warning Abbreviated names (Mgr, Ctx, Proc, Svc) in domain layer. Full words improve readability
DDD-090 temporal Use utc_today() not date.today() error date.today() or datetime.now() without timezone.utc in domain/ files
CQRS

CQRS

This doctrine audits Command Query Responsibility Segregation implementation, ensuring proper separation of read and write models without unnecessary complexity.

35 rules 7 categories
When to Use
Use CQRS when you have complex domains with different optimization needs for reads vs writes, collaborative systems where many users access the same data, task-based UIs that model business processes, or when read/write loads differ significantly. CQRS is overkill for simple CRUD systems and adds risky complexity to most applications (Fowler warns: "beware that for most systems CQRS adds risky complexity").
Why Use It
CQRS allows you to optimize read and write models independently. Write models focus on business logic and consistency, while read models optimize for query performance and denormalization. This separation enables: - Independent scaling of reads and writes - Different storage technologies (event store for writes, document DB for reads) - Task-based commands that capture intent, not CRUD - Eventually consistent projections optimized for specific views
Sources and Authority

Foundational Works:

Implementation Guidance:

Anti-Patterns and Pitfalls:

Violation Catalog
ID Category Rule Severity Scan For
CQR-200 command-design Commands must be task-based warning Commands named like UpdateEntity, SetField instead of business tasks like ApproveLoan, SubmitOrder
CQR-201 command-design Commands must not return query data error Command handlers returning domain data beyond ID/version (CQS principle)
CQR-202 command-design Commands must be immutable error Command classes with public setters or mutable fields
CQR-203 command-design One command per business operation warning Multiple commands to complete single business transaction
CQR-204 command-design Commands must have clear intent warning Generic commands like ProcessData or HandleRequest
CQR-205 command-design Commands should not contain logic warning Business logic in command classes instead of handlers
CQR-210 query-design Queries must not mutate state error Query handlers that modify database/aggregate state
CQR-211 query-design Queries should use read models warning Queries reconstructing state from event stream instead of projections
CQR-212 query-design Query models should be denormalized warning Read models with multiple JOINs or complex aggregations
CQR-213 query-design Queries must be side-effect free error Query handlers triggering events, sending emails, or calling external APIs
CQR-214 query-design Avoid N+1 queries error Loops fetching related data one-by-one instead of batch loading
CQR-220 separation Write models in read operations error Query handlers accessing aggregates or write model tables
CQR-221 separation Read models accepting commands error Projections/read models with methods that modify state
CQR-222 separation Shared models between read/write warning Same class/table used for both commands and queries
CQR-223 separation Direct aggregate queries error API endpoints querying aggregates instead of projections
CQR-224 separation Write-through cache antipattern error Updating read model synchronously in command handler
CQR-230 consistency Missing eventual consistency handling error No retry/compensation for failed projection updates
CQR-231 consistency Assuming immediate consistency error Commands followed immediately by queries expecting updated data
CQR-232 consistency No consistency boundary warning Transactions spanning multiple aggregates
CQR-233 consistency Projection lag not handled warning UI not accounting for eventual consistency delays
CQR-234 consistency Missing idempotency error Projectors without duplicate event handling
CQR-240 infrastructure Same database for read/write warning Commands and queries using same tables (may be valid for simple cases)
CQR-241 infrastructure No command/query routing error Direct aggregate access instead of command/query bus
CQR-242 infrastructure Missing projection rebuild warning No mechanism to rebuild read models from events
CQR-243 infrastructure Synchronous projections in write path warning Projection updates blocking command completion
CQR-244 infrastructure No monitoring of projection lag warning No metrics on event-to-projection delay
CQR-250 complexity CQRS for simple CRUD error Using CQRS for entities with only create/read/update/delete operations
CQR-251 complexity CQRS applied universally error Entire application using CQRS instead of specific bounded contexts
CQR-252 complexity Over-engineered read models warning Separate read model for each query instead of shared projections
CQR-253 complexity Unnecessary event sourcing warning Using event sourcing just because CQRS is used
CQR-260 anti-pattern Value objects in events error Event classes containing value object instances instead of primitives
CQR-261 anti-pattern Chatty commands warning Multiple commands for what should be one operation
CQR-262 anti-pattern Fat events warning Events containing entire aggregate state instead of deltas
CQR-263 anti-pattern Query-command hybrid error Single endpoint/method doing both query and command
CQR-264 anti-pattern Read model as source of truth error Business logic depending on projection data
EVS

Event Sourcing

This doctrine audits event sourcing implementation for correctness, performance, and maintainability. Grounded in best practices from Greg Young, Martin Fowler, and Microsoft CQRS/ES guidance.

48 rules 8 categories
When to Use
Use event sourcing when you need immutable audit trails, temporal queries (what was the state on date X?), retroactive corrections, or complex business flows that benefit from event-driven architecture. Essential for financial systems, regulatory compliance, multi-step workflows with compensations, and systems where "what happened" is as important as "what is." Not recommended for simple CRUD or high-volume sensor data without aggregation.
Why Use It
Event sourcing provides a complete history of state changes, not just current state. This enables: - Audit trail by default — every change is an immutable event with who/when/why - Time travel — reconstruct state at any point in history - Retroactive fixes — replay events with updated logic to fix past bugs - Event-driven integration — other systems subscribe to domain events - Debugging superpowers — replay exact sequence that led to a bug - Projection flexibility — create new read models without migrating data
Sources and Authority
Violation Catalog
ID Category Rule Severity Scan For
EVS-100 event-design Events must be immutable error Event classes without frozen=True config, or mutable fields, or setter methods
EVS-101 event-design Events need globally unique IDs error Events without unique identifiers (UUID v4/v7, ULID, or sequential per aggregate)
EVS-102 event-design Event names must be past tense warning Event class names not in past tense (e.g., CreateLoan instead of LoanCreated)
EVS-103 event-design Events must extend base event class error Event classes not inheriting from DomainEvent or equivalent base class
EVS-104 event-design Events must auto-register warning Event classes without __init_subclass__ registration or manual registry entry
EVS-105 event-design Events need occurred_at timestamp error Events missing occurred_at field with timezone-aware datetime
EVS-106 event-design Avoid large event payloads warning Event classes with >20 fields or storing full aggregate state instead of deltas
EVS-107 event-design Event cascade loops error Events that directly trigger other events without command mediation (infinite loop risk)
EVS-108 event-design Over-granular events warning Storing every field change as separate event (e.g., NameChanged, EmailChanged vs UserUpdated)
EVS-110 flow Events must be persisted before publishing error Service methods that call bus.publish() before store.append_events()
EVS-111 flow Uncommitted events must be cleared after save error Missing aggregate.clear_uncommitted() after successful append_events()
EVS-112 flow Events published outside transaction boundary warning Event publishing inside database transaction blocks (should be after commit)
EVS-113 flow Missing optimistic concurrency control error EventStore without catching unique violations or version conflicts on append
EVS-114 flow Synchronous projections blocking writes warning Projector handlers called within write transaction (should be async or after)
EVS-120 replay Event handlers must be idempotent error Handlers using +=, .append() without checking for duplicates, or lacking event_id tracking
EVS-121 replay Replay must be deterministic error Event handlers using datetime.now(), random, or external service calls
EVS-122 replay from_events() must not emit new events error from_events() or replay methods that add to uncommitted events list
EVS-123 replay Event handlers must use _when_* naming warning Public methods handling events, or handlers not following _when_<event_name> pattern
EVS-124 replay Missing handler for registered event warning Event type in registry but no corresponding _when_* method in any aggregate
EVS-125 replay Handler mutating state without event error _when_* methods that change state but aggregate method doesn't emit event first
EVS-130 snapshot Snapshots must include last_event_id error to_snapshot() not including last_event_id, or from_snapshot() not restoring it
EVS-131 snapshot Decimal fields must serialize as strings error Snapshot methods using float(decimal_value) instead of str(decimal_value)
EVS-132 snapshot Snapshot load must have fallback error Snapshot loading without try/catch fallback to full event replay
EVS-133 snapshot Snapshots need periodic cleanup warning No cleanup of old snapshots (should keep only N most recent per aggregate)
EVS-134 snapshot Snapshot interval not configured warning No snapshot interval config (Young: every 50-100 events typical)
EVS-135 snapshot from_snapshot() must validate invariants warning from_snapshot() that doesn't validate business rules after reconstruction
EVS-140 projection Projectors must be idempotent error Missing last_event_id check or ON CONFLICT DO NOTHING in projector handlers
EVS-141 projection Projections storing domain logic error Projections with business methods beyond simple getters (logic belongs in aggregates)
EVS-142 projection Missing projection rebuild script warning No script to rebuild projections from event stream
EVS-143 projection Projectors modifying event data error Projector handlers that mutate the event object instead of just reading it
EVS-144 projection Synchronous projector without error handling error Projector in sync path that can crash the write operation on failure
EVS-145 projection Projector accessing external services warning Projectors making HTTP calls or accessing external DBs (couples availability)
EVS-150 store Event store allows event mutation error Event store with UPDATE/DELETE permissions on event table
EVS-151 store Missing event serialization registry error No registry mapping event_type strings to event classes for deserialization
EVS-152 store Events stored without aggregate_type error Event records missing aggregate_type field (needed for filtering)
EVS-153 store No batch loading support warning EventStore without methods to load multiple aggregates in single query
EVS-154 store No event ordering strategy error No way to order events (via timestamp, sequence number, or time-ordered IDs)
EVS-155 store No event versioning strategy warning No version field or schema migration plan for event evolution
EVS-160 evolution Breaking event schema changes error Removing fields from events, renaming without alias, changing field types
EVS-161 evolution New required fields without defaults error Adding non-nullable fields to events without default values
EVS-162 evolution Missing backward-compatible aliases warning Renamed events without keeping old handler name as alias (e.g., _when_payment_received = _when_repayment_received)
EVS-163 evolution Event upcasting not documented warning Schema migrations without documentation of how old events map to new structure
EVS-170 performance Loading aggregates without snapshots warning Services loading aggregates with >100 events but no snapshot support
EVS-171 performance N+1 queries loading aggregates error Service loops calling load_aggregate() instead of using batch loading
EVS-172 performance Unbounded event queries warning Queries fetching all events without pagination or date ranges
EVS-173 performance Missing indexes on event queries warning No indexes on (aggregate_id, event_id), (aggregate_type, occurred_at) pairs
EVS-174 performance Snapshot on every save warning Taking snapshots too frequently (every event) instead of at intervals
EVS-175 performance Event sourcing applied universally error Using ES for entire app (Young: "not a top-level architecture")
HEX

Hexagonal Architecture

Hexagonal Architecture (Ports and Adapters) decouples core business logic from external concerns like databases, UIs, and third-party APIs. By treating the application as a central "inside" surrounded by an "outside," it ensures business rules remain testable in isolation and resilient to infrastructure evolution.

23 rules 5 categories
When to Use
Use Hexagonal Architecture for mid-to-high complexity systems where the business logic is the primary asset and infrastructure (DBs, message brokers, external APIs) is likely to evolve or change. It is ideal for teams practicing TDD and those requiring high degrees of testability without "mocking the world." Do NOT use it for simple CRUD applications, thin wrappers around a single database, or "serverless-first" glue code where the overhead of defining interfaces outweighs the benefit of isolation.
Why Use It
- Infrastructure Independence — Swap a SQL database for a Document store or an HTTP API for a gRPC interface without touching a line of domain logic. - Isolate Testability — Test business use cases in milliseconds by providing "mock" or "in-memory" adapters instead of hitting real databases or networks. - Domain Purity — Protects the core logic from "leaking" framework-specific or vendor-specific code, making the code easier to reason about for domain experts. - Parallel Development — Enables front-end and back-end teams to work against stable "Ports" (interfaces) before the actual "Adapters" (implementations) are built. - Deferred Decisions — Postpone choices about specific database vendors or delivery mechanisms until the domain requirements are fully understood.
Sources and Authority

Foundational Works:

Practitioner Guidance:

Anti-Patterns / Failure Cases:

Violation Catalog
ID Category Rule Severity Scan For
HEX-001 dependency-direction Domain layer must not import Infrastructure error import .*infrastructure pattern in domain/ files
HEX-002 dependency-direction Application layer must not import Infrastructure error import .*infrastructure pattern in application/ files
HEX-003 dependency-direction Domain layer must not import external frameworks error `import (fastapi\
HEX-004 dependency-direction Ports must not import Infrastructure error import .*infrastructure pattern in ports/ files
HEX-005 dependency-direction Core layers must not import test utilities error `import (pytest\
HEX-010 port-design Ports must be defined as Interfaces or Abstract Classes error Files in ports/ lacking abc.ABC, Protocol, or Interface definitions
HEX-011 port-design Application services must only interact with Ports error Class instantiations of infrastructure classes within application/
HEX-012 port-design Ports must use Domain Models or DTOs, never Infra Models error Port method signatures using types from sqlalchemy, pydantic.BaseModel, or ORM entities
HEX-013 port-design Ports must be named after capability, not implementation warning Port class names containing technology-specific words (e.g., Sql, Mongo, Http)
HEX-014 port-design Driven Ports must have at least one implementation error Port definition in ports/ with no corresponding implementation in infrastructure/
HEX-020 adapter-boundary Adapters must reside in the Infrastructure layer error Classes inheriting from Port or Repository found outside infrastructure/
HEX-021 adapter-boundary Adapters must not contain core business logic error Cyclomatic complexity > 10 in infrastructure/ methods
HEX-022 adapter-boundary Infrastructure models must be mapped to Domain models warning Adapter method returning a third-party or ORM object directly to application/
HEX-023 adapter-boundary Adapters must not depend on other Adapters warning import of one sub-module in infrastructure/ (e.g., persistence) by another (e.g., web)
HEX-024 adapter-boundary SQL or NoSQL queries must stay in Adapters error SQL strings, Query Builders, or Mongo selectors found outside infrastructure/
HEX-030 core-purity Domain Entities must not perform IO error Calls to print, logging (to file/network), or socket operations in domain/
HEX-031 core-purity Application services must be orchestration-focused warning Files in application/ with > 400 lines of code
HEX-032 core-purity Domain logic must not leak into Controllers error If/Else logic or calculations found in infrastructure/web/controllers
HEX-033 core-purity Business logic must not be in Ports error Concrete method logic (non-abstract) inside ports/ files
HEX-034 core-purity Use Cases must be atomic units of work warning application/ methods calling more than 3 different Driven Ports
HEX-040 mapping-integrity Domain must not be aware of persistence IDs warning Use of database-generated id fields (auto-increment) as primary keys in domain/ entities
HEX-041 mapping-integrity DTOs must be used for cross-boundary communication error Domain Entities used as request/response bodies in infrastructure/api
HEX-042 mapping-integrity Explicit mappers are required warning Direct attribute copying (a.x = b.x) in application services instead of using a Mapper class/function
MSG

Messaging & Async Communication

This doctrine audits message-driven architecture, ensuring reliable async communication, proper error handling, and correct implementation of messaging patterns.

43 rules 7 categories
When to Use
Use messaging patterns when you need to decouple components, handle async workflows, integrate distributed systems, or scale processing independently. Essential for event-driven architectures, microservices communication, background job processing, and systems requiring resilience to partial failures. Not recommended for simple synchronous request-response where latency is critical or when strong consistency is required immediately.
Why Use It
Messaging enables resilient, scalable architectures by decoupling producers from consumers. This provides: - Temporal decoupling — producers and consumers don't need to be online simultaneously - Load leveling — queues absorb traffic spikes, protecting downstream services - Scalability — add/remove consumers independently based on load - Resilience — messages survive crashes, network partitions heal automatically - Integration — connect heterogeneous systems via common message formats
Sources and Authority

Foundational Works:

Anti-Patterns & Pitfalls:

Broker-Specific Guidance:

Delivery Guarantees:

Violation Catalog
ID Category Rule Severity Scan For
MSG-001 message-design Messages must be immutable error Message classes with setters, mutable fields, or dataclass without frozen=True
MSG-002 message-design Messages need unique identifiers error Messages without ID field (UUID, ULID, or correlation_id)
MSG-003 message-design Message size must be bounded warning Message classes with unbounded collections or >100KB typical payload
MSG-004 message-design Avoid sensitive data in messages error Messages containing passwords, tokens, SSN, credit cards (scan for field names/patterns)
MSG-005 message-design Messages must be versioned warning Message classes without version field or schema registry
MSG-006 message-design Use correlation IDs for tracing warning Messages without correlation_id for distributed tracing
MSG-007 message-design Avoid entity-based events warning Events named like UserUpdated instead of business events like UserRegistered, EmailChanged
MSG-010 delivery Handlers must be idempotent error Message handlers without idempotency checks (no dedup by message_id)
MSG-011 delivery Missing acknowledgement handling error Consumers not calling ack/nack explicitly (auto_ack=True in production)
MSG-012 delivery No retry configuration warning Publishers/consumers without retry logic or exponential backoff
MSG-013 delivery Missing dead letter queue warning Queue declarations without DLQ configuration for failed messages
MSG-014 delivery Unbounded retries error Retry loops without max attempts or without backing off
MSG-015 delivery No timeout configuration warning RPC-style messaging without timeout (can block forever)
MSG-016 delivery Guaranteed delivery not configured warning Critical messages without persistence (delivery_mode=2 in RabbitMQ)
MSG-020 error-handling No poison message handling error Consumers without try/catch around processing or DLQ for failures
MSG-021 error-handling Swallowing exceptions error Catch blocks that don't log/rethrow/nack messages
MSG-022 error-handling Missing circuit breaker warning Consumers without circuit breaker for downstream service calls
MSG-023 error-handling No compensation logic warning Saga participants without compensating transactions
MSG-024 error-handling Synchronous error propagation error Publishing error messages synchronously in catch blocks (blocks recovery)
MSG-025 error-handling Missing monitoring/alerting warning No metrics for queue depth, consumer lag, or failure rates
MSG-030 ordering Assuming message order error Logic that depends on message order without explicit sequencing
MSG-031 ordering Race conditions in saga error Saga steps that can execute out of order without guards
MSG-032 ordering Missing version checks warning Updates without optimistic concurrency control
MSG-033 ordering Competing consumers issues warning Shared state modified by multiple consumers without locking
MSG-034 ordering No sequence numbers warning Related messages without sequence/version for ordering
MSG-040 infrastructure Hardcoded broker URLs error Connection strings in code instead of environment variables
MSG-041 infrastructure No connection pooling warning Creating new connections per message instead of reusing
MSG-042 infrastructure Missing heartbeat config warning Long-running consumers without heartbeat configuration
MSG-043 infrastructure No graceful shutdown error Consumers without signal handlers for clean shutdown
MSG-044 infrastructure Synchronous publishing in transaction warning Publishing messages inside database transactions (2PC issues)
MSG-045 infrastructure No outbox pattern warning Direct publishing without outbox table (dual write problem)
MSG-046 infrastructure Queue/topic name collisions error Hardcoded queue names without environment/tenant prefixes
MSG-050 patterns Sync over async antipattern error Blocking waiting for async response (request-reply with timeout)
MSG-051 patterns Chatty messaging warning Multiple messages for single logical operation
MSG-052 patterns Fat messages warning Messages >1MB or containing full entities instead of IDs
MSG-053 patterns Missing saga orchestrator error Distributed transactions without coordinator
MSG-054 patterns Two-phase commit over messaging error XA transactions spanning message broker and database
MSG-055 patterns Temporal coupling warning Expecting immediate response from async operations
MSG-060 performance No batching warning Publishing/consuming one message at a time in loops
MSG-061 performance Prefetch not configured warning Consumers without prefetch limit (can overwhelm worker)
MSG-062 performance No async I/O warning Blocking I/O in message handlers (use async/await or threads)
MSG-063 performance Unbounded queues warning No max length or TTL on queues (memory issues)
MSG-064 performance No consumer scaling warning Fixed number of consumers regardless of queue depth
SAG

Saga Pattern

This doctrine audits saga implementations for distributed transactions, ensuring proper compensation logic, isolation handling, and coordination patterns in microservices architectures.

44 rules 8 categories
When to Use
Use the saga pattern when you need distributed transactions across multiple services without using two-phase commit (2PC). Essential for maintaining data consistency in microservices where each service owns its database, long-running business processes that span multiple bounded contexts, and workflows requiring compensating actions on failure. The saga pattern is an anti-pattern if used too frequently — it often indicates services organized around entities instead of business capabilities.
Why Use It
Sagas enable distributed transactions without the scalability issues of 2PC. This provides: - Eventual consistency across service boundaries without distributed locks - Failure recovery via compensating transactions - Service autonomy — each service commits its local transaction independently - Observable progress — each step is visible for monitoring/debugging - Partial completion handling — business logic can handle partially completed workflows
Sources and Authority

Foundational Works:

Implementation Approaches:

Anti-Patterns & Limitations:

Practical Guides:

Violation Catalog
ID Category Rule Severity Scan For
SAG-001 design Saga overuse indicates design smell warning >30% of use cases require sagas (services organized by entities)
SAG-002 design Missing saga definition error Distributed transactions without explicit saga coordinator or choreography
SAG-003 design Mixing orchestration and choreography error Same saga using both patterns (events AND commands from orchestrator)
SAG-004 design Saga for technical errors error Compensating transactions triggered by network/timeout errors
SAG-005 design No saga state machine warning Saga without explicit state transitions (PENDING→PROCESSING→COMPLETED/FAILED)
SAG-006 design Unbounded saga duration warning Sagas without timeout or maximum duration
SAG-007 design Nested sagas error Saga triggering another saga (composition complexity)
SAG-010 compensation Missing compensating transaction error Saga steps without corresponding compensation logic
SAG-011 compensation Non-idempotent compensation error Compensating transactions without idempotency checks
SAG-012 compensation Compensation throws exceptions error Compensating transactions that can fail without retry
SAG-013 compensation Compensation order incorrect error Compensations not executed in reverse order
SAG-014 compensation No compensation timeout warning Compensating transactions without timeout handling
SAG-015 compensation Incomplete compensation error Compensation doesn't fully undo the forward action
SAG-016 compensation Compensation side effects warning Compensating transactions triggering new business logic
SAG-020 isolation No isolation strategy error Concurrent sagas modifying same entities without countermeasures
SAG-021 isolation Missing semantic locks warning No application-level locks for business resources
SAG-022 isolation No dirty read prevention warning Reading uncommitted saga changes without versioning
SAG-023 isolation No saga correlation error Cannot track which changes belong to which saga
SAG-024 isolation Race condition in compensation error Compensation can race with normal operations
SAG-025 isolation No version checks warning Updates without checking if data changed during saga
SAG-030 orchestration Single point of failure warning Orchestrator without high availability setup
SAG-031 orchestration Orchestrator contains business logic error Business rules in orchestrator instead of services
SAG-032 orchestration No orchestrator persistence error Orchestrator state only in memory
SAG-033 orchestration Synchronous orchestration error Orchestrator making blocking calls to services
SAG-034 orchestration No orchestrator monitoring warning No metrics/alerting for orchestrator health
SAG-035 orchestration Orchestrator versioning missing warning No strategy for upgrading running sagas
SAG-040 choreography Cyclic dependencies error Services consuming each other's events in cycles
SAG-041 choreography No event correlation error Events missing saga_id/correlation_id
SAG-042 choreography Implicit flow warning Saga flow not documented/discoverable
SAG-043 choreography Complex choreography warning >4 services in choreographed saga
SAG-044 choreography Missing event handlers error Service not handling expected saga events
SAG-045 choreography Event ordering assumptions error Choreography assuming event order without guarantees
SAG-050 state No saga persistence error Saga state not persisted to durable storage
SAG-051 state State machine violations error Invalid state transitions (COMPLETED→PROCESSING)
SAG-052 state Lost saga instances error No mechanism to recover in-flight sagas after crash
SAG-053 state No saga history warning Cannot reconstruct what happened in saga
SAG-054 state Saga state in multiple places error State split across orchestrator and services
SAG-055 state No idempotency tokens error Saga steps without idempotency tokens
SAG-060 testing No integration tests error Saga without end-to-end tests
SAG-061 testing No failure scenario tests error Tests don't cover compensation paths
SAG-062 testing No concurrent saga tests warning Tests don't verify isolation/conflicts
SAG-063 operations No saga observability error Cannot trace saga execution across services
SAG-064 operations No manual intervention warning No way to manually complete/compensate stuck sagas
SAG-065 operations No saga metrics warning No monitoring of saga success/failure rates
BACK

Backend For Frontend (BFF) Architecture

The BFF doctrine enforces the creation of specialized backend layers for specific user interfaces. It ensures that core domain services remain clean and "client-agnostic" while providing a tailored, high-performance experience for diverse clients (e.g., Mobile, Web, IoT).

50 rules 50 categories
When to Use
Use the BFF pattern when your application supports multiple diverse client types that have significantly different data requirements, display constraints, or security profiles. It is essential when a mobile client would otherwise need to make dozens of "chatty" calls to a general-purpose API, or when a Web UI requires a different authentication flow than a Mobile app. Do NOT use this pattern if you have only one client type or if your clients have identical data requirements. In those cases, a single "General API" is more cost-effective.
Why Use It
* Performance (Aggregation) — A BFF aggregates data from multiple downstream services into a single response, reducing mobile latency. * UI-Specific Optimization — Format and filter data specifically for the client's display needs (e.g., removing heavy fields for smartwatch UIs). * Decoupled Evolution — Frontend teams can change their BFF's API contract without waiting for core domain teams to update general services. * Security Specialization — Handle different security protocols (e.g., cookie-based sessions for Web, JWT for Mobile) within isolated layers.
Sources and Authority
Violation Catalog
ID Category Rule Severity Scan For
BFF-001 logic-leak Domain business logic must not reside in the BFF error Calculation or validation logic in bff/ that isn't purely about formatting
BFF-002 persistence-leak A BFF must not have its own database or direct DB access error Import of ORMs, SQL clients, or database drivers inside bff/
BFF-003 cross-bff-dependency BFFs must not depend on or call other BFFs error import <pkg>.bff.mobile found in bff/web/
BFF-004 direct-core-bypass Clients must not bypass the BFF to call Core Services directly warning Frontend code containing direct URLs to services/core/
BFF-005 shared-bff A single BFF must not serve multiple diverse client types warning One BFF handling both Mobile and Web if payloads are >30% different
BFF-021 model-coupling BFFs must not use Core Service DTOs as their own API response error bff/*/api/ methods returning classes defined in services/core/
BFF-022 missing-mapper Every BFF endpoint should use a dedicated mapper warning Controllers in bff/ performing inline data transformation
BFF-023 polymorphic-leak BFF should hide internal type hierarchies from the UI warning Returning internal class type names or discriminator fields in JSON
BFF-024 invalid-date-format BFF must standardize dates to a client-preferred format warning Returning raw DB timestamps instead of ISO-8601 or localized strings
BFF-025 enum-leak Do not expose internal service Enums directly to the UI warning Mapping core Enums 1:1 without a stable BFF-specific Enum
BFF-006 synchronous-bottleneck Downstream calls should be executed in parallel where possible warning Sequential await statements for independent data sources
BFF-007 missing-aggregation BFFs should provide aggregated endpoints for complex UI views warning UI making multiple BFF calls to load one screen instead of one composite call
BFF-008 payload-bloat BFF responses must only contain fields required by the UI error BFF returning 1:1 copies of Core Service entities without filtering
BFF-009 excessive-hop-count BFF endpoints should not call more than 10 downstream services warning A single BFF request triggering > 10 internal network calls
BFF-010 missing-pagination BFF must forward or implement pagination for large lists error Endpoints returning arrays without limit, offset, or cursor
BFF-035 thread-leak Async operations must use managed thread pools error Usage of new Thread() or unmanaged CompletableFuture in BFF logic
BFF-036 connection-monopoly Limit the number of concurrent calls to a single Core service warning Lack of bulkhead configuration for high-traffic downstream services
BFF-037 unconstrained-buffers Limit the size of incoming request bodies in the BFF error Missing max-payload-size configuration in BFF settings
BFF-038 event-loop-block BFF must not perform synchronous IO on the main event loop error fs.readFileSync or similar blocking calls in Node.js/Netty BFFs
BFF-011 secret-exposure Downstream API keys/secrets must not be exposed to the UI error BFF responses containing raw internal tokens or service-to-service keys
BFF-012 token-passing BFF must translate client auth to internal service auth error Passing UI cookies/session-ids directly to downstream domain services
BFF-013 missing-cors-policy Every BFF must have a strict, client-specific CORS policy error Usage of Access-Control-Allow-Origin: * in any bff/ configuration
BFF-014 plaintext-transmission PII or sensitive data must be encrypted if the client is public warning Lack of field-level encryption for sensitive fields in the mappers/ layer
BFF-015 inadequate-sanitization BFF must sanitize all inputs before forwarding to Core error Direct forwarding of raw UI request bodies to downstream PUT/POST calls
BFF-046 cross-region-leak BFF should live in the same region as the client's data warning A US-based BFF calling EU-based core services for EU users
BFF-047 excessive-pii-handling BFF should only "see" PII it absolutely needs for display warning Mapping logic that touches PII fields not used in the final response
BFF-048 missing-consent-check BFF must respect user data consent flags error Returning "Marketing" data through the BFF when the consent_flag is false
BFF-049 unmasked-id Mask or obfuscate internal database IDs in public responses warning Returning raw integer Auto-increment IDs instead of HashIDs or UUIDs
BFF-050 static-client-coupling Avoid hard-coupling BFF DTOs to specific UI components warning Naming BFF DTO fields after UI elements (e.g., SubmitButtonLabel)
BFF-016 missing-circuit-breaker Downstream calls must use circuit breakers error Client calls in bff/*/clients/ missing a resilience wrapper
BFF-017 missing-timeout Every downstream call must have a strict timeout (< 2s) error Client instantiations without an explicit readTimeout
BFF-018 missing-fallback BFF endpoints must define a fallback for failed downstream calls warning Lack of "graceful degradation" (e.g., returning cached or empty data)
BFF-019 retry-storm BFF retries must use exponential backoff and jitter error Simple loop retries found in bff/ client logic
BFF-020 bulkhead-isolation Use bulkheads to prevent one downstream service from killing the BFF warning Lack of dedicated thread pools or semaphores per downstream client
BFF-026 stateful-bff BFFs should be stateless to allow horizontal scaling error Usage of local file system or in-memory sessions for user data
BFF-027 missing-header-forwarding Cache-Control headers from Core should be respected/forwarded warning BFF ignoring downstream ETag or Cache-Control headers
BFF-028 over-caching Sensitive user data must not be cached at the BFF level error Caching responses containing PII without a user-specific cache key
BFF-029 distributed-cache-leak Avoid sharing a single cache instance between different BFFs warning bff/web and bff/mobile using the same Redis namespace/prefix
BFF-030 trace-interruption BFFs must propagate Correlation IDs error Downstream client calls that do not forward the X-Correlation-ID
BFF-031 opaque-error-mapping Translate downstream errors into UI-friendly codes warning Passing raw 500 or 403 errors from Core directly to the UI
BFF-032 missing-golden-signals BFFs must expose Rate, Error, and Duration metrics error Absence of Prometheus or OpenTelemetry metrics for BFF endpoints
BFF-033 logs-in-production Avoid logging PII or raw payloads in production logs error Log statements in bff/ printing request.body or response.data
BFF-034 slow-mapper Mapping logic must not exceed 50ms per request warning Complex recursive mappers in bff/*/mappers/ causing latency spikes
BFF-039 version-mismatch BFF should be deployable independently of Core Services warning Build scripts requiring a synchronized deploy of bff/ and services/core/
BFF-040 orphaned-bff BFF must be owned by the UI team, not the Core team warning Code review requirements for bff/ not including Frontend developers
BFF-041 hardcoded-core-urls Core service URLs must be injected via environment/discovery error Hardcoded http://core-service:8080 strings in BFF client code
BFF-042 missing-contract-tests BFF must have contract tests against the Core API error Absence of Pact or Consumer Driven Contract tests in bff/*/tests/
BFF-043 legacy-bloat Remove BFF endpoints that are no longer used by the UI warning Endpoints in bff/ with zero recorded traffic in the last 30 days
BFF-044 monolithic-bff-structure Every client must have a physically isolated BFF directory error bff/ containing all client logic without web/, mobile/ sub-folders
BFF-045 bypass-validation BFF must validate UI inputs before hitting the Core network error Lack of validation annotations on BFF API DTOs
LAYE

Layered (N-Tier) Architecture

The Layered (N-Tier) Architecture doctrine enforces strict horizontal separation of concerns. It ensures that changes in low-level details (like databases) do not leak into high-level business logic, and that user interface concerns remain isolated from data persistence.

38 rules 38 categories
When to Use
Layered architecture is the standard choice for small to medium-sized applications where the primary goal is a clean, predictable structure that a team can understand quickly. It is ideal for CRUD-heavy applications and projects where the domain complexity does not yet justify the overhead of ddd.md or hexagonal.md. Do NOT use this pattern if the application requires extreme scalability (use microservices.md), has highly complex lifecycle-based domain logic (use ddd.md), or requires multiple diverse delivery mechanisms for the same logic (use hexagonal.md).
Why Use It
* Simplicity and Familiarity — It is the most widely understood pattern in software engineering, lowering the barrier for new contributors. * Separation of Concerns — Each layer has a defined responsibility, making it easier to locate bugs and implement features. * Maintainability — Changes to the UI or Database technology are isolated to their respective layers. * Testability — Business logic can be tested in isolation by mocking the persistence layer.
Sources and Authority

Foundational Works:

Practitioner Guidance:

Anti-Patterns / Failure Cases:

Violation Catalog
ID Category Rule Severity Scan For
LNT-001 dependency-direction Lower layers must not depend on higher layers error import <pkg>.presentation in business/ or persistence/
LNT-002 dependency-skip Layers should not skip immediate neighbors warning import <pkg>.persistence directly in presentation/ bypassing business/
LNT-003 circular-dependency Bi-directional dependencies between layers are forbidden error Circular import paths between business/ and persistence/
LNT-004 external-leak Third-party UI or DB libraries must not leak into business logic error import of web frameworks (e.g., Express, Spring Web) or ORMs in business/
LNT-005 direct-instantiation Layers must not instantiate their own dependencies warning Usage of new Service() or new Repository() instead of Dependency Injection
LNT-006 leaky-abstraction Persistence-specific exceptions must not reach the presentation layer error catch blocks in presentation/ handling SQLException or ORM-specific errors
LNT-007 model-bleeding Database entities must not be used as API responses warning Method signatures in presentation/ controllers returning classes defined in persistence/
LNT-008 logic-placement Business logic must not reside in the presentation layer error presentation/ files containing complex conditionals, math, or data transformation > 15 lines
LNT-009 logic-displacement Business logic must not reside in the persistence layer error persistence/ classes containing non-query logic (e.g., tax calculation)
LNT-010 direct-db-access UI must not execute raw SQL or DB commands error SQL strings or DB client calls (e.g., db.query()) inside presentation/
LNT-011 excessive-exposure Internal service methods must be private or protected warning Public methods in business/ that are not called by presentation/
LNT-012 missing-abstraction Business layer should access persistence via interfaces warning business/ classes instantiating concrete persistence/ classes instead of using DI
LNT-013 contract-bypass Business methods must be used instead of direct data manipulation error presentation/ modifying objects and calling persistence.save() directly
LNT-014 service-bloat Business services should not exceed complexity thresholds warning Service classes in business/ with > 10 public methods or > 500 lines of code
LNT-015 utility-misplacement Utilities in common/ must be logic-free warning common/ files containing domain-specific rules or database access
LNT-016 global-state Layers must not communicate via global shared variables error Use of global or static variables to pass data between layers
LNT-017 connection-leak Persistence layer must manage its own connection lifecycle error Connection objects (e.g., SqlConnection) passed as arguments into business/
LNT-018 transaction-leak Transaction boundaries should be managed in the Business layer warning commit() or rollback() calls appearing inside presentation/ or persistence/
LNT-019 session-bleeding HTTP session objects must not be passed to the business layer error HttpServletRequest or Session objects found in business/ method signatures
LNT-020 sinkhole-pattern Avoid services that only delegate to the layer below warning Methods in business/ that consist of a single return call to persistence/
LNT-021 fat-controller Controllers must delegate to business services error presentation/ controllers exceeding 3 injected dependencies or 200 lines
LNT-022 smart-ui UI components must not contain data validation logic warning UI-tier code performing complex domain validation instead of calling business/
LNT-023 anemic-domain Ensure Business layer contains logic, not just getters/setters warning business/ folder consisting entirely of DTOs with no logic-bearing services
LNT-024 lasagne-architecture Do not create unnecessary sub-layers warning A single request path crossing > 5 layer boundaries
LNT-025 silent-failure Layers must not swallow exceptions without logging or rethrowing error Empty catch blocks or catch blocks that only log without re-escalation
LNT-026 generic-exceptions Layers must throw specific custom exceptions warning Usage of throw new Exception() or throw new RuntimeException()
LNT-027 validation-bypass Persistence must not be called without going through validation error Call sites for persistence/ methods found outside of business/
LNT-028 unit-test-isolation Business layer unit tests must use mocks for persistence error Unit tests in business/ that attempt to connect to a real database
LNT-029 missing-layer-tests Each layer should have a corresponding test suite warning A layer directory (e.g., persistence/) with 0 matching files in tests/
LNT-030 integration-test-scope Integration tests must span at least two layers warning Tests labeled "integration" that only exercise a single isolated class
LNT-031 hardcoded-config Configuration values must not be hardcoded in layers error Hardcoded DB URLs, API keys, or environment-specific flags in business/
LNT-032 circular-init Service initialization must not contain circular dependencies error constructor calls that eventually lead back to the same class during startup
LNT-033 side-effect-ctor Constructors must not perform heavy IO or logic warning constructor methods containing database queries or network calls
LNT-034 static-dependency Avoid static method calls for cross-layer logic warning business/ calling static methods in persistence/ (hinders mockability)
LNT-035 improper-common-dep Common layer must not depend on any other layer error import <pkg>.business or import <pkg>.persistence in common/
LNT-036 race-condition Business services should be stateless to ensure thread safety error Non-final instance variables in business/ services that are modified after init
LNT-037 missing-optimistic-lock Updates must handle concurrent modifications warning persistence/ update methods that do not use a version column or timestamp
LNT-038 async-leak Background tasks must not outlive the request scope in presentation warning async task creation or Thread / ExecutorService usage in presentation/ that stores references in objects with a lifecycle longer than the current request
MICR

Microservices Architecture

The Microservices Architecture doctrine enforces the decentralization of data, logic, and deployments. It ensures that services remain loosely coupled and independently scalable, preventing the formation of a "distributed monolith" where changes in one service require synchronized deployments across the entire fleet.

45 rules 45 categories
When to Use
Microservices should be used for large-scale, complex systems where multiple independent teams need to deliver features at different velocities. It is appropriate when different parts of an application have vastly different scaling requirements (e.g., a high-traffic ingest service vs. a low-traffic reporting service). Do NOT use this pattern for small teams (under 3-4 teams), early-stage startups searching for product-market fit, or applications where the overhead of network latency and distributed consistency outweighs the benefits of independent scaling. For these cases, refer to the modular-monolith.md doctrine.
Why Use It
* Independent Deployability — Services can be updated and deployed without affecting the rest of the system, enabling high-velocity CI/CD. * Technological Freedom — Teams can choose the best stack (language, database, framework) for their specific service's requirements. * Fault Isolation — A failure in one service (e.g., a memory leak or crash) can be contained, preventing a total system outage. * Granular Scalability — Resources can be allocated precisely to the services that need them most, optimizing cloud infrastructure costs.
Sources and Authority
Violation Catalog
ID Category Rule Severity Scan For
MCR-001 data-sovereignty Services must not share a database schema error Multiple services in services/ connecting to the same DB schema/catalog
MCR-002 direct-domain-access Services must not import domain logic from other services error import <pkg>.services.serviceA.domain in services/serviceB/
MCR-003 shared-state Services must not share mutable global state error Hardcoded shared keys or global caches accessed by multiple service roots
MCR-004 circular-dependency Circular synchronous dependencies between services are forbidden error Service A calls B via API, and Service B calls A via API
MCR-005 leaky-persistence Internal DB primary keys must not be exposed in public APIs warning Entity ID fields (UUID/Serial) from data/ used directly in api/ responses
MCR-026 distributed-transaction Do not use cross-service atomic locks or 2PC error Usage of JTA, XA transactions, or cross-service mutexes
MCR-027 missing-compensation Async operations must have compensation actions error Event listeners without a failure-handling/rollback path
MCR-028 ghost-writes Ensure idempotency for all event consumers error Event handlers that perform writes without checking duplicate message IDs
MCR-029 dual-write Do not write to DB and Broker in one local transaction warning Logic calling db.save() and broker.publish() sequentially
MCR-030 outbox-bypass Use Outbox pattern for guaranteed message delivery warning Direct publishing to broker from business logic instead of Outbox table
MCR-006 synchronous-chaining Avoid deep chains of synchronous HTTP/gRPC calls warning Service call depth > 3 in a single request trace (e.g., A -> B -> C -> D)
MCR-007 missing-contract API changes must be governed by explicit versioning error Breaking changes to api/ definitions without a version increment
MCR-008 hardcoded-endpoints Service locations must not be hardcoded error IP addresses or static DNS names for other services in configuration files
MCR-009 client-library-leak Service client libraries must not leak internal models warning Client SDKs in shared-libraries/ exposing internal DB entities
MCR-010 untyped-payloads Inter-service communication must use structured schemas error Use of JSON.parse or generic Maps for API payloads without schema validation
MCR-011 logic-in-shared Business logic must not reside in shared libraries error shared-libraries/ containing domain-specific validation or logic
MCR-012 shared-lib-bloat Shared libraries must be versioned and kept lean warning shared-libraries/ exceeding 1000 LOC or including heavy dependencies
MCR-013 version-lock Services must not be forced into a single global library version warning Root-level build files enforcing one version for all services/
MCR-014 transitive-leak Shared libraries must not expose transitive deps to services error Services relying on a library provided implicitly by a shared parent
MCR-015 binary-coupling Avoid sharing compiled DTOs across services warning Services sharing a .jar or .dll containing data transfer objects
MCR-016 missing-timeout Every inter-service call must have an explicit timeout error HTTP/gRPC client instantiations without a defined timeout duration
MCR-017 missing-circuit-breaker External service calls must be wrapped in circuit breakers error Inter-service calls lacking a circuit-breaker implementation
MCR-018 chatty-api Avoid fine-grained calls where one aggregate call suffices warning Loop constructs making repeated API calls to another service
MCR-019 blocking-io Prefer async communication for non-query operations warning Synchronous POST/PUT calls where an event-driven approach is viable
MCR-020 retry-storm Retries must implement exponential backoff and jitter error Retry logic with constant intervals (e.g., retry(3, 1000ms))
MCR-021 missing-trace-id Correlation IDs must be propagated through all calls error API handlers in services/*/api/ not forwarding X-Correlation-ID
MCR-022 inconsistent-logging Services must use a standardized structured logging format warning Log statements not using the approved JSON schema
MCR-023 missing-health-check Every service must expose a /health or /ready endpoint error Absence of standard health check route in the service's API
MCR-024 opaque-failures Services must return standard error codes (RFC 7807) warning Non-standard or generic 500 errors without diagnostic context
MCR-025 missing-metrics Services must expose standard golden signals (Rate/Errors/Dur) error Lack of Prometheus/Metrics endpoints in service initialization
MCR-031 secrets-in-code Secrets must never be stored in source code error Presence of API_KEY or SECRET strings in services/ files
MCR-032 environment-pollution Services must not rely on host-specific env vars error Hardcoded references to local machine paths or developer env vars
MCR-033 config-drift Configurations must be versioned alongside code error Services relying on unversioned external config stores (e.g., raw Etcd keys)
MCR-034 missing-default-config Every service must provide a safe local config warning Service failure to start without a connection to a remote config server
MCR-035 manual-deployment Services must have an automated CI/CD pipeline error Absence of Jenkinsfile, .github/workflows, or gitlab-ci.yml
MCR-036 static-scaling Services should not have hardcoded instance counts warning Deployment manifests with fixed replicas: X instead of HPA
MCR-037 stateful-service Services should be stateless for horizontal scaling error Usage of local file system or in-memory sessions for persistence
MCR-038 unconstrained-resources Manifests must define CPU/Memory limits error Manifests without resources.limits or resources.requests
MCR-039 hardcoded-image-tags Deployment manifests must not use latest tags error Usage of image: my-service:latest in deployment files
MCR-040 sidecar-bypass Services must use the designated service mesh for egress warning Direct socket calls bypassing the local mesh proxy (if configured)
MCR-041 missing-contract-tests Inter-service APIs must be covered by contract tests error Lack of Pact or Spring Cloud Contract files in services/*/tests/
MCR-042 slow-tests Service unit tests must not exceed 5-minute execution warning Local test suites taking > 300s to complete
MCR-043 fragile-integration Integration tests should not depend on "live" dependencies error Tests in services/ requiring a connection to a real Production/Staging DB
MCR-044 missing-load-test High-traffic services must have defined load test scripts warning Absence of k6, Gatling, or JMeter scripts for ingest services
MCR-045 shadow-deployment-lacking Major API changes must support canary or shadow traffic warning Lack of feature flags or traffic routing rules for new major versions
MODU

Modular Monolith Architecture

The Modular Monolith doctrine enforces strict logical isolation within a single deployment unit. It aims to provide the organizational benefits of microservices (team autonomy, clear boundaries) without the "distributed systems tax" of network latency and extreme operational complexity.

50 rules 50 categories
When to Use
This pattern is the "Goldilocks" choice for systems with high domain complexity but moderate scale. Use it when the team is large enough to require independent workstreams but the infrastructure budget does not yet justify a fleet of microservices. It is the ideal "Starting Point" to prevent a codebase from becoming a "Big Ball of Mud." Do NOT use this pattern if different modules require radically different execution environments or if the application requires "Scale-to-Zero" capabilities for specific sub-components (use microservices.md).
Why Use It
* Logical Decoupling — Clearly defined boundaries allow developers to reason about a single business capability at a time. * Refactoring Safety — Since all modules reside in one process, the compiler/IDE can verify boundary integrity and type safety. * Operational Simplicity — Single database, single deployment pipeline, and unified monitoring. * Strategic Flexibility — A well-implemented modular monolith can be easily decomposed into microservices later because the boundaries are already enforced.
Sources and Authority
Violation Catalog
ID Category Rule Severity Scan For
MOM-001 internal-leak Modules must not import from another module's internal folder error import <pkg>.modules.moduleA.internal in modules/moduleB/
MOM-002 api-bypass All inter-module calls must go through the designated API package error import of any non-API class or interface from a sibling module
MOM-003 circular-module-dep Circular dependencies between functional modules are forbidden error Module A depends on Module B, and Module B depends on Module A
MOM-004 shared-domain-leak Modules must not share internal domain entities or value objects warning modules/moduleA/internal using a domain class defined in modules/moduleB/internal
MOM-005 deep-nesting Functional modules should not be nested within other modules warning Directory structure exceeding modules/<name>/<layer> (e.g., modules/a/b/c)
MOM-006 cross-module-join Modules must not perform SQL joins across module boundaries error SQL queries in modules/moduleA joining tables owned by modules/moduleB
MOM-007 shared-table No database table should be modified by more than one module error Write/Update operations on table_x occurring in multiple module directories
MOM-008 foreign-key-leak Hard foreign keys across module boundaries should be avoided warning Database schema in moduleA defining a FOREIGN KEY to a table in moduleB
MOM-009 direct-repo-access A module must not use another module's Persistence/DAO layer error modules/moduleA instantiating or injecting a Repository from modules/moduleB
MOM-010 transaction-bypass Use local module transactions instead of global monolith transactions warning Cross-module calls that assume an open transaction from the caller
MOM-011 excessive-sync-calls Prefer internal events over synchronous calls for side effects warning Synchronous API call in moduleA that triggers a heavy write in moduleB
MOM-012 missing-event-contract Inter-module events must use shared DTOs, not internal types error Modules publishing events containing classes from their internal/ package
MOM-013 event-loop Modules should not create infinite event loops error Module A publishes E1 -> Module B receives and publishes E2 -> Module A receives
MOM-014 synchronous-coupling Module startup must not be blocked by other modules warning A module's init() or start() method waiting for a response from another module
MOM-015 blocking-event-bus Event subscribers should not block the main event bus error Event handlers in internal/ performing heavy IO without async wrappers
MOM-016 platform-logic-leak Business logic must not reside in the platform directory error platform/ containing domain-specific rules (e.g., calculateVAT)
MOM-017 platform-dependency Platform code must not depend on functional modules error import <pkg>.modules in any platform/ file
MOM-018 utility-overuse Avoid "Common" modules that become a dumping ground warning platform/common or modules/shared exceeding 2000 lines of code
MOM-019 transitive-dependency Modules must not rely on transitive dependencies from the platform warning A module using a library provided implicitly by a platform dependency
MOM-020 platform-bloat Platform layer must not exceed 20% of the total codebase size warning Codebase-wide LOC check: platform/ vs total project LOC
MOM-021 cross-module-di Modules must not inject internal classes from other modules error Dependency Injection of a class from modules/moduleB/internal into moduleA
MOM-022 missing-interface-binding API services should be requested by interface, not implementation warning moduleA injecting ModuleBServiceImpl instead of IModuleBService
MOM-023 hidden-initialization Modules must have a clear entry point for lifecycle management warning Modules using "magic" static initializers instead of an explicit onStart() hook
MOM-024 circular-init Module initialization must be acyclic error Constructor chains that lead back to the same module during startup
MOM-025 missing-graceful-shutdown Modules must implement a cleanup/shutdown hook warning Modules holding resources (sockets/files) without an onStop() implementation
MOM-026 thread-monopoly A module must not spawn unmanaged threads error Usage of new Thread() or Executors outside of the platform/ managed pool
MOM-027 memory-hog Large objects (>100MB) must be cleared or managed in a specific module scope warning In-memory caches in internal/ folders that do not have an eviction policy
MOM-028 connection-leak Modules must use namespaced connection pools warning A single module opening > 50 concurrent DB connections without explicit config
MOM-029 unconstrained-io Filesystem access must be scoped to a module-specific directory error modules/moduleA writing to a path used by modules/moduleB
MOM-030 compute-monopoly Background tasks must be priority-labeled per module warning Usage of high-priority scheduler flags for non-critical module tasks
MOM-031 static-state-leak Modules must not store request data in static variables error Usage of static fields to hold domain data, causing leaks between module calls
MOM-032 mutable-dto DTOs passed between modules must be immutable warning Classes in modules/*/api/ containing setter methods or mutable collections
MOM-033 unprotected-concurrency Shared module services must be thread-safe error Non-final instance variables in internal/ services modified after initialization
MOM-034 module-test-leak Tests for Module A must not depend on the internals of Module B error modules/moduleA/tests importing from modules/moduleB/internal
MOM-035 missing-module-isolation Unit tests should be executable per module warning Inability to run tests for a single folder in modules/ in isolation
MOM-036 deep-stubbing Do not stub the internal logic of other modules warning Tests in Module A using deep mocks for private methods in Module B
MOM-037 missing-api-tests Every public API class must have a corresponding contract test warning Classes in modules/*/api/ with 0 test coverage in the tests/ folder
MOM-038 integration-flakiness Integration tests must not depend on global shared state error Tests that fail if run in parallel due to database state clashes between modules
MOM-039 anonymous-logs Every log statement must include the originating module name error Log calls that do not include a module context or tag (e.g., [Billing] ...)
MOM-040 hidden-exceptions Cross-module exceptions must be wrapped with module context warning Module A throwing a generic error that hides the fact it originated in Module B
MOM-041 missing-module-metrics Each module must expose independent success/failure metrics error Global "Total Errors" metric without breakdown by module folder
MOM-042 trace-bypass Cross-module calls must maintain a single trace ID warning Calls to Module API that do not propagate the current Correlation-ID
MOM-043 global-config-clash Modules must use namespaced configuration keys error Use of generic config keys like db.url instead of modules.ordering.db.url
MOM-044 feature-flag-bypass New modules must be toggleable via feature flags warning Module registration code that cannot be disabled without a code change
MOM-045 dead-module Unused modules should be removed or archived warning Module directories with no incoming references or entry points
MOM-046 bypass-validation Cross-module calls must not bypass input validation error Module B calling Module A's API with raw, unvalidated data structures
MOM-047 monolithic-build Build scripts must allow for incremental compilation of modules warning A single file change in modules/A triggering a full re-compile of all modules
MOM-048 manual-migration Database migrations must be automated per module error Presence of SQL scripts in modules/*/db/ not managed by a migration tool
MOM-049 leaked-test-code Test utilities must not be included in production module builds error import <pkg>.modules.moduleA.tests found in internal/ code
MOM-050 version-drift All modules must share the same version of platform dependencies error Module A using Spring v5 while Module B uses Spring v6 in the same binary
RESI

Resilience & Fault Tolerance

The Resilience doctrine enforces patterns that ensure a system remains functional (perhaps in a degraded state) despite the inevitable failure of its components, network, or downstream dependencies. It aims to prevent cascading failures that turn a minor service hiccup into a total system outage.

50 rules 50 categories
When to Use
Resilience patterns must be applied to any system involving network boundaries, such as microservices.md, backend-for-frontend.md, or even a layered-n-tier.md monolith that communicates with external APIs. It is critical for systems with strict Availability SLAs and high-concurrency environments where failures can rapidly deplete shared resources. Do NOT use this pattern in purely local, single-process CLI tools or simple batch scripts where failing fast and exiting is the desired behavior.
Why Use It
* Stability Under Stress — Prevents a single slow dependency from backing up your entire thread pool and crashing your service. * Graceful Degradation — Allows the system to return "good enough" data (e.g., cached results) when the "perfect" data source is offline. * Self-Healing — Patterns like Circuit Breakers allow systems to automatically recover once a failing dependency stabilizes. * Blast Radius Reduction — Isolates failures to a single module or service using bulkheads.
Sources and Authority
Violation Catalog
ID Category Rule Severity Scan For
RES-001 missing-timeout Every network call must have an explicit timeout error HTTP/gRPC/DB client calls without a timeout configuration
RES-002 infinite-wait Blocking calls must not wait indefinitely for a response error Usage of default "infinite" or -1 timeout values in library configs
RES-003 excessive-timeout Timeouts should not exceed the user's patience threshold warning Timeouts configured for > 5s on user-facing request paths
RES-004 missing-deadline Propagate deadlines across service boundaries warning Lack of context.WithDeadline or X-Request-Deadline propagation
RES-005 static-timeout Use dynamic timeouts based on remaining request budget warning Hardcoded 500ms timeout when the total request budget is variable
RES-006 missing-breaker All external service calls must be wrapped in a circuit breaker error Network client calls missing a breaker decorator/wrapper
RES-007 misconfigured-threshold Breakers must have a defined failure rate threshold warning Circuit breaker settings with failureRateThreshold > 50%
RES-008 sticky-open-breaker Breakers must define an automatic transition to half-open state error Lack of waitDurationInOpenState or equivalent config
RES-009 missing-health-integration Breaker state must influence service health status warning /health returning UP when critical path breakers are OPEN
RES-010 slow-call-breaker Circuit breakers must also trigger on slow calls, not just errors warning Lack of slowCallRateThreshold in breaker configurations
RES-011 retry-storm Retries must implement exponential backoff and jitter error Retry logic using constant intervals (e.g., sleep(1s)) without randomization
RES-012 infinite-retries Retries must have a maximum attempt limit error Recursive or loop-based retries without a counter (max 3 recommended)
RES-013 side-effect-retry Do not retry non-idempotent operations (POST/PATCH) error Retry logic wrapping non-GET/non-HEAD requests without idempotency keys
RES-014 shallow-retries Do not retry on 4xx Client Errors warning Retries triggering for 401 Unauthorized or 404 Not Found
RES-015 hidden-retries Avoid nested retries (Library Retry + Application Retry) warning Both the HTTP library and the application logic having retry policies enabled
RES-016 shared-thread-pool Distinct dependencies must use separate thread pools warning Multiple clients/ sharing one global thread pool/executor
RES-017 missing-bulkhead Limit concurrent calls to any single dependency error Lack of semaphores or pool size limits on outgoing client calls
RES-018 resource-exhaustion Resilience wrappers must be registered as singletons warning Dynamic creation of breakers/retries per-request causing memory leaks
RES-019 unconstrained-queues Bulkhead queues must have a maximum capacity error Usage of unbounded queues (e.g., LinkedBlockingQueue without size)
RES-020 shared-circuit-breaker Do not share a single circuit breaker across different service endpoints error One breaker instance guarding multiple unrelated external APIs
RES-021 missing-fallback Every circuit breaker must have a defined fallback action error Breakers without a recover or fallback method
RES-022 opaque-fallback Fallbacks should indicate that the data is degraded warning Fallbacks returning "empty" data without a degraded: true flag
RES-023 recursion-in-fallback Fallbacks must not trigger secondary unprotected network calls error A fallback method that makes its own unprotected network call
RES-024 fallback-logic-leak Do not put complex business logic in fallback methods warning Fallbacks exceeding 10 lines of code or performing complex calculations
RES-025 fallback-failure-loop Fallback methods must be guaranteed to succeed or fail-fast error Fallback logic that itself contains nested retry loops or complex IO
RES-026 missing-load-shedding Services must drop requests when overloaded warning Lack of an "Active Request Limit" or "Admission Control" middleware
RES-027 queue-clogging Use LIFO or Priority queues when shedding load warning Standard FIFO queues that keep "old" requests during a spike
RES-028 expensive-health-check Health checks must be lightweight and non-blocking error /health endpoints that perform deep DB queries or external API calls
RES-029 startup-deadlock Do not wait for all dependencies before starting the app warning Blocking the main() thread until a DB or Cache is available
RES-030 missing-backpressure Propagate backpressure signals to the caller error Catching overload errors and returning generic 500 instead of 503 Service Unavailable
RES-031 silent-recovery State changes in circuit breakers must be logged warning Breaker transitions (CLOSED -> OPEN) without a log or event
RES-032 missing-chaos-test High-traffic integration points must be chaos-tested warning Lack of Toxiproxy or Chaos Mesh experiments in CI/CD
RES-033 invisible-retries Retries must be incrementing a specific metric error Retrying without incrementing a service_retries_total counter
RES-034 opaque-latency Measure latency *with* and *without* resilience overhead warning Only measuring final response time, ignoring time spent in retries
RES-035 missing-fallback-alert Frequent fallback triggering must trigger an alert warning Lack of an alert for fallback_calls_total exceeding a threshold
RES-036 cache-as-fallback-only Do not use cache-as-fallback for sensitive real-time data error Returning cached "Balance" or "Stock Level" during a service outage
RES-037 missing-ttl-on-fallback Fallback data must have a strict Time-To-Live warning Serving fallback data that hasn't been refreshed in over 24 hours
RES-038 cache-stampede-protection Use "Singleflight" or "Coalescing" for cache misses warning Multiple threads hitting the same DB record simultaneously on a cache miss
RES-039 hardcoded-resilience Resilience parameters must be configurable at runtime error Hardcoded failureRateThreshold: 0.5 inside the source code
RES-040 missing-dry-run Large resilience changes should be deployable in "Audit" mode warning Lack of a force_open or dry_run flag in the breaker configuration
RES-041 configuration-dependency Resilience config must not depend on a failing remote config server error Service failing to start its resilience layer because it can't fetch remote config
RES-042 shared-secrets-in-resilience Resilience logs must not contain authentication headers error Logging the raw downstream request/response when it contains Authorization tokens
RES-043 static-state-in-resilience Resilience policies must be stateless regarding user data error Storing user-specific data in a shared Retry or CircuitBreaker instance
RES-044 unmanaged-goroutines Background tasks must use a lifecycle-aware pool error Usage of go func() or Thread.start() for background resilience tasks
RES-045 context-leak Ensure contexts are passed to all async resilience tasks error Start of a background task that doesn't listen for parent shutdown
RES-046 blocking-event-loop Resilience logic must not block the main event loop error Synchronous Thread.sleep() in an async framework (Node.js/Netty)
RES-047 excessive-concurrency Limit the number of background retries to prevent OOM warning Lack of a global limit on the number of concurrently running retry workers
RES-048 missing-graceful-drain Wait for resilience tasks to complete before shutdown warning Service exiting immediately without letting retries finish their "last attempt"
RES-049 dependency-cycle-resilience Do not create a resilience dependency on a service you are protecting error A circuit breaker that calls a logging service which itself is behind that same breaker
RES-050 orphaned-resilience Ensure every resilience policy is actually attached to a client warning Definition of a CircuitBreaker or Retry policy that is never used in code

Configuration

One config file.
All doctrines obey it.

.architecture/config.yml tells Inquisition which doctrines to apply and which directories to scan. Generate it automatically with /puritan:covenant discover.

Override severity per doctrine or per rule in the optional .architecture/decisions.yml — set doctrines to strict, pragmatic, or aspirational.

# .architecture/config.yml
doctrines:
  - name: ddd
    enabled: true
    targets:
      - domain/
      - application/

  - name: cqrs
    enabled: true
    targets:
      - domain/commands/
      - infrastructure/projections/

layers:
  domain:
    - domain/
  application:
    - application/

exclude:
  - "**/migrations/**"
  - "**/*.generated.*"

Typical workflow

Greenfield project

  1. /puritan:covenant — discuss patterns, get recommendations
  2. Implement your architecture
  3. /puritan:inquisition — run on each PR to catch violations early
  4. /puritan:scriptorium — add doctrines as new patterns are adopted

Existing codebase

  1. /puritan:covenant discover — scan directory structure, generate config
  2. /puritan:inquisition full — full audit to establish baseline
  3. Set non-critical doctrines to aspirational to avoid noise
  4. /puritan:scriptorium — codify informal team rules