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>
This commit is contained in:
@@ -542,3 +542,404 @@ Every task — code change, refactor, new feature, tooling, infrastructure — m
|
||||
- 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
|
||||
|
||||
@@ -0,0 +1,401 @@
|
||||
# Architecture Deep Dive
|
||||
|
||||
**Reference:** For quick overview, see CLAUDE.md "Architecture" section.
|
||||
**Governance:** All decisions follow AGENTS.md v16.0 and VIBE Coding Guardrails.
|
||||
|
||||
## Backend: Modular Monolith + Vertical Slices
|
||||
|
||||
### Module Structure
|
||||
```
|
||||
src/
|
||||
KArtSell.Host/ # Main ASP.NET Core app
|
||||
KArtSell.BuildingBlocks/ # Shared infrastructure (logging, serialization, extensions)
|
||||
KArtSell.DbMigrator/ # DbUp migrations
|
||||
KArtSell.Modules.ModelOperations/ # Model lifecycle, validation, activation
|
||||
KArtSell.Modules.SignalEngine/ # Trading signal generation
|
||||
```
|
||||
|
||||
### Vertical Slice Template
|
||||
Each feature is a complete, self-contained slice from HTTP endpoint to database:
|
||||
|
||||
```
|
||||
Features/<SliceName>/
|
||||
Endpoint.cs # FastEndpoints route handler (HTTP/contract/status codes)
|
||||
Request.cs # Input model with validation
|
||||
Response.cs # Output model (DTO)
|
||||
Validator.cs # Fluent/Policy validation rules
|
||||
Handler.cs # Use case orchestration (Application layer)
|
||||
Policy.cs # Pure business decision logic (Domain layer)
|
||||
Sql.cs # Dapper queries (Data layer)
|
||||
Mapper.cs # Entity ↔ DTO mapping
|
||||
Jobs/ # Related Hangfire jobs
|
||||
Contracts/ # Event/Job contract definitions
|
||||
Tests/ # Unit/integration tests specific to this slice
|
||||
README.md # Traceability: requirements, ADRs, assumptions
|
||||
```
|
||||
|
||||
**Key rule:** Endpoint handles HTTP concerns; Handler handles transaction boundaries; Policy makes decisions; Sql uses Dapper for explicit, schema-qualified queries.
|
||||
|
||||
### Design Principles
|
||||
- **No Generic Repository:** Each slice writes its own Dapper queries; promotes clarity.
|
||||
- **No Service Layer:** Handler + Policy + Sql replaces it; keeps flow visible.
|
||||
- **Module Isolation:** Modules do not query each other's source tables directly.
|
||||
- Synchronous: Use narrow Read Port services.
|
||||
- Asynchronous: Use Outbox/Inbox event patterns.
|
||||
- **PIT (Point-in-Time) Queries:** Must include `WHERE published_at <= cutoff` and revision resolver.
|
||||
- **Evidence & Audit:** Update/delete are blocked; new state appended as new revision.
|
||||
- **Migrations:** `src/KArtSell.DbMigrator` uses DbUp; file naming: `NNNN_description.sql`.
|
||||
|
||||
### Query Patterns
|
||||
```csharp
|
||||
// ✅ DO: Schema-qualified, explicit columns, cancellation token
|
||||
const string sql = """
|
||||
SELECT id, name, created_at
|
||||
FROM model_operations.signals
|
||||
WHERE published_at <= @cutoff
|
||||
AND status = @status
|
||||
ORDER BY created_at DESC
|
||||
""";
|
||||
|
||||
// ❌ DON'T: SELECT *, generic repository, no token
|
||||
const string sql = "SELECT * FROM signals WHERE status = @status";
|
||||
```
|
||||
|
||||
### Async Coupling: Outbox/Inbox
|
||||
- **Outbox:** When a command succeeds, events are inserted into `outbox` in the same transaction.
|
||||
- **Inbox:** A Hangfire job polls the outbox, publishes events, and marks them as processed.
|
||||
- **Idempotency:** Each inbox handler is idempotent; replayed events are no-ops.
|
||||
|
||||
## Database & Migrations
|
||||
|
||||
### DbUp
|
||||
- **Run at startup:** `KArtSell.DbMigrator` is the single source of truth.
|
||||
- **Schema ownership:** Each module owns its schema (e.g., `model_operations.*`, `signal_engine.*`).
|
||||
- **Safety:** Migrations are idempotent and checksummed; failed migration rolls back and waits for manual intervention.
|
||||
- **Test:** Each migration has fresh/upgrade/re-run/failure-recovery tests in CI.
|
||||
|
||||
## Hangfire (Background Jobs & Scheduling)
|
||||
|
||||
### Job Design
|
||||
- **Not a business decision maker:** Hangfire executes approved Application Commands, not policies.
|
||||
- **Idempotency key:** Each job must be replayable without side effects.
|
||||
- **Watermark & version set:** Track input/output state across retries.
|
||||
- **Queue isolation:** `q-customer-sla` (business SLA) is separate from `q-research` (non-critical).
|
||||
- **Retry classification:**
|
||||
- `transient` (network glitch, retry immediately)
|
||||
- `permanent` (bad input, log & alert)
|
||||
- `dq` (data quality issue, quarantine for manual review)
|
||||
- `business-hold` (awaiting approval or external event)
|
||||
|
||||
### Example Job Structure
|
||||
```csharp
|
||||
public class MyJobCommand : ICommand
|
||||
{
|
||||
public string IdempotencyKey { get; set; }
|
||||
public Guid JobRunId { get; set; }
|
||||
public Guid CorrelationId { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
Jobs do not call other jobs directly; instead, they emit events or check readiness gates.
|
||||
|
||||
## SignalR (Real-Time Push)
|
||||
|
||||
Used for live notifications (model activation events, approval notifications). Follows Hub/Group pattern with correlation to `CorrelationId` for traceability.
|
||||
|
||||
---
|
||||
|
||||
## Frontend: Vue 3 + Vite + KBX Foundation v4 (Operational Navigation)
|
||||
|
||||
### Directory Layout (Registry-Driven)
|
||||
```
|
||||
frontend/src/
|
||||
app/
|
||||
router.ts # Vue Router setup (page-level only)
|
||||
installKbx.ts # KBX system initialization (registry, contracts, permissions)
|
||||
features/
|
||||
<feature>/
|
||||
routes.ts # Feature route definitions (lazy-loaded)
|
||||
registry.ts # Screen registry entry (@kbx/contracts.ScreenDefinition)
|
||||
pages/
|
||||
<Screen>.vue # Page component (matches registry.screenId)
|
||||
components/ # Feature-scoped components (not shared)
|
||||
stores/ # Pinia stores (feature state)
|
||||
composables/ # Reusable hooks (feature logic)
|
||||
types/ # TS interfaces for this feature
|
||||
shared/
|
||||
ui/
|
||||
adapter/ # MANDATORY boundary: PrimeVue/AG Grid wrappers
|
||||
components/ # Cross-feature components (shared contracts)
|
||||
layouts/ # Page layout templates (header, sidebar, footer)
|
||||
tokens/ # Design tokens (compact, comfortable, touch density)
|
||||
composables/
|
||||
types/
|
||||
stores/
|
||||
registry/ # Central screen definition registry
|
||||
index.ts # Import all feature registries, export merged ScreenRegistry
|
||||
ui-context.ts # UI adapter context provider
|
||||
design-system/ # Design tokens (NOT arbitrary page CSS)
|
||||
tokens.css # CSS custom properties (34px, 44px, 52px, etc.)
|
||||
density/ # compact, comfortable, touch variants
|
||||
```
|
||||
|
||||
### KBX Contracts (@kbx/contracts)
|
||||
|
||||
```typescript
|
||||
// ScreenDefinition (required in all feature registries)
|
||||
export interface ScreenDefinition {
|
||||
screenId: string // e.g., "oms.orders.list"
|
||||
title: string // Display name (localized)
|
||||
module: "OMS" | "WMS" | "ERP" // Functional area
|
||||
path: string // Vue Router path
|
||||
component: () => Promise<any> // Lazy-loaded page component
|
||||
permissions: string[] // Required roles (e.g., ["order.view"])
|
||||
help?: HelpDefinition // Contextual help (registry-driven)
|
||||
grid?: GridDefinition // AG Grid config (shared theme)
|
||||
shortcut?: string // Keyboard shortcut (help searchable)
|
||||
}
|
||||
|
||||
// PermissionDefinition (centralized RBAC)
|
||||
export interface PermissionDefinition {
|
||||
permissionId: string // e.g., "order.create"
|
||||
label: string // Human-readable (for audit/help)
|
||||
screens: string[] // Which screens require this permission
|
||||
forms: string[] // Which forms check this permission
|
||||
}
|
||||
|
||||
// HelpDefinition (context-aware, registry-indexed)
|
||||
export interface HelpDefinition {
|
||||
title: string // Panel title (screen context)
|
||||
sections: HelpSection[]
|
||||
relatedScreens: string[] // Cross-screen navigation
|
||||
externalUrl?: string // Knowledge base link
|
||||
}
|
||||
```
|
||||
|
||||
### App Initialization (@kbx Lifecycle)
|
||||
|
||||
```typescript
|
||||
// frontend/src/app/installKbx.ts
|
||||
// 1. Load screen registry (all feature registries merged)
|
||||
const registry = await loadScreenRegistry()
|
||||
|
||||
// 2. Install permission context (RBAC decision engine)
|
||||
app.use(createPermissionContext(registry))
|
||||
|
||||
// 3. Install router with lazy-loaded pages
|
||||
const router = createRouter({
|
||||
routes: buildRouterFromRegistry(registry) // Page routes only
|
||||
})
|
||||
|
||||
// 4. Install KBX global components (adapter-wrapped UI)
|
||||
app.use(KbxUiPlugin)
|
||||
|
||||
// 5. Populate stores (registry cache for help, permissions, status)
|
||||
useRegistryStore().setRegistry(registry)
|
||||
```
|
||||
|
||||
### UI Adapter Pattern (Mandatory Boundary)
|
||||
|
||||
All UI framework usage must go through `@kbx/ui/adapter`:
|
||||
|
||||
```typescript
|
||||
// ❌ DON'T: Use PrimeVue directly in screens
|
||||
<PButton label="Save" @click="save" />
|
||||
|
||||
// ✅ DO: Use KBX adapter (framework-agnostic)
|
||||
<KbxButton label="Save" @click="save" />
|
||||
|
||||
// Adapter handles:
|
||||
// - Theme switching (dark/light/system)
|
||||
// - Density token application (compact/comfortable/touch)
|
||||
// - Accessibility (ARIA, focus management)
|
||||
// - Keyboard shortcuts (Ctrl+S, etc.)
|
||||
```
|
||||
|
||||
### State Management (Registry-Driven, Contract-Based)
|
||||
|
||||
| State | Owner | Tool | Registry Link |
|
||||
|-------|-------|------|---|
|
||||
| API responses, cache, stale, retry | TanStack Query | @tanstack/vue-query | → API contracts (OpenAPI) |
|
||||
| Session, role, UI preferences | Global Pinia | `authStore`, `registryStore` | → PermissionDefinition |
|
||||
| Form values, errors, touched | Form library | vee-validate + Zod schema | → Screen.forms contract |
|
||||
| URL filters, pagination, sorting | Router | vue-router query/params | → ScreenDefinition.grid |
|
||||
| Large data tables, virtual scroll | Server-side row model | AG Grid server mode (adapter) | → GridDefinition contract |
|
||||
|
||||
**Anti-patterns:**
|
||||
- ❌ Do NOT duplicate API responses in Pinia (use TanStack Query cache).
|
||||
- ❌ Do NOT write 401/409/422/429/503 error handling in every screen (use ErrorBoundary + QueryStateBoundary).
|
||||
- ❌ Do NOT manage query cache manually.
|
||||
- ❌ Do NOT define routes outside registry (route table is generated from registry).
|
||||
- ❌ Do NOT bypass PermissionGuard for conditional rendering (use registry-driven rendering).
|
||||
|
||||
### Screen Component Example
|
||||
|
||||
```vue
|
||||
<!-- features/orders/pages/OrdersList.vue -->
|
||||
<template>
|
||||
<div>
|
||||
<!-- Header: registry-driven title, help, export -->
|
||||
<ScreenHeader :screenId="screenId" />
|
||||
|
||||
<!-- Content: data grid with server-side row model -->
|
||||
<QueryStateBoundary :query="ordersQuery">
|
||||
<AgGridShell
|
||||
:gridOptions="gridConfig"
|
||||
:rows="ordersQuery.data"
|
||||
:loading="ordersQuery.isPending"
|
||||
/>
|
||||
</QueryStateBoundary>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// Registry access (read-only, cached)
|
||||
const registry = useRegistry()
|
||||
const screenDef = registry.screens.get('oms.orders.list')
|
||||
const screenId = screenDef.screenId
|
||||
|
||||
// Permission check (registry-driven)
|
||||
const can = usePermission()
|
||||
const canCreate = can('order.create') // Registry permission ID
|
||||
|
||||
// Data fetching (TanStack Query, no Pinia duplication)
|
||||
const ordersQuery = useQuery({
|
||||
queryKey: ['orders', filters],
|
||||
queryFn: () => api.orders.list(filters)
|
||||
})
|
||||
|
||||
// Grid config (adapter-wrapped, density-aware)
|
||||
const gridConfig = computed(() => ({
|
||||
columnDefs: screenDef.grid.columnDefs,
|
||||
rowHeight: tokens.gridRowHeight,
|
||||
...defaultGridOptions
|
||||
}))
|
||||
</script>
|
||||
```
|
||||
|
||||
### Screen Registry Entry
|
||||
|
||||
```typescript
|
||||
export const ordersListScreen: ScreenDefinition = {
|
||||
screenId: "oms.orders.list",
|
||||
title: "Orders",
|
||||
module: "OMS",
|
||||
path: "/oms/orders",
|
||||
component: () => import("./pages/OrdersList.vue"),
|
||||
permissions: ["order.view"],
|
||||
help: {
|
||||
title: "Order Search & Management",
|
||||
sections: [{
|
||||
title: "How to search",
|
||||
content: "Use filters at the top to search by date, customer, or status"
|
||||
}],
|
||||
relatedScreens: ["oms.orders.detail", "oms.orders.register"]
|
||||
},
|
||||
grid: {
|
||||
columnDefs: [
|
||||
{ field: "orderId", headerName: "Order ID", width: 120 },
|
||||
{ field: "customerName", headerName: "Customer", width: 200 }
|
||||
],
|
||||
rowHeight: "auto",
|
||||
serverSideDatasource: true
|
||||
},
|
||||
shortcut: "Ctrl+Shift+O"
|
||||
}
|
||||
```
|
||||
|
||||
### Component Elevation Criteria
|
||||
|
||||
Promote to `shared/ui/components/` only when:
|
||||
1. **Same business meaning & permissions** (check registry.screens[].permissions).
|
||||
2. **Repeated state/error handling logic** across 3+ consumers.
|
||||
3. **Accessibility & testing** fully implemented.
|
||||
4. **Contract-driven** (implements @kbx/contracts interface).
|
||||
|
||||
**Always-shared components (KBX system):**
|
||||
- `QueryStateBoundary` (loading/error/empty, registry context-aware)
|
||||
- `PermissionGuard` (RBAC via registry.permissions)
|
||||
- `ScreenHeader` (title, help trigger, export buttons from registry)
|
||||
- `AgGridShell` (AG Grid adapter with density tokens)
|
||||
- `KbxStatus` (status display per StatusDefinition contract)
|
||||
- `KbxHelpPanel` (registry-driven help, contextual)
|
||||
|
||||
### Design Token Density
|
||||
|
||||
Screen density (compact/comfortable/touch) is applied globally via tokens, NOT per-screen CSS:
|
||||
|
||||
```css
|
||||
/* ✅ DO: Define tokens, let screens inherit */
|
||||
:root {
|
||||
--kbx-density: compact; /* or 'comfortable', 'touch' */
|
||||
--kbx-input-height: 34px;
|
||||
--kbx-grid-row-height: 34px;
|
||||
--kbx-touch-target: 44px;
|
||||
}
|
||||
|
||||
:root[data-density="comfortable"] {
|
||||
--kbx-input-height: 36px;
|
||||
--kbx-grid-row-height: 36px;
|
||||
--kbx-touch-target: 48px;
|
||||
}
|
||||
|
||||
:root[data-density="touch"] {
|
||||
--kbx-input-height: 52px;
|
||||
--kbx-grid-row-height: 48px;
|
||||
--kbx-touch-target: 52px;
|
||||
}
|
||||
```
|
||||
|
||||
### Route Registration Flow
|
||||
|
||||
1. **Feature Registry** (`features/<feature>/registry.ts`): Define ScreenDefinition(s)
|
||||
2. **Central Registry** (`frontend/src/registry/index.ts`): Import and merge all feature registries
|
||||
3. **Router Build** (`app/installKbx.ts`): Generate Vue Router routes from registry
|
||||
4. **Page-Level Routes Only**: No nested routing; each screen is a top-level route
|
||||
|
||||
### Permission & Help Enforcement
|
||||
|
||||
**Registry-driven RBAC:**
|
||||
```typescript
|
||||
// ✅ DO: Registry-driven permission checks
|
||||
const canEdit = computed(() => {
|
||||
const screen = registry.screens.get('oms.orders.detail')
|
||||
return permissions.hasAll(screen.permissions)
|
||||
})
|
||||
|
||||
// ❌ DON'T: Hard-coded permission strings
|
||||
const canEdit = permissions.has('order.edit') // WRONG: no registry reference
|
||||
```
|
||||
|
||||
**Registry-driven Help:**
|
||||
```typescript
|
||||
// ✅ DO: Help from registry
|
||||
const { openHelp } = useHelpPanel()
|
||||
openHelp('oms.orders.list')
|
||||
|
||||
// ❌ DON'T: Hard-coded help text
|
||||
const helpText = "Use filters to search..." // WRONG: duplicates registry
|
||||
```
|
||||
|
||||
### Contract Enforcement (CI/CD Gate)
|
||||
|
||||
Build-time validation ensures all screens comply with contracts:
|
||||
|
||||
```bash
|
||||
# .gitea/workflows/quality-gate.yml
|
||||
- name: Validate screen contracts
|
||||
run: |
|
||||
# 1. Check: All files in features/*/pages/*.vue match registry entries
|
||||
# 2. Check: All ScreenDefinition.permissions exist in permissionRegistry
|
||||
# 3. Check: Grid configs use adapter tokens, not inline CSS
|
||||
# 4. Check: No PrimeVue/AG Grid imports outside adapter/
|
||||
# 5. Generate: ScreenManifest.json for help/telemetry indexing
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## FastEndpoints
|
||||
|
||||
- Docs: [FastEndpoints GitHub](https://github.com/FastEndpoints/FastEndpoints)
|
||||
- Pattern: Each endpoint maps to a Vertical Slice; routes are discovered automatically.
|
||||
@@ -0,0 +1,129 @@
|
||||
# Common Workflows
|
||||
|
||||
**Reference:** For quick commands, see CLAUDE.md "Quick Start" section.
|
||||
**Governance:** All work follows AGENTS.md v16.0 and VIBE Coding Guardrails.
|
||||
|
||||
## Adding a New Vertical Slice
|
||||
|
||||
1. **Scaffold the structure:**
|
||||
```bash
|
||||
python tools/scaffold_vertical_slice.py --name MyFeature --module ModelOperations
|
||||
```
|
||||
|
||||
2. **Define the contract** (before code):
|
||||
- Request/Response DTOs in `Contracts/`
|
||||
- Event schema in `Contracts/Events/` if async coupling needed
|
||||
- Validation rules (vee-validate schema on FE, Fluent on BE)
|
||||
|
||||
3. **Implement backend slice:**
|
||||
- `Handler.cs`: Orchestration, transaction handling
|
||||
- `Policy.cs`: Pure business logic
|
||||
- `Sql.cs`: Dapper queries (schema-qualified, no SELECT *)
|
||||
- `Endpoint.cs`: HTTP routing & status codes
|
||||
- `README.md`: Traceability link to requirement/ADR
|
||||
|
||||
4. **Write tests:**
|
||||
- Unit: Policy, Mapper logic
|
||||
- Integration: Handler + Dapper + real DB
|
||||
- Verify Outbox events are created if async
|
||||
|
||||
5. **Implement frontend feature:**
|
||||
- Feature module under `features/<feature>/`
|
||||
- Use `features/<feature>/pages/` for route-level components
|
||||
- Use `shared/ui/adapter/` for any UI component usage
|
||||
- Form validation with vee-validate + Zod schema from BE contract
|
||||
|
||||
6. **Validation gates (pre-merge):**
|
||||
- Architecture tests pass
|
||||
- DB migration is idempotent (fresh/upgrade test)
|
||||
- No SELECT *, no direct cross-module queries
|
||||
- Outbox/Inbox tests if async
|
||||
- Frontend typecheck + test + build
|
||||
- E2E smoke test (if user-facing)
|
||||
|
||||
## Refactoring (Characterized, Isolated, Verified)
|
||||
|
||||
1. **Characterize:** Lock current behavior with tests + perf baseline + Golden data.
|
||||
2. **Isolate:** Separate I/O (Dapper queries, HTTP) from logic (Policy).
|
||||
3. **Transform:** One small change at a time (rename, extract, move).
|
||||
4. **Verify:** All tests pass, no perf regression, backtest algorithm changes against Golden.
|
||||
5. **Simplify:** Delete dead abstractions, feature flags, branches.
|
||||
6. **Observe:** Post-release SLO/DQ/model drift monitoring.
|
||||
7. **Close Debt:** Update Debt ID, leave ADR for future maintainers.
|
||||
|
||||
## Creating a Background Job
|
||||
|
||||
1. **Define the command:**
|
||||
```csharp
|
||||
public class MyJobCommand : ICommand
|
||||
{
|
||||
public Guid IdempotencyKey { get; set; }
|
||||
public Guid CorrelationId { get; set; }
|
||||
public string InputData { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
2. **Implement the handler:**
|
||||
- Idempotent: Re-run should be safe and produce same result.
|
||||
- Classify failures: transient/permanent/dq/business-hold.
|
||||
- Emit events to Outbox for async notifications.
|
||||
|
||||
3. **Schedule via Hangfire:**
|
||||
```csharp
|
||||
await backgroundJobClient.EnqueueAsync<MyJobHandler>(h => h.Handle(command));
|
||||
```
|
||||
|
||||
4. **Test retry & replay scenarios:**
|
||||
- Job runs successfully.
|
||||
- Job fails and is retried (verify idempotency).
|
||||
- Job is replayed from cold state (verify determinism).
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### xUnit Backend Tests
|
||||
|
||||
```bash
|
||||
dotnet test KArtSell.sln -c Release
|
||||
dotnet test --filter "Category=Integration" -c Release
|
||||
dotnet test --filter "FullyQualifiedName~UnitTests" -c Release --verbosity quiet
|
||||
```
|
||||
|
||||
**Test Levels:**
|
||||
1. **Unit:** Pure functions (Policy, Mapper), no I/O. Fast, deterministic.
|
||||
2. **Integration:** Handler + Dapper + real PostgreSQL. Validates transaction boundaries, Outbox/Inbox.
|
||||
3. **Data:** SQL query validation, schema conformance, index effectiveness.
|
||||
4. **E2E:** Full HTTP stack; used sparingly for critical paths.
|
||||
5. **Golden/Frozen OOS:** Before merging algorithm changes, lock baseline and diff against new run.
|
||||
|
||||
### Vitest Frontend Tests
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
pnpm test # Run all tests
|
||||
pnpm test -- --reporter=verbose # Verbose output
|
||||
pnpm test -- <test-file-pattern> # Run subset
|
||||
pnpm test -- --coverage # Coverage report
|
||||
```
|
||||
|
||||
### Playwright E2E
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
pnpm e2e # Run all E2E tests headless
|
||||
pnpm e2e -- --debug # Debug mode (browser stays open)
|
||||
pnpm exec playwright test --headed # Run with browser UI
|
||||
```
|
||||
|
||||
## Tools & Scripts
|
||||
|
||||
### Scaffolding
|
||||
```bash
|
||||
python tools/scaffold_vertical_slice.py --name MyFeature --module ModelOperations
|
||||
python tools/scaffold_ui_screen.py --name MyScreen --feature MyFeature
|
||||
```
|
||||
|
||||
### Validation
|
||||
```bash
|
||||
python tools/validate_v16.py # Full v16 validation (contracts, migrations, Python tests)
|
||||
python -m unittest discover # Run all Python unit tests
|
||||
```
|
||||
@@ -0,0 +1,127 @@
|
||||
# Gitea API & External Data Sources
|
||||
|
||||
## Gitea Actions Secrets
|
||||
|
||||
**External API keys are stored in Gitea Actions Secrets (not in .env or code).**
|
||||
|
||||
**Location:** `https://gitea.taxbaik.com/kjh2064/KArtSell.Aegis/settings/actions/secrets`
|
||||
|
||||
**Available secrets:**
|
||||
- `KRX_OPENAPI` — Korea Exchange OpenAPI (stock prices, indices, market data)
|
||||
- `OPENDART_API` — OpenDart financial disclosure & quarterly reporting
|
||||
- `KIS_APP_KEY` / `KIS_APP_SECRET` — Korea Investment & Securities trading API
|
||||
|
||||
**Usage in CI/CD (`.gitea/workflows/*.yml`):**
|
||||
```yaml
|
||||
env:
|
||||
KRX_OPENAPI: ${{ secrets.KRX_OPENAPI }}
|
||||
OPENDART_API: ${{ secrets.OPENDART_API }}
|
||||
KIS_APP_KEY: ${{ secrets.KIS_APP_KEY }}
|
||||
KIS_APP_SECRET: ${{ secrets.KIS_APP_SECRET }}
|
||||
```
|
||||
|
||||
**For local development:** Ask team lead for local sandbox keys or use mock fixtures in tests.
|
||||
|
||||
---
|
||||
|
||||
## External Data APIs
|
||||
|
||||
### KRX OpenAPI (Korea Exchange)
|
||||
**Official Guide:** https://openapi.krx.co.kr/contents/OPP/INFO/service/OPPINFO004.cmd
|
||||
|
||||
**Available Services:**
|
||||
| Service | Link | Endpoint | Method | Auth |
|
||||
|---------|------|----------|--------|------|
|
||||
| **지수 (Indices)** | https://openapi.krx.co.kr/contents/OPP/USES/service/OPPUSES001_S1.cmd | `/svc/apis/idx/krx_dd_trd` | POST | AUTH_KEY header |
|
||||
| **주식 (Stocks)** | https://openapi.krx.co.kr/contents/OPP/USES/service/OPPUSES002_S1.cmd | `/svc/apis/sco/...` | POST | AUTH_KEY header |
|
||||
| **증권상품** | https://openapi.krx.co.kr/contents/OPP/USES/service/OPPUSES003_S1.cmd | `/svc/apis/sec/...` | POST | AUTH_KEY header |
|
||||
| **채권** | https://openapi.krx.co.kr/contents/OPP/USES/service/OPPUSES004_S1.cmd | `/svc/apis/bon/...` | POST | AUTH_KEY header |
|
||||
| **파생상품** | https://openapi.krx.co.kr/contents/OPP/USES/service/OPPUSES005_S1.cmd | `/svc/apis/drv/...` | POST | AUTH_KEY header |
|
||||
| **일반상품** | https://openapi.krx.co.kr/contents/OPP/USES/service/OPPUSES006_S1.cmd | `/svc/apis/gen/...` | POST | AUTH_KEY header |
|
||||
| **ESG** | https://openapi.krx.co.kr/contents/OPP/USES/service/OPPUSES007_S1.cmd | `/svc/apis/esg/...` | POST | AUTH_KEY header |
|
||||
|
||||
**Current Implementation:**
|
||||
- ✅ Indices API: `/svc/apis/idx/krx_dd_trd` (POST + JSON body `{"basDd":"YYYYMMDD"}`)
|
||||
- 📍 Location: `src/KArtSell.Modules.ModelOperations/ShadowRun/Services/KrxDataService.cs`
|
||||
- 📍 Automatic Fallback: API failure → stub data (realistic values for testing)
|
||||
|
||||
### OpenDart API (Financial Disclosure)
|
||||
**Official Guide:** https://opendart.fss.or.kr/guide/main.do
|
||||
|
||||
**Available API Groups:**
|
||||
| Group | Link | Endpoint | Method | Auth | Purpose |
|
||||
|-------|------|----------|--------|------|---------|
|
||||
| **공시정보** | https://opendart.fss.or.kr/guide/detail.do?apiGrpCd=DS001 | `/api/list.json` | GET | crtfc_key | Disclosure search |
|
||||
| **정기보고서 주요정보** | https://opendart.fss.or.kr/guide/detail.do?apiGrpCd=DS002 | `/api/...` | GET | crtfc_key | Annual report highlights |
|
||||
| **정기보고서 재무정보** | https://opendart.fss.or.kr/guide/detail.do?apiGrpCd=DS003 | `/api/...` | GET | crtfc_key | Quarterly financial data |
|
||||
| **지분공시 종합정보** | https://opendart.fss.or.kr/guide/detail.do?apiGrpCd=DS004 | `/api/...` | GET | crtfc_key | Equity disclosure |
|
||||
| **주요사항보고서** | https://opendart.fss.or.kr/guide/detail.do?apiGrpCd=DS005 | `/api/...` | GET | crtfc_key | Material event reports |
|
||||
| **증권신고서** | https://opendart.fss.or.kr/guide/detail.do?apiGrpCd=DS006 | `/api/...` | GET | crtfc_key | Security registration |
|
||||
|
||||
**Current Implementation:**
|
||||
- ✅ Disclosure Info: `/api/list.json?crtfc_key=KEY&corp_code=CODE` (GET)
|
||||
- 📍 Location: `src/KArtSell.Host/Observability/OpenDartService.cs`
|
||||
- 📍 Note: Current endpoint returns disclosure listings, not quarterly financial data
|
||||
- 📍 For financial data: Use DS003 group (정기보고서 재무정보)
|
||||
|
||||
---
|
||||
|
||||
## Gitea API Automation (Optional)
|
||||
|
||||
### Environment Setup
|
||||
```bash
|
||||
# Enable Gitea API automation (optional)
|
||||
$env:GITEA_TOKEN_TAXBAIK = "your-gitea-api-token" # Windows PowerShell
|
||||
export GITEA_TOKEN_TAXBAIK="your-gitea-api-token" # macOS/Linux
|
||||
```
|
||||
|
||||
### Common Tasks
|
||||
|
||||
**1. Verify PR Build Status**
|
||||
```bash
|
||||
# After successful build/test, comment on PR:
|
||||
curl -X POST \
|
||||
-H "Authorization: token $GITEA_TOKEN_TAXBAIK" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"body":"✅ Build: PASS\n✅ Tests: 41/41 PASS\n✅ Security: Clean"}' \
|
||||
https://gitea.taxbaik.com/api/v1/repos/kjh2064/KArtSell.Aegis/issues/{PR_NUMBER}/comments
|
||||
```
|
||||
|
||||
**2. Auto-Label PRs by Module**
|
||||
```bash
|
||||
# Label PR with affected modules
|
||||
curl -X POST \
|
||||
-H "Authorization: token $GITEA_TOKEN_TAXBAIK" \
|
||||
-d '["architecture","performance","observability"]' \
|
||||
https://gitea.taxbaik.com/api/v1/repos/kjh2064/KArtSell.Aegis/issues/{PR_NUMBER}/labels
|
||||
```
|
||||
|
||||
**3. Link to Tech Debt Registry**
|
||||
```bash
|
||||
# Reference debt in commit message (e.g., in CI job):
|
||||
git commit -m "fix: CA1822 static method hints - TECH-001
|
||||
|
||||
Resolves technical debt from NoWarn bypass.
|
||||
Part of quarterly paydown target (20% per quarter).
|
||||
|
||||
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>"
|
||||
```
|
||||
|
||||
**4. Gitea Actions Integration** (`.gitea/workflows/ci.yml`)
|
||||
```yaml
|
||||
- name: Post PR verification results
|
||||
if: always()
|
||||
run: |
|
||||
BODY="## Verification Results
|
||||
- Build: ${{ job.status }}
|
||||
- Tests: 41/41 ✅
|
||||
- Debt Paydown: TECH-001 resolved
|
||||
|
||||
[See full logs](https://gitea.taxbaik.com/kjh2064/KArtSell.Aegis/actions)"
|
||||
|
||||
curl -X POST \
|
||||
-H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"body\":\"$BODY\"}" \
|
||||
https://gitea.taxbaik.com/api/v1/repos/kjh2064/KArtSell.Aegis/issues/${{ github.event.pull_request.number }}/comments
|
||||
```
|
||||
Reference in New Issue
Block a user