npx skills add 7spade/black-tortoiseREADME
Architecture Gate CI - Implementation Guide
Comment ID 3796470142 Implementation
This document describes the implementation of the Architecture Gate CI system for enforcing event-sourcing and DDD architectural invariants.
Files Created/Modified
1. Script: comprehensive-audit.js
The main architecture enforcement script that validates:
- Presentation layer isolation: No EventBus/EventStore/DomainEvent imports from domain
- Event publishing control: Only PublishEventUseCase and event handlers can call publish/append
- Store layer placement: All stores MUST be in application layer
- Sequential append-before-publish: No Promise.all with event operations
- Event causality propagation: Handlers MUST propagate correlationId and set causationId
- Signal-first architecture: Minimal RxJS in presentation layer
2. Package Script: package.json
Already configured with script command:
"architecture:gate": "node comprehensive-audit.js"
3. Documentation: .architectural-rules.md
Updated with:
- CI gate implementation details
- Detailed rule descriptions
- GitHub Actions workflow specification
- Local testing instructions
- Exit code documentation
- Workflow security features
4. GitHub Actions Workflow: .github/workflows/architecture-gate.yml
IMPORTANT: Create this file manually with the following content:
name: Architecture Gate
on:
push:
branches: ['**']
paths:
- 'src/**/*.ts'
- 'comprehensive-audit.js'
- '.github/workflows/architecture-gate.yml'
pull_request:
branches: ['**']
paths:
- 'src/**/*.ts'
- 'comprehensive-audit.js'
- '.github/workflows/architecture-gate.yml'
# Least-privilege permissions (security best practice)
permissions:
contents: read
# Prevent concurrent runs, cancel outdated PR builds
concurrency:
group: architecture-gate-${{ github.ref }}
cancel-in-progress: true
jobs:
enforce-architecture:
name: Enforce Event-Sourcing Invariants
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Run Architecture Gate
run: node comprehensive-audit.js
env:
NODE_ENV: production
Manual Steps Required
-
Create the workflow directory (if it doesn't exist):
mkdir -p .github/workflows -
Create the workflow file: Copy the workflow YAML above into
.github/workflows/architecture-gate.yml -
Test locally:
npm run architecture:gate -
Commit and push:
git add . git commit -m "feat: add architecture gate CI per comment_id 3796470142" git push
Architecture Rules Enforced
Rule 1: Presentation Layer Isolation
Forbidden in src/app/presentation/**:
- ❌
from '@domain/event-bus' - ❌
from '@domain/event-store' - ❌
from '@domain/event'
Must use:
- ✅ Application facades
- ✅ Application stores
Rule 2: Event Publishing Control
Only these can call eventBus.publish() or eventStore.append():
- ✅
publish-event.use-case.ts - ✅ Event handlers (in
application/*/handlers/*event-handler*.ts)
All other files:
- ❌ Cannot call
eventBus.publish() - ❌ Cannot call
eventStore.append()
Rule 3: Store Layer Placement
Stores (*.store.ts) MUST:
- ✅ Be in
src/app/application/** - ❌ NOT be in presentation or domain layers
Rule 4: Sequential Append-Before-Publish
Forbidden:
- ❌
Promise.all([eventStore.append(...), ...]) - ❌
Promise.all([eventBus.publish(...), ...])
Required:
- ✅ Sequential:
await eventStore.append()thenawait eventBus.publish()
Rule 5: Event Causality Propagation
Event handlers creating events MUST:
- ✅ Propagate
correlationIdfrom parent event - ✅ Set
causationIdto parenteventId
Rule 6: Signal-First Architecture
Presentation layer should:
- ✅ Use Angular Signals
- ⚠️ Minimize RxJS usage (warning, not error)
Allowlist (Exceptions)
Only these files are exempt from Rule 2:
-
publish-event.use-case.ts- Can call
eventBus.publish() - Can call
eventStore.append()
- Can call
-
Event Handlers (
application/*/handlers/*event-handler*.ts)- Can call use cases that trigger events
Testing the Gate
Test Case 1: Forbidden Import in Presentation
// ❌ This will FAIL the gate
// src/app/presentation/some-component.ts
import { EventBus } from '@domain/event-bus';
Test Case 2: Direct Event Publishing
// ❌ This will FAIL the gate (unless in PublishEventUseCase)
// src/app/application/some-use-case.ts
await eventBus.publish(event);
Test Case 3: Store in Wrong Layer
// ❌ This will FAIL the gate
// src/app/presentation/stores/some.store.ts <-
...