ci: no-docker mock money-path lane + fix silent-pass orchestrator exit#3
ci: no-docker mock money-path lane + fix silent-pass orchestrator exit#3shroominic wants to merge 1 commit into
Conversation
… exit Adds a two-lane GitHub Actions workflow (the repo had no CI at all) and fixes the bug that would have made the money-path lane a false-safety net. Root cause: runner.orchestrate.main() returned 0 regardless of the scenario outcome — a scenario whose tests FAILED (status=failed) or ERRORED (status=error) still exited 0. A CI guard that drives the orchestrator (the intended money-path gate) would stay green while the swap/refund logic under test was red — the silent-pass failure mode. Fix (surgical): - main() now reads the finalized run status from runs.db and exits 1 on failed/error; the JSON summary gains a "status" field (additive). - server.runs.spawn_orchestrator tolerates the new non-zero exit: a run that was recorded (run_id summary present) still returns its id so the UI shows status=failed; a non-zero exit with no run_id still raises. CI lanes (.github/workflows/ci.yml, docs/ci.md): - pr: no Docker, no funds. ruff + mypy(runner/) + unit/mock pytest + orchestrator pipeline guards (smoke MUST exit 0; smoke_fail MUST exit non-zero, inverted so a silent-pass fails the build). The mock money guard tests/integration/test_spend_unit.py runs here. - nightly: docker lane stub (make up/down) with the foreign-mint swap + retry scenario steps commented out until #1 merges (it adds those services_required scenarios; not on main yet). Tooling made hermetic: pinned ruff/mypy/types-PyYAML in a [dependency- groups] dev group; [tool.ruff]/[tool.mypy] config added (mypy scoped to the money-path runner/ package — server/ SQLModel typing is out of scope). Cleared 10 pre-existing ruff lint errors so the lint gate is meaningful. Regression tests: - tests/test_orchestrate_exit_code.py — passed→0, failed→non-zero, error→non-zero (failed/error cases fail without the fix). - tests/test_server_orchestrator_exit.py — server returns run_id on non-zero-with-run_id, raises on non-zero-without-run_id. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Took a proper pass through this (plus a second independent review) — the core change is solid. The exit-code bug is real and worth fixing: a scenario that fails/errors but still exits 0 makes the money-path lane false-green, which defeats the whole point of the harness. Reading the final status from One thing I'd genuinely fix before merge, then a couple of things I'd rather see split out so this doesn't turn into a mega-PR. Fix before merge — the suite isn't hermetic against Could you split this into two PRs? Right now it's bundling three concerns and I think it'd review/land faster apart:
That leaves this PR as purely "fix the silent-pass exit code + wire the CI lanes," which is the part I'm happy to approve as-is. Couple of non-blockers, your call:
tl;dr: pull the tooling bump and the test fix into their own PRs, land the env fix, and the exit-code fix here is good to go. |
Problem
routstr-testingis a working full-stack E2E harness but has no CI (no.github/onmain). The intended money-path guard is to drive a scenario throughrunner.orchestrate— and building that guard surfaced a latent CI-integrity bug:orchestrate.main()returned exit 0 regardless of the scenario outcome. A scenario whose tests FAIL (status=failed) or ERROR still exited 0, so a money-path CI lane would stay green while the swap/refund logic under test was red — the silent-pass / false-safety failure mode.Verified before fix:
SKIP_SYNC=1 python -m runner.orchestrate --scenario smoke_fail→ exit 0.Root cause
main()returned therun_idand unconditionallyreturn 0; the run's real outcome lived only in theruns.dbrow and was never reflected in the process exit code.Fix (surgical)
runner/orchestrate.py:main()reads the finalized run status fromruns.db(new_read_run_status) and returns exit 1 onfailed/error. The JSON summary gains an additivestatusfield;orchestrate()'srun_idcontract is unchanged.server/runs.py:spawn_orchestratortolerates the new non-zero exit — a recorded run still returns its id (UI showsstatus=failed); a non-zero exit with norun_idstill raisesOrchestratorError.CI (
.github/workflows/ci.yml,docs/ci.md)prlane (every PR/push, no Docker, no funds):ruff+mypy(scoped to the money-pathrunner/package) + unit/mockpytest+ two orchestrator guards —smokeMUST exit 0,smoke_failMUST exit non-zero (inverted, so a silent-pass fails the build).nightlylane (cron/manual, never on PRs): dockermake up/downstub; the foreign-mint swap+retry scenarios are commented out until test: e2e foreign-mint swap + retry (covers routstr-core #549) #1 merges (pin ROUTSTR_CORE_REF=refs/pull/549/headfor the retry scenario until core#549 lands).Tooling made hermetic (pinned
ruff/mypyin a dev group); cleared 10 pre-existingrufferrors so the lint gate is meaningful.Test evidence
tests/test_orchestrate_exit_code.py: passed→0, failed→non-zero, error→non-zero. With the fix neutralized, the failed/error cases FAIL (2 failed, 1 passed); with the fix, 3 passed. (smoke_failis a genuineassert 1 == 2, not a skip — a real negative control.)ruffclean,mypyclean, 86 passed / 73 skipped; guards:smokeexit 0,smoke_failexit 1.Risk
Low–medium. The one behavior change (a failing UI scenario now exits non-zero) is handled so the
run_idis still returned, pinned by a regression test. Nightly docker money scenarios are stubbed pending #1.Follow-ups (not in this PR)
test/foreign-mint-swap, mergeable) to add the realswap_foreign_mintregression scenarios, then uncomment the nightly steps.feat/remote-capable-scenariosbranch (mainis strictly ahead).🤖 Generated with Claude Code