# Architecture Deep Dive **Reference:** For quick overview, see CLAUDE.md "Architecture" section. **Governance:** All decisions follow AGENTS.md v16.0 and VIBE Coding Guardrails. ## Backend: Modular Monolith + Vertical Slices ### Module Structure ``` src/ KArtSell.Host/ # Main ASP.NET Core app KArtSell.BuildingBlocks/ # Shared infrastructure (logging, serialization, extensions) KArtSell.DbMigrator/ # DbUp migrations KArtSell.Modules.ModelOperations/ # Model lifecycle, validation, activation KArtSell.Modules.SignalEngine/ # Trading signal generation ``` ### Vertical Slice Template Each feature is a complete, self-contained slice from HTTP endpoint to database: ``` Features// Endpoint.cs # FastEndpoints route handler (HTTP/contract/status codes) Request.cs # Input model with validation Response.cs # Output model (DTO) Validator.cs # Fluent/Policy validation rules Handler.cs # Use case orchestration (Application layer) Policy.cs # Pure business decision logic (Domain layer) Sql.cs # Dapper queries (Data layer) Mapper.cs # Entity ↔ DTO mapping Jobs/ # Related Hangfire jobs Contracts/ # Event/Job contract definitions Tests/ # Unit/integration tests specific to this slice README.md # Traceability: requirements, ADRs, assumptions ``` **Key rule:** Endpoint handles HTTP concerns; Handler handles transaction boundaries; Policy makes decisions; Sql uses Dapper for explicit, schema-qualified queries. ### Design Principles - **No Generic Repository:** Each slice writes its own Dapper queries; promotes clarity. - **No Service Layer:** Handler + Policy + Sql replaces it; keeps flow visible. - **Module Isolation:** Modules do not query each other's source tables directly. - Synchronous: Use narrow Read Port services. - Asynchronous: Use Outbox/Inbox event patterns. - **PIT (Point-in-Time) Queries:** Must include `WHERE published_at <= cutoff` and revision resolver. - **Evidence & Audit:** Update/delete are blocked; new state appended as new revision. - **Migrations:** `src/KArtSell.DbMigrator` uses DbUp; file naming: `NNNN_description.sql`. ### Query Patterns ```csharp // ✅ DO: Schema-qualified, explicit columns, cancellation token const string sql = """ SELECT id, name, created_at FROM model_operations.signals WHERE published_at <= @cutoff AND status = @status ORDER BY created_at DESC """; // ❌ DON'T: SELECT *, generic repository, no token const string sql = "SELECT * FROM signals WHERE status = @status"; ``` ### Async Coupling: Outbox/Inbox - **Outbox:** When a command succeeds, events are inserted into `outbox` in the same transaction. - **Inbox:** A Hangfire job polls the outbox, publishes events, and marks them as processed. - **Idempotency:** Each inbox handler is idempotent; replayed events are no-ops. ## Database & Migrations ### DbUp - **Run at startup:** `KArtSell.DbMigrator` is the single source of truth. - **Schema ownership:** Each module owns its schema (e.g., `model_operations.*`, `signal_engine.*`). - **Safety:** Migrations are idempotent and checksummed; failed migration rolls back and waits for manual intervention. - **Test:** Each migration has fresh/upgrade/re-run/failure-recovery tests in CI. ## Hangfire (Background Jobs & Scheduling) ### Job Design - **Not a business decision maker:** Hangfire executes approved Application Commands, not policies. - **Idempotency key:** Each job must be replayable without side effects. - **Watermark & version set:** Track input/output state across retries. - **Queue isolation:** `q-customer-sla` (business SLA) is separate from `q-research` (non-critical). - **Retry classification:** - `transient` (network glitch, retry immediately) - `permanent` (bad input, log & alert) - `dq` (data quality issue, quarantine for manual review) - `business-hold` (awaiting approval or external event) ### Example Job Structure ```csharp public class MyJobCommand : ICommand { public string IdempotencyKey { get; set; } public Guid JobRunId { get; set; } public Guid CorrelationId { get; set; } } ``` Jobs do not call other jobs directly; instead, they emit events or check readiness gates. ## SignalR (Real-Time Push) Used for live notifications (model activation events, approval notifications). Follows Hub/Group pattern with correlation to `CorrelationId` for traceability. --- ## Frontend: Vue 3 + Vite + KBX Foundation v4 (Operational Navigation) ### Directory Layout (Registry-Driven) ``` frontend/src/ app/ router.ts # Vue Router setup (page-level only) installKbx.ts # KBX system initialization (registry, contracts, permissions) features/ / routes.ts # Feature route definitions (lazy-loaded) registry.ts # Screen registry entry (@kbx/contracts.ScreenDefinition) pages/ .vue # Page component (matches registry.screenId) components/ # Feature-scoped components (not shared) stores/ # Pinia stores (feature state) composables/ # Reusable hooks (feature logic) types/ # TS interfaces for this feature shared/ ui/ adapter/ # MANDATORY boundary: PrimeVue/AG Grid wrappers components/ # Cross-feature components (shared contracts) layouts/ # Page layout templates (header, sidebar, footer) tokens/ # Design tokens (compact, comfortable, touch density) composables/ types/ stores/ registry/ # Central screen definition registry index.ts # Import all feature registries, export merged ScreenRegistry ui-context.ts # UI adapter context provider design-system/ # Design tokens (NOT arbitrary page CSS) tokens.css # CSS custom properties (34px, 44px, 52px, etc.) density/ # compact, comfortable, touch variants ``` ### KBX Contracts (@kbx/contracts) ```typescript // ScreenDefinition (required in all feature registries) export interface ScreenDefinition { screenId: string // e.g., "oms.orders.list" title: string // Display name (localized) module: "OMS" | "WMS" | "ERP" // Functional area path: string // Vue Router path component: () => Promise // Lazy-loaded page component permissions: string[] // Required roles (e.g., ["order.view"]) help?: HelpDefinition // Contextual help (registry-driven) grid?: GridDefinition // AG Grid config (shared theme) shortcut?: string // Keyboard shortcut (help searchable) } // PermissionDefinition (centralized RBAC) export interface PermissionDefinition { permissionId: string // e.g., "order.create" label: string // Human-readable (for audit/help) screens: string[] // Which screens require this permission forms: string[] // Which forms check this permission } // HelpDefinition (context-aware, registry-indexed) export interface HelpDefinition { title: string // Panel title (screen context) sections: HelpSection[] relatedScreens: string[] // Cross-screen navigation externalUrl?: string // Knowledge base link } ``` ### App Initialization (@kbx Lifecycle) ```typescript // frontend/src/app/installKbx.ts // 1. Load screen registry (all feature registries merged) const registry = await loadScreenRegistry() // 2. Install permission context (RBAC decision engine) app.use(createPermissionContext(registry)) // 3. Install router with lazy-loaded pages const router = createRouter({ routes: buildRouterFromRegistry(registry) // Page routes only }) // 4. Install KBX global components (adapter-wrapped UI) app.use(KbxUiPlugin) // 5. Populate stores (registry cache for help, permissions, status) useRegistryStore().setRegistry(registry) ``` ### UI Adapter Pattern (Mandatory Boundary) All UI framework usage must go through `@kbx/ui/adapter`: ```typescript // ❌ DON'T: Use PrimeVue directly in screens // ✅ DO: Use KBX adapter (framework-agnostic) // Adapter handles: // - Theme switching (dark/light/system) // - Density token application (compact/comfortable/touch) // - Accessibility (ARIA, focus management) // - Keyboard shortcuts (Ctrl+S, etc.) ``` ### State Management (Registry-Driven, Contract-Based) | State | Owner | Tool | Registry Link | |-------|-------|------|---| | API responses, cache, stale, retry | TanStack Query | @tanstack/vue-query | → API contracts (OpenAPI) | | Session, role, UI preferences | Global Pinia | `authStore`, `registryStore` | → PermissionDefinition | | Form values, errors, touched | Form library | vee-validate + Zod schema | → Screen.forms contract | | URL filters, pagination, sorting | Router | vue-router query/params | → ScreenDefinition.grid | | Large data tables, virtual scroll | Server-side row model | AG Grid server mode (adapter) | → GridDefinition contract | **Anti-patterns:** - ❌ Do NOT duplicate API responses in Pinia (use TanStack Query cache). - ❌ Do NOT write 401/409/422/429/503 error handling in every screen (use ErrorBoundary + QueryStateBoundary). - ❌ Do NOT manage query cache manually. - ❌ Do NOT define routes outside registry (route table is generated from registry). - ❌ Do NOT bypass PermissionGuard for conditional rendering (use registry-driven rendering). ### Screen Component Example ```vue ``` ### Screen Registry Entry ```typescript export const ordersListScreen: ScreenDefinition = { screenId: "oms.orders.list", title: "Orders", module: "OMS", path: "/oms/orders", component: () => import("./pages/OrdersList.vue"), permissions: ["order.view"], help: { title: "Order Search & Management", sections: [{ title: "How to search", content: "Use filters at the top to search by date, customer, or status" }], relatedScreens: ["oms.orders.detail", "oms.orders.register"] }, grid: { columnDefs: [ { field: "orderId", headerName: "Order ID", width: 120 }, { field: "customerName", headerName: "Customer", width: 200 } ], rowHeight: "auto", serverSideDatasource: true }, shortcut: "Ctrl+Shift+O" } ``` ### Component Elevation Criteria Promote to `shared/ui/components/` only when: 1. **Same business meaning & permissions** (check registry.screens[].permissions). 2. **Repeated state/error handling logic** across 3+ consumers. 3. **Accessibility & testing** fully implemented. 4. **Contract-driven** (implements @kbx/contracts interface). **Always-shared components (KBX system):** - `QueryStateBoundary` (loading/error/empty, registry context-aware) - `PermissionGuard` (RBAC via registry.permissions) - `ScreenHeader` (title, help trigger, export buttons from registry) - `AgGridShell` (AG Grid adapter with density tokens) - `KbxStatus` (status display per StatusDefinition contract) - `KbxHelpPanel` (registry-driven help, contextual) ### Design Token Density Screen density (compact/comfortable/touch) is applied globally via tokens, NOT per-screen CSS: ```css /* ✅ DO: Define tokens, let screens inherit */ :root { --kbx-density: compact; /* or 'comfortable', 'touch' */ --kbx-input-height: 34px; --kbx-grid-row-height: 34px; --kbx-touch-target: 44px; } :root[data-density="comfortable"] { --kbx-input-height: 36px; --kbx-grid-row-height: 36px; --kbx-touch-target: 48px; } :root[data-density="touch"] { --kbx-input-height: 52px; --kbx-grid-row-height: 48px; --kbx-touch-target: 52px; } ``` ### Route Registration Flow 1. **Feature Registry** (`features//registry.ts`): Define ScreenDefinition(s) 2. **Central Registry** (`frontend/src/registry/index.ts`): Import and merge all feature registries 3. **Router Build** (`app/installKbx.ts`): Generate Vue Router routes from registry 4. **Page-Level Routes Only**: No nested routing; each screen is a top-level route ### Permission & Help Enforcement **Registry-driven RBAC:** ```typescript // ✅ DO: Registry-driven permission checks const canEdit = computed(() => { const screen = registry.screens.get('oms.orders.detail') return permissions.hasAll(screen.permissions) }) // ❌ DON'T: Hard-coded permission strings const canEdit = permissions.has('order.edit') // WRONG: no registry reference ``` **Registry-driven Help:** ```typescript // ✅ DO: Help from registry const { openHelp } = useHelpPanel() openHelp('oms.orders.list') // ❌ DON'T: Hard-coded help text const helpText = "Use filters to search..." // WRONG: duplicates registry ``` ### Contract Enforcement (CI/CD Gate) Build-time validation ensures all screens comply with contracts: ```bash # .gitea/workflows/quality-gate.yml - name: Validate screen contracts run: | # 1. Check: All files in features/*/pages/*.vue match registry entries # 2. Check: All ScreenDefinition.permissions exist in permissionRegistry # 3. Check: Grid configs use adapter tokens, not inline CSS # 4. Check: No PrimeVue/AG Grid imports outside adapter/ # 5. Generate: ScreenManifest.json for help/telemetry indexing ``` --- ## FastEndpoints - Docs: [FastEndpoints GitHub](https://github.com/FastEndpoints/FastEndpoints) - Pattern: Each endpoint maps to a Vertical Slice; routes are discovered automatically.