Codex adversarial review found no merge-blocking defects. Five
worth-checking items, all triaged for reachability:
- F1 (spurious pre-ready revoke): unreachable — the AEC's four nodes are
two Stream/* legs + a null-sink-like virtual sink/source, none claiming
a device.id, so the phase-3 observer never withholds them; index_present
goes false only on a genuine full unload. Documented why revoke is NOT
gated on graph_ready, and why gating it would reopen the reused-index
alias trap (F4) during a hot-reload-under-churn. Pinned with
revokes_on_empty_even_while_not_ready (mutation-verified: `&& graph_ready`
on the revoke guard dies here).
- F4 (test relies on observing the empty gap): documented the phase-5/6
integration contract it rests on (one observe per graph event, no
coalescing across a module lifetime boundary) and owed the robust fix
(serial-continuity / observer-generation) to a later hardening round.
- F2 (late positive evidence beats the deadline): intentional and correct
— a demonstrably-present identity is ground truth. Documented +
late_positive_evidence_wins_over_expired_deadline (both arms: node-first
validates, Tick-first fails closed and stays sticky).
- F3 (real P3 coverage hole): strengthened deadline_is_not_armed_until_
graph_ready to prove the budget starts at first-ready, not construction
(mutation-verified: a construction-relative deadline now dies).
- F5 (`+7` grammar mismatch): documented the producer contract — peerspeak
emits bare decimal (pactl returns unsigned decimal), the narrow parser
is deliberate. Unreachable on the measured stack.
No core logic change. 24 pure aec tests, cargo test --bins green (145 unit
+ 1 ignored live), clippy -D warnings + fmt clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bounded, read-only state machine that validates peerspeak's live
echo-cancel module identity before the taint engine trusts it, filling
the last ExclusionCtx field (aec_module_id). Design v3.4 §5.2/§5.3,
impl plan §4.
States (v3.4 §5.3 verbatim): NotConfigured / Validating / Validated /
Failed / Revoked. No fan-out while Validating; Failed and Revoked are
sticky terminals so a reused module index (indices ARE reused, §5.2
correction 3) cannot alias a Revoked epoch onto an unrelated reload.
Revocation is loss of the whole identity (every node bearing the index
gone), never one leg corking. The Failed deadline is armed only on the
first graph_ready, so a slow initial enumeration is "unknown" not
"absent" and never times out spuriously.
parse_aec_arg handles --aec=off|pulse-module:<idx> (D5): bare-u64
decimal accepted past u32::MAX, rejecting sign/whitespace/non-digit/
overflow/unknown-form.
22 pure tests (the exit-gate transition matrix), cargo test --bins
green (143 unit + 1 ignored live), clippy -D warnings + fmt clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>