mando-cli Simulator Runtime

Second first-class Docker Compose stack (mando-sim) added to mando-cli-v2 for running the BESS simulator alongside the dev stack. Coexists with the dev mando project — neither evicts the other. Landed on feature/simulator-runtime at commit b244f2e.

For Agents

Authoritative in-repo design + plan (do not transcribe — read directly):

  • Spec: /Volumes/bandi/coding/poc/mando-cli/docs/superpowers/specs/2026-05-28-simulator-runtime-design.md
  • Plan: /Volumes/bandi/coding/poc/mando-cli/docs/superpowers/plans/2026-05-28-simulator-runtime.md

Read those when implementing the orchestrator/runner services or extending CLI behavior. This note captures the durable knowledge that does not belong in the spec: status, decision rationale, audit findings, and process lessons.

Status (2026-05-30)

Services are WIP — CLI leads the contract

The CLI was built ahead of the simulator services. The runtime contract here (env names, ports, image references) is what services MUST implement.

  • mando-cli simulator runtime: landed on feature/simulator-runtime (commit b244f2e), pushed to origin. MR creation link: https://gitlab.com/alpiq_cicd/sales-and-origination/flexible-assets/bess/poc/mando-cli/-/merge_requests/new?merge_request[source_branch]=feature/simulator-runtime
  • mando-cli validation: unit-test validated. Full cargo test: 785 passed, 1 ignored (the docker-requiring tests/up_compose_smoke.rs). Clippy unchanged from main baseline (pre-existing dead-code only).
  • 27 files changed, +1280 / −240, +2 new files:
    • src/runtime/service_stack.rs — shared select_profile two-stack resolver
    • src/runtime/templates/simulator/simulator.yml
  • Authored solely by andras.lederer (no co-author trailer).

These do not exist yet

The following will be authored against the contract defined here:

  • mando_simulator crate + Dockerfile.simulator in the bess/poc/mando repo (orchestrator image)
  • simulator-runner image at bess/simulator/runner (base for the six runners)
  • Six sim repos: forecast, optimization, execution, market, asset, post-delivery-market

True end-to-end mando up -p simulator is deferred until those images land. Today: unit-test green only.

The Redesign Decision

Supersedes GitLab MR !2 / feature/BE-2256 (balint)

andras took over and redesigned. The shape below is materially different from MR !2.

Key choices vs MR !2:

#ChoiceRationale
1Simulator runs as its OWN Compose project mando-sim (not the dev -p mando project)Two stacks coexist; neither tears the other down. Stack isolation is a first-class concept.
2Six runners generated from a single data-driven Rust list (SIMULATOR_RUNNERS)Single source of truth. MR !2’s hardcoded/commented YAML was un-DRY and error-prone.
3Own Postgres (sim-postgres, host port 5433, DB bess_simulation)Isolated from dev DB. Avoids 5432 collision and accidental cross-talk.
4Dropped the host_project() concept entirelymando-simulator is a service, not a Project variant. Only Project::SimulatorRunner is added as a real cloned repo.
5New Project::DEV subset that excludes SimulatorRunnerLets dev iterations skip the simulator clone path entirely.
6RunProfile gains compose_project + derived layers(); commands route off profile.compose_project(); compose::assemble matches layers() to pick the compose-file setRouting decision is centralized on the profile, not scattered in command code.
7Env reaches compose by extending fill_build_args to merge the full workspace .env (NOT via --env-file)--env-file would hard-fail when .env is missing. fill_build_args is tolerant and already plumbed.
8Python healthcheck (not curl); Gurobi license is a file mount, not multiline envRunner image already has Python; avoids curl install. Multiline env vars cross compose/shell layers badly.
9Two-stack ergonomics: status shows both stacks; logs/exec/volume auto-resolve stack from service name via shared service_stack::select_profile; down -p simulator -v has a confirmation guardOne resolver, no duplicated flows. Volume wipe needs human confirmation.

§A Orchestrator ↔ Runner Env Contract

SUPERSEDED 2026-06-02 — see mando-cli-simulator-env-contract-2026-06-02

