07ad98ec12
## Summary - **CLAUDE.md optimization:** Move engineering guidelines to AGENTS.md only (governance lock) - Removed: Governance, Testing Strategy, Observability details, Common Workflows, Guardrails - Kept: Project status, timeline, architecture high-level overview, quick reference - Result: 47KB → 12.1KB (75% reduction, well within 40KB limit) - **AGENTS.md expansion:** Add 5 missing engineering procedure sections - v16.0 Testing Strategy (xUnit/Vitest/Playwright organization, commands, rules) - v16.0 Backend Architecture (Vertical Slice, Database/Migrations, Hangfire Job Design) - v16.0 Frontend Architecture (Registry-driven screens, KBX contracts, UI adapter boundary) - v16.0 Observability (Logging, Tracing, Dashboards, Metrics) - v16.0 Common Workflows (Adding Vertical Slices, Refactoring, Creating Jobs) - **New companion docs** (no duplication, supplement AGENTS.md): - docs/ARCHITECTURE_DETAILED.md — Deep dive on backend/frontend patterns - docs/COMMON_WORKFLOWS.md — Workflow procedures with examples - docs/GITEA_API_REFERENCE.md — Gitea API + External data sources ## Governance (enforced) - All engineering procedures now in AGENTS.md ONLY - CLAUDE.md = project context only (status, timeline, overview) - Companion docs reference AGENTS.md (no duplicate guidance) - No conflicting guidance across multiple sources ## Result - CLAUDE.md: 12.1KB ✅ (within 40KB limit) - AGENTS.md: 44.8KB (comprehensive procedures) - Single source of truth for all engineering guidelines Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
946 lines
48 KiB
Markdown
946 lines
48 KiB
Markdown
# K-ArtSell Aegis AI Coding Constitution v16.0
|
||
|
||
## 🔒 GOVERNANCE LOCK
|
||
|
||
**AGENTS.md IS THE ONLY AUTHORITATIVE SOURCE FOR ENGINEERING GUIDELINES.**
|
||
|
||
**Rules (Non-negotiable):**
|
||
1. **All engineering procedures, harnesses, and decision frameworks go in AGENTS.md only.**
|
||
2. **CLAUDE.md, GEMINI.md, and all other .md files follow AGENTS.md. They do NOT define rules.**
|
||
3. **If any document conflicts with AGENTS.md, AGENTS.md wins. Other text is void.**
|
||
4. **Never add guidelines to CLAUDE.md, GEMINI.md, or side documents.**
|
||
5. **Supplementary files reference AGENTS.md with explicit links only.**
|
||
|
||
**Scope:**
|
||
- **AGENTS.md owns:** Coding rules, development setup, procedures, harnesses, decision frameworks, anti-patterns, workflows
|
||
- **Other files provide:** Project status, architecture context, navigation, references (links to AGENTS.md)
|
||
|
||
**Enforcement:**
|
||
- Claude Code will not accept conflicting guidance from multiple sources
|
||
- When in doubt, check AGENTS.md section headers
|
||
- If you see conflicting guidance elsewhere, update that document to reference AGENTS.md instead
|
||
|
||
## Default execution procedure
|
||
|
||
All work in this repository MUST follow `docs/CURRENT/WBS_EXECUTION_PROCEDURES.md` as the default operating procedure, together with this constitution. Before editing, select exactly one WBS item from `docs/CURRENT/CATALOGS/WBS_MASTER.csv`, verify dependencies, Gate, Requirement/API/DB/Job/UI/Test IDs, Acceptance_Evidence, and Artifact. Record Source / Assumption / Unknown / Decision Required, then execute, collect actual evidence, update `WBS_PROGRESS_TRACKER.csv`, and commit with the WBS_ID. Do not mark a WBS item COMPLETED or claim a test/build/migration result without preserved execution evidence.
|
||
|
||
1. 자동주문과 KIS 제출 Capability는 OFF다. 별도 승인 Release 전 구현·활성화·우회하지 않는다.
|
||
2. 채팅과 생성 코드는 Source of Truth가 아니다. 모든 변경은 Source / Assumption / Unknown / Decision Required를 표시한다.
|
||
3. 한 PR은 한 Vertical Slice 또는 한 동작보존 리팩터링 목적만 가진다.
|
||
4. Endpoint→Application→Pure Policy→Dapper SQL→Outbox→Tests 경계를 지킨다.
|
||
5. Domain Policy는 시간·랜덤·네트워크·DB·DI Container를 직접 읽지 않는다.
|
||
6. Generic Repository, God Service, Service Locator, Job 내 비즈니스 정책, 조기 Microservice 분리를 금지한다.
|
||
7. 모듈은 다른 모듈 Source Table을 직접 조회하지 않는다. 승인된 Contract/Read Model만 사용한다.
|
||
8. `DateTime.Now/UtcNow`를 직접 사용하지 않고 IClock과 MarketCalendar를 사용한다.
|
||
9. 금융값은 decimal, 명시적 rounding, 단위 계약을 사용한다. 모델 내부 double은 경계에서 변환한다.
|
||
10. `SellRatioOfLot`, `SellQuantity`, `TargetPortfolioWeightAfter`, `StrategicCoreFloorWeight`를 혼용하지 않는다.
|
||
11. EvidenceSnapshot·DatasetId·Model/Config/Code SHA 없이 Decision/Recommendation을 저장하지 않는다.
|
||
12. Command/Job는 IdempotencyKey/JobRunId/Watermark를 갖고 replay를 견딘다.
|
||
13. Evidence/Decision/Audit는 update/delete하지 않고 append/correction event로 보존한다.
|
||
14. Write Model은 정규화·append·revision, 화면은 version/watermark/rebuild가 있는 Read Model이다.
|
||
15. 서버 상태는 TanStack Query가 소유한다. Pinia에 API 응답을 복제하지 않는다.
|
||
16. FE request/response는 Zod로 runtime validation하고 같은 retry에 같은 Idempotency-Key를 재사용한다.
|
||
17. `/internal/*` Endpoint는 Roles 또는 Policies를 선언하며 익명 접근을 허용하지 않는다.
|
||
18. 알고리즘 변경은 Policy ID, Golden, Frozen OOS, cost×2, false-exit/reentry/ES, ModelCard를 동반한다.
|
||
19. 새 library/pattern/table/threshold는 ADR/Issue 승인 없이 도입하지 않는다.
|
||
20. 실행하지 못한 build/test/migration을 통과했다고 기록하지 않는다.
|
||
21. AI 생성 Migration은 fresh/upgrade/re-run/failure rehearsal 및 DBA 승인 없이는 병합하지 않는다.
|
||
22. 실고객 데이터·실계좌·실주문 키·secret를 prompt/fixture/log/trace에 넣지 않는다.
|
||
23. Metric·Alert·Runbook·Rollback·Owner/Secondary가 없으면 Done이 아니다.
|
||
|
||
|
||
## v12.1 execution-readiness delta
|
||
|
||
- Every change must cite Requirement/Slice/Policy/Data/API/DB/Job/UI/Test IDs.
|
||
- Production commands load Evidence/Model/Config from approved server-side PIT context; never trust client-supplied evidence.
|
||
- Write models are normalized and append/correction based. Denormalization is allowed only in versioned, rebuildable read models.
|
||
- Outbox/Inbox/JobRun/Projection operations require scope, idempotency, watermark, hashes, and replay evidence.
|
||
- No generic repository, God service, reflection plugin framework, premature microservice, or unapproved threshold.
|
||
- Refactoring and policy changes must be separate PRs with characterization or Golden tests first.
|
||
- Never claim a build, migration, test, Shadow period, PBO, or DSR result that was not executed and preserved as evidence.
|
||
|
||
## v12.2 strategic data-semantics delta
|
||
|
||
- `CurrentSecurityPortfolioWeight`, `CurrentLotPortfolioWeight`, `SellRatioOfLot`, and `StrategicCoreFloorWeight` are distinct units. Never reintroduce the ambiguous `CurrentPortfolioWeight` into active decision code.
|
||
- A lot-relative sell changes security weight by `CurrentLotPortfolioWeight * SellRatioOfLot`.
|
||
- Persist the ordered policy trace with Applied/Blocked/NotApplicable dispositions; do not expose raw internal scores directly to customers.
|
||
- Prior migrations are immutable. v12.2 changes belong in migration `0014` or later.
|
||
- The six current-session attachments and their SHA-256 values are part of the release evidence.
|
||
- Use the reviewed templates under `templates/`; do not bulk-generate unapproved modules or placeholder implementations.
|
||
|
||
## v12.3 execution and semantic-version delta
|
||
|
||
- `weight_semantics_version=1` contexts are legacy ambiguous data and must never enter active decisions; rebuild them as version 2 from explicit security and lot weights.
|
||
- A positive opportunity edge with a zero or missing requested sell ratio is BLOCKED. Never clamp missing intent to a minimum sell.
|
||
- Policy IDs, priorities and thresholds must match `contracts/policies/sell-policy-contract.v1.json`; drift blocks G2.
|
||
- Decision/API/Event/DB evidence must preserve `decision_contract_version` and `policy_trace_schema_version`.
|
||
- Outcome metrics must name a definition version, population, numerator, denominator, window and aggregation. Do not equate 63-session research output with an annual target without approval.
|
||
- `scripts/scaffold_slice.py` is dry-run by default, refuses overwrite and produces SCAFFOLD_ONLY code. Generated files are not approved implementation.
|
||
- The seven cumulative attachments and their SHA-256 values are part of v12.3 release evidence.
|
||
|
||
## v12.4 Model Operations Constitution
|
||
|
||
- Scheduler automation is limited to EVALUATION_ONLY, PROPOSAL_ONLY and DRILL_ONLY.
|
||
- Never generate or merge automatic model promotion, rollback, threshold mutation, code change, automatic order or KIS submission paths.
|
||
- Every evaluation request freezes Dataset/Model/Config/Code/Contract VersionSet from an approved server-side context.
|
||
- Metric changes require a versioned numerator, denominator, window, aggregation, PIT/revision rule and Golden/OOS impact.
|
||
- Improvement proposals are documents and records only; they do not edit model, policy, configuration or source files.
|
||
- Drift thresholds, false-exit definition and retention policy are DECISION_REQUIRED until approved.
|
||
|
||
## v14.0 UI / Model Feedback non-negotiables
|
||
- Feature code MUST NOT import PrimeVue or AG Grid directly. Use shared UI ports and screen types.
|
||
- UI provider changes require contract, accessibility, visual, state-matrix and performance evidence.
|
||
- Model operation automation stops at evaluation/proposal. Model activation is human change approval only.
|
||
- J39 and every new schedule remain disabled until their source, calendar, ownership and alert contracts are approved.
|
||
- Never claim .NET, pnpm, PostgreSQL, Playwright or Shadow evidence passed unless the actual artifact is attached.
|
||
- **[CRITICAL IRON RULE] Viewport-Fit Zero-Scroll Layout**: Except for analytical dashboards, ALL workstation screens MUST fit 100% within the initial viewport upon loading WITHOUT page-level window scrolling. All primary grids, forms, and control panels must automatically calculate `height: calc(100vh - header/tabs)` and handle internal scrolling inside containers.
|
||
- **[CRITICAL IRON RULE] Standardized Button Layout Strategy**:
|
||
1. **Page Action Toolbar (Top-Right `.ks-page__actions`)**: Dedicated exclusively to **Primary Processing Actions** (e.g., `▶ 배치 실행`, `⚡ 리밸런싱 실행`, `📤 데이터 수집`) and **Global Page Operations** (e.g., `➕ 신규 등록`). Secondary actions are styled as outline/ghost.
|
||
2. **Grid Row & Item Context Actions (Table Row Actions)**: Dedicated to **Single-Row CRUD & Processing** (e.g., `✏️ 수정`, `🗑️ 삭제`, `🔍 상세보기`, `▶ 재처리`). Placed in a pinned right column or explicit context menu; never placed in page top toolbar.
|
||
3. **Multi-Selection Batch Toolbar (Grid Top/Bottom Selection Bar)**: Activated conditionally upon multi-row selection for **Bulk Actions** (e.g., `선택 일괄 승인(3)`, `선택 일괄 삭제`).
|
||
- **[CRITICAL IRON RULE] Standardized Loading Skeleton Rule**: ALL screen-level and section-level data loading MUST render animated `SkeletonLoader` (shimmer mode) matching the expected layout (e.g. `skeletonType="table"` for grids, `skeletonType="card"` for forms/summaries) through `QueryStateBoundary`/`StandardScreenBoundary`. Static text ("불러오는 중...") or empty screen placeholders during loading states are STRICTLY PROHIBITED.
|
||
- **[CRITICAL IRON RULE] Standardized Empty Data State Rule**: When zero records or empty dataset states occur, ALL grids, lists, and summary cards MUST render standard `EmptyStatePlaceholder` component (`📭` icon, clear title, descriptive helper text, and optional recovery action button). Blank white spaces or plain `<p>데이터가 없습니다</p>` text tags are STRICTLY PROHIBITED.
|
||
- **[CRITICAL IRON RULE] Standardized Form & Filter Control Width Rule**: ALL form & filter controls MUST adhere to central default width tokens (`tokens.css` / `base.css`). Controls MUST NOT stretch to 100% full width inside filter bars unless explicitly grouped in full-width grid layouts:
|
||
1. **Select / Dropdown (`select`, `.p-select`, `.ks-select`)**: Default width `--ks-control-width-select` (`160px`).
|
||
2. **Search Input (`.search-input`)**: Default width `--ks-control-width-search` (`220px`).
|
||
3. **Date Picker (`input[type="date"]`)**: Default width `--ks-control-width-date` (`140px`).
|
||
- **[CRITICAL IRON RULE] Standardized Grid Row Numbering Rule**: Unless explicitly disabled (`showRowNumber: false`), ALL data grids MUST automatically prepend a pinned left `No.` column rendering 1-indexed sequential row numbers (`node.rowIndex + 1`) centered with `54px` fixed width.
|
||
- **[CRITICAL IRON RULE] Standardized Grid Theme, Zebra Stripes & Color Palette Rule**: ALL data grids MUST inherit central Theme Color Tokens (`tokens.css`) without ad-hoc inline overrides. Grids MUST enforce:
|
||
1. **Header Background**: Premium Header Gray `#f1f5f9` (Dark Mode: `#1e293b`), font-weight: `700`.
|
||
2. **Zebra Stripes (Odd Rows)**: Even rows `#ffffff`, Odd rows (`.ag-row-odd`) `#f8fafc` (Dark Mode: `#0f172a`).
|
||
3. **Hover Color**: Sky Light Blue `#e0f2fe` (Dark Mode: `#334155`).
|
||
4. **Active Selection Color**: Active Selected Row Sky Blue `#dbeafe` with bold text `#1e3a8a` (Dark Mode: `#1e3a8a`).
|
||
|
||
## v16.0 Gitea API & CI/CD Automation
|
||
|
||
### Environment Setup
|
||
|
||
**Gitea API Token:**
|
||
```bash
|
||
# Set GITEA_TOKEN_TAXBAIK environment variable
|
||
# This token enables:
|
||
# - Pull request automation (labels, milestones, comments)
|
||
# - Issue management (create, update, close)
|
||
# - Release management (tags, release notes)
|
||
# - CI/CD pipeline integration
|
||
|
||
# On Windows (PowerShell):
|
||
$env:GITEA_TOKEN_TAXBAIK = "your-token-here"
|
||
|
||
# On macOS/Linux (bash):
|
||
export GITEA_TOKEN_TAXBAIK="your-token-here"
|
||
|
||
# Verify:
|
||
echo $GITEA_TOKEN_TAXBAIK
|
||
```
|
||
|
||
### Gitea API Patterns
|
||
|
||
**Common endpoints (https://gitea.taxbaik.com/api/v1):**
|
||
|
||
```bash
|
||
# Create a PR comment
|
||
curl -X POST \
|
||
-H "Authorization: token $GITEA_TOKEN_TAXBAIK" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"body":"Verification complete: 41/41 tests passed"}' \
|
||
https://gitea.taxbaik.com/api/v1/repos/kjh2064/KArtSell.Aegis/issues/{issue_id}/comments
|
||
|
||
# Add labels to PR
|
||
curl -X POST \
|
||
-H "Authorization: token $GITEA_TOKEN_TAXBAIK" \
|
||
-d '["architecture","verified"]' \
|
||
https://gitea.taxbaik.com/api/v1/repos/kjh2064/KArtSell.Aegis/issues/{pr_number}/labels
|
||
|
||
# Create release with notes
|
||
curl -X POST \
|
||
-H "Authorization: token $GITEA_TOKEN_TAXBAIK" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"tag_name":"v16.0.1","body":"Release notes..."}' \
|
||
https://gitea.taxbaik.com/api/v1/repos/kjh2064/KArtSell.Aegis/releases
|
||
|
||
# Query PR/Issue
|
||
curl -H "Authorization: token $GITEA_TOKEN_TAXBAIK" \
|
||
https://gitea.taxbaik.com/api/v1/repos/kjh2064/KArtSell.Aegis/pulls?state=open
|
||
```
|
||
|
||
### CI/CD Integration (Gitea Actions)
|
||
|
||
**Leverage in `.gitea/workflows/ci.yml`:**
|
||
|
||
```yaml
|
||
- name: Comment on PR with test results
|
||
if: github.event_name == 'pull_request'
|
||
run: |
|
||
curl -X POST \
|
||
-H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \
|
||
-H "Content-Type: application/json" \
|
||
-d "{\"body\":\"Build: ✅ Tests: 41/41 PASS\"}" \
|
||
https://gitea.taxbaik.com/api/v1/repos/kjh2064/KArtSell.Aegis/issues/${{ github.event.pull_request.number }}/comments
|
||
```
|
||
|
||
### Automation Best Practices (from v16.0)
|
||
|
||
- **PR Labels:** Auto-label based on affected module (e.g., `ModelOperations`, `SignalEngine`)
|
||
- **Milestones:** Link PRs to quarterly sprints for burndown tracking
|
||
- **Comments:** Post verification results (build, test, security scan) directly on PR
|
||
|
||
## v16.0 Development Environment Configuration
|
||
|
||
### Database & Backend Setup
|
||
|
||
**DO NOT make up or ask for database credentials.**
|
||
|
||
Read `src/KArtSell.Host/appsettings.Development.json` directly. Current values:
|
||
```json
|
||
{
|
||
"ConnectionStrings": {
|
||
"Postgres": "Host=127.0.0.1;Port=5432;Database=kartselldb;Username=kartsell;Password=kartsell4321@!"
|
||
},
|
||
"Authentication": {
|
||
"Mode": "DevelopmentHeader"
|
||
}
|
||
}
|
||
```
|
||
|
||
**Connection Parameters:**
|
||
- Host: `127.0.0.1` (localhost)
|
||
- Port: `5432`
|
||
- Database: `kartselldb` (NOT `kartsell`)
|
||
- Username: `kartsell`
|
||
- Password: `kartsell4321@!`
|
||
|
||
**SSH Tunnel (Required before starting backend):**
|
||
```powershell
|
||
ssh -L 5432:127.0.0.1:5432 kjh2064@178.104.200.7
|
||
```
|
||
|
||
**Start Backend (use config file, no env var injection):**
|
||
```powershell
|
||
cd D:\JobRoomz\KArtSell.Aegis
|
||
dotnet run --project src/KArtSell.Host --configuration Debug --no-build
|
||
```
|
||
|
||
### Frontend Development Server
|
||
|
||
**Port:** 5174 (fallback: 5173 if available)
|
||
|
||
**Start Frontend (from project root):**
|
||
```bash
|
||
cd frontend
|
||
pnpm install --frozen-lockfile
|
||
pnpm dev
|
||
```
|
||
|
||
**URL:** http://localhost:5174
|
||
|
||
### Frontend Layout & Responsive Design Standards (v16.0)
|
||
|
||
**CRITICAL:** Responsive web design is MANDATORY for all layouts, not optional.
|
||
|
||
#### CSS Variable Standards (base.css)
|
||
```css
|
||
:root {
|
||
--ks-sidebar-width: 16rem; /* Navigation sidebars */
|
||
--ks-aside-width: 22rem; /* Side panels */
|
||
--ks-preview-width: 24rem; /* Preview/summary panels */
|
||
--ks-detail-width: 28rem; /* Detail panels */
|
||
--ks-content-max: 1400px; /* Max content width (prevent excessive expansion) */
|
||
}
|
||
```
|
||
|
||
#### Layout Rules (Non-Negotiable)
|
||
1. **NO hardcoded pixel/rem widths in minmax.** Always use CSS variables: `minmax(0, 1fr) var(--ks-aside-width)` ✅, NOT `minmax(18rem, 26rem)` ❌
|
||
2. **Unified breakpoints (all layouts must use same):**
|
||
- **Desktop:** Default (no constraint)
|
||
- **Tablet:** `@media (max-width: 1100px) { grid-template-columns: 1fr; }` (2-col → 1-col)
|
||
- **Mobile:** `@media (max-width: 768px) { /* adjust padding, font sizes */ }`
|
||
3. **All flex children:** `flex: 1; min-height: 0;` required (prevents overflow squashing)
|
||
4. **Scrollable containers:** `overflow-y: auto; min-height: 0;` (enables internal scroll without page scroll)
|
||
5. **Grid layouts:** `align-items: start;` (NOT center/stretch) to prevent column stretching at different heights
|
||
6. **Max-width constraint:** Wrap pages in `.page-wrapper { max-width: var(--ks-content-max); margin: 0 auto; }` to prevent 2560px+ distortion
|
||
7. **PageLayout must use CSS Grid (NOT flexbox).** Flex + gap breaks `flex: 1` height propagation in children:
|
||
- ❌ **DON'T:** `display: flex; flex-direction: column; gap: var(--ks-space-2);` (gap is not counted in flex: 1 calculations)
|
||
- ✅ **DO:** `display: grid; grid-template-rows: auto auto auto auto 1fr auto auto; gap: var(--ks-space-2);` (gap auto-calculated)
|
||
- **Reason:** Grid gap is accounted for in row sizing; flex gap is invisible to flex: 1 child height calculations, causing overflow → unwanted scroll
|
||
- **Template rows:** header (auto) | subtitle (auto) | commandBar (auto) | summary (auto) | filters (auto) | workspace (1fr) | footer (auto)
|
||
|
||
#### Page Structure Rule: NO Footers (Global or Page-Level)
|
||
**CRITICAL:** Footers are EXCLUDED at all levels:
|
||
1. **Page-level footers** (PageLayout #footer slot) — ❌ FORBIDDEN
|
||
2. **Global footers** (AppShellLayout footer) — ❌ REMOVED
|
||
3. **All footer functionality** must be relocated to:
|
||
- **Primary actions:** `<template #actions>` (header right side) — e.g., Help, AI Suggest
|
||
- **Command bar:** `<template #commandBar>` — e.g., Save, Reset, Approve buttons
|
||
- **Status info:** `<template #summary>` — e.g., Watermark, Owner, System status
|
||
|
||
**Design Principle: Maximize Screen Real Estate**
|
||
- Footers waste ~48-64px of viewport height (non-recoverable on mobile)
|
||
- Single-screen principle: ALL controls must be visible without scrolling
|
||
- Mobile UX: bottom footer is hardest to reach (thumb-friendly zone = top 60%, sides)
|
||
- Information density: header/command-bar/summary can convey all necessary context
|
||
- Content-first: Every pixel should serve user goal, not chrome
|
||
|
||
**Example Migration:**
|
||
```vue
|
||
<!-- ❌ DON'T: Use footer -->
|
||
<PageLayout title="Market Data Import">
|
||
<template #footer>
|
||
<button @click="reset">초기화</button>
|
||
<button @click="schedule">수집 예약</button>
|
||
</template>
|
||
</PageLayout>
|
||
|
||
<!-- ✅ DO: Move to command bar -->
|
||
<PageLayout title="Market Data Import">
|
||
<template #commandBar>
|
||
<button @click="reset">초기화</button>
|
||
<button @click="schedule">수집 예약</button>
|
||
</template>
|
||
</PageLayout>
|
||
```
|
||
|
||
**Implementation:**
|
||
- PageLayout: `<template #footer>` must be removed (do not use)
|
||
- AppShellLayout: Global footer completely removed
|
||
- All pages: Design with content ending at viewport edge (no footer gap)
|
||
|
||
#### Height Propagation Chain (Single-Screen Principle)
|
||
```
|
||
PageLayout (.ks-page__content)
|
||
├─ height: 100%; min-height: 0; display: flex;
|
||
↓
|
||
QueryStateBoundary (.ks-query-boundary)
|
||
├─ flex: 1; height: 100%; min-height: 0; display: flex;
|
||
↓
|
||
Content Container (KsSplitter, .ks-stack, FormPageLayout)
|
||
├─ flex: 1; min-height: 0; height: 100%;
|
||
├─ display: flex/grid;
|
||
↓
|
||
Internal Panes (.request-list, .detail-panel, .items)
|
||
├─ flex: 1; min-height: 0; overflow-y: auto;
|
||
```
|
||
|
||
#### Banned Patterns
|
||
- ❌ `grid-template-columns: minmax(18rem, 26rem)` (hardcoded min/max)
|
||
- ❌ `calc(100vh - Xpx)` (brittle, changes with header size)
|
||
- ❌ `max-width: 600px` on full-page containers (prevents responsiveness)
|
||
- ❌ `align-items: center` in grid layouts (prevents height-based alignment)
|
||
- ❌ `position: fixed` sidebars without mobile fallback
|
||
- ❌ Multiple different breakpoints across layouts (950px, 900px, 1000px, 1200px all mixed)
|
||
|
||
#### Verification Checklist
|
||
For every layout change:
|
||
- [ ] Uses CSS variables, not hardcoded rem/px
|
||
- [ ] Breakpoints are 1100px (tablet) and 768px (mobile)
|
||
- [ ] All flex children have `flex: 1; min-height: 0`
|
||
- [ ] All scrollable panes have `overflow-y: auto; min-height: 0`
|
||
- [ ] Tested at 768px (mobile), 1100px (tablet breakpoint), 1512px (current test), 1920px (fullHD), 2560px (4K)
|
||
- [ ] No page-level scroll on first load (only internal pane scroll if needed)
|
||
- [ ] Content max-width prevents distortion on ultra-wide (>1400px)
|
||
|
||
#### Affected Layouts (Status)
|
||
| Layout | Issue | Status | Reason |
|
||
|--------|-------|--------|--------|
|
||
| PageLayout | None | ✅ COMPLIANT | Uses CSS variables |
|
||
| CrudWorkspaceLayout | None | ✅ COMPLIANT | Uses CSS variables |
|
||
| FormPageLayout | Hardcoded `minmax(18rem, 26rem)` | 🔴 FIX REQUIRED | Session 2026-08-16 |
|
||
| ReviewWorkbenchLayout | Mixed hardcoded widths | 🔴 FIX REQUIRED | Session 2026-08-16 |
|
||
| OperationsConsoleLayout | Hardcoded `minmax(18rem, 28rem)` | 🔴 FIX REQUIRED | Session 2026-08-16 |
|
||
|
||
### Authentication for Testing
|
||
|
||
Development mode uses `DevelopmentHeaderAuthenticationHandler`. Test requests with:
|
||
```powershell
|
||
$headers = @{
|
||
"X-KArtSell-User" = "kjh2064"
|
||
"X-KArtSell-Role" = "Admin"
|
||
"Content-Type" = "application/json"
|
||
}
|
||
|
||
Invoke-WebRequest -Uri "http://127.0.0.1:5002/api/shadow-runs" `
|
||
-Method POST `
|
||
-Headers $headers `
|
||
-Body $body
|
||
```
|
||
|
||
### Rules for Development Configuration
|
||
|
||
1. **Never invent credentials.** Read config files first.
|
||
2. **Never ask the user for settings.** Read `appsettings.Development.json` directly.
|
||
3. **Database name is `kartselldb`.** Not `kartsell`.
|
||
4. **SSH tunnel is mandatory.** PostgreSQL is not accessible without it.
|
||
5. **Authentication mode is `DevelopmentHeader`.** Use headers, not OIDC tokens.
|
||
- **Releases:** Tag with semver + architecture contract version (e.g., `v16.0.1-contract-v3.0`)
|
||
- **Issue Linking:** Reference debt IDs, ADRs, decision logs in commits (e.g., `TECH-001: Fix CA1822`)
|
||
|
||
---
|
||
|
||
## v16.0 Strategic Architecture & Engineering Excellence
|
||
|
||
### Decision Criteria for All Work
|
||
|
||
Every task — code change, refactor, new feature, tooling, infrastructure — must be evaluated against these dimensions before implementation:
|
||
|
||
#### 1. SOLID 원칙
|
||
- **S**ingle Responsibility: One class, one reason to change. Vertical Slice boundaries are trust boundaries.
|
||
- **O**pen/Closed: Open for extension (new Policies, new decision gates); closed for modification (immutable Evidence, append-only migrations).
|
||
- **L**iskov Substitution: Handlers, Policies, Adapters are swappable; never break contract.
|
||
- **I**nterface Segregation: IClock ≠ IDateTime; IOutboxWriter ≠ IEventBus. Ports are narrow.
|
||
- **D**ependency Inversion: Depend on abstractions (IClock, ILogger, IOutboxWriter); inject concretions at composition root only.
|
||
- **적용:** 모듈 경계 설계, 인터페이스 분리, 스태틱 메서드 vs 인스턴스 메서드 판단.
|
||
|
||
#### 2. 코드 리팩토링 (Code Mass & Complexity)
|
||
- Cyclomatic Complexity ≤ 10 per method (Policy는 예외: decision trees는 복잡해질 수 있음).
|
||
- Characterize → Isolate → Transform → Verify → Simplify → Observe → Close Debt (정공법).
|
||
- Dead code, unused flags, unreachable branches는 즉시 삭제. "혹시 필요할까봐"는 금지.
|
||
- Performance refactor와 기능 변경은 분리된 PR. 동시 변경은 회귀 탐지 불가.
|
||
|
||
#### 3. 데이터 정합성 (Data Integrity & Audit)
|
||
- **PIT (Point-in-Time):** `WHERE published_at <= cutoff AND revision = latest` 필수. 시간 여행 쿼리는 audit 목적만.
|
||
- **Revision Tracking:** update/delete 금지. 새 버전을 append로 저장. 수정은 correction event로 보존.
|
||
- **Audit Trail:** EvidenceSnapshot, DatasetId, Model/Config/Code SHA는 Decision과 함께 저장. Trace 불가능하면 미완성.
|
||
- **적용:** 모든 쓰기는 append/correction 패턴. 읽기는 PIT 조건. 마이그레이션은 회원가입 없음.
|
||
|
||
#### 4. 과유불급 (Necessity-Driven, No Gold-Plating)
|
||
- "혹시 나중에 필요하면"으로 코드를 추가하지 않는다. 근거 없는 추상화 금지.
|
||
- 한 곳에서만 쓰면 분리하지 않는다. 세 곳에서 반복되면 그때 abstract.
|
||
- Feature flag, backward-compat shim, deprecation layer는 근거 있을 때만. 사용하지 않는 코드는 삭제.
|
||
- **적용:** 새 service/interface/config 도입 전에 "이것이 정말 필요한가?" 자문.
|
||
|
||
#### 5. 정규화 & 역정규화 (Normalization Strategy)
|
||
- **Write Models:** 3NF + append + revision. 중복 없음, 관계 명확, 이상 불가.
|
||
- **Read Models:** Denormalized projections. 1NF 위반 허용 (성능, 접근성). 모든 Read는 versioned, rebuildable.
|
||
- **경계:** Write는 Dapper로 스키마-규정. Read는 쿼리 최적화. 양쪽 스키마 감시.
|
||
- **적용:** 새 column 추가 전에 "이것은 3NF 위반인가? 그렇다면 projection으로."
|
||
|
||
#### 6. 프로세스 단순화 (Simplicity & Clarity)
|
||
- 한 번에 한 가지만 한다. 다중 책임 = 다중 이해 실패 = 버그.
|
||
- "왜 이 순서인가?" 묻지 않아도 명확한 코드. 숨겨진 전제 금지.
|
||
- Circular dependency, magic numbers, implicit state 제거.
|
||
- **적용:** 코드 흐름이 위→아래로 읽혀야 함. 뒤로 돌아가며 읽어야 하면 리팩터.
|
||
|
||
#### 7. 패턴화, 표준화, 구조화 (Patterns & Standards)
|
||
- Vertical Slice는 단일 패턴. Endpoint → Handler → Policy → Dapper → Outbox.
|
||
- Job은 단일 책임. 비즈니스 정책은 들어가지 않음. 승인된 Command만 실행.
|
||
- Event schema는 contract-first. 구독자가 없으면 이벤트도 없음.
|
||
- **표준 컴포넌트:** QueryStateBoundary, CrudForm, PermissionGuard 등은 모든 화면에서 재사용. 직접 import 금지.
|
||
- **적용:** 새 pattern/component 도입 전 팀 검토. 코드 사본 3개 = abstract 신호.
|
||
|
||
#### 8. 바이브코딩 & 홀루시네이션 통제 (AI Guardrails)
|
||
- AI 생성 코드는 근거 없음. Source/Assumption/Decision 반드시 기록.
|
||
- **금지:** 근거 없는 Policy ID, threshold, DB column, API endpoint.
|
||
- SQL은 schema owner, PIT 조건, index, execution plan 검토 필수.
|
||
- 알고리즘 변경은 Golden/Frozen OOS diff 없이 병합 금지.
|
||
- **적용:** "이 값은 어디서 나왔나?" 묻는 습관. 답 없으면 DECISION_REQUIRED 표시.
|
||
|
||
#### 9. 현장감, 재현성, 이력성 (Traceability & Reproducibility)
|
||
- **현장감:** Build/test/migration artifact는 보존. "통과했다고 주장"하되 증거 없으면 거짓.
|
||
- **재현성:** 동일 input → 동일 output. Random, network, system time 의존은 격리. Mock 금지, integration test로.
|
||
- **이력성:** Commit message는 "왜"를 기록. "버그 수정"은 불충분. "X 기능에서 Y 조건에서 Z 버그 → 원인: 로직 오류" 기록.
|
||
- **적용:** CI/CD 결과물 저장, 회귀 테스트 잠금, ADR에 의사결정 기록.
|
||
|
||
#### 10. 안정성 (Reliability & Safety)
|
||
- Idempotency: 같은 요청 → 같은 결과. 두 번 실행해도 안전.
|
||
- Rollback 불가능한 변경 금지. Migration도 down script 필수.
|
||
- Failure mode: 각 Job/Endpoint은 실패했을 때 상태를 명확히. "실패했는데 부분 성공?" 금지.
|
||
- **적용:** Command/Job는 IdempotencyKey, Watermark 필수. DB constraint, NOT NULL 검증.
|
||
|
||
#### 11. 고도화 & 컴포넌트화 (Componentization & Maturity)
|
||
- 한 번 제대로. 임시방편 금지. 기술부채는 Debt register에 기록.
|
||
- 구현 전 contract/schema/test 먼저. "하면서 배운다"는 설계 부실의 신호.
|
||
- 버전 관리: 기능이 아닌 contract 버전. API/Event/DB schema 버전 분리.
|
||
- **적용:** Release note에는 contract version, breaking change, migration step 명시.
|
||
|
||
#### 12. 정공법 (Right Way, Not Shortcuts)
|
||
- 안 되는 길에 시간 낭비하지 말되, 편한 길도 피한다 (--no-verify, force push).
|
||
- 문제 근본 해결. Symptom 치료는 debt 증가.
|
||
- 리뷰어가 "이게 최선인가?" 묻는 코드는 재작성. "충분히 좋다" ≠ "최선".
|
||
- **적용:** Build 실패 → --no-verify X, 원인 파악. Merge conflict → cherry-pick X, rebase 제대로.
|
||
|
||
#### 13. 기술부채 관리 (Tech Debt Registry)
|
||
- 부채는 기록하는 순간부터 이자 발생. 미루지 말 것.
|
||
- **Debt ID:** TECH-001 등으로 추적. PR/commit에서 참조.
|
||
- **우선순위:** Impact (얼마나 큰가) × Effort (고치는 데 드는 비용). 고영향 저비용 우선.
|
||
- **Paydown:** 분기마다 debt 20% 감축 목표. 신규 debt > paydown이면 질식.
|
||
- **적용:** README.md의 TECH_DEBT_REGISTER 매월 검토. 3개월 미해결 = 리팩터 스프린트 필요.
|
||
|
||
### Work Decision Checklist
|
||
|
||
모든 task에 대해 다음을 자문:
|
||
|
||
- [ ] **SOLID:** 이 변경이 단일 책임인가? Dependency inversion을 지킬 것인가?
|
||
- [ ] **Complexity:** 함수/메서드 복잡도는 10 이하인가? (Policy 예외)
|
||
- [ ] **Audit:** Evidence/Revision 추적 가능한가? PIT 쿼리 있는가?
|
||
- [ ] **Necessity:** 근거 있는 변경인가? "혹시 필요하면" 아닌가?
|
||
- [ ] **Normalization:** 쓰기는 3NF, 읽기는 projection인가?
|
||
- [ ] **Simplicity:** 위→아래로 읽혀야 하는가? 숨겨진 전제 없는가?
|
||
- [ ] **Pattern:** 표준 Slice/Job/Component 따르는가? 새 패턴 도입은 검증됐는가?
|
||
- [ ] **Guardrails:** 근거 있는가? "왜"를 기록했는가?
|
||
- [ ] **Traceability:** Artifact 보존되는가? Reproduction 가능한가? ADR/Issue 링크 있는가?
|
||
- [ ] **Safety:** Idempotent인가? Rollback 가능한가? 부분 실패 케이스 처리했는가?
|
||
- [ ] **Maturity:** 임시방편 아닌가? Contract/schema/test 먼저 했는가?
|
||
- [ ] **Right Way:** Shortcut (--no-verify, force) 안 썼는가? 근본 해결했는가?
|
||
- [ ] **Debt:** 새로운 debt 만들지 않는가? 기존 debt 감축하는가?
|
||
- [ ] **Viewport Fit (UI):** 대시보드를 제외한 모든 업무 화면이 페이지 스크롤 없이 초기 로딩 시 100% 한눈에 들어오는가?
|
||
- [ ] **Button Standard (UI):** 상단 툴바(배치/등록), 행별 작업(수정/상세), 다중선택(일괄), 폼 푸터(취소/저장) 버튼 배치가 규칙 매트릭스를 따르는가?
|
||
- [ ] **Skeleton Loading (UI):** 로딩 상태 시 텍스트 대신 레이아웃에 반응하는 shimmer 스켈레톤(SkeletonLoader)이 제대로 노출되는가?
|
||
- [ ] **Empty State (UI):** 데이터 0건 또는 조회 결과가 없을 시 표준 EmptyStatePlaceholder(아이콘+설명+조치버튼)가 노출되는가?
|
||
- [ ] **Component Scale (UI):** 입력폼, 그리드, 버튼, 선택상자 높이/폰트가 중앙 토큰(`tokens.css`) 규격(헤더 30px, 행 28px, 컨트롤 28px, 폰트 12px)을 따르는가?
|
||
|
||
### Anti-Patterns (금지)
|
||
|
||
- ❌ "일단 만들고 나중에 리팩터" → Feature 초기부터 정공법
|
||
- ❌ "페이지에 창 스크롤바가 생기게 방치" → 대시보드 제외 모든 업무 화면은 Viewport-Fit Zero-Scroll 필수
|
||
- ❌ "버튼 위치 난잡 배치" → 상단 우측(페이지/배치), 행 내부(개별 CRUD), 선택바(일괄), 폼 푸터(저장/취소) 표준 무시 금지
|
||
- ❌ "로딩 시 '불러오는 중...' 텍스트 방치" → 반드시 레이아웃 맞춤형 애니메이션 스켈레톤(SkeletonLoader) 적용 필수
|
||
- ❌ "데이터 0건 시 빈 흰색 공간 방치" → 반드시 표준 EmptyStatePlaceholder 컴포넌트 렌더링 필수
|
||
- ❌ "개별 인라인 height/font style 난립" → 반드시 중앙 tokens.css / base.css 디자인 토큰 상속 필수
|
||
- ❌ "혹시 필요할까봐 추상화" → Necessity-driven만
|
||
- ❌ SELECT * / Generic Repository → Explicit columns, explicit logic
|
||
- ❌ "이건 작은 변경이라 테스트 스킵" → 모든 경로 characterize
|
||
- ❌ Timestamp를 직접 DateTime.Now → IClock 주입
|
||
- ❌ Policy 로직이 Job/Handler에 → Domain Policy만
|
||
- ❌ "나중에 monitoring 추가" → 배포 전 Metric/Alert/Runbook 필수
|
||
- ❌ Magic number → 근거 있는 상수, Policy ID로 추적
|
||
- ❌ "다른 모듈 테이블 조회" → Contract/Read Model만
|
||
- ❌ 스킵된 테스트 기록 안 함 → Debt register에 DECISION_REQUIRED
|
||
|
||
## Execution Protocol Addendum
|
||
|
||
### Before Any Change
|
||
|
||
- Read the current Source of Truth first: user-provided configuration, current schema, active contracts, and existing tests.
|
||
- Record `Source / Assumption / Unknown / Decision Required` in the Slice note before editing.
|
||
- Preserve user-fixed development and production configuration values. Never replace them with compose defaults, environment fallbacks, or guessed credentials.
|
||
- Classify the change as exactly one Vertical Slice or one behavior-preserving refactoring. Do not mix policy, schema, configuration, and unrelated cleanup.
|
||
|
||
### Database Test Routing
|
||
|
||
- Unit tests do not connect to a database.
|
||
- Integration and migration tests use the configured test database from the test project's Development settings.
|
||
- Production database access is read-only diagnostics only unless an explicitly approved production release step says otherwise.
|
||
- Before any destructive test-database operation, parse and verify the database name is the approved test database. Refuse all other names.
|
||
- Do not infer schema from a legacy migration file. Compare active runtime SQL, tests, and the current database schema first.
|
||
|
||
### Time and Timezone
|
||
|
||
- Persist instants in UTC with timezone-aware database types where the contract permits.
|
||
- Convert to KST only at display, reporting, scheduling, or MarketCalendar boundaries.
|
||
- Keep `IClock.UtcNow` as the application clock contract. A KST conversion requires an explicit contract and characterization test.
|
||
- Never change a timezone or reinterpret existing timestamps without a documented data-meaning decision and rehearsal evidence.
|
||
|
||
### Blockers Must Be Actionable
|
||
|
||
- Do not repeatedly report that work is blocked without a concrete resolution proposal.
|
||
- For each blocker, state: exact cause, safe options, recommended option, required command or approval, and the evidence that will be produced.
|
||
- If the user has provided the required authority or test resource, proceed within that scope instead of asking for the same approval again.
|
||
- If an external prerequisite is missing, perform all safe read-only checks first, then give one precise request to unblock the next Slice.
|
||
|
||
### Evidence and Completion
|
||
|
||
- Never claim completion from an intended command. Record the actual command result and artifact path.
|
||
- For migrations, preserve fresh-install, upgrade, re-run, and failure-rehearsal evidence before calling the Slice complete.
|
||
- When a change fails validation, revert or isolate the failed draft before starting the next Slice; do not leave an unapplied journal or partial scaffold as if it were approved.
|
||
|
||
---
|
||
|
||
## v16.0 Testing Strategy
|
||
|
||
### Backend Testing (xUnit)
|
||
|
||
**Test Organization:**
|
||
```
|
||
tests/
|
||
KArtSell.ArchitectureTests/ # SOLID + pattern compile-time rules
|
||
KArtSell.ModelOperations.UnitTests/
|
||
KArtSell.SignalEngine.UnitTests/
|
||
KArtSell.Integration.Tests/ # With real PostgreSQL
|
||
```
|
||
|
||
**Test Levels (in order of precedence):**
|
||
1. **Unit:** Pure functions (Policy, Mapper), no I/O. Fast, deterministic. NO mocks for domain logic.
|
||
2. **Integration:** Handler + Dapper + real PostgreSQL. Validates transaction boundaries, Outbox/Inbox idempotency, cascade behavior.
|
||
3. **Data:** SQL query validation, schema conformance, index effectiveness, PIT correctness.
|
||
4. **E2E:** Full HTTP stack + real DB; used sparingly for critical paths only.
|
||
5. **Golden/Frozen OOS:** Before merging algorithm changes, lock baseline and diff against new run.
|
||
|
||
**Run Tests:**
|
||
```bash
|
||
# All tests
|
||
dotnet test KArtSell.sln -c Release
|
||
|
||
# By category
|
||
dotnet test --filter "Category=Integration" -c Release
|
||
dotnet test --filter "FullyQualifiedName~UnitTests" -c Release
|
||
|
||
# Single test
|
||
dotnet test --filter "FullyQualifiedName=Namespace.Class.Method" -c Release
|
||
```
|
||
|
||
**Rules:**
|
||
- Integration tests MUST use real database. Never mock Dapper or EF.
|
||
- All tests must be repeatable. No DateTime.Now, no random seed, no network.
|
||
- Skipped tests MUST be recorded in TECH_DEBT_REGISTER with DECISION_REQUIRED.
|
||
- Failed tests are not skipped; they are fixed or marked as KNOWN_ISSUE with reproduction steps.
|
||
|
||
### Frontend Testing (Vitest + Playwright)
|
||
|
||
**Unit Tests (Vitest):**
|
||
```bash
|
||
cd frontend
|
||
pnpm test # Run all
|
||
pnpm test -- --reporter=verbose
|
||
pnpm test -- <test-file-pattern>
|
||
pnpm test -- --coverage
|
||
```
|
||
|
||
**E2E Tests (Playwright):**
|
||
```bash
|
||
cd frontend
|
||
pnpm exec playwright install --with-deps chromium
|
||
pnpm e2e # Headless
|
||
pnpm e2e -- --debug # Debug mode (browser open)
|
||
pnpm exec playwright test --headed # UI visible
|
||
```
|
||
|
||
**Coverage Expectations:**
|
||
- **Unit:** Screen/page components: ≥70% line coverage. Composables/hooks: ≥80%.
|
||
- **E2E:** Critical user workflows only (auth, search, create, approve, export). Do not aim for 100% E2E.
|
||
|
||
---
|
||
|
||
## v16.0 Backend Architecture
|
||
|
||
### Module Structure & Vertical Slices
|
||
|
||
Each feature is complete: `Endpoint → Handler → Policy → Sql → Outbox`
|
||
|
||
```
|
||
Features/<SliceName>/
|
||
Endpoint.cs # FastEndpoints handler (HTTP contract)
|
||
Request.cs # Input DTO + validation rules
|
||
Response.cs # Output DTO
|
||
Validator.cs # Fluent/Zod-style validation
|
||
Handler.cs # Use case orchestration (Application)
|
||
Policy.cs # Pure domain logic (Domain layer)
|
||
Sql.cs # Dapper queries (Data layer)
|
||
Mapper.cs # Entity ↔ DTO
|
||
Jobs/ # Related Hangfire jobs
|
||
Contracts/ # Event & Job schemas
|
||
Tests/ # Unit + integration tests
|
||
README.md # Traceability: Requirement, ADR, assumptions
|
||
```
|
||
|
||
**Key Rules:**
|
||
- Endpoint: HTTP concerns only (routing, content negotiation, status codes)
|
||
- Handler: Transaction boundary; orchestrates Policy + Sql
|
||
- Policy: Pure business logic; no I/O, no DateTime.Now, no mocks in tests
|
||
- Sql: Dapper with explicit columns, schema-qualified names, NO SELECT *
|
||
|
||
**Design Anti-Patterns (FORBIDDEN):**
|
||
- ❌ Generic Repository
|
||
- ❌ Service Layer (Handler + Policy + Sql replaces it)
|
||
- ❌ Cross-module direct table access
|
||
- ❌ DateTime.Now (use IClock)
|
||
- ❌ Reflection-based plugin framework
|
||
- ❌ Premature microservice split
|
||
|
||
### Database & Migrations
|
||
|
||
**DbUp (Single Source of Truth):**
|
||
- Runs at Host startup via `KArtSell.DbMigrator`
|
||
- Each module owns its schema (e.g., `model_operations.*`, `signal_engine.*`)
|
||
- Migrations are immutable; failed migration halts and requires manual recovery
|
||
- Every migration must have fresh-install, upgrade, re-run, and failure-recovery tests in CI
|
||
|
||
**Query Patterns (Dapper):**
|
||
```csharp
|
||
// ✅ DO: Schema-qualified, explicit columns, PIT condition, 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
|
||
""";
|
||
|
||
var result = await connection.QueryAsync<SignalDto>(sql, new { cutoff, status }, commandTimeout: 30);
|
||
|
||
// ❌ DON'T: SELECT *, no PIT, generic repo, no cancellation
|
||
const string sql = "SELECT * FROM signals";
|
||
```
|
||
|
||
**PIT (Point-in-Time) Queries (Mandatory for Audit):**
|
||
- Every query against time-series data must include: `WHERE published_at <= @cutoff`
|
||
- Revision resolver must select the latest non-deleted revision per entity
|
||
- Audit/compliance queries can use time-travel; business queries cannot
|
||
|
||
**Async Coupling (Outbox → Inbox):**
|
||
- When a command succeeds, events inserted into `outbox` in same transaction (atomic with command result)
|
||
- Hangfire job polls outbox, publishes to subscribers, marks processed
|
||
- Every inbox handler is idempotent; replay of same event = no-op
|
||
- Idempotency key ensures duplicate events are detected and skipped
|
||
|
||
### Hangfire (Background Jobs & Scheduling)
|
||
|
||
**Job Design Rules:**
|
||
- **Not a policy engine:** Jobs execute Commands; they do NOT make business decisions (Policy does)
|
||
- **Idempotency key:** Each job must be replayable with same input = same output
|
||
- **Watermark & version set:** Track job progress state across retries
|
||
- **Queue isolation:** `q-customer-sla` (business SLA) separate from `q-research` (non-critical)
|
||
- **Retry classification:**
|
||
- `transient` (network glitch) → retry immediately
|
||
- `permanent` (bad input, constraint violation) → log & alert
|
||
- `dq` (data quality issue) → quarantine for manual review
|
||
- `business-hold` (waiting for approval/external event) → hold until ready
|
||
|
||
**Job Structure:**
|
||
```csharp
|
||
public class MyJobCommand : ICommand
|
||
{
|
||
public string IdempotencyKey { get; set; } // Unique per logical job
|
||
public Guid JobRunId { get; set; } // Hangfire instance ID
|
||
public Guid CorrelationId { get; set; } // Trace correlation
|
||
public Guid? Watermark { get; set; } // Job progress state
|
||
}
|
||
|
||
public class MyJobHandler : ICommandHandler<MyJobCommand>
|
||
{
|
||
public async Task Handle(MyJobCommand cmd, CancellationToken ct)
|
||
{
|
||
// Idempotent: safe to replay
|
||
// Must emit to Outbox on success
|
||
// Must classify failure and throw appropriate exception
|
||
}
|
||
}
|
||
```
|
||
|
||
**Job Execution:**
|
||
```csharp
|
||
// Enqueue via client
|
||
await backgroundJobClient.EnqueueAsync<MyJobHandler>(h => h.Handle(command, CancellationToken.None));
|
||
|
||
// Never call jobs directly from other jobs. Instead:
|
||
// 1. Emit event to Outbox
|
||
// 2. Inbox handler subscribes and enqueues next job
|
||
```
|
||
|
||
---
|
||
|
||
## v16.0 Frontend Architecture
|
||
|
||
### Registry-Driven Screen Registry
|
||
|
||
**Single Source of Truth:** Screen definition is the contract for routing, permissions, help, grid config, and component layout.
|
||
|
||
```typescript
|
||
// features/<feature>/registry.ts
|
||
export interface ScreenDefinition {
|
||
screenId: string; // e.g., "oms.orders.list"
|
||
title: string; // Display name
|
||
module: "OMS" | "WMS" | "ERP"; // Functional area
|
||
path: string; // Vue Router path
|
||
component: () => Promise<any>; // Lazy-loaded page component
|
||
permissions: string[]; // Required RBAC permissions
|
||
help?: HelpDefinition; // Contextual help
|
||
grid?: GridDefinition; // AG Grid config
|
||
shortcut?: string; // Keyboard shortcut
|
||
}
|
||
|
||
export const myListScreen: ScreenDefinition = {
|
||
screenId: "oms.orders.list",
|
||
title: "Orders",
|
||
path: "/oms/orders",
|
||
component: () => import("./pages/OrdersList.vue"),
|
||
permissions: ["order.view"],
|
||
help: { title: "...", sections: [...] },
|
||
grid: { columnDefs: [...], rowHeight: "auto" },
|
||
shortcut: "Ctrl+Shift+O"
|
||
}
|
||
|
||
export default [myListScreen]
|
||
```
|
||
|
||
**Central Registry:**
|
||
```typescript
|
||
// frontend/src/registry/index.ts
|
||
// Import all feature registries and merge into ScreenRegistry
|
||
// Used by app initialization, permission checks, help system, routing
|
||
```
|
||
|
||
**Route Generation:**
|
||
```typescript
|
||
// app/installKbx.ts
|
||
const registry = await loadScreenRegistry()
|
||
const routes = buildRouterFromRegistry(registry) // Page routes only
|
||
```
|
||
|
||
**Rules:**
|
||
- Routing is generated from registry. DO NOT define routes in `app/router.ts`
|
||
- Each screen is a top-level route. NO nested routing.
|
||
- Registry is immutable at runtime; use `useRegistry()` composable to access
|
||
|
||
### UI Adapter Boundary (Framework Isolation)
|
||
|
||
**Mandatory Pattern:** All PrimeVue and AG Grid usage goes through `@kbx/ui/adapter/`
|
||
|
||
```typescript
|
||
// ❌ DON'T: Use PrimeVue directly in screens
|
||
import { Button } from 'primevue/button'
|
||
<PButton label="Save" @click="save" />
|
||
|
||
// ✅ DO: Use KBX adapter (framework-agnostic)
|
||
import { KbxButton } from '@shared/ui/adapter'
|
||
<KbxButton label="Save" @click="save" />
|
||
|
||
// Adapter handles:
|
||
// - Theme switching (dark/light/system)
|
||
// - Density token application
|
||
// - Accessibility (ARIA, focus management)
|
||
// - Keyboard shortcuts
|
||
```
|
||
|
||
**Adapter exports:**
|
||
- `KbxButton`, `KbxInput`, `KbxSelect`, `KbxDialog`, etc.
|
||
- `useGridTheme()` for AG Grid configuration
|
||
- `useDesignToken(name)` for CSS custom properties
|
||
|
||
### State Management (Contract-Based)
|
||
|
||
| State | Owner | Tool | Registry Link |
|
||
|-------|-------|------|---|
|
||
| API responses, cache, stale, retry | TanStack Query | @tanstack/vue-query | → OpenAPI contracts |
|
||
| Session, role, UI preferences | Global Pinia | `authStore`, `registryStore` | → PermissionDefinition |
|
||
| Form values, errors, touched | Form library | vee-validate + Zod | → Screen.forms contract |
|
||
| URL filters, pagination, sorting | Router | vue-router query/params | → ScreenDefinition.grid |
|
||
| Large data tables | Server-side row model | AG Grid server mode | → GridDefinition contract |
|
||
|
||
**Rules:**
|
||
- ❌ Do NOT duplicate API responses in Pinia (use TanStack Query cache)
|
||
- ❌ Do NOT write error handling in every screen (use ErrorBoundary + QueryStateBoundary)
|
||
- ✅ DO cache only session/auth data in Pinia (global, cross-screen)
|
||
- ✅ DO use TanStack Query for all API state
|
||
|
||
### Component Elevation Criteria
|
||
|
||
Promote to `shared/ui/components/` only when:
|
||
1. **Same business meaning & permissions** across 3+ consumers
|
||
2. **Repeated state/error handling logic** (not 1-off variations)
|
||
3. **Accessibility & testing** fully implemented
|
||
4. **Contract-driven** (implements @kbx/contracts interface)
|
||
|
||
**Always-Shared Components (KBX System):**
|
||
- `QueryStateBoundary` (loading/error/empty)
|
||
- `PermissionGuard` (RBAC via registry)
|
||
- `ScreenHeader` (title, help, export buttons from registry)
|
||
- `AgGridShell` (AG Grid adapter with density tokens)
|
||
- `KbxStatus` (status display per contract)
|
||
- `KbxHelpPanel` (registry-driven help)
|
||
- `SkeletonLoader` (animated shimmer while loading)
|
||
- `EmptyStatePlaceholder` (zero-record state)
|
||
|
||
---
|
||
|
||
## v16.0 Observability
|
||
|
||
### Logging (Serilog)
|
||
|
||
**Correlation & Structure:**
|
||
- All logs tagged with `CorrelationId`, `JobRunId`, `EvidenceId`
|
||
- Structured properties enable filtering and analysis
|
||
- Sensitive data (PII, tokens, API keys) NEVER logged (use redaction middleware)
|
||
|
||
**Log Levels:**
|
||
- **INFO:** User actions, job completion, state changes
|
||
- **DEBUG:** Internal flow, decision branches, cache hits/misses
|
||
- **WARN:** Recoverable issues, retries, fallback activation
|
||
- **ERROR:** Unrecoverable failures, requires alert
|
||
|
||
### Tracing & Metrics (OpenTelemetry)
|
||
|
||
**Spans:** HTTP requests, database queries, job execution, event processing, policy decisions
|
||
|
||
**Metrics:** Instrumented for:
|
||
- Job completion time, queue depth
|
||
- Query latency, row count
|
||
- Event throughput, retry rate
|
||
|
||
### 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` for manual review
|
||
3. **Duplicate Detection:** Outbox duplicate events
|
||
4. **Reconciliation Breaks:** State mismatch (Evidence vs current)
|
||
5. **Model Drift:** OOS (out-of-sample) performance metrics
|
||
|
||
---
|
||
|
||
## v16.0 Common Workflows
|
||
|
||
### Adding a New Vertical Slice
|
||
|
||
**Before Code:**
|
||
1. Scaffold: `python tools/scaffold_vertical_slice.py --name MyFeature --module ModelOperations`
|
||
2. Define contract: Request/Response DTOs, Event schema, Validation rules
|
||
|
||
**Backend Implementation:**
|
||
1. Handler: Orchestration, transaction handling
|
||
2. Policy: Pure business logic
|
||
3. Sql: Dapper queries (schema-qualified, explicit columns, PIT)
|
||
4. Endpoint: HTTP routing & status codes
|
||
5. Tests: Unit (Policy), Integration (Handler + Sql + real DB)
|
||
6. README.md: Traceability link
|
||
|
||
**Frontend Implementation:**
|
||
1. Feature registry: `ScreenDefinition` entry
|
||
2. Pages: Router-level components under `features/<feature>/pages/`
|
||
3. Components: Feature-scoped under `features/<feature>/components/`
|
||
4. Stores/Composables: Feature-specific state and logic
|
||
5. Form validation: vee-validate + Zod schema from BE contract
|
||
|
||
**Pre-Merge Validation Gates:**
|
||
- Architecture tests pass
|
||
- DB migration is idempotent (fresh/upgrade/re-run/failure tests)
|
||
- No SELECT *, no cross-module queries
|
||
- Outbox/Inbox tests if async
|
||
- Frontend typecheck + test + build passes
|
||
- 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, HTTP) from logic (Policy)
|
||
3. **Transform:** One small change at a time (rename, extract, move)
|
||
4. **Verify:** All tests pass, no perf regression, algorithm changes vs Golden
|
||
5. **Simplify:** Delete dead abstractions, feature flags, branches
|
||
6. **Observe:** Post-release monitoring (SLO, data quality, model drift)
|
||
7. **Close Debt:** Update Tech Debt Register, leave ADR
|
||
|
||
### Creating a Background Job
|
||
|
||
1. **Define command:**
|
||
```csharp
|
||
public class MyJobCommand : ICommand
|
||
{
|
||
public string IdempotencyKey { get; set; }
|
||
public Guid CorrelationId { get; set; }
|
||
}
|
||
```
|
||
|
||
2. **Implement handler:**
|
||
- Idempotent: re-run = same result
|
||
- Classify failures: transient/permanent/dq/business-hold
|
||
- Emit to Outbox on success
|
||
|
||
3. **Schedule via Hangfire:**
|
||
```csharp
|
||
await backgroundJobClient.EnqueueAsync<MyJobHandler>(h => h.Handle(command, CancellationToken.None));
|
||
```
|
||
|
||
4. **Test scenarios:**
|
||
- Normal execution
|
||
- Retry on transient failure
|
||
- Replay from cold state (idempotency verification)
|
||
- Data quality quarantine
|