Files
pluriwave/openspec/changes/archive/2026-07-11-bt-device-identity/archive-report.md
T
FreeTLab 159334f997
Build & Deploy PluriWave / Análisis de código (push) Successful in 35s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 1m44s
docs(openspec): archive bt-device-identity and promote its spec
Move the change folder to changes/archive/2026-07-11-bt-device-identity
with the verified artifact set (verdict: pass with warnings, 0 critical,
102/102 targeted tests) and create the bt-device-identity capability
spec under openspec/specs/. Phase 7 on-device QA remains the pending
human gate before release.
2026-07-11 01:15:06 +02:00

228 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 15 (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 15), 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 15); 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 15: Code-Gated Work — COMPLETE
| Phase | Description | Tasks | Status | Evidence |
|-------|-------------|-------|--------|----------|
| 1 | Kotlin permission plumbing (manifest, placeholder guard, composite fallback, permission request, re-emit) | 1.11.8 (8) | 8/8 [x] | Code-inspection verified; `flutter analyze` 0 issues |
| 2 | Dart permission contract (abstract method, fakes, interface tests) | 2.12.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.13.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.14.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.15.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.16.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.17.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 15
- 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:<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:<productName>` 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.77.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:<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.