ADR-047: Retire the ADR-006 D4 subscriber bus — compile is the guard for retired modules¶
Status: accepted (2026-09-12; architecture-review candidate #3, grilled + settled; epic Y4VMWH — bridge tail 967d1e81b (T1), Rust seam 5aace566a (T2)).
Context¶
ADR-006 D4 introduced a per-state-subject publisher/subscriber bus (the “subscriber
bus”): LogDispatcher owned a decoder registry plus a
Weak<dyn PoolStateSubscriber> registry keyed by pool_id; after the core write
lock released, it notified every live subscriber of the affected pool, and the engine
implemented PoolStateSubscriber::on_pool_state_updated to dirty the pool_id in its
own per-engine set. A Python bridge (PySubscriberAdapter / PySubscription /
register_subscriber plus a dedicated subscriber-drainer OS thread) let Python
callbacks subscribe to the same fan-out.
Two later decisions drained the bus of its consumers:
ADR-041 (epic
MROOY7) retiredEngineSubscriberand moved dirty-tracking to theEpochDeltaledger; solve triggering became theStageHandlershooks driven inline by the pump. The bus’s only non-test consumer path — the engine subscriber — disappeared.The surviving consumers were the in-tree test fakes and an uncalled Python bridge (CONTEXT.md recorded the
_ffi.subscribersubmodule as “0 production callers; test-only”). No production consumer ever appeared.
The dead seam was not free. On every applied forward log the hot path carried a
parking_lot::Mutex<HashMap<\u2026>> lock in LogDispatcher, plus a
NOTIFY-MISS warn when a state apply found no subscriber attached
(log_dispatcher.rs). With zero production subscribers the warn fired on the hot path
to nobody. Worse, the notify channel was a second dirt-recording mechanism: dirt is
already owned by the EpochDelta ledger, so the notify path was redundant with the
authoritative owner.
Decision¶
D1 — Hard-retire the bus and its Python bridge¶
Delete the whole tail, hard, no shims and no feature flag (AGENTS.md):
Rust bus:
PoolStateSubscriber,LogDispatcher::subscribe,LogDispatcher::notify, thesubscribers: Mutex<HashMap<u64, Vec<Weak<dyn PoolStateSubscriber>>>>field, and theNOTIFY-MISSwarn. Dispatch becomes decode → apply → record delta, with no subscriber branch; an unregistered-pool apply is the only miss.Python bridge:
bot/subscriber.rsin full —PySubscriberAdapter,PySubscription,register_subscriber, the queue, and thesubscriber-drainerOS thread + its init/shutdown — itsc_apiregistration, the_ffi.pyistubs, and the test-only surface (tests/fakes/subscribers.py,tests/test_pubsub_seam_parity.py,tests/types/test_subscriber_rust_routing.py).
The deletion left the suite green: the EpochDelta ledger already covered the dirt
the bus tests asserted, so no replacement test was needed.
D2 — notify_pool_state_changed → record_pool_state_changed¶
The Bot method is renamed to record_pool_state_changed. record names the sole
remaining meaning — append the changed pool to the EpochDelta ledger. notify
described a fan-out that no longer exists.
D3 — ADR-006 D4 is superseded in part¶
The D4 orchestrator rows — Bot / BotState / LogDispatcher / BlockPump /
ReorgCoordinator — remain accurate. The D4 “per-state-subject publisher/subscriber
event bus” (the solve-notification protocol in ADR-006’s Deferred section) is
retired; ADR-006 records the partial supersession.
D4 — SETTLED POLICY: no resurrection guards for retired modules¶
Resurrection-guard tests are rejected. Once a module is deleted, the compiler is the guard: any use of the retired name is a compile error, covering every consumer, typed, at build time. A source-scan test only greps text; it guards nothing a deleted module could violate.
Source-scan tests are for LIVE invariants only — rules a still-present code path
could silently violate without a compile error. The no-pyo3-in-cores scan is the
model: “core crates stay pyo3-free” can be violated by adding a dependency in another
crate, which compiles fine, so the scan is the only guard. A deleted module cannot
violate anything and needs no guard.
Concretely: do not add tests asserting PoolStateSubscriber / subscribe /
PySubscription / register_subscriber / notify_pool_state_changed do not exist.
Compile covers it. Future architecture reviews must not re-suggest resurrection guards
for retired modules.
Considered options rejected¶
Keep the bus for a future second state-subject type or second engine. Rejected: sample-of-one machinery (ADR-045’s replay-harness rule) — reintroduce when the second adapter actually exists, not speculatively.
Feature-flag the bus for a parallel implementation. Rejected: AGENTS.md hard cutover; there is no consumer to keep.
Keep the Python bridge as public API. Rejected: 0 production callers, test-only; an API with no user.
Add a resurrection-guard scan for the retired names. Rejected by D4 — compile is the guard; scans guard live invariants only.
Keep the
notify_pool_state_changedname. Rejected by D2 — it names fan-out that no longer exists.
Consequences¶
The per-log hot path drops the subscriber
Mutexlock and theNOTIFY-MISSwarn; dispatch has no subscriber branch.Dirt has exactly one owner: the
EpochDeltaledger.record_pool_state_changedis the sole write.No Python-visible API change for users: the deleted bridge was an un-homed
_ffi.subscriberwith no companion home and no production callers.Hard cutover; the build receipt was verified after the Rust edits (
5aace566a).The retired names and the rename are recorded in CONTEXT.md’s retired-name discipline.