From d55e5414fcbfa052e6f2d10e0089e37b57fabb8c Mon Sep 17 00:00:00 2001 From: Prabhat Ranjan Date: Sun, 5 Apr 2026 21:04:48 +1000 Subject: [PATCH] docs: add architectural guardrails and architecture diagrams - Add ARCHITECTURAL_GUARDRAILS.md with code size guidelines - Add container.mmd, classes.mmd, data-flow.mmd diagrams --- ARCHITECTURAL_GUARDRAILS.md | 485 +++++++++++++++++++++++ docs/architecture/diagrams/classes.mmd | 124 ++++++ docs/architecture/diagrams/container.mmd | 83 ++++ docs/architecture/diagrams/data-flow.mmd | 117 ++++++ 4 files changed, 809 insertions(+) create mode 100644 ARCHITECTURAL_GUARDRAILS.md create mode 100644 docs/architecture/diagrams/classes.mmd create mode 100644 docs/architecture/diagrams/container.mmd create mode 100644 docs/architecture/diagrams/data-flow.mmd diff --git a/ARCHITECTURAL_GUARDRAILS.md b/ARCHITECTURAL_GUARDRAILS.md new file mode 100644 index 00000000..2e77780c --- /dev/null +++ b/ARCHITECTURAL_GUARDRAILS.md @@ -0,0 +1,485 @@ +# Architectural Guardrails for AI-Generated Code + +> **Purpose**: Enforce architectural constraints on AI-generated code at Sensible Analytics +> **Audience**: AI agents, developers, and CI/CD pipelines +> **Last Updated**: April 2026 + +--- + +## 1. Tool Selection + +| Use Case | Recommended Tool | Why | +|----------|-----------------|-----| +| **Test-based enforcement** | ArchUnitTS | Integrates with Jest/Vitest | +| **ESLint-native** | eslint-plugin-boundaries | Real-time feedback during dev | +| **CI/CD gate** | dependency-cruiser | Fast CLI-first | +| **Hexagonal architecture** | ArchUnitTS | Built-in presets | + +### Installation + +```bash +# ArchUnitTS (test-based) +npm install --save-dev archunitts + +# eslint-plugin-boundaries (ESLint) +npm install --save-dev eslint-plugin-boundaries + +# dependency-cruiser (CLI) +npm install --save-dev dependency-cruiser +``` + +--- + +## 2. Separation of Concerns: Clean Architecture Layers + +### Layer Structure + +``` +src/ +├── presentation/ # UI, Controllers, HTTP handlers +├── application/ # Use cases, Application services +├── domain/ # Entities, Value objects, Domain services +└── infrastructure/ # Database, External services, Adapters +``` + +### Dependency Rules (STRICT) + +| Layer | Can Depend On | +|-------|---------------| +| `presentation` | application, domain | +| `application` | domain | +| `domain` | NOTHING external | +| `infrastructure` | domain | + +--- + +## 3. ArchUnitTS Configuration + +### Clean Architecture Tests + +```typescript +// tests/architecture/clean-layers.test.ts +import { projectFiles } from 'archunit'; + +describe('Clean Architecture Layers', () => { + it('domain should not depend on presentation', async () => { + const rule = projectFiles() + .inFolder('src/domain/**') + .shouldNot() + .dependOnFiles() + .inFolder('src/presentation/**'); + await expect(rule).toPassAsync(); + }); + + it('domain should not depend on infrastructure', async () => { + const rule = projectFiles() + .inFolder('src/domain/**') + .shouldNot() + .dependOnFiles() + .inFolder('src/infrastructure/**'); + await expect(rule).toPassAsync(); + }); + + it('application should not depend on presentation', async () => { + const rule = projectFiles() + .inFolder('src/application/**') + .shouldNot() + .dependOnFiles() + .inFolder('src/presentation/**'); + await expect(rule).toPassAsync(); + }); + + it('presentation should not depend on infrastructure', async () => { + const rule = projectFiles() + .inFolder('src/presentation/**') + .shouldNot() + .dependOnFiles() + .inFolder('src/infrastructure/**'); + await expect(rule).toPassAsync(); + }); +}); +``` + +### No Circular Dependencies + +```typescript +// tests/architecture/circular-deps.test.ts +import { projectFiles } from 'archunit'; + +it('should not have circular dependencies', async () => { + const rule = projectFiles().inFolder('src/**').should().haveNoCycles(); + await expect(rule).toPassAsync(); +}); +``` + +--- + +## 4. Hexagonal Architecture: Ports & Adapters + +### Directory Structure + +``` +src/ +├── core/ +│ ├── domain/ # Entities, business logic +│ └── ports/ # Interfaces (driven & driving) +├── adapters/ +│ ├── primary/ # REST, GraphQL, CLI (driving) +│ └── secondary/ # Database, External APIs (driven) +└── config/ # DI wiring +``` + +### Port Naming Convention + +- Interfaces: `*.port.ts` +- Adapters: `*.adapter.ts` + +### ArchUnitTS Hexagonal Rules + +```typescript +// tests/architecture/hexagonal.test.ts +import { projectFiles } from 'archunit'; + +describe('Hexagonal Architecture', () => { + it('core domain should not depend on adapters', async () => { + const rule = projectFiles() + .inFolder('src/core/domain/**') + .shouldNot() + .dependOnFiles() + .inFolder('src/adapters/**'); + await expect(rule).toPassAsync(); + }); + + it('ports should not depend on adapters', async () => { + const rule = projectFiles() + .inFolder('src/core/ports/**') + .shouldNot() + .dependOnFiles() + .inFolder('src/adapters/**'); + await expect(rule).toPassAsync(); + }); + + it('secondary adapters should not depend on primary', async () => { + const rule = projectFiles() + .inFolder('src/adapters/secondary/**') + .shouldNot() + .dependOnFiles() + .inFolder('src/adapters/primary/**'); + await expect(rule).toPassAsync(); + }); +}); +``` + +--- + +## 5. ESLint Boundaries Configuration + +```javascript +// eslint.config.js or .eslintrc.js +import boundaries from "eslint-plugin-boundaries"; + +export default [ + { + plugins: { boundaries }, + settings: { + "boundaries/elements": [ + { type: "controller", pattern: "src/presentation/**/*controller*" }, + { type: "service", pattern: "src/application/**/*service*" }, + { type: "repository", pattern: "src/infrastructure/**/*repository*" }, + { type: "domain", pattern: "src/domain/**" }, + { type: "port", pattern: "src/core/ports/**" }, + { type: "adapter", pattern: "src/adapters/**" } + ] + }, + rules: { + "boundaries/element-types": [2, { + default: "disallow", + rules: [ + // Domain: no external dependencies + { from: { type: "domain" }, disallow: { to: { type: "*" } } }, + + // Controllers: can use services, domain + { from: { type: "controller" }, allow: { to: { type: ["service", "domain", "port"] } } }, + + // Services: can use repositories, domain, ports + { from: { type: "service" }, allow: { to: { type: ["repository", "domain", "port"] } } }, + + // Repositories: can use ports + { from: { type: "repository" }, allow: { to: { type: ["port"] } } }, + + // Adapters: must implement ports + { from: { type: "adapter" }, allow: { to: { type: ["port", "domain"] } } } + ] + }] + } + } +]; +``` + +--- + +## 6. CI/CD Integration + +### GitHub Actions + +```yaml +# .github/workflows/architecture.yml +name: Architecture Validation + +on: + push: + branches: [main, develop] + pull_request: + branches: [main] + +jobs: + architecture: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '20' + - run: npm ci + - run: npm test -- --testPathPattern="architecture" + - run: npx depcruise --config .dependency-cruiser.json src +``` + +### Fail Build on Violations + +```yaml +- name: Fail on critical violations + run: npx depcruise --config .dependency-cruiser.json --fail-on critical src +``` + +--- + +## 7. CLAUDE.md Integration + +Add to every project's `CLAUDE.md`: + +```markdown +# Architecture Rules + +## Clean Architecture Layers +- `src/presentation` → `application`, `domain` +- `src/application` → `domain` +- `src/domain` → NO dependencies +- `src/infrastructure` → `domain` + +## Hexagonal Structure +- Ports in `src/core/ports/` (interface suffix) +- Adapters in `src/adapters/` (adapter suffix) +- Domain in `src/core/domain/` (no external deps) + +## Forbidden Patterns +- NO direct infrastructure imports in presentation +- NO domain entities in adapters +- NO circular dependencies + +## Before Generating Code +Run architecture tests first: +npm test -- --testPathPattern="architecture" +``` + +--- + +## 8. Quick Reference Card + +``` +ARCHITECTURE GUARDRAILS CHECKLIST +================================== +LAYERS: +[ ] presentation → application, domain only +[ ] application → domain only +[ ] domain → no external dependencies + +HEXAGONAL: +[ ] core/ports/*.port.ts define interfaces +[ ] adapters/*.adapter.ts implement ports +[ ] domain has no adapter imports + +ESLINT: +[ ] boundaries plugin configured +[ ] rules enforced in CI/CD + +TESTS: +[ ] ArchUnitTS layer tests pass +[ ] No circular dependencies +[ ] File size limits enforced +================================== +Violations → Block merge +``` + +--- + +## 🔧 GitHub Agentic Workflows (gh-aw) + +Use `gh-aw` for AI agent task automation: + +```bash +# Install gh-aw +gh extension install github/gh-aw + +# Initialize for a repo +gh aw init + +# Run a workflow +gh aw run my-workflow +``` + +### Key Commands + +| Command | Purpose | +|---------|---------| +| `gh aw init` | Set up new workflow | +| `gh aw new ` | Create workflow | +| `gh aw compile` | Compile to Actions YAML | +| `gh aw run ` | Execute workflow | +| `gh aw logs ` | View logs | + +### Safe Outputs +```markdown +safe-outputs: + add-comment: + create-issue: + labels: [ agent-task ] +``` + +--- + +## 9. Code Size Guidelines for AI-Generated Code + +AI agents tend to generate large files. These guidelines prevent oversized code that is hard to review, test, and maintain. + +### Size Thresholds + +| Metric | Warning | Error | Rationale | +|--------|---------|-------|-----------| +| **Lines per file** | 200 | 300 | AI context fits better; easier to review | +| **Lines per function** | 20 | 50 | Single responsibility; testable units | +| **Function complexity** | 8 | 10 | Predictable paths; fewer bugs | +| **Max nesting depth** | 3 | 4 | Readability; reduced cyclomatic complexity | +| **Max parameters** | 3 | 4 | Interface clarity; easier mocking | +| **Classes per file** | 1 | 1 | Clear ownership; easier navigation | + +### ESLint Configuration + +```json +{ + "rules": { + "max-lines": ["warn", { + "max": 200, + "skipBlankLines": true, + "skipComments": true + }], + "max-lines-per-function": ["warn", { + "max": 30, + "skipBlankLines": true, + "skipComments": true + }], + "complexity": ["warn", { "max": 8 }], + "max-depth": ["warn", 4], + "max-params": ["warn", 3], + "max-classes-per-file": ["error", 1] + } +} +``` + +### Stricter AI-Generated Code Rules + +For files modified by AI agents, use stricter limits to compensate for AI's tendency to generate verbose code: + +```json +{ + "rules": { + "max-lines": ["error", { "max": 200 }], + "max-lines-per-function": ["error", { "max": 20 }] + } +} +``` + +### Per-File Overrides + +```json +{ + "overrides": [ + { + "files": ["**/*.test.ts", "**/*.spec.ts"], + "rules": { + "max-lines": "off", + "max-lines-per-function": "off" + } + }, + { + "files": ["*.config.ts", "*.stories.tsx"], + "rules": { + "max-lines": "off" + } + } + ] +} +``` + +### Refactoring Large Files + +When a file exceeds limits, apply these patterns: + +| Pattern | Use When | +|---------|----------| +| **Extract Method** | Any chunk that needs a comment to explain itself | +| **Extract Class** | Multiple fields/methods serving different responsibilities | +| **Move Method** | A method belongs to another class | +| **Replace Conditional with Polymorphism** | Complex switch/if logic | +| **Introduce Parameter Object** | Too many parameters (3+) | + +### Refactoring Principles (Martin Fowler) + +1. **Fits in your head** - Code should be comprehensible as a single unit +2. **Tiny steps** - Compile and test after every small change +3. **Separate moves from edits** - Commit refactoring separately from behavioral changes +4. **One thing at a time** - Focus on single changes to avoid rabbit holes + +### Suppressing Warnings + +When absolutely necessary, use inline suppressions with justification: + +```typescript +// eslint-disable-next-line max-lines-per-function -- Legacy integration required +private _getLegacyConfig(applyObject: BaseObject) { + // ... 60+ lines of integration code that cannot be broken up +} +``` + +### AI Agent Instructions + +When generating code, AI agents MUST: + +1. **Keep files under 200 lines** - Prefer smaller, focused files +2. **Keep functions under 20 lines** - Extract long functions immediately +3. **Single class per file** - Never combine multiple classes +4. **Name for clarity** - File names should reveal intent +5. **One responsibility** - Each file/module does one thing well + +### Quick Reference + +``` +SPLIT IF: +├── File > 200 lines → Extract related modules +├── Function > 20 lines → Extract smaller methods +├── > 3 parameters → Introduce parameter object +├── > 4 nesting depth → Extract intermediate methods +└── Complex switch → Use polymorphism or lookup table +``` + +--- + +## Related Documentation + +- [AI_CODE_GENERATION_STANDARDS.md](./AI_CODE_GENERATION_STANDARDS.md) +- [AI_AGENT_DEVELOPMENT_METHODOLOGY.md](./AI_AGENT_DEVELOPMENT_METHODOLOGY.md) +- [AGENT_WORKFLOW.md](./AGENT_WORKFLOW.md) + +--- + +*These guardrails ensure AI-generated code maintains structural integrity across all Sensible Analytics projects.* \ No newline at end of file diff --git a/docs/architecture/diagrams/classes.mmd b/docs/architecture/diagrams/classes.mmd new file mode 100644 index 00000000..036c88d0 --- /dev/null +++ b/docs/architecture/diagrams/classes.mmd @@ -0,0 +1,124 @@ +```mermaid +classDiagram + %% Base Entities + class User { + +String id + +String email + +String name + +UserRole role + +DateTime createdAt + +create() User + +validate() boolean + } + + class Organization { + +String id + +String name + +List~User~ members + +Settings settings + +create() Organization + } + + %% Enums + class UserRole { + <> + ADMIN + MEMBER + VIEWER + } + + %% Services + class AuthService { + +login(credentials) Token + +register(user) User + +validateToken(token) User + } + + class DeploymentService { + +deploy(project, env) Deployment + +rollback(deploymentId) void + +getStatus(deploymentId) DeploymentStatus + } + + class EnvironmentRegistry { + +List~Environment~ environments + +addEnvironment(env) void + +updateEnvironment(env) void + +getEnvironment(name) Environment + } + + %% Data Models + class Environment { + +String name + +String url + +DatabaseConfig database + +TechStack techStack + +DeploymentInfo deployment + } + + class DatabaseConfig { + +String type + +String host + +int port + +String name + } + + class TechStack { + +String frontend + +String backend + +String database + +String deployment + } + + class Deployment { + +String id + +String project + +String environment + +String status + +DateTime deployedAt + +String commitHash + } + + %% Relationships + User "1" --> "*" Organization : belongs to + Organization "1" --> "*" Environment : manages + + AuthService --> User : authenticates + DeploymentService --> Deployment : creates + EnvironmentRegistry --> Environment : tracks + + User <|-- UserRole + Environment --> DatabaseConfig : uses + Environment --> TechStack : uses + Environment --> Deployment : has +``` + +### Documentation Standards for Types + +Every important type should be documented with: + +1. **Purpose** - What the type represents +2. **Properties** - All fields with types and meanings +3. **Relationships** - How it connects to other types +4. **Usage** - Where it's used in the system + +Example: + +```typescript +/** + * Represents a deployment environment. + * + * @property name - Environment identifier (e.g., "production", "staging") + * @property url - Base URL for the deployed application + * @property database - Database connection configuration + * @property techStack - Technology stack used (frontend, backend, database) + * @property deployment - Deployment details (platform, status, commit) + */ +interface Environment { + name: string; + url: string; + database: DatabaseConfig; + techStack: TechStack; + deployment: DeploymentInfo; +} +``` \ No newline at end of file diff --git a/docs/architecture/diagrams/container.mmd b/docs/architecture/diagrams/container.mmd new file mode 100644 index 00000000..2f50d946 --- /dev/null +++ b/docs/architecture/diagrams/container.mmd @@ -0,0 +1,83 @@ +```mermaid +C4Container +title Container Diagram for Sensible Analytics Platform + +Person(admin, "Administrator", "Manages settings and configurations") +Person(user, "User", "Uses analytics applications") + +Container_Boundary(mobile, "Mobile Applications") { + Container(mobile_app, "CardScannerApp", "React Native", "Scans business cards") +} + +Container_Boundary(web_frontend, "Web Frontends") { + Container(prop_roo_frontend, "PropRoo Frontend", "React + Vite", "Property analytics UI") + Container(folio_frontend, "Folio Frontend", "React", "Portfolio tracking UI") + Container(qullamaggie_frontend, "Qullamaggie Frontend", "React", "Stock screening UI") + Container(rentroo_frontend, "Rentroo Frontend", "React", "Property management UI") + Container(crewcircle_frontend, "Crewcircle Frontend", "React", "Workforce management UI") + Container(video_frontend, "Video Analysis Frontend", "React", "Educational video UI") +} + +Container_Boundary(api_layer, "API Layer") { + Container(prop_roo_api, "PropRoo API", "FastAPI", "Property analytics backend") + Container(folio_api, "Folio API", "Rust/Tauri", "Portfolio tracking backend") + Container(qullamaggie_api, "Qullamaggie API", "Python", "Stock screening backend") + Container(rentroo_api, "Rentroo API", "Node.js", "Property management backend") + Container(crewcircle_api, "Crewcircle API", "Node.js", "Workforce management backend") + Container(video_api, "Video Analysis API", "Python", "Video processing backend") +} + +Container_Boundary(ai_services, "AI Services") { + Container(llm_service, "LLM Service", "OpenAI", "Language model processing") + Container(ocr_service, "OCR Service", "OpenAI Vision", "Business card scanning") + Container(transcription_service, "Transcription Service", "OpenAI Whisper", "Video transcription") +} + +Container_Boundary(data_layer, "Data Layer") { + ContainerDb(prop_roo_db, "PropRoo DB", "DuckDB/Parquet", "Property data storage") + ContainerDb(folio_db, "Folio DB", "SQLite", "Portfolio data storage") + ContainerDb(rentroo_db, "Rentroo DB", "PostgreSQL", "Property management data") + ContainerDb(crewcircle_db, "Crewcircle DB", "PostgreSQL", "Workforce data") + ContainerDb(neon_main, "Main Neon DB", "PostgreSQL", "Shared data storage") +} + +System_Ext(vercel, "Vercel", "Deployment platform") +System_Ext(cloudflare, "Cloudflare", "CDN and WAF") +System_Ext(gcp, "Google Cloud Platform", "Compute and storage") +System_Ext(oracle, "Oracle Cloud", "Infrastructure") +System_Ext(yahoo_finance, "Yahoo Finance", "Stock data") +System_Ext(email_provider, "Email Provider", "SendGrid/Resend", "Email notifications") + +Rel(user, prop_roo_frontend, "Uses") +Rel(user, folio_frontend, "Uses") +Rel(user, qullamaggie_frontend, "Uses") +Rel(user, rentroo_frontend, "Uses") +Rel(user, crewcircle_frontend, "Uses") +Rel(user, mobile_app, "Uses") + +Rel(prop_roo_frontend, prop_roo_api, "Calls") +Rel(folio_frontend, folio_api, "Calls") +Rel(qullamaggie_frontend, qullamaggie_api, "Calls") +Rel(rentroo_frontend, rentroo_api, "Calls") +Rel(crewcircle_frontend, crewcircle_api, "Calls") +Rel(mobile_app, card_scanner, "Uses") + +Rel(prop_roo_api, prop_roo_db, "Reads/Writes") +Rel(folio_api, folio_db, "Reads/Writes") +Rel(rentroo_api, rentroo_db, "Reads/Writes") +Rel(crewcircle_api, crewcircle_db, "Reads/Writes") + +Rel(prop_roo_api, vercel, "Hosted on") +Rel(qullamaggie_api, vercel, "Hosted on") +Rel(crewcircle_api, vercel, "Hosted on") +Rel(rentroo_api, oracle, "Hosted on") +PropRoo_API --> cloudflare +Rel(prop_roo_api, neon_main, "Uses") + +Rel(qullamaggie_api, yahoo_finance, "Fetches stock data") +Rel(qullamaggie_api, llm_service, "Uses") + +Rel(card_scanner, ocr_service, "Uses") +Rel(video_api, transcription_service, "Uses") +Rel(video_api, llm_service, "Uses") +``` \ No newline at end of file diff --git a/docs/architecture/diagrams/data-flow.mmd b/docs/architecture/diagrams/data-flow.mmd new file mode 100644 index 00000000..d86d283d --- /dev/null +++ b/docs/architecture/diagrams/data-flow.mmd @@ -0,0 +1,117 @@ +```mermaid +flowchart TD + subgraph Client["Client Layer"] + User["User"] + Mobile["Mobile App"] + Browser["Browser"] + end + + subgraph Edge["Edge Layer"] + Cloudflare["Cloudflare"] + Vercel["Vercel Edge"] + end + + subgraph API["API Services"] + Gateway["API Gateway"] + Auth["Auth Service"] + Webhook["Webhook Handler"] + end + + subgraph Services["Business Services"] + Property["Property Service"] + Portfolio["Portfolio Service"] + Stock["Stock Service"] + Workforce["Workforce Service"] + OCR["OCR Service"] + Transcription["Transcription Service"] + end + + subgraph External["External Services"] + OpenAI["OpenAI"] + Yahoo["Yahoo Finance"] + Email["Email Provider"] + Stripe["Payment Providers"] + end + + subgraph Data["Data Layer"] + Neon["Neon (PostgreSQL)"] + DuckDB["DuckDB"] + Redis["Redis Cache"] + S3["S3 Storage"] + end + + %% Client to Edge + User -->|HTTPS| Cloudflare + Mobile -->|HTTPS| Vercel + Browser -->|HTTPS| Vercel + + %% Edge to API + Cloudflare -->|Proxy| Gateway + Vercel -->|Proxy| Gateway + + Gateway -->|Validate| Auth + Auth -.->|JWT Token| Gateway + + %% API to Services + Gateway -->|Route| Property + Gateway -->|Route| Portfolio + Gateway -->|Route| Stock + Gateway -->|Route| Workforce + + %% Service interactions + Property -->|Cache| Redis + Property -->|Query| DuckDB + Property -->|Webhook| Webhook + + Portfolio -->|Read/Write| Neon + Stock -->|Fetch| Yahoo + Stock -->|AI Analysis| OpenAI + Workforce -->|Email| Email + + %% OCR Flow + Mobile -->|Upload Image| OCR + OCR -->|Vision API| OpenAI + OpenAI -->|Extract Text| OCR + OCR -->|Save Contact| Neon + + %% Video Analysis Flow + Browser -->|Upload Video| Transcription + Transcription -->|Whisper API| OpenAI + OpenAI -->|Transcript| Transcription + Transcription -->|LLM Analysis| OpenAI + OpenAI -->|Summary| Transcription + + %% Responses + Property -.->|JSON| Gateway + Portfolio -.->|JSON| Gateway + Stock -.->|JSON| Gateway + Workforce -.->|JSON| Gateway + OCR -.->|JSON| Mobile + Transcription -.->|JSON| Browser + + Gateway -.->|HTTPS| Cloudflare + Gateway -.->|HTTPS| Vercel + + %% Data flow labels + style User fill:#f9f,stroke:#333 + style OpenAI fill:#ff9,stroke:#333 + style Neon fill:#9f9,stroke:#333 +``` + +### Data Flow Documentation Requirements + +For each data flow path, document: + +1. **Source** - Where data originates +2. **Destination** - Where data goes +3. **Format** - Data format (JSON, binary, etc.) +4. **Authentication** - How identity is verified +5. **Error handling** - What happens on failure + +Example entry: + +| Flow | Source → Dest | Format | Auth | Error Handling | +|------|--------------|--------|------|----------------| +| User Login | Browser → Gateway | JSON | Password | 401 on failure | +| Stock Query | Stock → Yahoo | API | API Key | Cache fallback | +| OCR Scan | Mobile → OCR | Image | JWT | Retry 3x | \ No newline at end of file