Repository navigation
feat: split mode across a VM boundary — a host ledger service over a socket - #173
Merged
Merged
Conversation
TDD-Run: 1 TDD-Cycle: 1 TDD-Phase: refactor
…ethods TDD-Run: 1 TDD-Cycle: 2 TDD-Phase: refactor
…he run TDD-Run: 1 TDD-Cycle: 3 TDD-Phase: refactor
TDD-Run: 1 TDD-Cycle: 4 TDD-Phase: refactor
TDD-Run: 1 TDD-Cycle: 5 TDD-Phase: red
TDD-Run: 1 TDD-Cycle: 5 TDD-Phase: green
TDD-Run: 1 TDD-Cycle: 6 TDD-Phase: red
TDD-Run: 1 TDD-Cycle: 6 TDD-Phase: green
TDD-Run: 1 TDD-Cycle: 7 TDD-Phase: red
TDD-Run: 1 TDD-Cycle: 7 TDD-Phase: green
TDD-Run: 1 TDD-Cycle: 8 TDD-Phase: red
TDD-Run: 1 TDD-Cycle: 8 TDD-Phase: green
TDD-Run: 1 TDD-Cycle: 9 TDD-Phase: red
TDD-Run: 1 TDD-Cycle: 9 TDD-Phase: green
TDD-Run: 1 TDD-Cycle: 10 TDD-Phase: red
TDD-Run: 1 TDD-Cycle: 10 TDD-Phase: green
TDD-Run: 1 TDD-Cycle: 11 TDD-Phase: red
TDD-Run: 1 TDD-Cycle: 11 TDD-Phase: green
TDD-Run: 1 TDD-Cycle: 12 TDD-Phase: red
TDD-Run: 1 TDD-Cycle: 12 TDD-Phase: green
TDD-Run: 1 TDD-Cycle: 13 TDD-Phase: pin
TDD-Run: 1 TDD-Cycle: 14 TDD-Phase: pin
TDD-Run: 1 TDD-Cycle: 15 TDD-Phase: red
TDD-Run: 1 TDD-Cycle: 15 TDD-Phase: green
TDD-Run: 1 TDD-Cycle: 16 TDD-Phase: red
TDD-Run: 1 TDD-Cycle: 16 TDD-Phase: green
TDD-Run: 1 TDD-Cycle: 17 TDD-Phase: red
TDD-Run: 1 TDD-Cycle: 17 TDD-Phase: green
TDD-Run: 1 TDD-Cycle: 18 TDD-Phase: red
TDD-Run: 1 TDD-Cycle: 34 TDD-Phase: red
TDD-Run: 1 TDD-Cycle: 34 TDD-Phase: green
TDD-Run: 1 TDD-Cycle: 35 TDD-Phase: red
TDD-Run: 1 TDD-Cycle: 35 TDD-Phase: green
TDD-Run: 1 TDD-Cycle: 36 TDD-Phase: red
TDD-Run: 1 TDD-Cycle: 36 TDD-Phase: green
TDD-Run: 1 TDD-Cycle: 36 TDD-Phase: refactor
TDD-Run: 1 TDD-Cycle: 37 TDD-Phase: red
TDD-Run: 1 TDD-Cycle: 37 TDD-Phase: green
TDD-Run: 1 TDD-Cycle: 38 TDD-Phase: red
TDD-Run: 1 TDD-Cycle: 38 TDD-Phase: green
TDD-Run: 1 TDD-Cycle: 39 TDD-Phase: red
TDD-Run: 1 TDD-Cycle: 39 TDD-Phase: green
TDD-Run: 1 TDD-Cycle: 40 TDD-Phase: red
TDD-Run: 1 TDD-Cycle: 40 TDD-Phase: green
TDD-Run: 1 TDD-Cycle: 41 TDD-Phase: red
TDD-Run: 1 TDD-Cycle: 41 TDD-Phase: green
TDD-Run: 1 TDD-Cycle: 42 TDD-Phase: red
TDD-Run: 1 TDD-Cycle: 42 TDD-Phase: green
TDD-Run: 1 TDD-Cycle: 43 TDD-Phase: red
TDD-Run: 1 TDD-Cycle: 43 TDD-Phase: green
TDD-Run: 1 TDD-Cycle: 44 TDD-Phase: red
TDD-Run: 1 TDD-Cycle: 44 TDD-Phase: green
TDD-Run: 1 TDD-Cycle: 45 TDD-Phase: red
TDD-Run: 1 TDD-Cycle: 45 TDD-Phase: green
TDD-Run: 1 TDD-Cycle: 46 TDD-Phase: refactor
…and dropped connections Found by the pre-PR mutation pass: a Connection closed after a dropped write raised BrokenPipeError from its flush, masking ledger_unreachable. Removes two unreachable branches in _hung_up and the unused Row.__eq__.
geuben-dispatch
Bot
force-pushed
the
worktree-issue-169-ledger-service
branch
from
October 14, 2026 19:58
73e2648 to
fea8a91
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Split mode across a VM boundary: a runner inside a disposable guest keeps no ledger. It reads and writes the host's through a unix socket served by a new ledger service (
tdd ledger serve), which owns the SQLite files and is their only writer. Implementstasks/issue-169-ledger-service.md(46 cycles, all delivered).Ledgermethod.cli,machine,advance,fleetandrenderno longer speak SQL or importsqlite3, andtests/test_ledger_surface.pyguards that. Claims returnNoneon conflict instead of raisingIntegrityError, because no sqlite exception can cross a socket. The final close now settlesblocked/completebefore it ends the run, instead of ending itcompleteand flipping it toblocked.runandplan_contractgain asourcecolumn. The baseline/advance claims and the baseline cache are rebuilt keyed by source, so two guests at the same path neither block each other nor share a cached baseline. Existing rows migrate aslocal.tdd metricsreports each run'ssource.{method, args, kwargs}, never SQL. The service enforces:foreign_run: another source's run, cycle or contract, or one that does not exist.run_closed: writes to acomplete/abandonedrun (add_noteis exempt;blockedruns stay writable).method_not_allowed: anything outside the guest registry.bad_repo: a repository path that is not absolute.operator; an unbound guest'soperatorclaim becomesclaimed.ledger_unreachable. Nothing falls back to a local ledger.doctornames the source it reaches.runner importis refused on a socket runner.ledger_socketandledger_hometogether are refused.tdd ledger serve|sources|add-source|remove-source|bind. They are always answered locally and never forwarded to a runner. Sources persist in<home>/sources.json. The admin socket is mode 600, guest sockets 660.fleetreports the host's live leases.tdd ledger metrics|fleet|logcover every source, andtdd ledger import <file> --source <name>brings an existing ledger in as one source and refuses one the host already holds.docs/split-runner.md"Across a VM boundary", a README split-mode bullet, and a CHANGELOG entry.tdd ledger metricsover two sourcesTwo guest runs in one repository, as
vm-1andvm-2, read on the host through the admin socket:{ "ok": true, "envelope_version": 1, "run": null, "result": { "repos": { "<repo slug>": { "runs": [ { "run": 1, "plan_contract": 1, "source": "vm-1", "executor": "pytest-executor", "executor_source": "claimed", "outcome": null, "cycles_declared": 1, "cycles_closed": 0, "cycles_skipped": 0, "red_first_violation_rate": 0.0, "impl_attempts_total": 0, "impl_attempts_max": 0, "cycles_by_project": { "backend": 1 }, "blockers": {}, "human_interventions": 0, "time": { "wall_clock_s": 1.0, "suite_s": 0, "suite_share": 0.0, "by_phase": {} }, "integrity_events": {} }, { "run": 2, "plan_contract": 2, "source": "vm-2", "executor": "pytest-executor", "executor_source": "claimed", "outcome": null, "cycles_declared": 1, "cycles_closed": 0, "cycles_skipped": 0, "red_first_violation_rate": 0.0, "impl_attempts_total": 0, "impl_attempts_max": 0, "cycles_by_project": { "backend": 1 }, "blockers": {}, "human_interventions": 0, "time": { "wall_clock_s": 0.0, "suite_s": 0, "suite_share": 0.0, "by_phase": {} }, "integrity_events": {} } ], "note": "Cross-plan aggregates are not comparable: cycle difficulty varies too much. Compare runs of the same contract only (R11.1)." } } }, "next_action": { "verb": "complete", "detail": "Metrics computed over every source.", "terminal": true, "verb_set_version": 2 } }Scope cut: the socket forwarding itself
The real forwarding (an ssh
-Rof a unix socket into a VM, vsock, or a container bind mount) cannot run in CI: there is no VM or ssh pair to forward through. It is a named scope cut in the plan. The service's side of the boundary is built and tested: a socket per source, its mode, and identity by listener. The tests run the service in-process, andledger serveas a real subprocess.docs/split-runner.mddocuments the forwarding setups.Commits
88 commits (red/green/refactor per cycle, then the friction log and one post-run test commit)
Test coverage note
All decision-point operators in new code are covered by at least one test (pseudo-mutation analysis passed).
The pre-PR pass found reachable branches the run's tests did not catch. Commit
0f3023aadds tests for them:cycle_idof a closed run;wire.Row's missing-keyIndexError;Each of those nine mutations was applied and confirmed caught. The pass also found a real bug:
Connection.close()after a dropped write re-raisedBrokenPipeErrorfrom its flush, maskingledger_unreachable. That is fixed in the same commit. Two unreachable branches in_hung_upand the unusedRow.__eq__were removed rather than tested.Closes #169
Gates run
uv run pytest -q(Python 3.14): 715 passedUV_PROJECT_ENVIRONMENT=/tmp/tdd169-py311 uv run --python 3.11 pytest -q: 715 passeduv run ruff check src tests: all checks passeduv run ruff format --check src tests: 107 files already formatteduv build+ wheel smoke test (tdd ledger --helpfrom a fresh venv): OKInterventions
none. The friction log records 0 human interventions, no blockers, no
resume --unblock, no--accept-failures, no skipped cycles and no amended baseline. Deviations from the plan, each recorded as atdd noteintasks/friction-logs/issue-169-ledger-service-friction.md:"vm-1"in theledger reachabledetail) was already true, because the socket file is namedvm-1.sock. I ran the sensitivity check on the test as written (it bit), then in the refactor phase tightened it to"source vm-1"and implemented the planned doctor change. This shows in the friction log as ared_first_violation.--kind plan_defect, because the plan's--kind environmentis not a blocker kind.ledger.py:active_claim/active_advance_claimstayed guest reads until cycle 25 moved them to host-only, as cycle 25's expected failure assumes.tests/test_split_client.py: its local-verb guard pinned{docs, init}, andledgerjoins it by design. The test is renamedtest_only_docs_init_and_ledger_stay_local.cli.py, so an admin refusal (ledger_exists) is reported as the reason.