mando-cli Simulator Env Contract Realignment + Extras Passthrough

Follow-up to mando-cli-simulator-runtime-2026-05-30. The §A env contract there has been superseded to match the real orchestrator image (simulator:1.11.0-feat.2569583284.7d69a070) and the real runner image (shape f7aa42f). Landed as c3f0af7 on feature/simulator-runtime. 8 files, +440 / −67. 813 tests passing, no new clippy warnings. Sole author andras.lederer, no co-author trailer.

The old §A names (POSTGRES_*, SIMULATOR_DB_SCHEMA, SIMULATOR_RUNNERS CSV, orchestrator-owned SIMULATOR_<NAME>_REPO_URL) are GONE. Use the contract below.

Real Env Contract (replaces §A v3)

Orchestrator (mando-simulator)

VariableReplaces / Notes
SIMULATOR_DATABASE_HOST(was POSTGRES_HOST)
SIMULATOR_DATABASE_NAME(was POSTGRES_DB)
SIMULATOR_DATABASE_USERNAME(was POSTGRES_USER)
SIMULATOR_DATABASE_PASSWORD(was POSTGRES_PASSWORD)
SIMULATOR_DATABASE_SCHEMA(was SIMULATOR_DB_SCHEMA) — schema renamed bess_simulationsimulator
SIMULATOR_DATABASE_MAX_CONNECTIONSnew
SIMULATOR_START_DATE / SIMULATOR_END_DATE / SIMULATOR_SHUTDOWN_AT_ENDnew time-window vars
SIMULATOR_<NAME>_HOST (per-runner)replaces the SIMULATOR_RUNNERS CSV. Default: simulator-<name>:<port>
SIMULATOR_<NAME>_REPO_URL/BRANCH/COMMIT_HASHmoved to runners — orchestrator no longer tells runners what to clone

Runner (per simulator-<name> container)

  • Healthcheck reverted to curl (was Python in §A).
  • GUROBI_LIC is reverted to an env var, not a file mount (was GRB_LICENSE_FILE mount in §A).
  • SIMULATION_MANDO_HOST / SIMULATION_MANDO_PORT now generalized to all six runners (was orchestrator-only routing).
  • Container reads unprefixed SIMULATOR_REPO_URL / SIMULATOR_BRANCH / SIMULATOR_COMMIT_HASH from its own env.
  • The workspace .env keeps the prefixed override convention: each runner service is rendered with SIMULATOR_REPO_URL: "${SIMULATOR_<NAME>_REPO_URL:-<default>}", so a single .env line per runner still overrides.

Open assumption — verify against the runner image when it lands

The renderer ASSUMES the runner image reads unprefixed SIMULATOR_REPO_URL/BRANCH/COMMIT_HASH. If the actual runner reads the prefixed SIMULATOR_<NAME>_* names directly, flip the renderer (the .env override convention stays).

Orchestrator image: registry.gitlab.com/alpiq_cicd/sales-and-origination/flexible-assets/bess/poc/mando/simulator:1.11.0-feat.2569583284.7d69a070.

Arbitrary Extras Passthrough (new capability)

For Agents

filter_extras in src/runtime/templates.rs (line ~116) filters any workspace .env key against a managed-set const MANAGED_SIM_ENV_KEYS (line ~56). Survivors are auto-injected as KEY: "${KEY}" into every simulator service (orchestrator + all six runners). Keys are sorted alphabetically — deterministic output.

  • Each injection emits tracing::info!(env_var, simulator, "passing env var to simulator") (one per (key, service) pair) — visible with RUST_LOG=info.
  • src/workspace/bootstrap.rs::ensure_all_generated reads .env via Adapter::Dotenv and threads the filtered set into SimulatorGenCtx.
  • Rule: if you add a new managed key to the simulator templates, also add it to MANAGED_SIM_ENV_KEYS, otherwise the user’s .env value will be double-injected (managed slot + extras slot).

sim-postgres Aliasing Pattern (broadly reusable)

Pattern: route shared defaults through the orchestrator's env contract, not the dependent service's native env

Originally the postgres image’s own env (POSTGRES_USER/POSTGRES_PASSWORD/POSTGRES_DB) was independent of the orchestrator’s SIMULATOR_DATABASE_* — setting one wouldn’t propagate to the other. Silent-auth footgun. Fix: alias the dependent service’s env to the orchestrator’s:

POSTGRES_USER: "${SIMULATOR_DATABASE_USERNAME:-postgres}"
POSTGRES_PASSWORD: "${SIMULATOR_DATABASE_PASSWORD:-postgres}"
POSTGRES_DB: "${SIMULATOR_DATABASE_NAME:-simulator}"

Plus a pg_isready healthcheck so the dependent service waits on the credential the orchestrator will actually use. One .env override controls both.

Generalize: whenever two services share a default DB credential (or any default), route the dependent service’s native env through the orchestrator’s contract names. Otherwise you ship a footgun where one override silently desynchronizes from the other.

Audit-Driven Polish Patterns Worth Keeping

  1. Derive UI summary strings from the actual invocation-args function — never hardcode the verb + flags in summary text. Pre-fix bug: success-row literal said "up -d --build --remove-orphans" while up_compose_args omitted --build for sim → silent drift. Fix: derive both call and summary from one source (up_compose_args output).
  2. Single-source the image tag and orchestrator service nameSIM_IMAGE_TAG = "dev", SIM_ORCHESTRATOR_SERVICE = "mando-simulator". Templates use placeholders (PH_SIM_IMAGE_TAG = "__MANDO_SIM_IMAGE_TAG__", PH_SIMULATOR_SERVICE = "__MANDO_SIMULATOR_SERVICE__") substituted by the renderer in templates.rs:354-355. Bare literals like mando-simulator:dev in the YAML template are duplication — kill them.
  3. volume.rs error string uses the project-name consts (COMPOSE_PROJECT_NAME / SIMULATOR_PROJECT_NAME) — same single-source rule applied to operator-facing messages.
  4. -b/--build help text reworded: “no-op on dev, primarily for sim” — flag behavior diverges across the two stacks; the help string says so.
  5. MANDO_SIMULATOR_IMAGE-without-pull Warn row added to the simulator plan render — surfaces the case where the user pinned an image but didn’t pull it.

Commit Metadata

  • Commit: c3f0af7 on feature/simulator-runtime
  • Author: andras.lederer <andras.lederer@alpiq.com> (no Co-Authored-By trailer)
  • 8 files: cli.rs, commands/mod.rs, commands/up.rs (+99), commands/volume.rs, runtime/templates.rs (+348), runtime/templates/simulator/simulator.yml, workspace/bootstrap.rs, tests/up_compose_smoke.rs
  • +440 / −67 lines
  • cargo test: 813 passing / 1 ignored (docker smoke). No new clippy warnings.