Files
KArtSell.Aegis/docs/CURRENT/SLICE_SPECS/VS-10-SLICE_SPEC.md
T
kjh2064 b1e38ac374 feat: Phase 3 J/K/L (Sell Decision, Trade Execution, Portfolio Reconciliation) + fix pre-existing build/boot breakage
Completes VS-10/VS-12/VS-14 and makes the solution and Host actually
build and boot for the first time on this branch (main did not build
before this commit).

Root-cause fixes required to reach a green build/boot (not scoped to
J/K/L but blocking any verification of it):
- Restore Polly PackageVersion accidentally deleted from
  Directory.Packages.props (broke KArtSell.Host).
- Remove MediatR dependency from Compliance/VS-04 (package was never
  installed; ICommand/ICommandHandler/IMediator never existed) and
  wire Endpoint -> Handler directly per this repo's convention.
- Migrate FastEndpoints v5 API calls (SendOkAsync/SendAsync/
  SendCreatedAtAsync/SendNotFoundAsync, Description().WithName()) to
  the v7 Send.* fluent API across ~10 endpoint files.
- Fix migrations 0036/0038/0039/0040: rewritten from invalid T-SQL
  (`IF NOT EXISTS ... BEGIN ... END`) to idiomatic Postgres
  (`CREATE TABLE/INDEX IF NOT EXISTS`) — these could not apply to any
  fresh database before this fix.
- Collapse 3 duplicate cross-cutting abstractions that shadowed the
  BuildingBlocks versions and caused type-mismatch compile errors:
  IKrxDataService, IOutboxWriter (ReconcileTradeHandler), IClock
  (ApprovalWorkflow/ApprovalPolicy).
- Inject IClock (BuildingBlocks.Time) in place of direct
  DateTime.Now/UtcNow across 19 files to satisfy the architecture
  test AGENTS.md#DateTime-abstraction rule (13/13 architecture tests
  now pass, was 12/13).
- Register all new and previously-unregistered slices in
  Program.cs DI (SellDecision, TradeExecution, PortfolioReconciliation,
  Compliance, Features/ApprovalWorkflow) — the Host had never
  successfully completed a boot with this code present.
- Disable ("[DontRegister]") the older, route-colliding
  ApprovalWorkflow/ (Workstream H) endpoint set in favor of
  Features/ApprovalWorkflow/ (Workstream G, matches the documented
  Features/<Slice>/ convention); kept for its existing test coverage.
  See TECH_DEBT-017 for the follow-up decision needed.

Verified: dotnet build 0 errors/0 warnings; architecture tests 13/13;
unit tests 54/54 + 18/18; integration tests 34/36 (2 failures are a
local test-DB migration-journal/schema mismatch, not a code defect);
Host boots cleanly and registers all 34 endpoints.

New tech debt recorded: DEBT-017 (duplicate VS-03 implementation),
DEBT-018 (outbox write not co-transactional with entity write in
TradeExecution/PortfolioReconciliation), DEBT-019 (duplicate
BuildingBlocks-shadowing abstractions, partially resolved).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-07 19:53:38 +09:00

13 KiB

VS-10: Sell Decision Engine

Status: SPECIFICATION (Workstream J, Phase 3)
Owner: Quant Lead + Backend
Duration: 4-5 weeks (parallel with K/L)


1. User Story

As a portfolio manager making risk-adjusted sell decisions,
I want a quantitative sell decision engine that ranks candidates by priority and validates readiness gates,
So that all trades comply with PBO/DSR/OOS standards before approval.

Acceptance Criteria:

  • Sell priority ranking (HARD_IMPAIRMENT → REENTRY_OPTION)
  • PBO/DSR/OOS validation gates (thresholds configurable)
  • Approval workflow integration (VS-03 maker-checker)
  • Immutable decision history (PIT tracking)
  • Evidence linkage to S3 artifacts
  • 15+ unit tests, 8+ integration tests

2. State Machine

PENDING
  ↓
  [Generate signal from model recommendations]
  ↓
SIGNAL_GENERATED
  ↓
  [Validate PBO score ≥ 0.65]
  ↓
PBO_VALIDATED
  ↓
  [Validate DSR ratio ≥ 0.015]
  ↓
DSR_VALIDATED
  ↓
  [Validate OOS performance (>= baseline)]
  ↓
