AprilNEA/OpenLogi · error

cassette audit has multiple sanitized receiver identities

Error message

cassette audit has multiple sanitized receiver identities

What it means

During cassette replay, the audit must supply a single sanitized (synthetic) receiver identity for a given replacement kind. `unique_replacement` filters the audit's replacements by kind and errors both when none is found and — this error — when more than one is found. It throws because a cassette audit that contains multiple sanitized identities for one kind is ambiguous: the replay cannot decide which synthetic value to substitute for that kind.

Solutions

  1. Open the cassette's audit and remove or merge duplicate sanitized receiver entries so exactly one replacement exists per kind
  2. Re-record the fixture in a session that uses only one receiver so the audit sanitizes a single identity
  3. If multiple identities are legitimately needed, change the code to select a specific replacement (by id) instead of requiring uniqueness

Example fix

// before: cassette audit has two entries of kind Receiver
{"replacements":[{"kind":"receiver","syntheticValue":"SYN-RCV-1"},{"kind":"receiver","syntheticValue":"SYN-RCV-2"}]}
// after: keep one per kind
{"replacements":[{"kind":"receiver","syntheticValue":"SYN-RCV-1"}]}
Defensive patterns

Strategy: validation

Validate before calling

let kinds: Vec<_> = audit.replacements.iter().map(|r| r.kind).collect();
let dupes: Vec<_> = kinds.iter().filter(|k| kinds.iter().filter(|k2| k2 == *k).count() > 1).collect();
if !dupes.is_empty() { /* fix the cassette before replay */ }

Type guard

fn unique_replacement_of<'a>(audit: &'a Audit, kind: Kind) -> Option<&'a Replacement> {
    let mut it = audit.replacements.iter().filter(|r| r.kind == kind);
    let first = it.next()?;
    it.next().is_none().then_some(first)
}

Prevention

When it happens

Trigger: Calling `unique_replacement` (via `derive_replay_route`) on a cassette whose audit recorded more than one replacement of the same `kind` — e.g. a recording session that saw two different physical receivers and sanitized them into two distinct synthetic identities.

Common situations: Merging or hand-editing cassette JSON and accidentally duplicating a receiver entry; recording a fixture while switching between receivers (Bolt vs Unifying vs a second dongle) so the audit accumulates multiple sanitized identities for one kind.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of AprilNEA/OpenLogi@e846e6f4b4 (2026-09-13). Data as JSON: /api/errors/3397bceabdb1157a. Report an issue: GitHub.

Appendix: source

Thrown at crates/openlogi-cli/src/cmd/fixture/record_case/replay.rs:160

            product_id: *product_id,
        }),
        DeviceRoute::RawHid { .. } => {
            bail!("raw HID routes are outside HID++ fixture case capture")
        }
    }
}

fn unique_replacement(audit: &HidCassetteAudit, kind: SanitizedIdentityKind) -> Result<&[u8]> {
    let mut replacements = audit
        .replacements
        .iter()
        .filter(|replacement| replacement.kind == kind);
    let value = replacements
        .next()
        .map(|replacement| replacement.synthetic_value.as_slice())
        .ok_or_else(|| anyhow::anyhow!("cassette audit has no sanitized receiver identity"))?;
    if replacements.next().is_some() {
        bail!("cassette audit has multiple sanitized receiver identities");
    }
    Ok(value)
}

#[cfg(test)]
mod tests {
    use openlogi_device::write::FeatureEntry;
    use openlogi_fixture::{
        CassetteExchange, FIXTURE_SCHEMA_VERSION, HidCassette, ReportSupport, RequestMatch,
    };
    use openlogi_hid::FeatureType;
    use openlogi_hid::recording::IdentityReplacement;

    use super::*;

    fn target(route: DeviceRoute, product_id: u16) -> TargetCandidate {
        TargetCandidate {
            route,

View on GitHub (pinned to e846e6f4b4)