# 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 `
๋ฐ์ดํฐ๊ฐ ์์ต๋๋ค
` 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:** `` (header right side) โ e.g., Help, AI Suggest
- **Command bar:** `` โ e.g., Save, Reset, Approve buttons
- **Status info:** `` โ 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
```
**Implementation:**
- PageLayout: `` 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 --
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//
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(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
{
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(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//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; // 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'
// โ DO: Use KBX adapter (framework-agnostic)
import { KbxButton } from '@shared/ui/adapter'
// 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//pages/`
3. Components: Feature-scoped under `features//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(h => h.Handle(command, CancellationToken.None));
```
4. **Test scenarios:**
- Normal execution
- Retry on transient failure
- Replay from cold state (idempotency verification)
- Data quality quarantine