diff --git a/docs/CURRENT/PHASE-1_ACTIVATION_RUNBOOK.md b/docs/CURRENT/PHASE-1_ACTIVATION_RUNBOOK.md new file mode 100644 index 00000000..65ee07cc --- /dev/null +++ b/docs/CURRENT/PHASE-1_ACTIVATION_RUNBOOK.md @@ -0,0 +1,331 @@ +# 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: + - Model: 00000000-0000-0000-0000-000000000001 + - Dataset: 00000000-0000-0000-0000-000000000002 + - Status: FROZEN + āœ… Inserted evaluation.dataset_manifest: + - ID: + - Dataset: 00000000-0000-0000-0000-000000000002 + - Model: 00000000-0000-0000-0000-000000000001 + - Status: FROZEN + +āœ… VersionSet FROZEN successfully + Correlation ID: + 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: + āœ… JobId: + āœ… JobRunId: + āœ… CorrelationId: + āœ… IdempotencyKey: + +[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": "", + "jobId": "", + "jobRunId": "", + "correlationId": "", + "idempotencyKey": "", + "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: ' 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: + Correlation: + 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 diff --git a/scripts/freeze-versionset.ps1 b/scripts/freeze-versionset.ps1 new file mode 100644 index 00000000..666c3cbd --- /dev/null +++ b/scripts/freeze-versionset.ps1 @@ -0,0 +1,232 @@ +#!/usr/bin/env pwsh +<# +.SYNOPSIS + Freeze an approved model/dataset VersionSet for Phase 1 shadow run. + +.DESCRIPTION + Parameterized tool to INSERT approved model_id + dataset_id into: + - governance.model_version_registry (FROZEN status) + - evaluation.dataset_manifest (FROZEN status) + + NO default values; all parameters REQUIRED. Fails immediately if any parameter is missing. + +.PARAMETER ModelId + UUID of the approved model (e.g., "00000000-0000-0000-0000-000000000001") + Required. No default. + +.PARAMETER DatasetId + UUID of the approved dataset (e.g., "00000000-0000-0000-0000-000000000002") + Required. No default. + +.PARAMETER ApprovedBy + Email/ID of the approver (e.g., "kjh2064@gmail.com") + Required. No default. + +.PARAMETER ConfigVersion + Configuration version string (e.g., "v1.0.0") + Required. No default. + +.PARAMETER CodeSha + Git commit SHA (e.g., "acaa731b3f") + Required. No default. + +.PARAMETER ConnectionString + PostgreSQL connection string. + Default: $env:KARTSELL_POSTGRES + +.EXAMPLE + # Freeze a versionset (all parameters required) + .\freeze-versionset.ps1 ` + -ModelId "00000000-0000-0000-0000-000000000001" ` + -DatasetId "00000000-0000-0000-0000-000000000002" ` + -ApprovedBy "kjh2064@gmail.com" ` + -ConfigVersion "v1.0.0" ` + -CodeSha "acaa731b3f" + +.EXAMPLE + # Will fail: missing -ConfigVersion + .\freeze-versionset.ps1 ` + -ModelId "00000000-0000-0000-0000-000000000001" ` + -DatasetId "00000000-0000-0000-0000-000000000002" ` + -ApprovedBy "kjh2064@gmail.com" ` + -CodeSha "acaa731b3f" + # Error: Cannot bind argument to parameter 'ConfigVersion' because it is an empty string. +#> + +[CmdletBinding()] +param( + [Parameter(Mandatory, HelpMessage = "Model UUID (e.g., 00000000-0000-0000-0000-000000000001)")] + [ValidateScript({ $_ -match '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' })] + [string]$ModelId, + + [Parameter(Mandatory, HelpMessage = "Dataset UUID")] + [ValidateScript({ $_ -match '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' })] + [string]$DatasetId, + + [Parameter(Mandatory, HelpMessage = "Approver email/ID (e.g., kjh2064@gmail.com)")] + [ValidateScript({ $_ -match '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' })] + [string]$ApprovedBy, + + [Parameter(Mandatory, HelpMessage = "Config version (e.g., v1.0.0)")] + [ValidateScript({ $_ -match '^v[0-9]+\.[0-9]+\.[0-9]+' })] + [string]$ConfigVersion, + + [Parameter(Mandatory, HelpMessage = "Git commit SHA (at least 10 chars)")] + [ValidateScript({ $_.Length -ge 10 })] + [string]$CodeSha, + + [string]$ConnectionString = $env:KARTSELL_POSTGRES +) + +$ErrorActionPreference = 'Stop' + +Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor Cyan +Write-Host "Phase 1: Freeze VersionSet" -ForegroundColor Cyan +Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor Cyan + +# Validate connection string +if (-not $ConnectionString) { + Write-Error "ConnectionString not provided and `$env:KARTSELL_POSTGRES not set. Aborting." + exit 1 +} + +Write-Host "`n[1/3] PRE-FLIGHT CHECK" +Write-Host " Model ID: $ModelId" +Write-Host " Dataset ID: $DatasetId" +Write-Host " Approved By: $ApprovedBy" +Write-Host " Config Version: $ConfigVersion" +Write-Host " Code SHA: $CodeSha" +Write-Host " Connection: $(($ConnectionString -split 'Password=')[0])***" + +# Verify 0032 migration is deployed +Write-Host "`n[2/3] VERIFY Migration 0032 deployed..." +try { + $conn = New-Object System.Data.NpgsqlClient.NpgsqlConnection($ConnectionString) + $conn.Open() + + $cmd = $conn.CreateCommand() + $cmd.CommandText = @" +SELECT schema_version FROM schema_version_history +WHERE script_name = '0032_shadow_run_queued_status_contract.sql' +LIMIT 1 +"@ + $result = $cmd.ExecuteScalar() + + if ($null -eq $result) { + throw "Migration 0032 NOT FOUND. Run DbMigrator first." + } + + Write-Host " āœ… Migration 0032 deployed (schema_version: $result)" + $conn.Close() +} +catch { + Write-Error " āŒ Pre-flight failed: $_`n`nCorrective: Run DbMigrator to deploy 0032_*.sql before freezing." + exit 1 +} + +# Insert into governance.model_version_registry +Write-Host "`n[3/3] FREEZE VersionSet..." + +try { + $conn = New-Object System.Data.NpgsqlClient.NpgsqlConnection($ConnectionString) + $conn.Open() + + $correlationId = [System.Guid]::NewGuid() + $now = [System.DateTime]::UtcNow + + $cmd = $conn.CreateCommand() + $cmd.CommandText = @" +INSERT INTO governance.model_version_registry ( + id, model_id, dataset_id, status, approved_by, config_version, code_sha, + effective_at, published_at, revision, correlation_id +) VALUES ( + @id, @model_id, @dataset_id, 'FROZEN', @approved_by, @config_version, @code_sha, + @effective_at, @published_at, 1, @correlation_id +) +ON CONFLICT (model_id, dataset_id) DO UPDATE SET + status = 'FROZEN', + approved_by = EXCLUDED.approved_by, + config_version = EXCLUDED.config_version, + code_sha = EXCLUDED.code_sha, + effective_at = EXCLUDED.effective_at, + revision = governance.model_version_registry.revision + 1, + published_at = EXCLUDED.published_at +RETURNING id, model_id, dataset_id, status, effective_at +"@ + + $cmd.Parameters.AddWithValue("@id", [System.Guid]::NewGuid()) | Out-Null + $cmd.Parameters.AddWithValue("@model_id", [System.Guid]$ModelId) | Out-Null + $cmd.Parameters.AddWithValue("@dataset_id", [System.Guid]$DatasetId) | Out-Null + $cmd.Parameters.AddWithValue("@approved_by", $ApprovedBy) | Out-Null + $cmd.Parameters.AddWithValue("@config_version", $ConfigVersion) | Out-Null + $cmd.Parameters.AddWithValue("@code_sha", $CodeSha) | Out-Null + $cmd.Parameters.AddWithValue("@effective_at", $now) | Out-Null + $cmd.Parameters.AddWithValue("@published_at", $now) | Out-Null + $cmd.Parameters.AddWithValue("@correlation_id", $correlationId) | Out-Null + + $reader = $cmd.ExecuteReader() + if ($reader.Read()) { + $insertedId = $reader['id'] + $insertedModelId = $reader['model_id'] + $insertedDatasetId = $reader['dataset_id'] + $insertedStatus = $reader['status'] + + Write-Host " āœ… Inserted governance.model_version_registry:" + Write-Host " - ID: $insertedId" + Write-Host " - Model: $insertedModelId" + Write-Host " - Dataset: $insertedDatasetId" + Write-Host " - Status: $insertedStatus" + } + $reader.Close() + + # Update evaluation.dataset_manifest + $cmd2 = $conn.CreateCommand() + $cmd2.CommandText = @" +INSERT INTO evaluation.dataset_manifest ( + id, dataset_id, model_id, status, freeze_reason, + published_at, revision, correlation_id +) VALUES ( + @id, @dataset_id, @model_id, 'FROZEN', 'Phase 1 VersionSet freeze', + @published_at, 1, @correlation_id +) +ON CONFLICT (dataset_id, model_id) DO UPDATE SET + status = 'FROZEN', + freeze_reason = 'Phase 1 VersionSet freeze', + revision = evaluation.dataset_manifest.revision + 1, + published_at = EXCLUDED.published_at +RETURNING id, dataset_id, model_id, status +"@ + + $cmd2.Parameters.AddWithValue("@id", [System.Guid]::NewGuid()) | Out-Null + $cmd2.Parameters.AddWithValue("@dataset_id", [System.Guid]$DatasetId) | Out-Null + $cmd2.Parameters.AddWithValue("@model_id", [System.Guid]$ModelId) | Out-Null + $cmd2.Parameters.AddWithValue("@published_at", $now) | Out-Null + $cmd2.Parameters.AddWithValue("@correlation_id", $correlationId) | Out-Null + + $reader2 = $cmd2.ExecuteReader() + if ($reader2.Read()) { + $mId = $reader2['id'] + $mDatasetId = $reader2['dataset_id'] + $mModelId = $reader2['model_id'] + $mStatus = $reader2['status'] + + Write-Host " āœ… Inserted evaluation.dataset_manifest:" + Write-Host " - ID: $mId" + Write-Host " - Dataset: $mDatasetId" + Write-Host " - Model: $mModelId" + Write-Host " - Status: $mStatus" + } + $reader2.Close() + + $conn.Close() + + Write-Host "`nāœ… VersionSet FROZEN successfully" + Write-Host " Correlation ID: $correlationId" + Write-Host " Next: Run generate-shadow-run-identifiers.ps1 to create RunId/JobId" +} +catch { + Write-Error " āŒ Failed to freeze VersionSet: $_" + exit 1 +} + +Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor Cyan diff --git a/scripts/generate-shadow-run-identifiers.ps1 b/scripts/generate-shadow-run-identifiers.ps1 new file mode 100644 index 00000000..91fe4993 --- /dev/null +++ b/scripts/generate-shadow-run-identifiers.ps1 @@ -0,0 +1,90 @@ +#!/usr/bin/env pwsh +<# +.SYNOPSIS + Generate Phase 1 shadow run identifiers (RunId, JobId, JobRunId, CorrelationId, Idempotency-Key). + +.DESCRIPTION + Produces a JSON-formatted versionset.json file with all identifiers needed to enqueue Phase 1. + Uses CRYPTOGRAPHIC random UUIDs and correlation for full traceability. + +.PARAMETER OutputPath + Path to save versionset.json (default: ./versionset.json in current directory) + +.EXAMPLE + .\generate-shadow-run-identifiers.ps1 -OutputPath ./phase1-versionset.json + +.OUTPUTS + JSON file with structure: + { + "phase1_run": { + "runId": "UUID", + "jobId": "UUID", + "jobRunId": "UUID", + "correlationId": "UUID", + "idempotencyKey": "UUID", + "generatedAt": "ISO8601 timestamp", + "usage": "Use these IDs to enqueue Job 893 in Hangfire..." + } + } +#> + +[CmdletBinding()] +param( + [string]$OutputPath = "./versionset.json" +) + +$ErrorActionPreference = 'Stop' + +Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor Cyan +Write-Host "Phase 1: Generate Shadow Run Identifiers" -ForegroundColor Cyan +Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor Cyan + +Write-Host "`n[1/3] Generating cryptographic UUIDs..." + +$runId = [System.Guid]::NewGuid() +$jobId = [System.Guid]::NewGuid() +$jobRunId = [System.Guid]::NewGuid() +$correlationId = [System.Guid]::NewGuid() +$idempotencyKey = [System.Guid]::NewGuid() + +Write-Host " āœ… RunId: $runId" +Write-Host " āœ… JobId: $jobId" +Write-Host " āœ… JobRunId: $jobRunId" +Write-Host " āœ… CorrelationId: $correlationId" +Write-Host " āœ… IdempotencyKey: $idempotencyKey" + +Write-Host "`n[2/3] Creating JSON payload..." + +$payload = @{ + phase1_run = @{ + runId = $runId.ToString() + jobId = $jobId.ToString() + jobRunId = $jobRunId.ToString() + correlationId = $correlationId.ToString() + idempotencyKey = $idempotencyKey.ToString() + generatedAt = [System.DateTime]::UtcNow.ToString("o") + windowStart = "2024-01-02" + windowEnd = "2024-09-10" + usage = "Use these IDs to enqueue Job 893 (Phase 1 shadow run) in Hangfire. Command: `n Invoke-WebRequest -Uri 'http://127.0.0.1:5002/api/shadow-runs' -Method POST -Headers @{ 'X-KArtSell-User'='admin'; 'X-KArtSell-Role'='Admin'; 'Content-Type'='application/json' } -Body (ConvertTo-Json @{ modelId=''; datasetId=''; windowStart='2024-01-02'; windowEnd='2024-09-10'; phaseFilter='All' })" + } +} + +Write-Host " āœ… JSON payload generated" + +Write-Host "`n[3/3] Writing to file: $OutputPath" + +$json = $payload | ConvertTo-Json -Depth 10 +$json | Out-File -FilePath $OutputPath -Encoding UTF8 + +Write-Host " āœ… File saved: $(Resolve-Path $OutputPath)" + +Write-Host "`nāœ… IDENTIFIERS GENERATED`n" +Write-Host $json -ForegroundColor Green + +Write-Host "`nNext Steps:`n" +Write-Host " 1. Copy the identifiers from above or read from $OutputPath" +Write-Host " 2. Call POST /api/shadow-runs with modelId/datasetId from frozen VersionSet" +Write-Host " 3. Hangfire will enqueue Job 893 with these correlation IDs" +Write-Host " 4. Monitor logs: grep 'CorrelationId: $correlationId' app.log" + +Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor Cyan