The contract below was CLI-led and predated the real orchestrator/runner images. On 2026-06-02 (commit c3f0af7) it was realigned to match simulator:1.11.0-feat.2569583284.7d69a070 + runner shape f7aa42f. Major changes: POSTGRES_*SIMULATOR_DATABASE_*; SIMULATOR_RUNNERS CSV → per-runner SIMULATOR_<NAME>_HOST; repo URL/branch/commit_hash moved from orchestrator to runners; schema bess_simulationsimulator; healthcheck Python → curl; GRB_LICENSE_FILE file mount → GUROBI_LIC env var. Use the follow-up note as the authoritative contract.

CLI-defined — services implement to this

This is the load-bearing API surface. Changing env names or default ports breaks the simulator runtime contract.

Runner env (per simulator-<name> container)

VariableValue / Default
SERVER_PORTper-runner port (see runner list)
APP_NAMEsimulator-<name>
SIMULATION_MANDO_HOSTmando-simulator
SIMULATION_MANDO_PORT8080 (internal)
GITLAB_TOKENfrom workspace env
NEXUS_INDEX_URLfrom workspace env
GRB_LICENSE_FILEoptimization runner only; mounted from ${GUROBI_LIC_FILE:-./gurobi.lic}

Runner contract: must expose GET /health.

Orchestrator env (mando-simulator)

VariablePurpose
SIMULATOR_<NAME>_REPO_URLper-runner repo URL
SIMULATOR_<NAME>_BRANCHper-runner branch (defaults to main)
SIMULATOR_<NAME>_COMMIT_HASHper-runner pinned commit
SIMULATOR_RUNNERScomma-separated endpoint list simulator-<name>:<port> pairs
POSTGRES_HOSTsim-postgres
POSTGRES_PORT5432 (internal — host port is 5433)
POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DBfrom workspace env
SIMULATOR_DB_SCHEMAsimulator schema name

Audit + Fixes

A multi-perspective audit after the initial implementation found the following; all closed before final commit.

HIGH severity

  1. mando status showed spurious mando-sim failure rowrun_capture had no empty-files guard. With no simulator compose files present, the call returned a synthesized “failure” row. Fix: guard run_capture against empty file sets.

Functional bugs

  1. volume clear sim-postgres couldn’t find the containervolume clear was assembling dev compose files and not finding the simulator service. Fix: route through the same service_stack::select_profile flow as logs/exec. Single resolver, no duplication.
  2. mando get simulator-runner 404’dget didn’t resolve aliases, so the back-fill hint pointed to a non-existent target. Fix: make get alias-aware.
  3. pull / status alarmed on the un-cloned runner — Loud red rows for what is intentionally a not-yet-cloned repo. Fix: pull/collector show calm “not cloned” rows.

DRY violations (six single-source-of-truth misses)

  1. No shared SIMULATOR_PROJECT_NAME const → string-literal duplication. Fix: added shared const.
  2. No shared SIMULATOR_PROFILE_NAME const. Fix: added shared const.
  3. No shared sim-service-name const. Fix: added shared const.
  4. Redundant SimRunner.repo field (derivable). Fix: removed.
  5. volume clear had its own resolution path duplicating logs/exec. Fix: all three use shared service_stack::select_profile.

A final fresh-eyes re-review confirmed all closed; no new comments / no new anyhow / no new duplicated flows introduced.

Process Lesson

Verify with full cargo test, NOT cargo test --bin mando

During per-task verification we used cargo test --bin mando. This skips integration tests in tests/ — which masked a compile break in tests/up_compose_smoke.rs where RunProfile literals lacked the new compose_project field. The final review caught it.

Rule going forward: verify with plain cargo test (or cargo test --workspace if multi-crate). Never --bin mando for green-light validation — it lies by omission.

File-level Touchpoints

  • New: src/runtime/service_stack.rsselect_profile(service_name) two-stack resolver

  • New: src/runtime/templates/simulator/simulator.yml

  • Modified core: src/runtime/runprofile.rs (added compose_project, layers()), src/runtime/templates.rs, runconfig artifact/build/build_dev yaml files, and src/commands/up.rs

  • Modified commands: status, logs, exec, volume, down, get, pull for two-stack awareness

  • Modified workspace: Project enum gains SimulatorRunner; new Project::DEV subset