mirror of
https://github.com/koala73/worldmonitor.git
synced 2026-04-25 17:14:57 +02:00
* feat(simulation): MiroFish Phase 2 — theater-limited simulation runner 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 * fix(simulation): address P1/P2 code review findings from PR #2220 Security (P1 #018): - 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 #019): - Added validateRunId() regex guard - Applied in enqueueSimulationTask() and processNextSimulationTask() loop Type safety (P1 #020): - Added isOutcomePointer() and isPackagePointer() type guards in TS handlers - Replaced unsafe as-casts with runtime-validated guards in both handlers Correctness (P2 #022): - Log warning when pkgPointer.runId does not match task runId Architecture (P2 #024): - isMaritimeChokeEnergyCandidate() accepts both flat and nested topBucketId - Call site simplified to pass theater directly Performance (P2 #025): - SIMULATION_ROUND1_MAX_TOKENS raised 1800 to 2200 - Added max 3 initialReactions instruction to Round 1 prompt Maintainability (P2 #026): - Simulation pointer keys exported from server/_shared/cache-keys.ts - Both TS handlers import from shared location Documentation (P2 #027): - Strengthened runId no-op description in proto and OpenAPI spec * fix(todos): add blank lines around lists in markdown todo files * style(api): reformat openapi yaml to match linter output * test(simulation): add flat-shape filter test + getSimulationOutcome handler 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).
468 lines
17 KiB
YAML
468 lines
17 KiB
YAML
openapi: 3.1.0
|
|
info:
|
|
title: ForecastService API
|
|
version: 1.0.0
|
|
paths:
|
|
/api/forecast/v1/get-forecasts:
|
|
get:
|
|
tags:
|
|
- ForecastService
|
|
summary: GetForecasts
|
|
operationId: GetForecasts
|
|
parameters:
|
|
- name: domain
|
|
in: query
|
|
required: false
|
|
schema:
|
|
type: string
|
|
- name: region
|
|
in: query
|
|
required: false
|
|
schema:
|
|
type: string
|
|
responses:
|
|
"200":
|
|
description: Successful response
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/GetForecastsResponse'
|
|
"400":
|
|
description: Validation error
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ValidationError'
|
|
default:
|
|
description: Error response
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/Error'
|
|
/api/forecast/v1/get-simulation-package:
|
|
get:
|
|
tags:
|
|
- ForecastService
|
|
summary: GetSimulationPackage
|
|
operationId: GetSimulationPackage
|
|
parameters:
|
|
- name: runId
|
|
in: query
|
|
description: Currently ignored; always returns the latest package. Reserved for Phase 3 per-run lookup.
|
|
required: false
|
|
schema:
|
|
type: string
|
|
responses:
|
|
"200":
|
|
description: Successful response
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/GetSimulationPackageResponse'
|
|
"400":
|
|
description: Validation error
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ValidationError'
|
|
default:
|
|
description: Error response
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/Error'
|
|
/api/forecast/v1/get-simulation-outcome:
|
|
get:
|
|
tags:
|
|
- ForecastService
|
|
summary: GetSimulationOutcome
|
|
operationId: GetSimulationOutcome
|
|
parameters:
|
|
- name: runId
|
|
in: query
|
|
description: |-
|
|
IMPORTANT: Currently a no-op. Always returns the latest available outcome regardless of runId.
|
|
Per-run lookup is reserved for Phase 3. Check the response 'note' field when runId is supplied
|
|
and you need to detect a mismatch between requested and returned run.
|
|
required: false
|
|
schema:
|
|
type: string
|
|
responses:
|
|
"200":
|
|
description: Successful response
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/GetSimulationOutcomeResponse'
|
|
"400":
|
|
description: Validation error
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ValidationError'
|
|
default:
|
|
description: Error response
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/Error'
|
|
components:
|
|
schemas:
|
|
Error:
|
|
type: object
|
|
properties:
|
|
message:
|
|
type: string
|
|
description: Error message (e.g., 'user not found', 'database connection failed')
|
|
description: Error is returned when a handler encounters an error. It contains a simple error message that the developer can customize.
|
|
FieldViolation:
|
|
type: object
|
|
properties:
|
|
field:
|
|
type: string
|
|
description: The field path that failed validation (e.g., 'user.email' for nested fields). For header validation, this will be the header name (e.g., 'X-API-Key')
|
|
description:
|
|
type: string
|
|
description: Human-readable description of the validation violation (e.g., 'must be a valid email address', 'required field missing')
|
|
required:
|
|
- field
|
|
- description
|
|
description: FieldViolation describes a single validation error for a specific field.
|
|
ValidationError:
|
|
type: object
|
|
properties:
|
|
violations:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/FieldViolation'
|
|
description: List of validation violations
|
|
required:
|
|
- violations
|
|
description: ValidationError is returned when request validation fails. It contains a list of field violations describing what went wrong.
|
|
GetForecastsRequest:
|
|
type: object
|
|
properties:
|
|
domain:
|
|
type: string
|
|
region:
|
|
type: string
|
|
GetForecastsResponse:
|
|
type: object
|
|
properties:
|
|
forecasts:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/Forecast'
|
|
generatedAt:
|
|
type: integer
|
|
format: int64
|
|
description: 'Warning: Values > 2^53 may lose precision in JavaScript'
|
|
Forecast:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: string
|
|
domain:
|
|
type: string
|
|
region:
|
|
type: string
|
|
title:
|
|
type: string
|
|
scenario:
|
|
type: string
|
|
feedSummary:
|
|
type: string
|
|
probability:
|
|
type: number
|
|
format: double
|
|
confidence:
|
|
type: number
|
|
format: double
|
|
timeHorizon:
|
|
type: string
|
|
signals:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/ForecastSignal'
|
|
cascades:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/CascadeEffect'
|
|
trend:
|
|
type: string
|
|
priorProbability:
|
|
type: number
|
|
format: double
|
|
calibration:
|
|
$ref: '#/components/schemas/CalibrationInfo'
|
|
createdAt:
|
|
type: integer
|
|
format: int64
|
|
description: 'Warning: Values > 2^53 may lose precision in JavaScript'
|
|
updatedAt:
|
|
type: integer
|
|
format: int64
|
|
description: 'Warning: Values > 2^53 may lose precision in JavaScript'
|
|
perspectives:
|
|
$ref: '#/components/schemas/Perspectives'
|
|
projections:
|
|
$ref: '#/components/schemas/Projections'
|
|
caseFile:
|
|
$ref: '#/components/schemas/ForecastCase'
|
|
ForecastSignal:
|
|
type: object
|
|
properties:
|
|
type:
|
|
type: string
|
|
value:
|
|
type: string
|
|
weight:
|
|
type: number
|
|
format: double
|
|
CascadeEffect:
|
|
type: object
|
|
properties:
|
|
domain:
|
|
type: string
|
|
effect:
|
|
type: string
|
|
probability:
|
|
type: number
|
|
format: double
|
|
CalibrationInfo:
|
|
type: object
|
|
properties:
|
|
marketTitle:
|
|
type: string
|
|
marketPrice:
|
|
type: number
|
|
format: double
|
|
drift:
|
|
type: number
|
|
format: double
|
|
source:
|
|
type: string
|
|
Perspectives:
|
|
type: object
|
|
properties:
|
|
strategic:
|
|
type: string
|
|
regional:
|
|
type: string
|
|
contrarian:
|
|
type: string
|
|
Projections:
|
|
type: object
|
|
properties:
|
|
h24:
|
|
type: number
|
|
format: double
|
|
d7:
|
|
type: number
|
|
format: double
|
|
d30:
|
|
type: number
|
|
format: double
|
|
ForecastCase:
|
|
type: object
|
|
properties:
|
|
supportingEvidence:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/ForecastCaseEvidence'
|
|
counterEvidence:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/ForecastCaseEvidence'
|
|
triggers:
|
|
type: array
|
|
items:
|
|
type: string
|
|
actorLenses:
|
|
type: array
|
|
items:
|
|
type: string
|
|
baseCase:
|
|
type: string
|
|
escalatoryCase:
|
|
type: string
|
|
contrarianCase:
|
|
type: string
|
|
changeSummary:
|
|
type: string
|
|
changeItems:
|
|
type: array
|
|
items:
|
|
type: string
|
|
actors:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/ForecastActor'
|
|
worldState:
|
|
$ref: '#/components/schemas/ForecastWorldState'
|
|
branches:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/ForecastBranch'
|
|
ForecastCaseEvidence:
|
|
type: object
|
|
properties:
|
|
type:
|
|
type: string
|
|
summary:
|
|
type: string
|
|
weight:
|
|
type: number
|
|
format: double
|
|
ForecastActor:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: string
|
|
name:
|
|
type: string
|
|
category:
|
|
type: string
|
|
role:
|
|
type: string
|
|
objectives:
|
|
type: array
|
|
items:
|
|
type: string
|
|
constraints:
|
|
type: array
|
|
items:
|
|
type: string
|
|
likelyActions:
|
|
type: array
|
|
items:
|
|
type: string
|
|
influenceScore:
|
|
type: number
|
|
format: double
|
|
ForecastWorldState:
|
|
type: object
|
|
properties:
|
|
summary:
|
|
type: string
|
|
activePressures:
|
|
type: array
|
|
items:
|
|
type: string
|
|
stabilizers:
|
|
type: array
|
|
items:
|
|
type: string
|
|
keyUnknowns:
|
|
type: array
|
|
items:
|
|
type: string
|
|
ForecastBranch:
|
|
type: object
|
|
properties:
|
|
kind:
|
|
type: string
|
|
title:
|
|
type: string
|
|
summary:
|
|
type: string
|
|
outcome:
|
|
type: string
|
|
projectedProbability:
|
|
type: number
|
|
format: double
|
|
rounds:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/ForecastBranchRound'
|
|
ForecastBranchRound:
|
|
type: object
|
|
properties:
|
|
round:
|
|
type: integer
|
|
format: int32
|
|
focus:
|
|
type: string
|
|
developments:
|
|
type: array
|
|
items:
|
|
type: string
|
|
actorMoves:
|
|
type: array
|
|
items:
|
|
type: string
|
|
probabilityShift:
|
|
type: number
|
|
format: double
|
|
GetSimulationPackageRequest:
|
|
type: object
|
|
properties:
|
|
runId:
|
|
type: string
|
|
description: Currently ignored; always returns the latest package. Reserved for Phase 3 per-run lookup.
|
|
GetSimulationPackageResponse:
|
|
type: object
|
|
properties:
|
|
found:
|
|
type: boolean
|
|
runId:
|
|
type: string
|
|
pkgKey:
|
|
type: string
|
|
schemaVersion:
|
|
type: string
|
|
theaterCount:
|
|
type: integer
|
|
format: int32
|
|
generatedAt:
|
|
type: integer
|
|
format: int64
|
|
description: 'Unix timestamp in milliseconds (from Date.now()). Warning: Values > 2^53 may lose precision in JavaScript.. Warning: Values > 2^53 may lose precision in JavaScript'
|
|
note:
|
|
type: string
|
|
description: |-
|
|
Populated when req.runId was supplied but does not match the returned package's runId.
|
|
Indicates that per-run filtering is not yet active and the latest package was returned instead.
|
|
error:
|
|
type: string
|
|
description: |-
|
|
Populated when the Redis lookup failed. Distinguish from healthy not-found (found=false, error="").
|
|
Value: "redis_unavailable" on Redis errors.
|
|
GetSimulationOutcomeRequest:
|
|
type: object
|
|
properties:
|
|
runId:
|
|
type: string
|
|
description: |-
|
|
IMPORTANT: Currently a no-op. Always returns the latest available outcome regardless of runId.
|
|
Per-run lookup is reserved for Phase 3. Check the response 'note' field when runId is supplied
|
|
and you need to detect a mismatch between requested and returned run.
|
|
GetSimulationOutcomeResponse:
|
|
type: object
|
|
properties:
|
|
found:
|
|
type: boolean
|
|
runId:
|
|
type: string
|
|
outcomeKey:
|
|
type: string
|
|
schemaVersion:
|
|
type: string
|
|
theaterCount:
|
|
type: integer
|
|
format: int32
|
|
generatedAt:
|
|
type: integer
|
|
format: int64
|
|
description: 'Unix timestamp in milliseconds (from Date.now()). Warning: Values > 2^53 may lose precision in JavaScript.. Warning: Values > 2^53 may lose precision in JavaScript'
|
|
note:
|
|
type: string
|
|
description: |-
|
|
Populated when req.runId was supplied but does not match the returned outcome's runId.
|
|
Indicates that per-run filtering is not yet active and the latest outcome was returned instead.
|
|
error:
|
|
type: string
|
|
description: |-
|
|
Populated when the Redis lookup failed. Distinguish from healthy not-found (found=false, error="").
|
|
Value: "redis_unavailable" on Redis errors.
|