Architecture decisions
Skill Canhada-Labs/ceo-orchestration/.claude/skills/core/architecture-decisions
Architecture decision-making framework for {{PROJECT_NAME}}. Covers ADR (Architecture Decision Record) format, trade-off analysis matrices, dependency graph analysis, blast radius assessment, system boundary identification, scalability testing ("10x scale" rule), when to refactor vs rewrite, multi-process architecture patterns, and cross-cutting concern management. Use when evaluating changes that touch 3+ modules, planning new features, assessing refactoring proposals, reviewing system boundaries, or making decisions that will be hard to reverse. This is the VP Engineering archetype's operating manual for architectural governance.From its SKILL.md
npx -y skills add Canhada-Labs/ceo-orchestration --skill architecture-decisionsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
18.4 KB, as published. Nobody here has run it
Architecture Decisions
Fail-Fast Rule
If an architecture decision is irreversible and affects 3+ modules, stop and write an ADR first. Never implement a cross-cutting change without documenting the trade-offs. Never assume a "quick fix" to a structural problem won't have cascading effects.
The 10x Scale Rule
Every architecture decision must pass this test:
"Does this scale to 10x the current number of integrations / users / events without a rewrite?"
If the answer is no, the architecture is wrong — even if it works today.
For a system with N upstream integrations across M pool workers, plan for 10·N:
- Pool workers may need to grow roughly linearly (resource allocation)
- IPC throughput grows linearly with the number of entities
- Memory footprint grows linearly — watch for per-entity overhead
- New integration quirks multiply combinatorially
ADR Template
Where ADRs live in this framework: .claude/adr/<ADR-NNN-slug>.md —
see .claude/adr/README.md for naming, lifecycle, and the existing
records (ADR-001 runtime state directory, ADR-002 Python hooks
package layout, ADR-003 branch protection replaces skill signing).
The existing ADRs are concrete examples to follow.
Every significant architecture decision gets recorded:
# ADR-{NNN}: {Title}
## Status: PROPOSED | ACCEPTED | REJECTED | SUPERSEDED
## Context
What problem are we solving? Why now?
## Decision Drivers
- Driver 1 (e.g., latency budget)
- Driver 2 (e.g., memory constraint)
## Options Considered
### Option A: {Name}
- Pros: ...
- Cons: ...
- Risk: ...
### Option B: {Name}
- Pros: ...
- Cons: ...
- Risk: ...
## Decision
Option {X} because {rationale}.
## Consequences
### Positive
- ...
### Negative
- ...
### Neutral
- ...
## Blast Radius
Modules affected: {list}
Reversibility: HIGH | MEDIUM | LOW
System Boundaries (example layout)
Every project has its own boundaries; the diagram below is a template. Fill in with your actual boundaries and assign owners from your team archetypes.
┌──────────────────────────────────────────────────────┐
│ BOUNDARY 1: External APIs (upstream integrations) │
│ Trust: ZERO. Validate everything. │
│ Owner archetype: Integration Engineer + API Engineer │
└───────────────────────┬──────────────────────────────┘
│
┌───────────────────────┴──────────────────────────────┐
│ BOUNDARY 2: Worker Process ↔ Main Process │
│ IPC: binary (hot) + JSON (control) │
│ Owner archetype: Real-Time Systems + Performance │
└───────────────────────┬──────────────────────────────┘
│
┌───────────────────────┴──────────────────────────────┐
│ BOUNDARY 3: Main Process ↔ Clients │
│ HTTP/WS/SSE. Auth required. │
│ Owner archetype: API Engineer + Security │
└───────────────────────┬──────────────────────────────┘
│
┌───────────────────────┴──────────────────────────────┐
│ BOUNDARY 4: Main Process ↔ Database │
│ PostgREST + RLS, or your ORM + access control. │
│ Owner archetype: Data Engineer + Security │
└───────────────────────┬──────────────────────────────┘
│
┌───────────────────────┴──────────────────────────────┐
│ BOUNDARY 5: Main Process ↔ Mutation Workers │
│ HTTP + HMAC auth or signed tokens. │
│ Owner archetype: API Engineer + Security │
└──────────────────────────────────────────────────────┘
Trade-Off Analysis Framework
For any decision with 2+ viable options:
| Dimension | Weight | Option A | Option B |
|---|---|---|---|
| Latency impact | HIGH | Score 1-5 | Score 1-5 |
| Memory impact | HIGH | Score 1-5 | Score 1-5 |
| Complexity | MEDIUM | Score 1-5 | Score 1-5 |
| Reversibility | MEDIUM | Score 1-5 | Score 1-5 |
| Maintenance | LOW | Score 1-5 | Score 1-5 |
| Blast radius | HIGH | L1-L5 | L1-L5 |
Score: 5 = best, 1 = worst. Weighted sum decides.
When to Refactor vs Rewrite
| Signal | Action |
|---|---|
| 1-3 fixes solve the problem | Fix. Don't refactor. |
| Same area breaks 3+ times | Refactor the module. |
| Fix requires touching 5+ files | Architecture problem. Write ADR. |
| "It works but I don't know why" | Rewrite with tests first. |
| Performance < 50% of budget | Profile → targeted optimization. |
| Performance < 10% of budget | Architecture is wrong. Redesign. |
Dependency Analysis
Before approving any cross-cutting change:
- List every file that imports the changed module
- List every module that the changed module imports
- Draw the dependency subgraph (who depends on whom)
- Identify circular dependencies
- Assess: if this module breaks, what else breaks?
Current Architecture Invariants (NEVER violate)
- Main thread does NO heavy computation — all CPU work in workers
- IPC is batched — never send individual events, always batches
- Fast path bypasses the batch worker — raw events reach main in ~50ms
- Low-priority entities skipped on fast path — only HOT+WARM get fast delivery
- Per-entity state uses in-place mutation — no object creation on hot path
- PubSub guarded by subscriber count — skip serialize when nobody listens
- SharedArrayBuffer is display-only — never for business-critical math
- Each adapter pool is independent — one pool crash doesn't affect others
Review Protocol
When reviewing an architecture proposal:
- Does it pass the 10x scale rule?
- What's the blast radius? (L1-L5)
- Is it reversible? (can we undo in < 1 hour)
- Does it violate any invariant above?
- Does it add a new system boundary?
- Does it change the data flow diagram?
- Who else needs to review? (e.g. the financial-math specialist for numeric correctness, the security engineer for auth/crypto, the performance engineer for hot-path impact)
Domain-Driven Design Reference
Domain modeling is a prerequisite for drawing system boundaries. Without explicit bounded contexts, components grow entangled and ADRs reference concepts the codebase and the business define differently.
Bounded contexts as ADR candidates
Every context boundary is a potential ADR. When two sub-domains share a concept that means different things to each (e.g., "Account" in billing vs. "Account" in identity), that semantic clash must be captured before any code crosses the boundary.
Rules:
- One ubiquitous language per bounded context. Terms used in code MUST
match terms used in the product spec and business documentation for that
context. If the code says
UserAccountand the business saysSubscriber, one is wrong. - Context mapping is strategic, not tactical. The upstream/downstream relationship between two contexts — and whether an anti-corruption layer (ACL) is needed — belongs in an ADR, not a code comment.
- Aggregate roots own invariants. An aggregate root is the only entry point for mutations inside its boundary. NEVER reach inside an aggregate to mutate its children directly.
CORRECT vs WRONG — ubiquitous language
# CORRECT
# Billing context: "Invoice" (amount in cents, VAT line items, issue date)
# The code model matches the business spec vocabulary exactly.
class Invoice:
id: InvoiceId
line_items: list[LineItem]
issued_at: datetime
# WRONG
# Billing context uses "Invoice" but code says "Bill" or "PaymentRequest"
# — the mismatch means every code review requires a mental translation step
# and the spec/ADR will drift from the implementation silently.
class Bill: # DO NOT use when the domain says "Invoice"
...
Strategic vs tactical patterns
| Decision type | Mechanism |
|---|---|
| Context boundary + ownership | ADR (strategic — cross-cutting, hard to reverse) |
| Upstream/downstream relation, ACL, conformist | ADR (strategic) |
| Aggregate root, value object, domain event | Code + PR comment (tactical — reversible within the context) |
| Repository interface vs. direct ORM access | ADR if shared across contexts; PR comment if local |
Anti-pattern: anemic domain model
An anemic domain model is a domain object that holds data but contains no behavior — all business logic lives in service classes outside the object. This is WRONG when the domain has invariants that must be enforced on every mutation path.
# WRONG — anemic model, invariants enforced nowhere
class Order:
items: list[OrderItem]
total: Decimal
class OrderService:
def add_item(self, order: Order, item: OrderItem) -> None:
order.items.append(item) # no invariant check
order.total += item.price # no constraint validation
# CORRECT — invariants live in the aggregate root
class Order:
_items: list[OrderItem]
def add_item(self, item: OrderItem) -> None:
if len(self._items) >= MAX_ITEMS_PER_ORDER:
raise OrderLimitExceeded(MAX_ITEMS_PER_ORDER)
self._items.append(item)
# total is derived, not stored separately
Tool and Library Evaluation Rubric
Every new dependency is a long-term commitment. Evaluate before adopting; re-evaluate on a cadence after adoption. The rubric below applies to any third-party library, framework, or infrastructure tool.
Scoring axes (rate each 1–5, 5 = favorable)
| Axis | What to measure | Weight |
|---|---|---|
| Security posture | CVE history, disclosure policy, signed releases, dependency chain depth | HIGH |
| Ecosystem health | Downstream adoption count, corporate backer vs. community-only, forks activity | MEDIUM |
| Maintenance signal | Commit recency, issue response latency, breaking-change cadence, semver discipline | HIGH |
| Lock-in cost | Proprietary API surface, data format portability, migration complexity if ejected | HIGH |
| Learning curve | Time-to-first-working-prototype for a mid-level engineer unfamiliar with the tool | LOW |
Adoption gates (apply by axis weight; the weights are gate priorities, not numeric multipliers):
- HIGH-weight axes (security, maintenance, lock-in): MUST score ≥ 4.
- MEDIUM-weight axes (ecosystem): MUST score ≥ 3.
- LOW-weight axes (learning curve): score ≥ 2 acceptable.
If any HIGH-weight axis scores ≤ 3, REJECT or require explicit Owner override with a documented mitigation in the ADR. Tradeoffs are not absolved by averaging across axes.
Reversibility tiers
Classify the candidate before scoring. Tier determines how much due diligence is required.
| Tier | Definition | ADR required? | Re-evaluation cadence |
|---|---|---|---|
| Drop-in | Can be swapped for another implementation in ≤1 day; no data format lock-in | No | At major version bumps |
| Embedded | Permeates call sites or data models; migration cost 1–4 weeks | Yes (≤200 LoC ADR) | 12 months or on major version |
| Infrastructure-coupled | Owns a data store, protocol, or deployment surface; ejection cost > 1 month | Yes + security review | 6 months |
NEVER adopt an infrastructure-coupled dependency without a written exit strategy in the ADR. "We'll figure it out" is not an exit strategy.
Decision artifact
For Embedded and Infrastructure-coupled candidates, produce a small ADR (target ≤200 LoC) using the template in this skill. Include:
## Dependency: {name} v{version}
## Reversibility tier: Drop-in | Embedded | Infrastructure-coupled
## Exit strategy: {how we remove it if needed, in concrete steps}
## Re-evaluation date: {YYYY-MM-DD}
## Score summary:
| Axis | Score | Notes |
|------|-------|-------|
| Security posture | N/5 | ... |
| Ecosystem health | N/5 | ... |
| Maintenance signal | N/5 | ... |
| Lock-in cost | N/5 | ... |
| Learning curve | N/5 | ... |
| HIGH-axis gates (security / maintenance / lock-in) | pass / fail | all three MUST score ≥ 4 |
| MEDIUM-axis gate (ecosystem) | pass / fail | MUST score ≥ 3 |
| LOW-axis (learning curve) | informational | recorded for context; no minimum gate (a low score is a budget signal for onboarding/training, not a rejection signal) |
| Decision | ADOPT / DEFER / REJECT | with explicit rationale if any HIGH-axis gate fails |
Anti-patterns
- Adopting because it is popular. Popularity is an ecosystem-health signal, not a security or reversibility signal. Measure all axes.
- Skipping re-evaluation. A library that scored 4.2 at adoption can score 2.8 eighteen months later (maintainer abandonment, CVE accumulation, API churn). Set a calendar reminder at adoption time.
- Treating the default package manager choice as a decision. Transitive dependencies require the same reversibility classification as direct ones when they own a data format or protocol surface.
Hexagonal Boundaries (Ports & Adapters) — folded from hexagonal-architecture (PLAN-157 W1)
Boundary doctrine for keeping business logic independent of frameworks,
transport, and persistence — distilled from the sunset architecture squad's
hexagonal-architecture skill (full text in git history; fold ratified
PLAN-157 OQ1-OQ4, S270).
The one rule: dependencies point inward. The core — domain rules plus the
use cases that orchestrate them — never imports a framework, driver, transport
type, or concrete client; it depends only on abstractions it owns (ports). An
entity or use case that imports an ORM row, a web Request, or a vendor SDK
has already broken the architecture.
The pieces: domain model (entities, value objects, invariants — knows nothing external); use case (orchestrates one intent — knows ports only); inbound port (what the application can do); outbound port (what the application needs: repository, gateway, publisher, clock, id-generator); adapter (concrete edge — HTTP controller, DB repository, queue consumer, SDK wrapper — knows frameworks); composition root (the single place that instantiates adapters and injects them into use cases — knows everything, by design). Outbound port interfaces belong to the application layer; their implementations live in infrastructure.
Build recipe: (1) draw one use-case boundary with explicit input/output
DTOs — no transport wrappers cross the line; (2) name every side effect as an
outbound port first — model capabilities (StockRepository), never
technologies (PostgresStockTable); (3) write the use case as pure
orchestration over injected ports; (4) build adapters at the edge — mapping
lives there and never leaks inward; (5) wire everything in one auditable
composition root; (6) test per boundary.
Testing, by boundary: domain tests as pure rules (no mocks); use-case unit tests with in-memory fakes for every port; one shared contract suite per outbound port, run against every adapter implementing it; inbound adapter tests for protocol mapping in both directions; adapter integration tests against real infrastructure; end-to-end on the critical journeys; characterization tests before any extraction.
Anti-patterns: domain entities importing ORM/web/SDK types; use cases
reading req/res or queue metadata; use cases returning raw database rows;
adapters calling each other instead of flowing through use-case ports; wiring
scattered behind hidden global singletons.
Checklist: validation at both boundaries (inbound adapter AND use-case invariants); transformations return new values, never mutate shared state; errors translated across boundaries (infra error → application/domain error); use cases runnable under simple in-memory fakes for every port.
For migrating a tangled service slice-by-slice, use
core/incremental-refactoring — its Migration Playbook and legacy-code
sections cover the strangler path the source skill described.