Codex authored the adapter half of the phase-3 split; I reviewed it and
applied one robustness fix (below). Additive/read-only — does not yet replace
the existing audio.rs router (that migration is a later integration phase).
adapter.rs: a dedicated libpipewire main-loop thread translating registry
globals into RegEvents and publishing the latest Projection via
RegistryObserverHandle::latest(). core.sync(0)/done is matched one-shot →
ServerSynced; a 250 ms loop timer emits Tick for the fail-closed readiness
timeout; pulse-PID candidates are probed from /proc/<pid>/comm only when the
candidate changes; Links missing endpoint props are bound (LinkInfoRef, weak
back-ref to avoid the listener cycle) and resolved via LinkEndpointsResolved.
mod.rs: `pub mod adapter;`. audio.rs: parse_object_serial → pub(crate) so the
adapter reuses the strict 64-bit parser.
Review fix: record_global was called unconditionally per global (including
unknown object types and dropped globals), which could desync the bound-link
FIFO from the model's live_ids and leak a Link proxy on a recycled id. Now
folded into `add()` so a slot is recorded only when an Added event is applied
— the two id queues are provably lockstep.
Exit gate complete: 5 pure rows + the live topology-diff row (row 6) — the
#[ignore] adapter test PASSES on this host against the live daemon
(module-null-sink + module-loopback observed appearing and disappearing).
cargo test --bins 117 + 1 live green, clippy clean, no fmt sweep.
Co-Authored-By: Codex (gpt-5.6-sol) <codex@openai.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
My half of the phase-3 split (impl plan §4). Pure, no PipeWire: the adapter
(Codex's half) translates live registry callbacks / binds / /proc reads /
core.sync into RegEvents and feeds this reducer.
- RegistryModel::apply folds RegEvents into serial-keyed maps with an
insertion-ordered id index so global_remove accounts for the oldest
generation first; recycled ids stay Ambiguous until accounted (v3.4 §6.1.3).
- Readiness epoch: graph_ready false until ServerSynced + no outstanding
obligations (withheld nodes, pending link binds); bounded timeout fails
closed. Gates sticky retirement only; sticky once terminal.
- session_device classifier: hardware-PCM factory allowlist, exact match,
fail closed to false; a node on an unresolved Device is withheld, never
admitted provisional.
- pulse-PID derivation split into pure candidate (repeated sec_pid) + validate
(/proc comm), so the 6-case failure matrix is unit-testable; any failure =>
None (key 4 unusable).
34 tests cover 5 of 6 exit-gate rows (the live topology-diff row is the
adapter's). cargo test --bins 117 green, fmt + clippy clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>