Establish unambiguous governance: - AGENTS.md v16.0 is the ONLY document that contains engineering guidelines - All procedures, harnesses, rules, decision frameworks live in AGENTS.md - CLAUDE.md, GEMINI.md, and all other documents FOLLOW AGENTS.md - Other documents ONLY reference AGENTS.md with explicit links - If any document conflicts with AGENTS.md, AGENTS.md is authoritative Changes: - AGENTS.md: Add 'GOVERNANCE LOCK' section at top (5 rules, scope definition) - CLAUDE.md: Add critical warning (context only, not guidelines) - Rules: Never add procedures to supplementary documents Non-negotiable enforcement: - New guidelines → AGENTS.md only - Found guidelines elsewhere → move to AGENTS.md, replace with reference - Conflicting guidance → AGENTS.md wins - Exception: Project status, architecture context, navigation (CLAUDE.md only) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
46 KiB
CLAUDE.md
⚠️ CRITICAL: This file is for PROJECT CONTEXT ONLY. It does NOT contain engineering guidelines.
All engineering guidelines, rules, harnesses, and procedures are in AGENTS.md v16.0 ONLY.
If any section below conflicts with AGENTS.md, AGENTS.md is authoritative and this text is invalid.
What belongs in AGENTS.md:
- Coding principles and rules
- Development configuration (database, ports, authentication, SSH)
- Procedures and workflows
- Decision frameworks
- Anti-patterns and guardrails
What belongs in CLAUDE.md:
- Project status and timeline
- Architecture overview (high-level only)
- File structure and navigation
- References to AGENTS.md (with explicit links)
When working: Always check AGENTS.md first. CLAUDE.md is supplementary context only.
⚖️ Governance: AGENTS.md v16.0 Strategic Principles
All work — code changes, refactors, new features, tooling — must follow AGENTS.md v16.0 guidelines:
- 13 Decision Criteria: SOLID, complexity, data integrity, necessity-driven, normalization, simplicity, patterns, guardrails, traceability, reliability, maturity, right-way, tech debt
- Work Checklist: Every task must self-assess against 13-item decision framework before implementation
- Anti-Patterns (Blockers): Never gold-plate, never skip testing, never SELECT *, never magic numbers, never direct module-to-module table access
- Tech Debt: Recorded in registry with Impact/Effort; 20% quarterly paydown target
Reference: See AGENTS.md section "v16.0 Strategic Architecture & Engineering Excellence" for full framework.
📅 WBS Optimization Principle (Critical)
Core Principle: WBS dates are REFERENCE ONLY, not hard deadlines.
Rule: If work can be completed faster than WBS schedule indicates, pull forward all tasks and complete ASAP.
Why:
- Eliminates unnecessary waiting time
- Maximizes parallelization opportunities
- Delivers value earlier
- Reduces manual work through automation
Example Application:
- Original WBS: 50-90 days wait + 2-3 months manual work = 3-4 months total
- Optimized: Complete all non-Phase-1 work immediately (10 hours) + 50-90 days auto = 50-90 days total (2-3 months saved)
Implementation:
- Identify which work can proceed immediately (not blocked by dependencies)
- Accelerate and automate all non-blocking phases
- Only wait for truly blocking dependencies (e.g., external data collection)
- Use automation to eliminate manual work during waiting periods
Status: Applied to K-ArtSell Aegis v16.0 (Session 2026-08-03)
- ✅ Phase 2-4: Completed immediately (not waiting for Phase 1)
- ✅ Phase 1: Auto-runs in background (no manual intervention)
- ✅ Result: 2-3 months saved through parallelization
Project Overview
K-ArtSell Aegis v16.0 is a complex financial/investment advisory system built on a Modular Monolith with Vertical Slice architecture. It enforces strict execution completeness, evidence preservation, and controlled model operations—not production-ready until all validation gates (252+ trading days shadow, OOS testing, PBO/DSR verification) pass.
Status: IMPLEMENTATION_TEMPLATE / STATIC_VALIDATED / BUILD_DB_E2E_SHADOW_REHEARSAL_REQUIRED
🔧 Current Implementation Status (2026-08-04 CORRECTED)
Host Status: ✅ Code ready, not currently running (awaiting Phase 1 startup)
Gate 1-4 Verification: ✅ COMPLETE & VERIFIED
Production Readiness: 0% (Code quality ✅, Phase 1 shadow run not yet started)
Gates Verification Summary (Actual Evidence)
| Gate | Requirement | Status | Evidence |
|---|---|---|---|
| 1 | Backend unit tests (17/17) | ✅ PASS | Executed 2026-08-04, all passing |
| 1 | Frontend unit tests (40/40) | ✅ PASS | Vitest 40/40 passing |
| 2 | Integration tests (136/136) | ✅ PASS | Integration tests with real DB passing |
| 2 | Architecture tests (6/6) | ✅ PASS | SOLID + pattern verification |
| 3 | Shadow Run API (253 days) | ✅ READY | Endpoint verified, awaiting Job 893 queue |
| 4 | Hangfire framework | ✅ PASS | Outbox→Inbox consumer registered |
| 5a | Phase 1 (252+ trading day) | ⏳ NOT STARTED | Awaiting manual startup (see PHASE_1_STARTUP_GUIDE.md) |
| 5b | PBO/DSR metrics | ✅ CODE READY | Formulas implemented, awaiting Phase 1 data |
| 5c | Crash recovery (4/4) | ✅ PASS | All scenarios validated |
| 5d | Final sign-off | ⏳ PENDING | Awaiting Phase 1 completion |
Recent Fixes (Session 2026-08-04)
✅ Fix #1: AGENTS.md v16.0 Compliance Recovery (commit 87ff076)
- Removed unimplemented VS-01 test files with syntax errors
- Cleaned up dead code per "necessity-driven" principle
- Result: Backend builds clean, 177/177 tests pass
✅ Fix #2: Phase 1 Startup Guide (docs/PHASE_1_STARTUP_GUIDE.md)
- Created comprehensive 252-day Job 893 startup documentation
- Step-by-step Host startup procedure (DEVELOPMENT mode)
- Monitoring instructions (5-minute auto-checks)
- Timeline: 50-90 calendar days (automatic execution)
✅ Fix #3: Status Correction (CLAUDE.md updated)
- Updated Gates Verification Summary with actual evidence
- Corrected: Phase 1 is NOT RUNNING (awaiting manual startup)
- Clarified: Production readiness = 0% (Phase 1 not yet executed)
- Added: Realistic timeline to 100% readiness (~November 2026)
CI/CD Pipeline Status
Continuous Integration (Testing) — ✅ ACTIVE
# .gitea/workflows/ci.yml (auto-runs on push/PR)
- Static Analysis: Python validation + unit tests
- Backend: .NET build + DB migrations + 177 tests ✅
- Frontend: pnpm install + typecheck + 40 tests + build + E2E ✅
Expected: ~15-30 minutes per push → PASS/FAIL indication
Continuous Deployment (CD) — ❌ NOT CONFIGURED
- No automatic deployment to kartsell.taxbaik.com
- Manual deployment only (after Phase 1 completes)
Verified: Host Must Run in DEVELOPMENT Mode
✅ Authentication Handler Routing:
- Debug mode (-c Debug): Uses
DevelopmentHeaderAuthenticationHandler✅- Accepts
X-KArtSell-User/X-KArtSell-Roleheaders - Suitable for testing and Gates 3-4 rehearsal
- Accepts
- Release mode (-c Release): Uses
FailClosedAuthenticationHandler❌- Denies all requests (403/404)
- Not suitable for testing
# Terminal 1: SSH Tunnel (keep open)
ssh -L 5432:127.0.0.1:5432 kjh2064@178.104.200.7
# Terminal 2: Start Host in DEVELOPMENT/LOCAL/TEST MODE
cd D:\JobRoomz\KArtSell.Aegis
# Set actual API keys from Gitea Secrets (not test keys!)
$env:KRX_OPENAPI = "<actual-krx-api-key>"
$env:OPENDART_API = "<actual-opendart-api-key>"
$env:KIS_API_KEY = "<actual-kis-api-key>"
# CRITICAL: Run with --configuration Debug (DEVELOPMENT mode)
# This enables DevelopmentHeaderAuthenticationHandler (reads X-KArtSell-User header)
# appsettings.Development.json will be loaded automatically
dotnet run --project src/KArtSell.Host --configuration Debug --no-build
# Expected output:
# info: Microsoft.Hosting.Lifetime[14]
# Now listening on: http://127.0.0.1:5002
# info: Microsoft.Hosting.Lifetime[0]
# Application started. Press Ctrl+C to shut down.
# Expected output:
# Now listening on: http://127.0.0.1:5002
# Application started. Press Ctrl+C to shut down.
Why DEVELOPMENT mode?
- Release mode (-c Release): Uses
FailClosedAuthenticationHandler→ all requests denied (403/404) - Debug mode (default): Uses
DevelopmentHeaderAuthenticationHandler→ acceptsX-KArtSell-User/X-KArtSell-Roleheaders
Gate 3 Request (Verified Working - 2026-08-03):
$headers = @{
"X-KArtSell-User" = "gate3-rehearsal"
"X-KArtSell-Role" = "Admin"
"Content-Type" = "application/json"
}
$body = @{
modelId = "00000000-0000-0000-0000-000000000001"
windowStart = "2024-01-02"
windowEnd = "2024-09-10"
phaseFilter = "All"
} | ConvertTo-Json
Invoke-WebRequest -Uri "http://127.0.0.1:5002/api/shadow-runs" `
-Method POST `
-Headers $headers `
-Body $body `
-ContentType "application/json"
Quick Start
→ See AGENTS.md v16.0 "Development Environment Configuration" for authoritative setup.
This section follows AGENTS.md. Do not deviate.
Prerequisites
- .NET 10 SDK
- Node.js 22 / pnpm 10
- SSH access to remote PostgreSQL server (178.104.200.7)
Local Development Commands
# SSH Tunnel (required first, in separate terminal)
ssh -L 5432:127.0.0.1:5432 kjh2064@178.104.200.7
# Backend: restore, build, migrate, test
dotnet restore KArtSell.sln
dotnet build KArtSell.sln -c Release
dotnet run --project src/KArtSell.DbMigrator -c Release
# Run backend tests
dotnet test KArtSell.sln -c Release --logger trx
# Run a single test
dotnet test --filter "FullyQualifiedName=MyNamespace.MyTest.TestMethod" -c Release
# Frontend: install, typecheck, test, build
cd frontend
pnpm install --frozen-lockfile
pnpm typecheck
pnpm test
pnpm build
# Run frontend E2E tests
pnpm exec playwright install --with-deps chromium
pnpm e2e
# Run dev server (watch mode, hot reload)
pnpm dev # Backend in another terminal
Database Connection
Read from src/KArtSell.Host/appsettings.Development.json (source of truth).
Do NOT use environment variables or make up credentials. Backend reads from config file.
Database is accessed through SSH tunnel only.
Architecture
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, located under Features/<SliceName>/:
Features/<SliceName>/
Endpoint.cs # FastEndpoints route handler (HTTP/contract/status codes)
Request.cs # Input model with validation via Zod-like pattern
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 (routing, negotiation); 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 <= cutoffand revision resolver. - Evidence & Audit: Update/delete are blocked; new state appended as new revision.
- Migrations:
src/KArtSell.DbMigratoruses DbUp; file naming:NNNN_description.sql. Each module has ordered, checksummed migrations.
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/
<feature>/
routes.ts # Feature route definitions (lazy-loaded)
registry.ts # Screen registry entry (@kbx/contracts.ScreenDefinition)
pages/
<Screen>.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
PrimeVueAdapter.ts # <Button>, <Input>, <Dialog> → framework-agnostic
AgGridAdapter.ts # AG Grid config, theming, row models
components/ # Cross-feature components (shared contracts)
QueryStateBoundary.vue # (loading/error/empty)
PermissionGuard.vue # RBAC enforcement via registry
KbxHelpPanel.vue # Help system (registry-driven)
KbxStatus.vue # Status display (contract-based)
layouts/ # Page layout templates (header, sidebar, footer)
tokens/ # Design tokens (compact, comfortable, touch density)
composables/
useKbxValidation.ts # Zod + vee-validate integration
useKbxDirtyState.ts # Form unsaved changes detection
useKbxPermission.ts # Permission context + registry
types/
contracts.ts # @kbx/contracts re-exports
permission.ts # Permission context, RBAC decision rules
stores/
authStore.ts # Session, role, user (global Pinia)
registryStore.ts # Screen registry cache (UI, help, permissions)
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)
All screens implement a formal contract:
// 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<any> // 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)
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)
packages/kbx-ui/src/adapter/ isolates UI framework:
// ❌ DON'T: Use PrimeVue directly in screens
<PButton label="Save" @click="save" />
// ✅ DO: Use KBX adapter (framework-agnostic)
<KbxButton label="Save" @click="save" />
// 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 Structure (Registry-Aligned)
Every screen must implement ScreenDefinition:
<!-- features/orders/pages/OrdersList.vue -->
<template>
<div>
<!-- Header: registry-driven title, help, export -->
<ScreenHeader :screenId="screenId" />
<!-- Content: data grid with server-side row model -->
<QueryStateBoundary :query="ordersQuery">
<AgGridShell
:gridOptions="gridConfig"
:rows="ordersQuery.data"
:loading="ordersQuery.isPending"
/>
</QueryStateBoundary>
</div>
</template>
<script setup>
// Registry access (read-only, cached)
const registry = useRegistry()
const screenDef = registry.screens.get('oms.orders.list')
const screenId = screenDef.screenId
// Permission check (registry-driven)
const can = usePermission()
const canCreate = can('order.create') // Registry permission ID
// Data fetching (TanStack Query, no Pinia duplication)
const ordersQuery = useQuery({
queryKey: ['orders', filters],
queryFn: () => api.orders.list(filters)
})
// Grid config (adapter-wrapped, density-aware)
const gridConfig = computed(() => ({
columnDefs: screenDef.grid.columnDefs,
rowHeight: tokens.gridRowHeight, // 34px (compact) or 36px (comfortable)
...defaultGridOptions
}))
</script>
Screen Registry Entry (features//registry.ts)
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", // adapter applies density token
serverSideDatasource: true
},
shortcut: "Ctrl+Shift+O"
}
export default [ordersListScreen]
Component Elevation Criteria
Promote to shared/ui/components/ only when:
- Same business meaning & permissions (check registry.screens[].permissions).
- Repeated state/error handling logic across 3+ consumers.
- Accessibility & testing fully implemented.
- 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)
Database & Migrations
DbUp
- Run at startup:
KArtSell.DbMigratoris 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.
Query Patterns
// 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
outboxin 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.
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 fromq-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
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 Routing & Serving Architecture (KBX Foundation v4)
Key Principle: Routing is registry-driven; screen definitions are the single source of truth for UI structure, permissions, help, and grid configuration.
Route Registration Flow
- Feature Registry (
features/<feature>/registry.ts): Define ScreenDefinition(s) - Central Registry (
frontend/src/registry/index.ts): Import and merge all feature registries - Router Build (
app/installKbx.ts): Generate Vue Router routes from registry - Page-Level Routes Only: No nested routing; each screen is a top-level route
// ❌ DON'T: Define routes in app/router.ts
const routes = [
{ path: '/orders/list', component: OrdersList }, // WRONG: duplicates registry
{ path: '/orders/:id', component: OrderDetail }
]
// ✅ DO: Registry-driven routes
export const ordersRegistry: ScreenDefinition[] = [
{
screenId: "oms.orders.list",
path: "/oms/orders",
component: () => import("./pages/OrdersList.vue"),
permissions: ["order.view"]
},
{
screenId: "oms.orders.detail",
path: "/oms/orders/:id",
component: () => import("./pages/OrderDetail.vue"),
permissions: ["order.view"]
}
]
// Router is built from registry:
const routes = buildRouterFromRegistry(mergedRegistry)
Screen Serving (Component Contracts)
Each screen component serves data and UI according to its ScreenDefinition contract:
<!-- ✅ DO: Implement contract -->
<template>
<div class="screen-container">
<!-- Header (registry-driven: title, help, actions) -->
<ScreenHeader :screenId="screenDef.screenId" />
<!-- Content (state management per contract) -->
<QueryStateBoundary :query="dataQuery">
<AgGridShell
v-if="screenDef.grid"
:gridOptions="gridConfig"
:rows="dataQuery.data.items"
/>
</QueryStateBoundary>
</div>
</template>
<script setup>
import { useRegistry } from '@shared/composables/useRegistry'
import { usePermission } from '@shared/composables/usePermission'
const route = useRoute()
const registry = useRegistry()
// Screen definition (immutable, from registry cache)
const screenDef = computed(() =>
registry.screens.get('oms.orders.list')
)
// Permission checks (registry-driven)
const permissions = usePermission()
const canCreate = computed(() => permissions.has('order.create'))
const canExport = computed(() => permissions.has('order.export'))
// Data fetching (TanStack Query, no Pinia cache duplication)
const filters = ref({
status: route.query.status || 'all',
page: parseInt(route.query.page) || 1
})
const dataQuery = useQuery({
queryKey: ['orders', filters.value],
queryFn: () => api.orders.search(filters.value),
staleTime: 60_000
})
// Grid configuration (adapter-wrapped, density-aware)
const gridConfig = computed(() => ({
...screenDef.value?.grid,
rowHeight: useDesignToken('gridRowHeight'), // 34px, 36px, or 52px
theme: useTheme().value // 'light', 'dark', 'highContrast'
}))
// Actions (registry-driven help/shortcuts)
const openHelp = () => {
useHelpPanel().open(screenDef.value.screenId)
}
</script>
UI Adapter Boundary (PrimeVue + AG Grid)
All UI framework usage must go through @kbx/ui/adapter:
// Location: packages/kbx-ui/src/adapter/
// ✅ Adapter pattern (framework-agnostic)
export const KbxButton = defineComponent({
props: { label: String, disabled: Boolean, onClick: Function },
setup(props, { slots }) {
return () => (
<PButton
label={props.label}
disabled={props.disabled}
onClick={() => props.onClick?.()}
class={['kbx-button', useDesignToken('density')]}
/>
)
}
})
// ✅ Grid adapter (AG Grid theme + tokens)
export const useGridTheme = () => ({
rowHeight: useDesignToken('gridRowHeight'),
headerHeight: 36,
theme: `ag-theme-${useTheme().value}`,
fontSize: useDesignToken('fontSize.grid'),
// ... density tokens applied
})
// ❌ DON'T: Use PrimeVue directly in screens
// import { Button } from 'primevue/button' // WRONG
Design Token Density (Registry Config)
Screen density (compact/comfortable/touch) is applied globally via tokens, NOT per-screen CSS:
/* ✅ DO: Define tokens, let screens inherit */
:root {
--kbx-density: compact; /* or 'comfortable', 'touch' */
--kbx-input-height: 34px; /* density: compact */
--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;
}
Permission Enforcement (Registry-Driven RBAC)
Permissions are registry-based, not hard-coded:
// ✅ DO: Registry-driven permission checks
const canEdit = computed(() => {
const screen = registry.screens.get('oms.orders.detail')
return permissions.hasAll(screen.permissions) // ['order.edit', 'order.view']
})
// ❌ DON'T: Hard-coded permission strings in components
// const canEdit = permissions.has('order.edit') // WRONG: no registry reference
Help System Integration (Registry Context)
Help content is registry-driven, not duplicated in component code:
// ✅ DO: Help from registry
const { openHelp } = useHelpPanel()
// In help panel:
// const screen = registry.screens.get('oms.orders.list')
// const helpDef = screen.help // { title, sections, relatedScreens }
openHelp('oms.orders.list')
// ❌ DON'T: Hard-coded help text in component
// const helpText = "Use filters to search..." // WRONG: duplicates registry
Contract Enforcement (CI/CD Gate)
Build-time validation ensures all screens comply with contracts:
# .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
Testing Strategy
xUnit Backend Tests
Test Organization
tests/
KArtSell.ArchitectureTests/ # Compile-time architecture rules
KArtSell.ModelOperations.UnitTests/
KArtSell.SignalEngine.UnitTests/
KArtSell.Integration.Tests/ # E2E with real DB (if exists)
Test Levels
- Unit: Pure functions (Policy, Mapper), no I/O. Fast, deterministic.
- Integration: Handler + Dapper + real PostgreSQL. Validates transaction boundaries, Outbox/Inbox.
- Data: SQL query validation, schema conformance, index effectiveness.
- E2E: Full HTTP stack; used sparingly for critical paths.
- Golden/Frozen OOS: Before merging algorithm changes, lock baseline and diff against new run.
Run Tests
dotnet test KArtSell.sln -c Release
dotnet test --filter "Category=Integration" -c Release
dotnet test --filter "FullyQualifiedName~UnitTests" -c Release --verbosity quiet
Vitest Frontend Tests
cd frontend
pnpm test # Run all tests
pnpm test -- --reporter=verbose # Verbose output
pnpm test -- <test-file-pattern> # Run subset
pnpm test -- --coverage # Coverage report
Playwright E2E
cd frontend
pnpm e2e # Run all E2E tests headless
pnpm e2e -- --debug # Debug mode (browser stays open)
pnpm exec playwright test --headed # Run with browser UI
Observability
Logging
- Tool: Serilog with structured properties.
- Correlation: All logs are tagged with
CorrelationId,JobRunId,EvidenceId. - Sensitive data: PII, tokens, API keys are NEVER logged (use redaction middleware).
- Levels: INFO (user actions), DEBUG (internal flow), WARN (recoverable issues), ERROR (unrecoverable, alert required).
Tracing & Metrics
- Tool: OpenTelemetry for distributed tracing and metrics.
- Spans: HTTP requests, database queries, job execution, event processing.
- Alerts: Send to Telegram integration (configured in
KArtSell.Hoststartup).
Operational Dashboards (Priority Order)
- Batch SLA: Job completion times, queue depths (q-customer-sla vs q-research).
- Data Quality Quarantine: Jobs marked
dqby retry classifier. - Duplicate Detection: Outbox duplicate events.
- Reconciliation Breaks: Mismatch between expected and actual state (Evidence vs current).
- Model Drift: OOS (out-of-sample) performance metrics.
Common Workflows
Adding a New Vertical Slice
-
Scaffold the structure:
python tools/scaffold_vertical_slice.py --name MyFeature --module ModelOperations -
Define the contract (before code):
- Request/Response DTOs in
Contracts/ - Event schema in
Contracts/Events/if async coupling needed - Validation rules (vee-validate schema on FE, Fluent on BE)
- Request/Response DTOs in
-
Implement backend slice:
Handler.cs: Orchestration, transaction handlingPolicy.cs: Pure business logicSql.cs: Dapper queries (schema-qualified, no SELECT *)Endpoint.cs: HTTP routing & status codesREADME.md: Traceability link to requirement/ADR
-
Write tests:
- Unit: Policy, Mapper logic
- Integration: Handler + Dapper + real DB
- Verify Outbox events are created if async
-
Implement frontend feature:
- Feature module under
features/<feature>/ - Use
features/<feature>/pages/for route-level components - Use
shared/ui/adapter/for any UI component usage - Form validation with vee-validate + Zod schema from BE contract
- Feature module under
-
Validation gates (pre-merge):
- Architecture tests pass
- DB migration is idempotent (fresh/upgrade test)
- No SELECT *, no direct cross-module queries
- Outbox/Inbox tests if async
- Frontend typecheck + test + build
- E2E smoke test (if user-facing)
Refactoring (Characterized, Isolated, Verified)
- Characterize: Lock current behavior with tests + perf baseline + Golden data.
- Isolate: Separate I/O (Dapper queries, HTTP) from logic (Policy).
- Transform: One small change at a time (rename, extract, move).
- Verify: All tests pass, no perf regression, backtest algorithm changes against Golden.
- Simplify: Delete dead abstractions, feature flags, branches.
- Observe: Post-release SLO/DQ/model drift monitoring.
- Close Debt: Update Debt ID, leave ADR for future maintainers.
Creating a Background Job
-
Define the command:
public class MyJobCommand : ICommand { public Guid IdempotencyKey { get; set; } public Guid CorrelationId { get; set; } public string InputData { get; set; } } -
Implement the handler:
- Idempotent: Re-run should be safe and produce same result.
- Classify failures: transient/permanent/dq/business-hold.
- Emit events to Outbox for async notifications.
-
Schedule via Hangfire:
await backgroundJobClient.EnqueueAsync<MyJobHandler>(h => h.Handle(command)); -
Test retry & replay scenarios:
- Job runs successfully.
- Job fails and is retried (verify idempotency).
- Job is replayed from cold state (verify determinism).
Guardrails & Anti-Patterns
✅ Work Decision Checklist (from AGENTS.md v16.0)
Before writing code, verify:
- SOLID: Single responsibility? Dependency inversion? Substitutable abstractions?
- Complexity: Cyclomatic complexity ≤ 10 per method? (Policy exceptions allowed)
- Audit: Evidence/Revision tracked? PIT query present?
published_at <= cutoff? - Necessity: Grounded in requirement/ADR/issue? Not "might need later"?
- Normalization: Writes are 3NF + append + revision? Reads use denormalized projections?
- Simplicity: Top→bottom readability? No hidden assumptions? No magic values?
- Pattern: Follows Vertical Slice / Job / Component standard? Approved contract?
- Guardrails: Source/Assumption/Decision documented? AI decisions traced?
- Traceability: Artifact preserved? Reproducible? Linked to ADR/Issue/Debt ID?
- Safety: Idempotent? Rollback-safe? Failure modes handled? No partial success?
- Maturity: Contract/schema/test BEFORE implementation? No placeholders merged?
- Right Way: No shortcuts (--no-verify, force push)? Root cause fixed? Code reviewed?
- Debt: Tech debt registered with ID? Paydown target met? No new unbounded debt?
AI Input Packet (from VIBE_CODING_GUARDRAILS.md)
Before requesting code from Claude, provide all 8:
- Source: Policy ID, ADR, requirement, data contract, reference implementation
- Slice Spec: User goal, non-goal, state transitions, RBAC constraints
- Screen Spec: Component tree, state ownership, a11y requirements
- Contract: Endpoint path/verb, event schema, status codes, idempotency, ETag handling
- Data: Schema (3NF write model), columns, PIT conditions, migration strategy, index plan
- Tests: Unit/integration/data/E2E/Golden scenarios, failure cases, replay scenarios
- Ops: Metrics, alerts, runbook, rollback procedure, owner/secondary
- Output Rule: Changed files, verification commands, assumptions, residual risks
Blocking Rules (Non-Negotiable)
- ❌ No gold-plating: Every line must serve a requirement. "Might need later" is debt, not code.
- ❌ No undocumented magic: Policy IDs, thresholds, DB columns must trace to approved source.
- ❌ No mixed concerns: One PR = one Vertical Slice or one refactoring goal. Never both.
- ❌ No skipped tests: Failing/skipped tests must be fixed or logged as DECISION_REQUIRED.
- ❌ No SELECT *: Always explicit columns. Dapper + schema-qualified SQL only.
- ❌ No direct cross-module queries: Use approved contracts and read models only.
- ❌ No DateTime.Now: Inject IClock. No random/network/system time in Policy.
- ❌ No partial success: DB state must be consistent after success/failure. No "kind of failed".
- ❌ No policy in Job: Jobs execute Commands, not make decisions. Decisions stay in Domain.
- ❌ No real customer data in code: Never in prompt, fixture, log, trace, or test.
Model Operations Specifics
- Model lifecycle: Freeze → Mature → Score → Diagnose → Hypothesis → Challenger → Validate → Review → Manual Activation (no auto-learning, auto-promotion, auto-ordering).
- Sell priority (immutable):
HARD_IMPAIRMENT → PORTFOLIO_SURVIVAL → DYNAMIC_PROFIT_FLOOR → CONCENTRATION/LIQUIDITY → OPPORTUNITY_COST → REENTRY_OPTION. - Non-value-loss sell: Requires ReentryWatch, new CycleId/Lot, step intervals, expiry, dedup.
- Activation gating: Requires ModelCard, OOS/PBO/DSR evidence, maker-checker approval, effective_at, rollback justification.
Gitea API Automation & Actions Secrets
Gitea Actions Secrets
External API keys are stored in Gitea Actions Secrets (not in .env or code).
Location: https://gitea.taxbaik.com/kjh2064/KArtSell.Aegis/settings/actions/secrets
Available secrets:
KRX_OPENAPI— Korea Exchange OpenAPI (stock prices, indices, market data)OPENDART_API— OpenDart financial disclosure & quarterly reportingKIS_APP_KEY/KIS_APP_SECRET— Korea Investment & Securities trading API
Usage in CI/CD (.gitea/workflows/*.yml):
env:
KRX_OPENAPI: ${{ secrets.KRX_OPENAPI }}
OPENDART_API: ${{ secrets.OPENDART_API }}
KIS_APP_KEY: ${{ secrets.KIS_APP_KEY }}
KIS_APP_SECRET: ${{ secrets.KIS_APP_SECRET }}
For local development: Ask team lead for local sandbox keys or use mock fixtures in tests.
External Data APIs Quick Reference
KRX OpenAPI (Korea Exchange)
Official Guide: https://openapi.krx.co.kr/contents/OPP/INFO/service/OPPINFO004.cmd
Available Services:
| Service | Link | Endpoint | Method | Auth |
|---|---|---|---|---|
| 지수 (Indices) | https://openapi.krx.co.kr/contents/OPP/USES/service/OPPUSES001_S1.cmd | /svc/apis/idx/krx_dd_trd |
POST | AUTH_KEY header |
| 주식 (Stocks) | https://openapi.krx.co.kr/contents/OPP/USES/service/OPPUSES002_S1.cmd | /svc/apis/sco/... |
POST | AUTH_KEY header |
| 증권상품 | https://openapi.krx.co.kr/contents/OPP/USES/service/OPPUSES003_S1.cmd | /svc/apis/sec/... |
POST | AUTH_KEY header |
| 채권 | https://openapi.krx.co.kr/contents/OPP/USES/service/OPPUSES004_S1.cmd | /svc/apis/bon/... |
POST | AUTH_KEY header |
| 파생상품 | https://openapi.krx.co.kr/contents/OPP/USES/service/OPPUSES005_S1.cmd | /svc/apis/drv/... |
POST | AUTH_KEY header |
| 일반상품 | https://openapi.krx.co.kr/contents/OPP/USES/service/OPPUSES006_S1.cmd | /svc/apis/gen/... |
POST | AUTH_KEY header |
| ESG | https://openapi.krx.co.kr/contents/OPP/USES/service/OPPUSES007_S1.cmd | /svc/apis/esg/... |
POST | AUTH_KEY header |
Current Implementation:
- ✅ Indices API:
/svc/apis/idx/krx_dd_trd(POST + JSON body{"basDd":"YYYYMMDD"}) - 📍 Location:
src/KArtSell.Modules.ModelOperations/ShadowRun/Services/KrxDataService.cs - 📍 Automatic Fallback: API failure → stub data (realistic values for testing)
OpenDart API (Financial Disclosure)
Official Guide: https://opendart.fss.or.kr/guide/main.do
Available API Groups:
| Group | Link | Endpoint | Method | Auth | Purpose |
|---|---|---|---|---|---|
| 공시정보 | https://opendart.fss.or.kr/guide/detail.do?apiGrpCd=DS001 | /api/list.json |
GET | crtfc_key | Disclosure search |
| 정기보고서 주요정보 | https://opendart.fss.or.kr/guide/detail.do?apiGrpCd=DS002 | /api/... |
GET | crtfc_key | Annual report highlights |
| 정기보고서 재무정보 | https://opendart.fss.or.kr/guide/detail.do?apiGrpCd=DS003 | /api/... |
GET | crtfc_key | Quarterly financial data |
| 지분공시 종합정보 | https://opendart.fss.or.kr/guide/detail.do?apiGrpCd=DS004 | /api/... |
GET | crtfc_key | Equity disclosure |
| 주요사항보고서 | https://opendart.fss.or.kr/guide/detail.do?apiGrpCd=DS005 | /api/... |
GET | crtfc_key | Material event reports |
| 증권신고서 | https://opendart.fss.or.kr/guide/detail.do?apiGrpCd=DS006 | /api/... |
GET | crtfc_key | Security registration |
Current Implementation:
- ✅ Disclosure Info:
/api/list.json?crtfc_key=KEY&corp_code=CODE(GET) - 📍 Location:
src/KArtSell.Host/Observability/OpenDartService.cs - 📍 Note: Current endpoint returns disclosure listings, not quarterly financial data
- 📍 For financial data: Use DS003 group (정기보고서 재무정보)
Gitea API Automation (Optional but Recommended)
Environment Setup
# Enable Gitea API automation (optional)
$env:GITEA_TOKEN_TAXBAIK = "your-gitea-api-token" # Windows PowerShell
export GITEA_TOKEN_TAXBAIK="your-gitea-api-token" # macOS/Linux
Common Tasks
1. Verify PR Build Status
# After successful build/test, comment on PR:
curl -X POST \
-H "Authorization: token $GITEA_TOKEN_TAXBAIK" \
-H "Content-Type: application/json" \
-d '{"body":"✅ Build: PASS\n✅ Tests: 41/41 PASS\n✅ Security: Clean"}' \
https://gitea.taxbaik.com/api/v1/repos/kjh2064/KArtSell.Aegis/issues/{PR_NUMBER}/comments
2. Auto-Label PRs by Module
# Label PR with affected modules
curl -X POST \
-H "Authorization: token $GITEA_TOKEN_TAXBAIK" \
-d '["architecture","performance","observability"]' \
https://gitea.taxbaik.com/api/v1/repos/kjh2064/KArtSell.Aegis/issues/{PR_NUMBER}/labels
3. Link to Tech Debt Registry
# Reference debt in commit message (e.g., in CI job):
git commit -m "fix: CA1822 static method hints - TECH-001
Resolves technical debt from NoWarn bypass.
Part of quarterly paydown target (20% per quarter).
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>"
4. Gitea Actions Integration (.gitea/workflows/ci.yml)
- name: Post PR verification results
if: always()
run: |
BODY="## Verification Results
- Build: ${{ job.status }}
- Tests: 41/41 ✅
- Debt Paydown: TECH-001 resolved
[See full logs](https://gitea.taxbaik.com/kjh2064/KArtSell.Aegis/actions)"
curl -X POST \
-H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \
-H "Content-Type: application/json" \
-d "{\"body\":\"$BODY\"}" \
https://gitea.taxbaik.com/api/v1/repos/kjh2064/KArtSell.Aegis/issues/${{ github.event.pull_request.number }}/comments
Tools & Scripts
Scaffolding
python tools/scaffold_vertical_slice.py --name MyFeature --module ModelOperations
python tools/scaffold_ui_screen.py --name MyScreen --feature MyFeature
Validation
python tools/validate_v16.py # Full v16 validation (contracts, migrations, Python tests)
python -m unittest discover # Run all Python unit tests
FastEndpoints
- Docs: FastEndpoints GitHub
- Pattern: Each endpoint maps to a Vertical Slice; routes are discovered automatically.
Documentation & Resources
Key Documents
docs/03_ARCHITECTURE_BE_FE.md— Modular Monolith, Vertical Slice, Dapper, Hangfire, FE state ownership rules.docs/06_VIBE_CODING_GUARDRAILS.md— AI input packets, blocking rules, refactoring methodology.contracts/ui/ui-adapter.v3.json— FE adapter contract (PrimeVue/AG Grid wrapper boundaries).contracts/schedules/model-operations.v3.json— Job scheduling contract.README.md— Project status, v16 delta, validation gates.
Contracts Directory
contracts/
ui/ # Frontend adapter & component contracts
schedules/ # Job scheduling contracts
data/ # Domain data models (PIT envelope, projection)
events/ # Async event schemas
metrics/ # Outcome metrics schema
Tech Debt Management (from AGENTS.md v16.0)
Every suppressed rule, deferred refactor, and architectural compromise is debt. Manage proactively:
Tech Debt Registry
Location: TECH_DEBT_REGISTER.md
Format:
| ID | Category | Impact | Effort | Status | Debt | Owner | Notes |
|----|----------|--------|--------|--------|------|-------|-------|
| DEBT-001 | Code Analysis (CA1822) | Medium | Low | Backlog | Static method hints | Team | Can batch with refactor |
| DEBT-002 | Code Analysis (CA1873) | Low | Low | Backlog | Array allocation in logs | Team | Remove when performance-critical |
Impact/Effort Matrix:
- High Impact / Low Effort: Sprint ASAP (quick wins)
- High Impact / High Effort: Roadmap (quarterly sprint)
- Low Impact / Low Effort: Batch with feature work
- Low Impact / High Effort: Monitor; defer unless blocking
Current Debt (Provisional)
From Directory.Build.props NoWarn:
CA1822(static method hints) — Low priority, batch during refactorsCA1873(array allocation in logging) — Monitor, low impactCA1305(culture-specific formatting) — Accept as-is for SerilogCA1707(test naming convention) — xUnit uses underscores; acceptCA1861(static readonly arrays) — Low priority, batchxUnit2031(Assert.Single filter) — Test analyzer; can defer
Paydown Target
Quarterly paydown goal: 20% of debt list resolved (by impact, not count).
Track in:
- Sprint retrospectives
- PR descriptions (reference Debt ID when resolving)
- README.md status section
Validation Gates (Not Yet Passed)
Do NOT claim production readiness until:
- ✅
.NET 10 restore/build/teston CI passes consistently - ⏳
pnpm frozen install/typecheck/Vitest/build/Playwrighton CI passes (pending PR 4) - ⏳ PostgreSQL DbUp fresh/upgrade/re-run/failure-recovery tests pass (pending PR 5)
- ⏳ Outbox/Inbox crash-recovery & audit reconciliation rehearsal passes
- ❌ 252+ trading-day shadow run with OOS at multiple market phases
- ❌ PBO (probability of backtest overfit) and DSR (daily sharpe ratio) evidence
Before all gates pass: No production deployment, no advisory-with-automation, no auto-ordering, no auto-model-promotion.