Files
KArtSell.Aegis/AGENTS.md
T
kjh2064 07ad98ec12 refactor(docs): Optimize CLAUDE.md structure (47KB→12KB) + expand AGENTS.md v16.0
## 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>
2026-08-16 14:47:41 +09:00

48 KiB
Raw Permalink Blame History

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:

# 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):

# 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:

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

{
  "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):

ssh -L 5432:127.0.0.1:5432 kjh2064@178.104.200.7

Start Backend (use config file, no env var injection):

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):

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)

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

<!--  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:

$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 원칙

  • Single Responsibility: One class, one reason to change. Vertical Slice boundaries are trust boundaries.
  • Open/Closed: Open for extension (new Policies, new decision gates); closed for modification (immutable Evidence, append-only migrations).
  • Liskov Substitution: Handlers, Policies, Adapters are swappable; never break contract.
  • Interface Segregation: IClock ≠ IDateTime; IOutboxWriter ≠ IEventBus. Ports are narrow.
  • Dependency 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:

# 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):

cd frontend
pnpm test                           # Run all
pnpm test -- --reporter=verbose
pnpm test -- <test-file-pattern>
pnpm test -- --coverage

E2E Tests (Playwright):

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):

// ✅ 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:

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:

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

// 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:

// frontend/src/registry/index.ts
// Import all feature registries and merge into ScreenRegistry
// Used by app initialization, permission checks, help system, routing

Route Generation:

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

// ❌ 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:

    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:

    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