feat(simulation): MiroFish Phase 2 — theater-limited simulation runner#2220
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Contributor
|
Preview deployment for your docs. Learn more about Mintlify Previews.
|
koala73
added a commit
that referenced
this pull request
Mar 25, 2026
Security (P1 #18): - sanitizeForPrompt() applied to all entity/seed fields interpolated into Round 1 prompt (entityId, class, stance, seedId, type, timing) - sanitizeForPrompt() applied to actorId and entityIds in Round 2 prompt - sanitizeForPrompt() + length caps applied to all LLM array fields written to R2 (dominantReactions, stabilizers, invalidators, keyActors, timingMarkers) Validation (P1 #19): - Added validateRunId() regex guard - Applied in enqueueSimulationTask() and processNextSimulationTask() loop Type safety (P1 #20): - Added isOutcomePointer() and isPackagePointer() type guards in TS handlers - Replaced unsafe as-casts with runtime-validated guards in both handlers Correctness (P2 #22): - Log warning when pkgPointer.runId does not match task runId Architecture (P2 #24): - isMaritimeChokeEnergyCandidate() accepts both flat and nested topBucketId - Call site simplified to pass theater directly Performance (P2 #25): - SIMULATION_ROUND1_MAX_TOKENS raised 1800 to 2200 - Added max 3 initialReactions instruction to Round 1 prompt Maintainability (P2 #26): - Simulation pointer keys exported from server/_shared/cache-keys.ts - Both TS handlers import from shared location Documentation (P2 #27): - Strengthened runId no-op description in proto and OpenAPI spec
Adds the simulation execution layer that consumes simulation-package.json and produces simulation-outcome.json for maritime chokepoint + energy/logistics theaters, closing the WorldMonitor → MiroFish handoff loop. Changes: - scripts/seed-forecasts.mjs: 2-round LLM simulation runner (prompt builders, JSON extractor, runTheaterSimulation, writeSimulationOutcome, task queue with NX dedup lock, runSimulationWorker poll loop) - scripts/process-simulation-tasks.mjs: standalone worker entry point - proto: GetSimulationOutcome RPC + make generate - server/worldmonitor/forecast/v1/get-simulation-outcome.ts: RPC handler - server/gateway.ts: slow tier for get-simulation-outcome - api/health.js: simulationOutcomeLatest in STANDALONE + ON_DEMAND keys - tests: 14 new tests for simulation runner functions
Security (P1 #18): - sanitizeForPrompt() applied to all entity/seed fields interpolated into Round 1 prompt (entityId, class, stance, seedId, type, timing) - sanitizeForPrompt() applied to actorId and entityIds in Round 2 prompt - sanitizeForPrompt() + length caps applied to all LLM array fields written to R2 (dominantReactions, stabilizers, invalidators, keyActors, timingMarkers) Validation (P1 #19): - Added validateRunId() regex guard - Applied in enqueueSimulationTask() and processNextSimulationTask() loop Type safety (P1 #20): - Added isOutcomePointer() and isPackagePointer() type guards in TS handlers - Replaced unsafe as-casts with runtime-validated guards in both handlers Correctness (P2 #22): - Log warning when pkgPointer.runId does not match task runId Architecture (P2 #24): - isMaritimeChokeEnergyCandidate() accepts both flat and nested topBucketId - Call site simplified to pass theater directly Performance (P2 #25): - SIMULATION_ROUND1_MAX_TOKENS raised 1800 to 2200 - Added max 3 initialReactions instruction to Round 1 prompt Maintainability (P2 #26): - Simulation pointer keys exported from server/_shared/cache-keys.ts - Both TS handlers import from shared location Documentation (P2 #27): - Strengthened runId no-op description in proto and OpenAPI spec
…andler coverage Two tests identified as missing during PR #2220 review: 1. isMaritimeChokeEnergyCandidate flat-shape tests — covers the || candidate.topBucketId normalization added in the P1/P2 review pass. The existing tests only used the nested marketContext.topBucketId shape; this adds the flat root-field shape that arrives from the simulation-package.json JSON (selectedTheaters entries have topBucketId at root). 2. getSimulationOutcome handler structural tests — verifies the isOutcomePointer guard, found:false NOT_FOUND return, found:true success path, note population on runId mismatch, and redis_unavailable error string. Follows the readSrc static-analysis pattern used elsewhere in server-handlers.test.mjs (handler imports Redis so full integration test would require a test Redis instance).
koala73
force-pushed
the
feat/mirofish-phase2-simulation-runner
branch
from
March 25, 2026 09:37
a7e066f to
95b81e3
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why this PR?
Phase 1 (PR #2204) built
buildSimulationPackageFromDeepSnapshot— it writessimulation-package.jsonto R2 with selected theaters, entities, event seeds, constraints, and evaluation targets. Phase 2 prerequisites (PR #2219) addedgetSimulationPackageRPC so agents can discover the latest package pointer from Redis.This PR closes the loop: a simulation runner reads the package, runs a 2-round LLM actor simulation for maritime chokepoint + energy/logistics theaters, and writes
simulation-outcome.jsonback to R2 with a Redis pointer. A newgetSimulationOutcomeRPC exposes the pointer to clients.What changed
scripts/seed-forecasts.mjs— simulation runner coreSIMULATION_OUTCOME_LATEST_KEY, task queue keys, lock keys, round token budgets, TTLsisMaritimeChokeEnergyCandidate(theater): Phase 2 scope gate — passes only maritime chokepoint + energy/logistics theatersbuildSimulationRound1SystemPrompt(theater, pkg): structured prompt from entities, event seeds, constraints, evaluation targets, simulationRequirement. Produces 3 divergent paths (escalation/containment/spillover) with initialReactions and dominantReactions. All interpolated theater fields sanitized against prompt injectionbuildSimulationRound2SystemPrompt(theater, pkg, round1): builds 72h evolution prompt from round 1 path summaries. Produces keyActors, roundByRoundEvolution (2 entries), timingMarkers, stabilizers, invalidators, confidence, globalObservations, confidenceNotesextractSimulationRoundPayload(text, round): 3-stage extractor (strip think tags → fenced blocks → raw JSON → extractFirstJsonObject fallback) following same pattern asextractImpactExpansionPayloadrunTheaterSimulation(theater, pkg): callscallForecastLLMfor round 1, then conditionally round 2 if round 1 succeeded. Round 2 failure is partial-valid (round 1 results preserved)writeSimulationOutcome(pkg, outcome, { storageConfig }): writessimulation-outcome.jsonto R2 viabuildSimulationOutcomeKey, then writesSIMULATION_OUTCOME_LATEST_KEYto Redis with{ runId, outcomeKey, schemaVersion, theaterCount, generatedAt }andTRACE_REDIS_TTL_SECONDSTTLenqueueSimulationTask,claimSimulationTask(NX lock deduplication),completeSimulationTask,processNextSimulationTask(idempotency check against Redis pointer before LLM calls),runSimulationWorker({ once, runId })scripts/process-simulation-tasks.mjs— standalone entry point12-line script: loads env, delegates to
runSimulationWorker. Supports--onceand--run-id=<id>flags.Proto + RPC —
getSimulationOutcomeproto/worldmonitor/forecast/v1/get_simulation_outcome.protowithGetSimulationOutcomeRequest(optionalrunIdquery param) andGetSimulationOutcomeResponse(found,runId,outcomeKey,schemaVersion,theaterCount,generatedAt,note,error)service.protoupdated with import + RPCmake generateregenerated TypeScript server + client typesserver/worldmonitor/forecast/v1/get-simulation-outcome.ts: identicalgetRawJson + markNoCacheResponsepattern from PR feat(forecast): Phase 2 simulation package read path #2219server/worldmonitor/forecast/v1/handler.ts: wiredgetSimulationOutcomeserver/gateway.ts:slowcache tier (same reasoning as simulation package)Bootstrap/health registration
api/health.js:simulationOutcomeLatestadded toSTANDALONE_KEYS+ON_DEMAND_KEYS(WARN if empty, not CRIT — only written after simulation runs, not seeded)cache-keys.tsandbootstrap.jsnot touched (on-demand key, not seeded)Tests —
tests/forecast-trace-export.test.mjs14 new tests across 4 suites:
extractSimulationRoundPayload: valid R1/R2, fenced blocks, think tag stripping, invalid JSON, missing paths, no valid pathId, extractFirstJsonObject fallbacksimulation-outcome.json, canonical Redis key, schema version v1writeSimulationOutcome: null when storageConfig absent, null when pkg has no runIdAll 2316 tests pass. TypeScript clean.
Scope boundary (Phase 2)
Only maritime chokepoint + energy/logistics theaters are simulated. Third-order commodity expansion and additional theater classes are Phase 3.
Post-Deploy Monitoring & Validation
node scripts/seed-forecasts.mjs && node scripts/process-simulation-tasks.mjs --once[processNextSimulationTask],[runSimulationWorker],[writeSimulationOutcome]status: completed,theaterCount >= 1, R2 keyforecast:simulation-outcome:latestpresent in RedisGET /api/forecast/v1/get-simulation-outcomereturns{ found: true, theaterCount: N }status: completed_no_theaters→ package has no maritime chokepoint + energy/logistics theaters;status: failed→ LLM or R2 write error — check logs🤖 Generated with Claude Sonnet 4.6 (200K context) via Claude Code