OOS_APPROVED
  ↓
  [Check governance readiness: All gates passed]
  ↓
READY_FOR_APPROVAL
  ↓
  [Maker creates approval proposal (VS-03)]
  ↓
APPROVED
  ↓
  [Execute trade via KIS API (Workstream K)]
  ↓
EXECUTED
  ↓
  [Confirm settlement from KIS]
  ↓
CONFIRMED

Allowed Transitions:

PENDING → SIGNAL_GENERATED (always, model consensus)
SIGNAL_GENERATED → PBO_VALIDATED (on valid score)
SIGNAL_GENERATED → READY_FOR_APPROVAL (if skip PBO)
PBO_VALIDATED → DSR_VALIDATED (on valid ratio)
PBO_VALIDATED → READY_FOR_APPROVAL (if skip DSR)
DSR_VALIDATED → OOS_APPROVED (on valid backtest)
OOS_APPROVED → READY_FOR_APPROVAL (gate check passed)
READY_FOR_APPROVAL → APPROVED (via VS-03 approver)
APPROVED → EXECUTED (via Workstream K)
EXECUTED → CONFIRMED (via KIS settlement confirmation)

Reject paths:
Any state → READY_FOR_APPROVAL (if gate validation fails, bypass to approval anyway)

3. RBAC & Approval

Role Action Constraint
Quant View decisions, run validation gates Read-only
Maker Create sell decisions, propose approval Must not be Checker
Checker Approve/reject decisions Must not be Maker (VS-03 separation of duties)
Admin Adjust thresholds, override gates (audit required) Rare, logged

4. API Contracts

4.1 POST /sell-decisions

Purpose: Generate a new sell decision from model signals.

Request:

{
  "modelId": "00000000-0000-0000-0000-000000000001",
  "windowStart": "2024-01-02",
  "windowEnd": "2024-09-10",
  "thresholdPbo": 0.65,
  "thresholdDsr": 0.015,
  "justification": "Model consensus: sell signal strength > 0.8"
}

Response (202 Accepted):

{
  "decisionId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "modelId": "00000000-0000-0000-0000-000000000001",
  "status": "PENDING",
  "correlationId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "createdAt": "2026-08-10T10:30:00Z"
}

Status Codes:

  • 202 Accepted — Decision created, validation gates queued
  • 400 Bad Request — Invalid model_id, thresholds out of range
  • 403 Forbidden — Insufficient role (not Maker)
  • 409 Conflict — Duplicate decision (idempotency key conflict)
  • 503 Service Unavailable — Phase 1 data not ready

4.2 GET /sell-decisions

Purpose: List sell decisions with filtering.

Query Parameters:

?status=READY_FOR_APPROVAL        # Filter by status
&modelId=xxx                       # Filter by model
&executionDateFrom=2026-08-10      # Date range
&executionDateTo=2026-08-20
&limit=50&offset=0                 # Pagination

Response (200 OK):

