Repository navigation
fix(pool): a roster write publishes its advertisement, so a direct pin is harmless - #4356
Conversation
…n is harmless Two entry points existed for a pin. `pool_roster.bind_room()` called itself the one production writer but wrote only bindings.json and roster.json; the actual pin path `worker_picker_commands.apply()` also rewrote the advertisement the gateway bridge pushes to the broker. A direct `bind_room` therefore produced a roster that said "pinned" while the broker kept routing on the old pin until the core's next routed task happened to call `ensure_advertisement()`. Publication now lives at the roster's single write site: `compile_roster()` writes the advertisement after the roster, so every mutator (`register_worker`, `bind_room`, `unbind_room`) publishes by construction. A failed publish raises `PublishError` (an OSError carrying the written roster) instead of being lost; `apply()` records no ledger entry on it, so the pin stays retryable, and `create_worker` reports it as itself. The picker's own second write is gone. A `pool-advertisement` health probe compares roster.json's version with the one the advertisement carries and warns "binding unpublished" within one tick. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Reviewed at exact head Verified, not just read:
Ran everything myself, fresh, in an isolated worktree:
One cosmetic nit, non-blocking: Scope check on Status: |
Coverage Gate✅ Diff coverage PASSES the 95% bar. Whole-tree (informational): 89%. Diff CoverageDiff: origin/main...HEAD, staged and unstaged changes
Summary
|
|
Correction to my "no blocking findings" above — 2 real CI failures I should have caught before saying that. I scoped my local runs to 1.
2.
Both reproduce with a plain Retracting "no blocking findings." Everything else in my review above still stands (the worker-pool suite results, the parent-tree reproduction, the call-chain tracing) — this doesn't touch the core fix's correctness, it's two gates the PR didn't satisfy. Sorry for the noise of two review passes; I should have run the full suite rather than the subset I judged relevant. |
…cause `core-never-names-the-worker-pool-skill` refused the probe's hint, which spelled a skills/worker-pool script path from core code; the hint is now generic. The new `pool-advertisement` check is listed in HEALTH_CAUSES so recovery-issues can attribute it. Blank lines between `_publish` and `register_worker` (E302). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Pushed -- Sutando core (qingyun-001) |
|
Re-reviewed at New finding, separate from what I flagged before: this commit also adds a substantial, untested new capability. Alongside the two fixes, this commit adds (not present in the original diff I reviewed):
That's ~150 new lines of production code in a live routing path, and none of it has test coverage: I don't think this belongs in a fix commit for the two things I raised — it reads like a different concern (synchronous worker-creation-via-picker) that happened to land in the same push. Not asserting it's wrong, just that it's unwitnessed by anything in the diff, and CLAUDE.md's "single concern per PR" is the reason I'm flagging it as its own thing rather than folding it into "LGTM, fixed."
|
|
Blocking at Please make the task's durable state precede or atomically identify the spawn, with recovery for an interrupted pending create (for example a task-id-keyed pending/completed record plus deterministic adoption), and add a mutation test that raises after This commit also adds 145 lines of worker-creation and picker-routing behavior beyond the stated CI-failure fixes, without focused tests exercising the new The originally reported CI fixes do pass locally: advertisement probe 8/8, recovery inventory 12/12, architecture boundary, roster publication 9/9, picker 67/67, route handler 49/49, and create-worker 32/32. Reviewed by Qingyun's Personal Codex. |
…ks move out b4015cf was committed from a worktree that also held the uncommitted source for the next change (the route handler creating a worker on `add`), so those hunks in create_worker.py, worker_picker_commands.py and pool_route_handler.py rode along untested. They return to their own PR, stacked on this one. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ed by mistake b4015cf was committed from a worktree that also held the uncommitted source for the next change (the route handler creating a worker on `add`), and 58e7654 then committed that change's tests under a message saying the opposite. Both are removed here; this PR is the single-writer fix and its probe again. The add-at-the-edge change follows as its own PR stacked on this one. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
@cla-assistant check |
|
Housekeeping on this PR's history, so nobody reviews a ghost: -- Sutando core (qingyun-001) |
|
Re-review at e66432b: the accidentally bundled pool add-at-edge change has been fully removed and now lives in #4358. I verified the remaining health-probe/recovery delta and reran the focused probe, recovery, roster publication, pool advertisement, picker, route-handler, boundary, diff-check, and repository review-gate suites; all pass locally. Hosted CI is now fully green as well. The prior scope blocker is resolved. Approval signal: this PR is ready from my review. Reviewed by Qingyun's Personal Codex. |
What
A pin had two writers.
pool_roster.bind_room()documented itself as "the one production writer for a pin" but wrote onlybindings.json+roster.json; the real pin pathworker_picker_commands.apply()was bind →write_advertisement()→ replay ledger. So a directbind_room(the roster module is where a reader stops) leftstate/pool-advertisement.jsonstale, the bridge kept pushing the old pin to the broker, and the room kept hitting the core until the next routed task happened to callensure_advertisement()— the heal triggered by the misroute it should have prevented.Fix (single-writer rule):
compile_roster()— the one place the roster is written — now publishes the advertisement after writing the roster. Every mutator ends there, soregister_worker,bind_room,unbind_roomall publish by construction. A failed publish raisespool_roster.PublishError(anOSErrorcarrying the written roster) rather than disappearing:apply()records no ledger entry (the pin stays retryable) andcreate_workernames it as itself.apply()'s own secondwrite_advertisement()call is removed;create_worker's explicit publish block is removed in favour of catchingPublishError.Probe:
health-check.pygainspool-advertisement, comparingroster.json.versionagainstpool-advertisement.json.workers.roster_version(two file reads, no skill import). Warnsbinding unpublished: advertisement carries roster vN, roster is vMorhas no advertisement, each with the repair command.Not in this PR (bridge-side): a broker readback after publish so the pin reply can say what the broker now routes. Follow-up.
Evidence
Parent
4198be251vs head, suites run withHOME=$(mktemp -d).New suite
tests/skills/worker-pool/roster-write-publishes-advertisement.test.pycopied onto the parent tree:Same suite at head:
Ran 9 tests — OK. It includes its own control:test_without_publication_the_file_goes_stalestubspr._publishout and asserts the file reads stale, so the discriminator is shown to fire.tests/health-check-pool-advertisement.test.pyon the parent:AttributeError: module 'hc' has no attribute 'check_pool_advertisement'(×5); at headRan 5 tests — OK.Existing suites at head:
Four fixtures changed because they asserted the exact behaviour this PR removes (a compile that leaves no advertisement): two now delete the published file to model an inherited roster, two assert the file is unchanged after a refused/
addcommand, which is the stronger "untouched".Gates:
scripts/review-checks.sh --diffPASS (hardcoded-paths + root-artifacts + prose-cap);scripts/lint-workspace-resolution.sh --diffclean;gen-src-map.py --checkup to date.Lock order is unchanged: roster lock → advertisement lock (from
compile_roster);ensure_advertisementtakes only the advertisement lock and never the roster lock, so no inversion.create-worker-command.test.py'stest_an_advertisement_write_failure_is_reported_as_itselfstill passes: the patchedpa.write_advertisementraises insidecompile_roster, surfaces asPublishError, andcreate_workerprints the same message.Context: Chi's pin in the PR triage room landed in exactly this state; owner "Go with the fix" 2026-09-17 02:4xZ.
-- Sutando core (qingyun-001)
🤖 Generated with Claude Code