|
| 1 | +<!-- SPDX-License-Identifier: PMPL-1.0-or-later --> |
| 2 | +<!-- SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk> --> |
| 3 | + |
| 4 | +# S4 Loop-Closure Runbook |
| 5 | + |
| 6 | +This runbook describes how to verify, locally and in CI, that ECHIDNA's |
| 7 | +cross-prover learning loop (Roadmap stage 3a/3b — "S4") is closing. |
| 8 | + |
| 9 | +## What is the loop? |
| 10 | + |
| 11 | +``` |
| 12 | +ProverDispatcher::verify_proof ──fire-and-forget──► |
| 13 | + VeriSimDBClient::record_proof_attempt |
| 14 | + └─► POST /api/v1/proof_attempts |
| 15 | + └─► ClickHouse proof_attempts row |
| 16 | + └─► mv_prover_success_by_class (materialised view) |
| 17 | + └─► GET /api/v1/mv_prover_success_by_class |
| 18 | + └─► VeriSimAdvisor::suggest_prover |
| 19 | + └─► next dispatch routes smarter |
| 20 | +``` |
| 21 | + |
| 22 | +Three echidna-side wirings carry the loop: |
| 23 | + |
| 24 | +| Wiring | Module | Wired in commit | |
| 25 | +|------------------------|---------------------------------|-----------------| |
| 26 | +| Write at dispatch exit | `dispatch::spawn_record_attempt`| `60d2e75` | |
| 27 | +| Class threading | `dispatch::verify_proof_with_class` | `cf94b49` | |
| 28 | +| Cross-prover read | `vcl_ut::cross_prover_search_names` | `cf94b49` | |
| 29 | + |
| 30 | +## What actually has to be standing up |
| 31 | + |
| 32 | +Just **verisim-api** on a known port. The test never touches Elixir |
| 33 | +orchestration, Svalinn, federation, drift monitor, or the seven backing |
| 34 | +databases — it talks directly to the rust-core HTTP endpoints |
| 35 | +`/api/v1/proof_attempts` and `/api/v1/mv_prover_success_by_class`. |
| 36 | + |
| 37 | +## Local verification |
| 38 | + |
| 39 | +### 1. Start verisim-api |
| 40 | + |
| 41 | +The canonical way is `selur-compose up rust-core` from the verisimdb |
| 42 | +checkout, but for a smoke run any of these work: |
| 43 | + |
| 44 | +```bash |
| 45 | +# Option A — selur-compose (preferred) |
| 46 | +cd ~/Documents/hyperpolymath-repos/verisimdb |
| 47 | +selur-compose up rust-core --detach |
| 48 | + |
| 49 | +# Option B — podman-compose fallback |
| 50 | +cd ~/Documents/hyperpolymath-repos/verisimdb |
| 51 | +podman-compose -f container/compose.toml up rust-core --detach |
| 52 | + |
| 53 | +# Option C — bare cargo run (development only) |
| 54 | +cd ~/Documents/hyperpolymath-repos/verisimdb/rust-core |
| 55 | +cargo run --release --bin verisim-api |
| 56 | +``` |
| 57 | + |
| 58 | +Wait for `/health` to respond: |
| 59 | + |
| 60 | +```bash |
| 61 | +until curl -sf http://localhost:8080/health >/dev/null; do sleep 1; done |
| 62 | +echo "verisim-api ready" |
| 63 | +``` |
| 64 | + |
| 65 | +### 2. Run the loop-closure test |
| 66 | + |
| 67 | +```bash |
| 68 | +cd ~/Documents/hyperpolymath-repos/echidna |
| 69 | +just test-s4-loop |
| 70 | +``` |
| 71 | + |
| 72 | +Expected output (success): |
| 73 | + |
| 74 | +``` |
| 75 | +running 2 tests |
| 76 | +test s4_record_then_read_back_by_goal_hash ... ok |
| 77 | +test s4_class_aggregation_visible_in_mv ... ok |
| 78 | +
|
| 79 | +test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured |
| 80 | +``` |
| 81 | + |
| 82 | +Expected output (verisim-api not running — graceful skip): |
| 83 | + |
| 84 | +``` |
| 85 | +skip: VeriSimDB at http://localhost:8080 is unreachable — |
| 86 | + set VERISIM_URL or start verisim-api |
| 87 | +test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured |
| 88 | +``` |
| 89 | + |
| 90 | +The skip path is intentional: the test is a no-op rather than a failure |
| 91 | +when the dependency is absent, so the same `cargo test` command works on |
| 92 | +machines without verisim-api in the loop. |
| 93 | + |
| 94 | +### 3. Override the URL |
| 95 | + |
| 96 | +```bash |
| 97 | +VERISIM_URL=http://verisim.staging.example:9090 just test-s4-loop |
| 98 | +``` |
| 99 | + |
| 100 | +## CI wiring |
| 101 | + |
| 102 | +A workflow that brings up verisim-api and runs `test-s4-loop` is |
| 103 | +**not yet committed**. The blocker is that no published |
| 104 | +`ghcr.io/hyperpolymath/verisimdb-api` image exists yet — every CI run |
| 105 | +would have to build verisim-api from a sibling checkout, which is too |
| 106 | +heavy for `on: pull_request`. |
| 107 | + |
| 108 | +When that image is published (verisim's `ghcr-publish.yml` is the |
| 109 | +expected channel — currently absent from |
| 110 | +`verification-ecosystem/verisimdb/.github/workflows/`), the workflow |
| 111 | +becomes: |
| 112 | + |
| 113 | +```yaml |
| 114 | +name: S4 Loop Closure |
| 115 | +on: |
| 116 | + workflow_dispatch: |
| 117 | + schedule: [{ cron: '0 4 * * 1' }] # Mondays 04:00 UTC |
| 118 | + |
| 119 | +jobs: |
| 120 | + s4-loop: |
| 121 | + runs-on: ubuntu-latest |
| 122 | + services: |
| 123 | + verisim: |
| 124 | + image: ghcr.io/hyperpolymath/verisimdb-api:latest |
| 125 | + ports: ['8080:8080'] |
| 126 | + options: >- |
| 127 | + --health-cmd "curl -sf http://localhost:8080/health" |
| 128 | + --health-interval 10s |
| 129 | + --health-timeout 5s |
| 130 | + --health-retries 12 |
| 131 | + env: |
| 132 | + VERISIM_URL: http://localhost:8080 |
| 133 | + steps: |
| 134 | + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 |
| 135 | + - uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 |
| 136 | + - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 |
| 137 | + - run: just test-s4-loop |
| 138 | +``` |
| 139 | +
|
| 140 | +This workflow is left as an unfiled artefact in this runbook precisely |
| 141 | +to avoid landing it as a perpetually-skipping no-op. File it the same |
| 142 | +day verisim publishes its image. |
| 143 | +
|
| 144 | +## What "loop closing in production" looks like |
| 145 | +
|
| 146 | +In a production deployment with both echidna and verisim-api running: |
| 147 | +
|
| 148 | +1. Run any successful `verify_proof_verisim_guided(..., obligation_class="…")`. |
| 149 | +2. Within the verisim writer's HTTP timeout (default 10s) the row appears in |
| 150 | + the `proof_attempts` table. |
| 151 | +3. ClickHouse refreshes `mv_prover_success_by_class` on the next interval. |
| 152 | +4. Subsequent calls to `verify_proof_verisim_guided` with the same class |
| 153 | + start receiving a non-default routing decision (look for |
| 154 | + `VeriSimAdvisor: obligation_class=… → SomeProver` at INFO level in |
| 155 | + echidna's logs). |
| 156 | + |
| 157 | +If step 4 never observes a non-default routing for a class that has |
| 158 | +genuinely accumulated rows, the breakdown is one of: |
| 159 | + |
| 160 | +- **MV not refreshing** → ClickHouse-side problem, check the MV definition. |
| 161 | +- **Writer silently dropping** → look for `VeriSim record_proof_attempt failed` |
| 162 | + warnings in echidna logs. |
| 163 | +- **Class string mismatch** → confirm the dispatch caller passes the same |
| 164 | + `obligation_class` the read path queries (the `cf94b49` threading fix |
| 165 | + closed the most common version of this). |
| 166 | + |
| 167 | +## Future work tracked elsewhere |
| 168 | + |
| 169 | +- Verisim image publish: `verification-ecosystem/verisimdb` repo, not echidna. |
| 170 | +- Workflow filing once image is available: `.github/workflows/s4-loop.yml`. |
| 171 | +- Tier-3 weekly cadence so it doesn't gate every PR. |
0 commit comments