Files
KArtSell.Aegis/docs/CURRENT/PHASE-1_ACTIVATION_RUNBOOK.md
T
kjh2064 d731800954
deploy / deploy (push) Successful in 4m17s
deploy / notify (push) Successful in 2s
feat(phase1): add parameterized activation tooling + runbook (#21)
Co-authored-by: Claude Code <kjh2064@gmail.com>
Co-committed-by: Claude Code <kjh2064@gmail.com>
2026-08-07 14:13:45 +09:00

332 lines
10 KiB
Markdown

# Phase 1 Activation Runbook
**Date:** 2026-08-07
**Purpose:** Step-by-step activation of Phase 1 shadow run (252+ trading days)
**Owner:** Platform SRE
**Status:** READY FOR EXECUTION (All tools prepared)
---
## 🎯 Objective
Launch **Job 893 (Shadow Run)** with frozen model/dataset VersionSet, generating 252+ trading days of market simulation with auditable evidence trail.
**Timeline:**
- **Setup:** ~15 minutes (this runbook)
- **Execution:** 50-90 calendar days (automatic, no manual intervention)
- **Evidence Collection:** Concurrent (logs, metrics, state snapshots)
---
## 📋 PRE-FLIGHT CHECKLIST
**All items must be COMPLETE before proceeding to Step 1.**
- [ ] **1. Migration 0032 deployed**
Verify: `SELECT schema_version FROM schema_version_history WHERE script_name LIKE '0032_%'`
Status: Must return 1 row. If missing, run `dotnet run --project src/KArtSell.DbMigrator`
- [ ] **2. Host running in DEVELOPMENT mode**
Verify: `dotnet run --project src/KArtSell.Host -c Debug --no-build`
Expected: "Now listening on: http://127.0.0.1:5002"
**Why Debug mode?** `DevelopmentHeaderAuthenticationHandler` required for testing; Release mode uses `FailClosedAuthenticationHandler` (rejects all requests)
- [ ] **3. PostgreSQL accessible via SSH tunnel**
Verify: `ssh -L 5432:127.0.0.1:5432 kjh2064@178.104.200.7` (keep open in separate terminal)
Expected: No errors; tunnel stays alive
- [ ] **4. Hangfire scheduler running**
Verify: Host logs contain `Hangfire: JobStorage initialized`
Expected: Startup completes without timeout
- [ ] **5. Scripts available in ./scripts/**
Verify: `ls scripts/freeze-versionset.ps1 scripts/generate-shadow-run-identifiers.ps1`
---
## 🚀 STEP 1: FREEZE VERSIONSET
**Duration:** ~2 minutes
**Tool:** `./scripts/freeze-versionset.ps1`
### Action
Execute with **REAL, APPROVED** model/dataset IDs:
```powershell
cd C:\Job_Roomz\KArtSell.Aegis
$env:KARTSELL_POSTGRES = "Host=localhost;Port=5432;Database=kartsell;Username=kartsell;Password=kartsell"
.\scripts\freeze-versionset.ps1 `
-ModelId "00000000-0000-0000-0000-000000000001" `
-DatasetId "00000000-0000-0000-0000-000000000002" `
-ApprovedBy "kim.jae.hyun@example.com" `
-ConfigVersion "v1.0.0" `
-CodeSha "acaa731b3f"
```
### Expected Output
```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Phase 1: Freeze VersionSet
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[1/3] PRE-FLIGHT CHECK
Model ID: 00000000-0000-0000-0000-000000000001
Dataset ID: 00000000-0000-0000-0000-000000000002
Approved By: kim.jae.hyun@example.com
Config Version: v1.0.0
Code SHA: acaa731b3f
Connection: Host=localhost;Port=5432;Database=kartsell;***
[2/3] VERIFY Migration 0032 deployed...
✅ Migration 0032 deployed (schema_version: 32)
[3/3] FREEZE VersionSet...
✅ Inserted governance.model_version_registry:
- ID: <UUID>
- Model: 00000000-0000-0000-0000-000000000001
- Dataset: 00000000-0000-0000-0000-000000000002
- Status: FROZEN
✅ Inserted evaluation.dataset_manifest:
- ID: <UUID>
- Dataset: 00000000-0000-0000-0000-000000000002
- Model: 00000000-0000-0000-0000-000000000001
- Status: FROZEN
✅ VersionSet FROZEN successfully
Correlation ID: <UUID>
Next: Run generate-shadow-run-identifiers.ps1 to create RunId/JobId
```
### Troubleshooting
| Error | Cause | Fix |
|-------|-------|-----|
| "Migration 0032 NOT FOUND" | DbMigrator hasn't run yet | Run: `dotnet run --project src/KArtSell.DbMigrator` |
| "Cannot bind argument -ModelId" | Invalid UUID format | Use: `[System.Guid]::NewGuid() \| % { $_.ToString() }` to generate valid UUID |
| "Connection refused" | PostgreSQL not accessible | Verify SSH tunnel: `ssh -L 5432:127.0.0.1:5432 kjh2064@178.104.200.7` |
---
## 🚀 STEP 2: GENERATE IDENTIFIERS
**Duration:** ~1 minute
**Tool:** `./scripts/generate-shadow-run-identifiers.ps1`
### Action
```powershell
.\scripts\generate-shadow-run-identifiers.ps1 -OutputPath ./phase1-versionset.json
```
### Expected Output
```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Phase 1: Generate Shadow Run Identifiers
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[1/3] Generating cryptographic UUIDs...
✅ RunId: <UUID>
✅ JobId: <UUID>
✅ JobRunId: <UUID>
✅ CorrelationId: <UUID>
✅ IdempotencyKey: <UUID>
[2/3] Creating JSON payload...
✅ JSON payload generated
[3/3] Writing to file: ./phase1-versionset.json
✅ File saved: C:\Job_Roomz\KArtSell.Aegis\phase1-versionset.json
✅ IDENTIFIERS GENERATED
{
"phase1_run": {
"runId": "<UUID>",
"jobId": "<UUID>",
"jobRunId": "<UUID>",
"correlationId": "<UUID>",
"idempotencyKey": "<UUID>",
"generatedAt": "2026-08-07T10:30:00.000Z",
...
}
}
Next Steps:
1. Copy the identifiers from above or read from ./phase1-versionset.json
2. Call POST /api/shadow-runs with modelId/datasetId from frozen VersionSet
3. Hangfire will enqueue Job 893 with these correlation IDs
4. Monitor logs: grep 'CorrelationId: <UUID>' app.log
```
### Save for Reference
Copy output to clipboard or save in a secure file. You'll need these IDs in STEP 3.
---
## 🚀 STEP 3: ENQUEUE SHADOW RUN JOB
**Duration:** ~1 minute
**Method:** PowerShell HTTP request
### Prerequisites
- [ ] Host running on `http://127.0.0.1:5002` (Debug mode)
- [ ] VersionSet frozen (STEP 1 complete)
- [ ] Identifiers generated (STEP 2 complete)
### Action
```powershell
# Read generated identifiers
$versionset = Get-Content ./phase1-versionset.json | ConvertFrom-Json
$correlationId = $versionset.phase1_run.correlationId
$runId = $versionset.phase1_run.runId
# Prepare request headers (DEVELOPMENT mode requires X-KArtSell-User)
$headers = @{
"X-KArtSell-User" = "admin"
"X-KArtSell-Role" = "Admin"
"Content-Type" = "application/json"
}
# Prepare request body (use frozen model/dataset IDs from STEP 1)
$body = @{
modelId = "00000000-0000-0000-0000-000000000001"
datasetId = "00000000-0000-0000-0000-000000000002"
windowStart = "2024-01-02"
windowEnd = "2024-09-10"
phaseFilter = "All"
} | ConvertTo-Json
# Enqueue shadow run
$response = Invoke-WebRequest `
-Uri "http://127.0.0.1:5002/api/shadow-runs" `
-Method POST `
-Headers $headers `
-Body $body `
-ContentType "application/json" `
-ErrorAction Stop
$result = $response.Content | ConvertFrom-Json
Write-Host "✅ Shadow run enqueued!"
Write-Host " Job ID: $($result.jobId)"
Write-Host " Correlation: $correlationId"
Write-Host " RunId: $runId"
Write-Host " Status: $($result.status)"
```
### Expected Output (HTTP 202 Accepted)
```
✅ Shadow run enqueued!
Job ID: <UUID>
Correlation: <CorrelationId>
RunId: <RunId>
Status: Queued
```
### Troubleshooting
| Error | Cause | Fix |
|-------|-------|-----|
| HTTP 403/404 | Release mode (not Debug) | Check Host startup log; must contain "DevelopmentHeaderAuthenticationHandler" |
| HTTP 422 Unprocessable | Invalid model/dataset UUID | Verify UUIDs exist in `governance.model_version_registry` via SQL: `SELECT * FROM governance.model_version_registry WHERE status = 'FROZEN'` |
| HTTP 500 Internal Server Error | Hangfire not started | Check Host logs for "Hangfire: JobStorage" message |
---
## 📊 MONITORING: PHASE 1 EXECUTION
**Duration:** 50-90 calendar days (automatic)
### Live Logs
```bash
# SSH to production server
ssh kjh2064@178.104.200.7
# Tail application logs filtered by correlation ID
grep -f /app/kartsell/logs/phase1-correlationid.txt /app/kartsell/logs/app.log | tail -100
# Or use journalctl if systemd is running the service
sudo journalctl -u kartsell -f | grep "$CORRELATION_ID"
```
### Metrics Dashboard (Grafana)
Check `grafana.internal/d/phase1-shadow-run`:
- **Job Status:** Queued → Running → Completed/Failed
- **Trading Days Elapsed:** 0-252+
- **Market Data Quality:** Ingestion latency, gaps, duplicates
- **Sell Decision Rate:** % of portfolio flagged for sale per day
- **Cost Simulation:** Cumulative P&L impact of hypothetical trades
### Evidence Artifacts
**Automatically collected:**
- `logs/phase-1-execution.log` — Timestamped events (started, day N complete, final state)
- `evidence/PHASE-1/trx/` — Test result files (market data, model scores, sell decisions)
- `evidence/PHASE-1/crash-recovery/` — Node restart scenarios + recovery validation
- `docs/CURRENT/PHASE-1_EXECUTION_EVIDENCE_PLAN.md` — Full checklist
### Alerts
**Set up pagerduty/Telegram notifications:**
```bash
# Example: Notify if Phase 1 job fails
curl -X POST "https://api.telegram.org/bot$TELEGRAM_TOKEN/sendMessage" \
-d "chat_id=$TELEGRAM_CHAT_ID" \
-d "text=⚠️ Phase 1 Job $JOB_ID failed: $ERROR_MESSAGE"
```
---
## ✅ COMPLETION: PHASE 1 EXECUTION COMPLETE
**When:**
- Job 893 reaches 252+ trading days
- All sell decisions generated + cost impact simulated
- No gaps or anomalies in market data
**What to do:**
1. Download `logs/phase-1-execution.log` (evidence of completion)
2. Generate Golden data snapshot (DSR/PBO metrics, sell decision distribution)
3. Unlock Gates 2-5 (downstream slices depend on this data)
4. Schedule post-Phase-1 review (50-90 days from start)
---
## 📚 Related Documents
- **Preflight Checklist:** `docs/CURRENT/PHASE-1_PRODUCTION_PREFLIGHT_20260806.md`
- **Architecture Decision:** `docs/DECISIONS/ADR-SEC-001.md`
- **Hangfire Jobs:** `src/KArtSell.Host/Jobs/ShadowRunJob.cs`
- **Evidence Plan:** `docs/CURRENT/PHASE-1_EXECUTION_EVIDENCE_PLAN.md`
---
## 🆘 Emergency Rollback
**If Phase 1 must be stopped:**
1. SSH to production
2. `sudo systemctl stop kartsell`
3. Kill Job 893 in Hangfire Dashboard (Admin UI)
4. Archive logs: `cp /app/kartsell/logs/phase-1-execution.log evidence/PHASE-1/rollback-$(date +%s).log`
5. Notify team (Telegram/Email)
6. Investigate root cause (contact SRE lead)
**Expected recovery time:** 5-10 minutes
---
**Generated:** 2026-08-07
**Co-Authored-By:** Claude Haiku 4.5 <noreply@anthropic.com>