Files
KArtSell.Aegis/CLAUDE.md
T
kjh2064 9d541a5982 docs: GOVERNANCE LOCK — AGENTS.md is the ONLY source of guidelines
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>
2026-08-15 10:55:10 +09:00

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:

  1. Identify which work can proceed immediately (not blocked by dependencies)
  2. Accelerate and automate all non-blocking phases
  3. Only wait for truly blocking dependencies (e.g., external data collection)
  4. 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-Role headers
    • Suitable for testing and Gates 3-4 rehearsal
  • 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 → accepts X-KArtSell-User / X-KArtSell-Role headers

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 <= 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. 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:

  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)

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.

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 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.

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

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

  1. Feature Registry (features/<feature>/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
// ❌ 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

  1. Unit: Pure functions (Policy, Mapper), no I/O. Fast, deterministic.
  2. Integration: Handler + Dapper + real PostgreSQL. Validates transaction boundaries, Outbox/Inbox.
  3. Data: SQL query validation, schema conformance, index effectiveness.
  4. E2E: Full HTTP stack; used sparingly for critical paths.
  5. 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.Host startup).

Operational Dashboards (Priority Order)

  1. Batch SLA: Job completion times, queue depths (q-customer-sla vs q-research).
  2. Data Quality Quarantine: Jobs marked dq by retry classifier.
  3. Duplicate Detection: Outbox duplicate events.
  4. Reconciliation Breaks: Mismatch between expected and actual state (Evidence vs current).
  5. Model Drift: OOS (out-of-sample) performance metrics.

Common Workflows

Adding a New Vertical Slice

  1. Scaffold the structure:

    python tools/scaffold_vertical_slice.py --name MyFeature --module ModelOperations
    
  2. 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)
  3. Implement backend slice:

    • Handler.cs: Orchestration, transaction handling
    • Policy.cs: Pure business logic
    • Sql.cs: Dapper queries (schema-qualified, no SELECT *)
    • Endpoint.cs: HTTP routing & status codes
    • README.md: Traceability link to requirement/ADR
  4. Write tests:

    • Unit: Policy, Mapper logic
    • Integration: Handler + Dapper + real DB
    • Verify Outbox events are created if async
  5. 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
  6. 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)

  1. Characterize: Lock current behavior with tests + perf baseline + Golden data.
  2. Isolate: Separate I/O (Dapper queries, HTTP) from logic (Policy).
  3. Transform: One small change at a time (rename, extract, move).
  4. Verify: All tests pass, no perf regression, backtest algorithm changes against Golden.
  5. Simplify: Delete dead abstractions, feature flags, branches.
  6. Observe: Post-release SLO/DQ/model drift monitoring.
  7. Close Debt: Update Debt ID, leave ADR for future maintainers.

Creating a Background Job

  1. Define the command:

    public class MyJobCommand : ICommand
    {
        public Guid IdempotencyKey { get; set; }
        public Guid CorrelationId { get; set; }
        public string InputData { get; set; }
    }
    
  2. 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.
  3. Schedule via Hangfire:

    await backgroundJobClient.EnqueueAsync<MyJobHandler>(h => h.Handle(command));
    
  4. 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:

  1. Source: Policy ID, ADR, requirement, data contract, reference implementation
  2. Slice Spec: User goal, non-goal, state transitions, RBAC constraints
  3. Screen Spec: Component tree, state ownership, a11y requirements
  4. Contract: Endpoint path/verb, event schema, status codes, idempotency, ETag handling
  5. Data: Schema (3NF write model), columns, PIT conditions, migration strategy, index plan
  6. Tests: Unit/integration/data/E2E/Golden scenarios, failure cases, replay scenarios
  7. Ops: Metrics, alerts, runbook, rollback procedure, owner/secondary
  8. 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 reporting
  • KIS_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 (정기보고서 재무정보)

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 refactors
  • CA1873 (array allocation in logging) — Monitor, low impact
  • CA1305 (culture-specific formatting) — Accept as-is for Serilog
  • CA1707 (test naming convention) — xUnit uses underscores; accept
  • CA1861 (static readonly arrays) — Low priority, batch
  • xUnit2031 (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/test on CI passes consistently
  • pnpm frozen install/typecheck/Vitest/build/Playwright on 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.