# Archive Report: Canonical Bluetooth Device Identity **Change**: bt-device-identity **Archived**: 2026-07-11 **Status**: ARCHIVED AND CLOSED **Archive Path**: `openspec/changes/archive/2026-07-11-bt-device-identity/` **Spec Merged To**: `openspec/specs/bt-device-identity/spec.md` (new capability domain) --- ## Executive Summary The **bt-device-identity** change is now archived with a **PASS WITH WARNINGS** verification verdict. All code-gated implementation work is complete across Phases 1–5 (41 of 57 tasks done, 6 N/A by design). Phase 7 (manual/on-device QA, 10 tasks, human sign-off gate) remains pending as the mandatory pre-shipping gate. The change restores stable Bluetooth device identity by acquiring `BLUETOOTH_CONNECT` at point-of-intent, guarding against the OS placeholder MAC, caching per-device names, and migrating corrupted placeholder-keyed entries. All test suites pass (102/102 tests), `flutter analyze` reports 0 issues, and the re-verify phase closed the single CRITICAL finding (CRITICAL-1: untested "rename survives re-pair" scenario — now covered by new task 3.10 composed regression test that passes against existing code). --- ## Artifact Traceability | Artifact | Engram Observation ID | Location | Notes | |----------|----------------------|----------|-------| | Proposal | #2299 | sdd/bt-device-identity/proposal | Root cause analysis, scope, approach, risks | | Spec | #2304 | sdd/bt-device-identity/spec; merged to openspec/specs/bt-device-identity/spec.md | 7 requirement groups, 21 scenarios; on-disk version (openspec/changes/bt-device-identity/spec.md) reconciled and merged to new domain | | Design | #2305 | sdd/bt-device-identity/design | 6 ADRs, technical approach, file changes, testing strategy | | Explore | (embedded) | sdd/bt-device-identity/proposal (no separate observe) | Root cause evidence, bug analysis | | Tasks | #2314 | sdd/bt-device-identity/tasks | 57 checkboxes: 41 complete (Phase 1–5), 6 N/A (Phase 6), 10 pending (Phase 7 human QA) | | Apply-Progress | #2328 | sdd/bt-device-identity/apply-progress | Batches 1/2/2b complete (Phases 1–5); Batch 3 (Phase 7) pending; full merged evidence of all 41 tasks | | Verify-Report | #2329 | sdd/bt-device-identity/verify-report | Initial FAIL (CRITICAL-1 untested scenario); re-verify PASS WITH WARNINGS (CRITICAL-1 closed via task 3.10); 15/21 scenarios COMPLIANT, 4/21 DEFERRED (manual QA), 2/21 N/A (l10n justified), 0/21 still UNTESTED | --- ## Implementation Completion ### Phases 1–5: Code-Gated Work — COMPLETE | Phase | Description | Tasks | Status | Evidence | |-------|-------------|-------|--------|----------| | 1 | Kotlin permission plumbing (manifest, placeholder guard, composite fallback, permission request, re-emit) | 1.1–1.8 (8) | 8/8 [x] | Code-inspection verified; `flutter analyze` 0 issues | | 2 | Dart permission contract (abstract method, fakes, interface tests) | 2.1–2.5 (5) | 5/5 [x] | All 3 fake implementers updated; contract tests pass | | 3 | Dart platform-name cache + duplicate-entry guard (in-memory map, composites don't auto-create, regression tests) | 3.1–3.10 (10) | 10/10 [x] | 49/49 estado_ecualizador_test.dart pass (incl. post-verify task 3.10) | | 4 | Dart display fix + permission trigger (call sites updated, permission fires on toggle-ON) | 4.1–4.8 (8) | 8/8 [x] | 11/11 pantalla_ajustes_test.dart pass; provider wiring resolved via passthrough | | 5 | Dart one-time migration purge (guarded flag, exact-literal placeholder removal, idempotent) | 5.1–5.10 (10) | 10/10 [x] | 20/20 servicio_ecualizador_test.dart pass; migration isolation verified | ### Phase 6: Localization — N/A (By Design) | Phase | Description | Tasks | Status | Rationale | |-------|-------------|-------|--------|-----------| | 6 | Localization (l10n keys for all 13 locales IF UI copy is shown) | 6.1–6.6 (6) | N/A | No in-app rationale sheet or migration-notice UI exists; OS `BLUETOOTH_CONNECT` dialog only; migration purge entirely silent; Phase 6 condition ("IF UI copy is shown") not met | ### Phase 7: Manual/On-Device QA — PENDING (Human Gate) | Phase | Description | Tasks | Status | Notes | |-------|-------------|-------|--------|-------| | 7 | Manual/on-device QA (permission dialog flow, real MAC after grant, rename survives re-pair, no duplicates, migration idempotency, locale rendering if Phase 6 ships) | 7.1–7.10 (10) | 0/10 [ ] | Mandatory human sign-off gate; requires real/emulated Android 12+ device; Kotlin permission path not unit-testable (code-inspected only); these 10 items are the ONLY remaining work for this change | **Task Summary**: - 41 tasks complete (`[x]`) across Phases 1–5 - 6 tasks N/A (`[ ]` with justification) in Phase 6 - 10 tasks pending (`[ ]`) in Phase 7 - **Total: 57 checkboxes, 0 unaccounted** --- ## Test Results ### Unit/Widget Tests | Suite | Tests | Result | Notes | |-------|-------|--------|-------| | `estado_ecualizador_test.dart` | 49/49 | PASS | +1 from task 3.10 (was 48/48); Phase D regression group (D.1-D.5) confirmed green | | `pantalla_ajustes_test.dart` | 11/11 | PASS | Display fix + permission trigger scenarios | | `servicio_ecualizador_test.dart` | 20/20 | PASS | Migration tests including matrix-key and near-miss scenarios | | `servicio_dispositivo_audio_real_test.dart` | 10/10 | PASS | Permission contract (MethodChannel invokeMethod) | | `servicio_dispositivo_audio_test.dart` | 7/7 | PASS | Abstract contract tests | | `servicio_dispositivo_audio_toggle_test.dart` | 5/5 | PASS | Ripple-fix (NullServicioDispositivoAudio) included | | **Total** | **102/102** | **PASS** | — | ### Static Analysis | Tool | Result | Coverage | |------|--------|----------| | `flutter analyze` | 0 issues | Whole project | | `rg` (placeholder string checks) | No dead code (`00:00:00:00:00:00` removed completely) | Kotlin, Dart | ### Code Coverage - Exact-line coverage metrics not collected (per verification scope). - Scenario-level mapping: 15/21 spec scenarios COMPLIANT (Dart-testable, all pass); 4/21 DEFERRED (manual QA); 2/21 N/A (l10n justified). - CRITICAL-1 closed by new task 3.10 composed test (spec.md L95-100, rename survives re-pair). --- ## Verification Summary ### Verdict Evolution | Pass | Result | Finding | |------|--------|---------| | Initial (2026-07-11 00:44) | **FAIL** | CRITICAL-1: Untested Dart-testable scenario "rename persists after re-pair" (spec.md L95-100) — constituent behaviors tested individually (reconnect-dedup D.4, rename-priority 4.4), composition untested | | Re-verify (2026-07-11 post-verify) | **PASS WITH WARNINGS** | CRITICAL-1 CLOSED: task 3.10 adds composed regression test; passes against existing code (coverage gap confirmed, not functional bug). WARNING-1 spec wording reconciled (composite shape documented as `bt_a2dp:name:` per ADR-3). WARNING-2 cosmetic (arithmetic drift in apply-progress narrative, no code impact) accepted as-is. | ### Finding Breakdown | Severity | Count | Status | |----------|-------|--------| | CRITICAL | 1 | CLOSED (task 3.10 composed regression test) | | WARNING | 2 | CLOSED (WARNING-1, re-verify wording fix); OPEN/ACCEPTED (WARNING-2, cosmetic) | | SUGGESTION | 3 | OPEN (non-blocking: wording nuance SUGGESTION-1, mock call-count pattern SUGGESTION-2, Engram housekeeping SUGGESTION-3) | ### Compliance Matrix Snapshot | Req. | Scenarios | Compliant | Deferred | N/A | Untested | |------|-----------|-----------|----------|-----|----------| | BLUETOOTH_CONNECT at point-of-intent | 2 | 0 | 2 | 0 | 0 | | Placeholder MAC guarded | 5 | 4 | 1 | 0 | 0 | | Platform-name cache feeds display | 3 | 3 | 0 | 0 | 0 | | Rename survives re-pair | 2 | 1 (via 3.10) | 1 | 0 | 0 | | No duplicate entries on transient | 2 | 2 | 0 | 0 | 0 | | One-time guarded migration | 6 | 6 | 0 | 0 | 0 | | Localization (conditional on UI copy) | 2 | 0 | 0 | 2 | 0 | | **Total** | **21** | **15** | **4** | **2** | **0** | --- ## File Changes Summary **Estimated Changed Lines**: ~450 (Kotlin ~90, Dart prod ~140, Dart tests ~58 post-verify, manifest ~1) **400-Line Budget Risk**: High (mitigated by chained PR strategy; PR 1: Kotlin, PR 2: Dart, PR 3: l10n + manual QA) **Build Execution**: Not run (per verification scope — `flutter build` explicitly excluded) ### Kotlin (Code-Inspection Verified) - `android/app/src/main/AndroidManifest.xml`: +1 permission declaration - `android/app/.../MainActivity.kt`: ~90 lines (permission request code, placeholder guard, composite fallback, re-emit logic, doc comment updates) ### Dart Production - `lib/servicios/servicio_dispositivo_audio.dart`: Abstract + real impl of `solicitarPermisoBluetooth()` - `lib/estado/estado_ecualizador.dart`: In-memory `_nombresPlataforma` cache, `nombrePlataforma()` getter, duplicate-entry guard update - `lib/pantallas/pantalla_ajustes.dart`: Two `nombreVisible()` call sites updated, permission trigger added - `lib/servicios/servicio_ecualizador.dart`: Migration constants and `migrarClavesPlaceholder()` function ### Dart Tests - `test/estado/estado_ecualizador_test.dart`: +58 lines (task 3.10 post-verify regression test) - `test/pantallas/pantalla_ajustes_test.dart`: Display fix + permission trigger tests - `test/servicios/servicio_ecualizador_test.dart`: Migration test suite (exact placeholder, matrix suffix, near-miss, idempotency) - `test/servicios/servicio_dispositivo_audio_real_test.dart`: Permission contract tests - `test/helpers/fakes.dart`: Fake implementers updated (ripple-fix: NullServicioDispositivoAudio also patched) ### Localization - `lib/l10n/*.arb` (13 files): **0 changes** (Phase 6 N/A — no in-app copy shipped) --- ## Deviations from Design **Documented and Signed Off**: 1. **Trigger-Point Deviation (Phase 4, Task 4.7)**: Design specified "on opening device-management UI" (StatefulWidget on-build hook). Actual: permission fires on toggle-turn-ON tap in `_SeccionEcualizadorAvanzado` (StatelessWidget). Rationale: avoids unnecessary StatelessWidget → StatefulWidget conversion; toggle-turn-ON satisfies both spec scenarios. Signed off by orchestrator. 2. **Provider Wiring Resolution (Phase 4, Task 4.8)**: Design assumed `ServicioDispositivoAudio` would be a top-level Provider. Actual: not registered in `app.dart`. Solution: added `solicitarPermisoBluetooth()` passthrough on `EstadoEcualizador` (delegates to private field). Maintains test isolation, avoids ProviderNotFoundException. **No design deviations were introduced in Batch 2b** (task 3.10 is test-only, zero production-code changes). --- ## Risks and Mitigations | Risk | Likelihood | Status | |------|------------|--------| | Migration deletes legitimate data | Low | Exact-literal `bt_a2dp:02:00:00:00:00:00` match only; all other keys preserved; Task 5.3 near-miss test confirms `02:00:00:00:00:01` is NOT purged | | BT permission denied → unstable ids | Med | Composite `bt_a2dp:name:` fallback keeps app functional; re-prompt on next settings open; no crash path | | Colon-delimiter key safety broken | Low | Single leading `bt_a2dp:` segment preserved; productName colons sanitized to `-`; `eq_presets_matriz_v1` split-on-first-`:` remains valid (ADR-3, multi-device-eq ADR-3 RFC4122 rationale) | | Kotlin permission flow has no precedent | Med | Copied verbatim from existing `requestPostNotificationsPermission` pattern (L306-318 struct); request codes distinct (4821/4822/4823); human verification in Phase 7 | | No Kotlin test harness | Med | Code-inspection verified against proven pattern; Phase 7 manual QA is the runtime gate | --- ## Next Steps ### Mandatory Before Shipping 1. **Phase 7 Manual/On-Device QA** (pending, 10 tasks): Requires real or emulated Android 12+ device. - Permission dialog flow (grant/deny, no dialog at launch) - Real MAC after grant (7.3) - Rename survives re-pair without duplicates (7.5, the original bug report symptom) - Migration purge on affected install + idempotency (7.7–7.8) - Locale rendering if Phase 6 ships (7.9; N/A this change) - Sign-off (7.10) before merge 2. **Chain Strategy** (pending, from tasks.md): Orchestrator should confirm/name the chain strategy if chained PRs are used: - `stacked-to-main`: Each PR merges to main in order (fast, fix on the go) - `feature-branch-chain`: Tracker branch accumulates; PR #1→PR 2→PR 3 (rollback control) ### Follow-Up Opportunities (Out of Scope) - **WARNING-1 (now closed)**: Spec wording reconciled during re-verify. Composite shape (`bt_a2dp:name:`) is now documented in both spec and design. - **WARNING-2 (cosmetic, accepted)**: Arithmetic drift in apply-progress narrative (pre-existing test counts off by 3/2 respectively) — no functional impact. Accepted as-is per task brief. - **SUGGESTION-1/2/3 (non-blocking)**: Wording nuance (composite not single-entry), mock-call-count pattern, Engram conflict housekeeping — all flagged for future review. - **Broader transient-churn dedup** (design Open Question): Deferred; acceptable now that MAC is canonical. Revisit if duplicates persist post-fix. --- ## Archive Integrity **Moved From**: `openspec/changes/bt-device-identity/` → `openspec/changes/archive/2026-07-11-bt-device-identity/` **Verified Empty**: Source folder no longer exists under `changes/` (not a residual copy, true move) **Spec Merged**: `openspec/specs/bt-device-identity/spec.md` created as new domain (not merged into `multi-device-eq`, which focuses on EQ preset resolution; bt-device-identity is about device identity + persistence) **Artifacts Preserved**: All 6 openspec files (design, explore, proposal, spec, tasks, verify-report) + new state.yaml and this archive-report --- ## Sign-Off **Archive Ready For**: Production shipping (pending Phase 7 human QA sign-off) **Blocking Issues**: None; Phase 7 is a known human gate, not a blocker **Recommendation**: Proceed to Phase 7 manual QA; no code changes expected; expect 10 pass/fail items to be recorded against real device access --- ## Appendix: Observation IDs for Traceability - #2299: sdd/bt-device-identity/proposal (root cause, scope, approach) - #2304: sdd/bt-device-identity/spec (21 scenarios, 7 requirement groups) - #2305: sdd/bt-device-identity/design (6 ADRs, technical approach, testing strategy) - #2314: sdd/bt-device-identity/tasks (57 checkboxes, 3 batches, phase status) - #2328: sdd/bt-device-identity/apply-progress (Batches 1/2/2b merged evidence, 41 tasks complete) - #2329: sdd/bt-device-identity/verify-report (PASS WITH WARNINGS, re-verify, CRITICAL-1 closed) All artifacts archived and accessible at `openspec/changes/archive/2026-07-11-bt-device-identity/` with full change history preserved for audit trail.