{
  "decisions": [
    {
      "decisionId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
      "modelId": "00000000-0000-0000-0000-000000000001",
      "status": "READY_FOR_APPROVAL",
      "pboScore": 0.72,
      "dsrMetric": 0.018,
      "sellPriority": 2,
      "targetQuantity": 500,
      "targetPrice": 150.25,
      "approvalId": null,
      "executionId": null,
      "createdAt": "2026-08-10T10:30:00Z",
      "correlation_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    }
  ],
  "total": 42,
  "limit": 50,
  "offset": 0
}

Status Codes:

  • 200 OK — Success
  • 403 Forbidden — Insufficient role (not Quant/Maker/Checker)

4.3 POST /sell-decisions/{id}/execute

Purpose: Trigger execution of an approved sell decision.

Request:

{
  "approvalId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "executionPrice": 150.25,
  "quantity": 500,
  "justification": "Approved via VS-03, ready for KIS submission"
}

Response (202 Accepted):

{
  "decisionId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "executionId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "status": "EXECUTED",
  "kisOrderId": "20260810001",
  "submittedAt": "2026-08-10T10:35:00Z",
  "correlationId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}

Status Codes:

  • 202 Accepted — Trade submitted to KIS
  • 400 Bad Request — Invalid approval_id, quantity mismatch
  • 403 Forbidden — Insufficient role (not Maker/Checker)
  • 409 Conflict — Decision not in APPROVED state
  • 503 Service Unavailable — KIS API unavailable

5. Data Contracts

5.1 Sell Decisions Table

CREATE TABLE model_operations.sell_decisions (
  id UUID PRIMARY KEY,
  model_id UUID NOT NULL REFERENCES model_operations.models(id),
  status VARCHAR(50) NOT NULL,  -- PENDING, SIGNAL_GENERATED, PBO_VALIDATED, etc.
  pbo_score DECIMAL(5,4),       -- Probability of backtest overfit (0-1)
  dsr_metric DECIMAL(5,4),      -- Daily Sharpe ratio (0-1)
  oos_performance JSONB,        -- Out-of-sample test results
  sell_priority INT,             -- 1 (HARD_IMPAIRMENT) to 6 (REENTRY_OPTION)
  target_quantity INT,           -- Qty to sell
  target_price DECIMAL(15,2),    -- Limit price
  approval_id UUID REFERENCES model_operations.approval_proposals(id),
  execution_id UUID,             -- Reference to KIS trade (set by K)
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  created_by VARCHAR(255) NOT NULL,
  created_justification TEXT,
  published_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  correlation_id UUID NOT NULL,
  revision INT NOT NULL DEFAULT 1
);

CREATE INDEX ix_sell_decisions_model_id ON model_operations.sell_decisions(model_id);
CREATE INDEX ix_sell_decisions_status ON model_operations.sell_decisions(status);
CREATE INDEX ix_sell_decisions_correlation_id ON model_operations.sell_decisions(correlation_id);
CREATE INDEX ix_sell_decisions_published_at ON model_operations.sell_decisions(published_at DESC);

5.2 Sell Decision Evidence Table

CREATE TABLE model_operations.sell_decision_evidence (
  id UUID PRIMARY KEY,
  decision_id UUID NOT NULL REFERENCES model_operations.sell_decisions(id),
  evidence_type VARCHAR(50) NOT NULL,  -- PBO_REPORT, DSR_METRIC, OOS_BACKTEST
  evidence_url TEXT NOT NULL,          -- S3 URI to artifact
  validated_at TIMESTAMPTZ,
  validator_email VARCHAR(255),
  comments TEXT,
  published_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  correlation_id UUID NOT NULL
);

CREATE INDEX ix_sell_decision_evidence_decision_id ON model_operations.sell_decision_evidence(decision_id);
CREATE INDEX ix_sell_decision_evidence_type ON model_operations.sell_decision_evidence(evidence_type);
CREATE INDEX ix_sell_decision_evidence_correlation_id ON model_operations.sell_decision_evidence(correlation_id);

6. Sell Priority Ranking

Immutable priority order (per business policy):

1. HARD_IMPAIRMENT      — Position at serious loss (>30% drawdown) — IMMEDIATE
2. PORTFOLIO_SURVIVAL   — Margin/liquidity crisis risk — URGENT
3. DYNAMIC_PROFIT_FLOOR — Profit protection (stop-loss) — HIGH
4. CONCENTRATION        — Single position >25% of portfolio — MEDIUM
5. LIQUIDITY            — Illiquid holding approaching lock-in — MEDIUM
6. OPPORTUNITY_COST     — Better risk/reward elsewhere — LOW
7. REENTRY_OPTION       — Tactical sell for re-entry at lower price — LOWEST

Algorithm:

// Scoring: lower score = higher priority
// HARD_IMPAIRMENT: 1000 points (always first)
// PORTFOLIO_SURVIVAL: 500 points
// etc.

decimal Score(SellPriority priority, decimal fundAge, decimal liquidityPct)
{
  decimal baseScore = priority switch
  {
    SellPriority.HardImpairment => 1000,
    SellPriority.PortfolioSurvival => 500,
    SellPriority.DynamicProfitFloor => 300,
    SellPriority.Concentration => 200,
    SellPriority.Liquidity => 200,
    SellPriority.OpportunityCost => 100,
    SellPriority.ReentryOption => 50,
    _ => 0
  };

  // Adjust: older funds, illiquid positions get boost (lower score)
  decimal ageBoost = (fundAge > 365) ? -50 : 0;
  decimal liquidityBoost = (liquidityPct < 0.2) ? -25 : 0;

  return baseScore + ageBoost + liquidityBoost;
}

7. Validation Gates

7.1 PBO Validation

Rule: pbo_score >= threshold_pbo (default: 0.65)
Interpretation: Probability of backtest overfit ≤ 35%
Action: If PASS → PBO_VALIDATED, If FAIL → flag for override

7.2 DSR Validation

Rule: dsr_metric >= threshold_dsr (default: 0.015)
Interpretation: Daily Sharpe ratio ≥ 0.015 (1.5% daily return/risk)
Action: If PASS → DSR_VALIDATED, If FAIL → flag for override

7.3 OOS Validation

Rule: oos_performance.return >= oos_performance.baseline_return
Interpretation: Out-of-sample performance meets or exceeds baseline
Action: If PASS → OOS_APPROVED, If FAIL → requires justification

8. Dependencies & Integration

Phase 2 Integration (Already Implemented)

  • VS-03 Approval Workflow: Sell decisions integrate with maker-checker approval
  • VS-04 Audit Trail: All state transitions logged to compliance.audit_events
  • Models: Reference model_operations.models(id) for model_id FK

Phase 3 Integration (Downstream)

  • Workstream K (Trade Execution): Approved decisions → KIS trades
  • Workstream L (Portfolio Reconciliation): Executed trades → cost basis updates

External Dependencies

  • Phase 1 Evidence: OOS/PBO/DSR metrics generated autonomously (Job 893)
  • S3 Artifacts: Evidence links point to evidence/{ModelId}/{EvidenceType}/*.json

9. Testing Strategy

Unit Tests (15+)

  1. Sell priority ranking (3 tests: normal case, ties, boundary values)
  2. PBO validation (3 tests: pass, fail, edge cases)
  3. DSR validation (3 tests: pass, fail, edge cases)
  4. OOS validation (3 tests: pass, fail, baseline mismatch)
  5. State machine transitions (3 tests: valid, invalid, idempotency)

Integration Tests (8+)

  1. E2E: Create → PBO_VALIDATED → DSR_VALIDATED → OOS_APPROVED
  2. E2E: READY_FOR_APPROVAL → APPROVED (via VS-03)
  3. E2E: APPROVED → EXECUTED (via Workstream K)
  4. Approval integration: Decision linked to approval_id
  5. Audit integration: All state changes logged
  6. Pagination & filtering
  7. Idempotency: Duplicate POST returns 409
  8. RBAC enforcement (Quant read-only, Maker propose)

Contract Tests (3+)

  1. vs-03-approval-workflow-integration
  2. vs-04-audit-trail-integration
  3. workstream-k-sell-decision-trade-link

10. AGENTS.md v16.0 Compliance

Criterion Evidence
1. SOLID 3 validators (Pbo, Dsr, Oos) + ranker (separate SRP)
2. Complexity All classes <300 lines (validators, ranker, handlers)
3. Audit correlation_id, published_at, revision on all records
4. Necessity Grounded in Phase 1 evidence (PBO/DSR/OOS)
5. Normalization 3NF schemas, append-only decisions, PIT tracked
6. Simplicity State machine clearly defined, no hidden assumptions
7. Pattern Vertical Slice (Services/Handlers/Endpoints/Sql/Tests)
8. Guardrails Validation gates (PBO/DSR/OOS) + RBAC enforcement
9. Traceability Evidence links, CorrelationId, ADR-DECISION-01
10. Safety Idempotent operations, no partial success
11. Maturity Spec-before-code (this document)
12. Right-Way Formal validation, no shortcuts
13. Debt No new tech debt, enables Phase 3

11. Runbook

Deployment

# 1. Apply migration
dotnet run --project src/KArtSell.DbMigrator

# 2. Run tests
dotnet test --filter "Category=VS10" -c Release

# 3. Deploy Host
dotnet run --project src/KArtSell.Host --configuration Debug

Troubleshooting

Q: "Phase 1 data not ready" error
A: Job 893 still running; check /api/phase-1-status for progress

Q: PBO score returns NULL
A: Model OOS evidence not yet generated; retry after Phase 1 checkpoint

Q: Approval workflow rejects decision
A: Check VS-03 status; Maker must be different from Checker

Co-Authored-By: Claude Haiku 4.5 noreply@anthropic.com