diff --git a/ai-sessions/0093_AUDIT_PROMPT_2026_10_15.md b/ai-sessions/0093_AUDIT_PROMPT_2026_10_15.md new file mode 100644 index 0000000..a82ae6c --- /dev/null +++ b/ai-sessions/0093_AUDIT_PROMPT_2026_10_15.md @@ -0,0 +1,350 @@ +# 0093_AUDIT_PROMPT_2026_10_15.md — Exhaustive end-to-end audit of the whole project after 1.3.0, with a deep re-review of `ai-sessions/0090` (the pbpctrl cross-check that produced ADR-064) and `ai-sessions/0091` (the 1.3.0 build), a reverse-engineering pass over the official APK to clarify and extend the protocol findings, and improvement proposals for 19 project documents + +**Number:** 0093 +**Category:** AUDIT +**Date:** 2026-10-15 +**Title:** Comprehensive end-to-end technical audit of OpenControl for Pixel Buds Pro 2 after the publication of 1.3.0 — documentation, protocol specification, +reverse-engineering findings, captures, architecture, decisions, `TODO.md`, the release process and the Android app — including a deep re-review of +`ai-sessions/0090` and `ai-sessions/0091` (and of `ai-sessions/0092`, the hardware run that released 1.3.0), a structured reverse-engineering pass over the +official Pixel Buds APK `v1.0.955078536-10253511` to clarify, correct and extend the protocol findings, and improvement proposals for 19 project documents; +review only — no file other than the RESULT is changed + +--- + +## 0. How to use this prompt + +You are an expert software architect, reverse engineer and technical auditor. This is a **review-only** session for a **fresh** Claude Code session in this +repository. It produces one structured findings report, **ai-sessions/0093_AUDIT_RESULT_2026_10_15.md**, and changes **no other file**. It is intentionally +large, runs in **phases that are executed strictly in order** (§6), and is not expected to finish in one context window (§5). + +**Context (measured 2026-10-15 by the session that wrote this prompt — re-derive every number in Phase 0).** +- **1.3.0 was published on GitHub on 2026-10-15** (tag `v1.3.0` on the build commit `ff27ee1`; `RELEASING.md` §13's Release log; built by `ai-sessions/0091`, + tested in `CAP-076` by `ai-sessions/0092`). `main` is at `b9828ba` (the sitemap merge after PR #58 `release/1.3.0-after`). Since the previous full audit the + project released 1.1.0, 1.2.0, 1.2.1, 1.2.2 and 1.3.0. +- **The previous full audit was `ai-sessions/0068`** (2026-10-03, after 1.0.0: 85 findings, 10 leads, 18 document proposals), processed by `ai-sessions/0069`. + `ai-sessions/0081` (2026-10-08) was a targeted cross- and consistency check before 1.2.0, not a full audit. **The sessions since the last audit are + `0069` … `0092`** (24 prompt/result pairs); the maintainer asks for a **deep re-review of `0090` and `0091`** in particular (§2, last block). +- `DECISIONS.md` holds ADR-001 … **ADR-065** (plus the draft ADR-060, which is not a decision). The newest decisions, ADR-064 and ADR-065 (2026-10-14), moved + the noise-control mode to the app's own channel (DLCI 0x02: `SubscribeToSettingsChanges` and `ReadSetting 4:13`, one of each per MAESTRO session) and + **replaced** ADR-061 (the hold), ADR-062 (the re-claim and its "Noise control with Google Play services" card) and ADR-063 (the Play-services line) — all three + removed from the app in 1.3.0. `AGENTS.md` §1 and §5 carry dated pointer notes for these; check that every other document followed. +- `captures/` holds **76 folders, `CAP-001` … `CAP-076`** (`CAP-076` = Group BL, the 1.3.0 run in the Owner user with Google Play services and in a secondary + user without). `PROTOCOL.md` is ≈ 429 KB with **32 🔴 and 54 🟡 markers in §6 (Open questions)** — the starting list for the protocol investigation (§2 item 8). +- The Android tree (`android/app`, `android/data`, `android/domain`, `android/hardware`, `android/ui`) has 82 main and 59 test Kotlin files; `ai-sessions/0091` + §D recorded the gate as `:data` 1672 per variant, `:hardware` 64, `:domain` 42, `:ui` 102, `:app` 0 — **measure it yourself** in Phase 0. +- **The official APK** `reverse-engineering/apk/v1.0.955078536-10253511/` (gitignored, local only; registered in `reverse-engineering/APK_VERSIONS.md`) is + decompiled: `jadx-output/sources` (≈ 12,500 `.java` files), `apktool-output` and `apktool-output-arm64_v8a` (≈ 13,100 `.smali` files), `pbtk-output` (empty by + design, ADR-041). A **newer APK, `v1.0.990706425-10260911`, is pulled (2026-10-05) but not decompiled**; the maintainer said on 2026-10-05 *"not yet, later"* + (`TODO.md` §4). This session does **not** decompile it unless the maintainer says so at Checkpoint 0 (§9) — and note that `reverse-engineering/APK_VERSIONS.md` + still describes that version as "Not pulled" (`TODO.md` §4 already lists this; verify and report its current state, do not fix it). +- **Memory and the machine.** The laptop has 15 GB; `/tmp` is a 7.7 GB tmpfs. Gradle: one invocation at a time, always `--max-workers=2` (an earlier session was + OOM-killed running Gradle repeatedly). `tshark`/JADX-tree greps: one job at a time; never read a whole snoop log, a whole `jadx-output` tree or a film into + context — `grep`/`awk`/`tshark -Y` them. **Never `git worktree add`** (it checks out `captures/`, gigabytes, and a leftover worktree makes `scripts/lint_docs.py` + fail). Scratch files go into the session's scratchpad directory; write their names in the RESULT **without backticks** (`scripts/lint_docs.py` flags a + backticked file name that does not exist in the repository). Running a Python script may rewrite a tracked `scripts/__pycache__/*.pyc`; restore it with + `git checkout -- scripts/__pycache__` and say so. + +**Reading first (mandatory, `AGENTS.md` §0.1, `AI_SESSION_LOG_PROCEDURE.md` §8/§9).** Before any other action, take the time to read **in full**, in this +order, to understand the ground rules before auditing anything: `AGENTS.md` → `PROJECT_RULES.md` → `PROJECT.md` → `ARCHITECTURE.md` → `PROTOCOL.md` → +`DECISIONS.md` (**every** ADR, ADR-001 … ADR-065, with every dated Update, and the draft ADR-060 — you need the full decision history to judge consistency) → +`TODO.md`. Then `AI_SESSION_LOG_PROCEDURE.md` (incl. §4b, §5, §9), `ai-sessions/INDEX.md` (the whole session history), `RELEASING.md`, +`CAPTURE_BLUETOOTH_HCI_SNOOP.md`, `TESTPLAN_BLUETOOTH_HCI_SNOOP.md`, `APP_TESTPLAN.md`, `REVERSE_ENGINEERING.md`, `APK_REVERSE_ENGINEERING_PROCEDURE.md`, +`reverse-engineering/APK_VERSIONS.md`, `reverse-engineering/tools/BACKLOG.md`, `DESKRESEARCH_FINDINGS.md`, `MAINTAINING_DOCS_SITE.md`, `id_registry.csv`, +`CHANGELOG.md`, `README.md`, `SECURITY.md`, `CONTRIBUTING.md`, `WORKSTATION_PREPARATIONS.md`. Large files (`PROTOCOL.md`, `DECISIONS.md`, +`REVERSE_ENGINEERING.md`, `CAPTURE_BLUETOOTH_HCI_SNOOP.md`, `CHANGELOG.md`, `TODO.md`) may be read in consecutive chunks, but **every chunk is read**. Say in +the RESULT which files you read in full and which only partly, and why. + +**Then, before Phase 7, read in full** the sessions the maintainer singled out: `ai-sessions/0090_CROSSCHECK_PROMPT_2026_10_14.md`, +`ai-sessions/0090_CROSSCHECK_RESULT_2026_10_14.md` (E-01 … E-23, the verdict table, the checkpoint answers, "Applied", and the "Continuation in chat" section), +`ai-sessions/0091_FEATURE_PROMPT_2026_10_14.md`, `ai-sessions/0091_FEATURE_RESULT_2026_10_14.md` (§A the evidence and design, §B the checkpoint answers Q1–Q11, +§C the build as made, §D the gate and mutations, §E–§G), and — because it closed 1.3.0 and is the newest state of the project — +`ai-sessions/0092_CAPTURE_PROMPT_2026_10_15.md` and `ai-sessions/0092_CAPTURE_RESULT_2026_10_15.md`, with +`captures/CAP-076-2026-10-15_06-35-26_07-08-20-Group_BL/CAP-076-EVENT-NOTES.md` and `CAP-076-FINDINGS.md`. + +**Follow every guardrail in `AGENTS.md` throughout**, in particular: +- §6 / §15: an audit may **propose** a FACT promotion, an ADR, a superseding ADR or a correction to `REVERSE_ENGINEERING.md` — clearly labelled as a proposal for + the maintainer — but never writes one into `PROTOCOL.md` / `DECISIONS.md` / `REVERSE_ENGINEERING.md` (or anywhere else). +- §13 and `PROJECT_RULES.md` §1 / rule 4a: every claim about a capture needs a frame number (and the command + raw hex it rests on); every claim about the app + needs a `file:line`; every claim about the APK needs the class, method and literal (and the `jadx-output`/smali path) it was read from; every conclusion + carries a FACT / HYPOTHESIS / ASSUMPTION / OPEN QUESTION label. An audit report is not exempt from the evidence rules it audits for. +- §13.6: zero creativity when parsing hex dumps or decompiled code — byte and field meanings come from evidence, never from a plausible guess. §13 step 8: a + negative ("never sent", "no class references it") needs its command, its exit status and a positive control (the same command matching a known hit). +- §12: **no code is copied from the official APK.** Record behaviour — identifiers, opcodes, field numbers, literals, call relations, class and method names + with a readable alias — never decompiled statements beyond the single identifier or literal a finding rests on. The RESULT must not contain reproduced + Google source. +- §13 (Reverse engineering the APK): field *numbers* come from `scripts/decode_rawmessageinfo.py` (or the schema tools under `reverse-engineering/tools/`), + not from getter/setter names; when JADX shows "Method dump skipped", grep the smali before recording "not found". + +**Lessons from earlier audits (read before starting).** `ai-sessions/0049` was validated claim by claim in `ai-sessions/0050`: of 34 claims, 16 were correct, +12 partly correct, **5 wrong** and 1 an opinion. `ai-sessions/0068` used sub-reviews and marked every finding **M** (re-derived in the main session) or **S** +(taken from a sub-review, not re-derived — an open question with its check, not a fact); `AI_SESSION_LOG_PROCEDURE.md` §4b item 4 made that mark mandatory. +Therefore: **re-derive every finding from the primary source in this session** (the file, the line, the frame, the decompiled class); never report from +memory, from a summary in another document, or from an earlier session's RESULT alone; quote the exact text you are criticising; state for each finding how you +verified it. A finding you could not verify is an OPEN QUESTION with the check that would settle it — not a defect. Do not re-report a `0068` finding that +`0069` resolved, or an `0081` finding that a later session resolved, unless you verified it is still (or again) present — and say so. + +**Nothing is taken on trust** — not this prompt, not the maintainer's statements, not an earlier RESULT, not a FACT label, not a memory note. If this prompt's +own description of the project is wrong, record that as a finding too. + +--- + +## 1. Objective + +Perform a rigorous, **complete** (not sampled) analysis of all project files — documentation, architecture decisions, reverse-engineering findings, the +protocol specification, the captures, `TODO.md`, the release process and the Android app's development — **and** carry out a structured reverse-engineering +pass over the official Pixel Buds APK to clarify, correct and extend the protocol findings. Nothing gets fixed here; everything gets found, explained with +evidence, and handed off with a concrete recommendation precise enough that a later session can act on it without re-deriving your reasoning. Where the +maintainer must decide, say so and give your advice. + +## 2. Scope of the assessment — what to look for + +For every file and area in scope (§3), actively identify: + +1. **Errors** — technical inaccuracies, logical mistakes, incorrect Bluetooth / Android knowledge, factual errors. +2. **Inconsistencies** — contradictions within one file or across files (e.g. a protocol detail that contradicts an architecture decision; a rule in + `PROJECT_RULES.md` or `AGENTS.md` that is violated elsewhere without acknowledgement; the same fact stated differently in two places; an ADR Update whose + consequence a pointer note in `AGENTS.md` records but another document does not). +3. **Improvements** — concrete opportunities to improve the architecture, documentation clarity, project structure, or reverse-engineering methodology. +4. **Incompleteness** — missing documentation, undocumented features or behaviour, dangling references, broken links, IDs not registered in `id_registry.csv`, + a capture or session referenced but not indexed. +5. **Ambiguities** — vague statements, poorly defined statuses, or unclear instructions that could lead a future contributor (human or AI) to misinterpret them. +6. **Impossibilities / unworkable plans** — architecture decisions, code plans or protocol interpretations that are technically impossible, violate + fundamental Android / Bluetooth constraints, or violate the Zero-GMS rule (`AGENTS.md` §1). +7. **Misplaced information** — content that, per the project's own structure, is in the wrong file (e.g. protocol specification in `ARCHITECTURE.md` instead of + `PROTOCOL.md`; log analysis missing from its own `CAP-NNN-FINDINGS.md`; a finding recorded only in an `ai-sessions/` RESULT or only in `DESKRESEARCH_FINDINGS.md`). +8. **Undiscovered protocol findings — two sources, one section of the RESULT.** + - **(a) The captures.** Actively investigate what has not been investigated yet. Look at the existing `captures/`, hex dumps and protocol notes for patterns, + opcodes, frame envelopes or correlations that were overlooked or never documented. Start from `PROTOCOL.md` §6's 🔴 OPEN QUESTION and 🟡 HYPOTHESIS entries + and `TODO.md` §4's open questions, and check them against evidence that **already exists** in `captures/` but may not have been cross-referenced (tools: + `tshark`, `scripts/pwrpc_decode.py`, `scripts/decode_dlci08_tlv.py`, `scripts/stream_events.py`, `scripts/message_stream_tally.py`, + `scripts/anc_setting13_crosscheck.py`, `scripts/wear_mode_crosscheck.py`, and the per-direction DLCI 2/3 decoder method of `CAP-065-FINDINGS.md`). Filter + every log by the Buds' address or, where `bluetooth.addr` is empty, by the Buds' connection handle first (`AGENTS.md` §13, CLI hygiene). Report each lead + with frame numbers, the command and the raw hex — as a lead / proposal, never as a FACT. + - **(b) The official APK — the intensive pass this prompt adds (Phase 9).** Work from `reverse-engineering/apk/v1.0.955078536-10253511/jadx-output/sources` + (fall back to the smali under `apktool-output*/` when JADX fails or skips a method), with the project's own tools (`reverse-engineering/tools/`: + `citation_checker`, `pwrpc_name_table`, `structural_index`, `schema_batch_extractor`, `lambda_dispatcher_resolver`, `limited_dataflow`, `uuid_ble_context` — + read each tool's README and SPEC first; and `scripts/decode_rawmessageinfo.py`), following `APK_REVERSE_ENGINEERING_PROCEDURE.md` §4–§7. The purpose is + threefold: **clarify** (what do the fields of the frames the app already sends and receives mean — the settings stream's packets `4:{13:n}`, the + `WriteSetting`/`ReadSetting` schema, the `HardwareInfo`/`SoftwareInfo` answers, the DLCI 0x08 `[Group][Code][Length][Value]` envelope's codes), **correct** + (does `REVERSE_ENGINEERING.md` / `PROTOCOL.md` name the right class, method, field number or literal — re-derive each citation you touch), and **discover** + (what does the official app send, read or subscribe to that this project has never documented or never seen on the wire). Targets, in this order of value: + 1. The MAESTRO settings whose meaning is open: **23, 24, 26, 30, 31, 32** (answered by the Buds, no writer or reader found so far) and **39** (asked by the + newer app in `CAP-069`) — who writes or reads them, their value schemas, their UI strings (`TODO.md` §4; `PROTOCOL.md` §4/§6). + 2. The settings-stream and `ReadSetting` schema around **setting 13** (ADR-064/065): every field the `SettingValue` message can carry, how the official app + distinguishes a Buds-initiated change from its own write, and whether any field the app does not decode is present in `CAP-076`'s stream packets. + 3. The **DLCI 0x08** private envelope (🔴 identity, `PROTOCOL.md` §2.2a/§2.3) and the **DLCI 0x0a** assistant sessions (`TODO.md` §4): which classes build + or parse `[Group][Code][Length][Value]`, which codes exist, and what the GSND CONTROL codes `0x03`, `0x05`, `0x16` carry. + 4. The **15 MAESTRO service descriptors** (`REVERSE_ENGINEERING.md`) and the unnamed service ids seen on the wire (`0xbf6c9399`, `0x8d99df93`): is every id in + the captures named, and is every named service's method list complete and correct? + 5. The **Fast Pair Message Stream** groups and codes the official app handles (`REVERSE_ENGINEERING.md`'s Message Group / Code register) against what the + captures show and what `PROTOCOL.md` §4 documents — including the ANC Group `0x08`, battery, ring, and any group never seen in a capture. + 6. The hold / touch gesture actions 1–4 (`ai-sessions/0090` E-14), wear-detection and the stored mode per wear state (`PROTOCOL.md` §6, 🟡 2026-10-14), the + one-bud / two-bud mode, the dosimeter (`WELL-002`, ADR-060 draft), OOBE, the wall clock (E-10 … E-13). + 7. Anything the official app does over BLE/GATT (UUIDs, characteristics) that `PROTOCOL.md` §1/§4.3 does not cover. + For every APK finding record: the class and method (obfuscated name **and** a readable alias), the `jadx-output` or smali path, the literal or field number + and how it was derived (which tool, which command), the related wire evidence (capture, frame) where any exists, a FACT / HYPOTHESIS / OPEN QUESTION label, + and the capture or experiment that would confirm it. **Never** write into `REVERSE_ENGINEERING.md` or `PROTOCOL.md`; **never** brute-force or guess opcodes; + **never** send anything to the Buds (there is no hardware in this session). +9. **Architectural shortcomings** — soundness of the architecture, module division, separation of concerns, dependency direction, dependency injection (Hilt, + ADR-028), state management (no ViewModel, ADR-048), the error model (`AGENTS.md` §8), testability, and fitness for maintenance after five public releases + (versioning, the upgrade path 1.0.0 → 1.3.0, Safe Mode on new firmware, the re-open chain of ADR-044, the settings stream's lifecycle per ADR-065). +10. **Application shortcomings** — in the Kotlin source (`android/app`, `android/data`, `android/domain`, `android/hardware`, `android/ui`): bugs, state + management gaps, threading / cancellation issues, resource leaks, error handling, UI honesty (a value shown that the Buds did not report; `ValueCurrency`), + wording that misleads, accessibility, test coverage gaps (measure the current test counts per module yourself), fixtures (`AGENTS.md` §11: real capture bytes + with frame numbers; fuzz tests for every `FrameDecoder`), and whether the code does what `ARCHITECTURE.md` / `PROTOCOL.md` / `DECISIONS.md` say it does — + feature by feature, module boundary by module boundary; in particular the 1.3.0 changes (`ai-sessions/0091` §C: `subscribeToSettingsChanges`, + `readAncSetting`, the cause rules `causeOfStream`/`causeOfReadAnswer`/`causeOfNotify`, `UnreadSetting`, the removal of the hold/re-claim code and the + Play-services card) and the two Known issues of `CHANGELOG.md` `[1.3.0]`. +11. **Search for the unknown** — an open-ended pass: what has nobody asked yet? Capabilities the official app or the protocol clearly has that were never even + logged as a `TODO.md` candidate (the APK pass of item 8b feeds this); ADR consequences never followed through; test-plan items (`TESTPLAN_BLUETOOTH_HCI_SNOOP.md`, + `APP_TESTPLAN.md`) that no capture has ever exercised; assumptions every document silently shares; risks that only appear now that strangers install the APK + (other firmware, other phones, other Android versions, the Play-services collision of `CAP-075`/`CAP-076`, upgrades from 1.0.0 … 1.2.2). +12. **Documents that are not up to date** — statements overtaken by later sessions, ADR Updates, the releases or the code (e.g. a count, a status line, a "not yet + built", "not yet released", a "planned" capture that has been run, a superseded ADR still described as current, a file name or folder that changed, the + "Not pulled" APK paragraph); for each, name the newer source that supersedes it. +13. **Improvements for documents, now that 1.3.0 is published on GitHub.** For **each** of the following 19 files, give a short assessment and concrete + improvement proposals — what to keep, what to shorten, split, archive or move, what is missing for users, contributors and future AI sessions, and what is + now obsolete (pre-release wording, history that belongs in `CHANGELOG.md` or `ai-sessions/`, rules that no longer match practice, pointer notes that could be + folded into the rule they amend): `ARCHITECTURE.md`, `DESKRESEARCH_FINDINGS.md`, `AGENTS.md`, `CAPTURE_BLUETOOTH_HCI_SNOOP.md`, `MAINTAINING_DOCS_SITE.md`, + `REVERSE_ENGINEERING.md`, `AI_SESSION_LOG_PROCEDURE.md`, `PROJECT.md`, `TESTPLAN_BLUETOOTH_HCI_SNOOP.md`, `CHANGELOG.md`, `PROJECT_RULES.md`, `TODO.md`, + `PROTOCOL.md`, `WORKSTATION_PREPARATIONS.md`, `APK_REVERSE_ENGINEERING_PROCEDURE.md`, `CONTRIBUTING.md`, `RELEASING.md`, `SECURITY.md`, `APP_TESTPLAN.md`. + Compare each with what `ai-sessions/0068` §6 proposed for it and what `ai-sessions/0069` applied: which proposals were taken, which were declined (and whether + the reason still holds), which are still open. Proposals that would change project law (`AGENTS.md`, `PROJECT_RULES.md`) or an ADR are drafted as proposals + for the maintainer, never applied. + +**Deep re-review of the sessions since the last audit.** Read **in full** every prompt and result of `0069` … `0092` and audit them with the same rigour as +the reference documents (categories 1–7 and 12 apply to session logs too): does each RESULT's claim (tests, frames, fixtures, "byte-identical", "approved in +chat", "hardware-verified", "committed", "released") match the files, the git history (`git log`, `git show`), the tags and the captures? Are their `Status` +fields and `ai-sessions/INDEX.md` rows correct (`AI_SESSION_LOG_PROCEDURE.md` §4/§4a/§4b)? Did every approved change actually land in the documents and the code? +Were every RESULT's "Deferred documentation" items added to `TODO.md` with the same wording, and are their "Commits" sections back-filled? +**Give `0090`, `0091` and `0092` the deepest treatment**, because they produced the project's newest decisions and build: +- `0090`: re-derive the verdict of **every** item E-01 … E-23 from the primary sources it cites (the captures' frames, the pinned pbpctrl commit and files, the + APK classes, this repository's documents); check that the "Applied" section's changes landed as described (PR #55) and that the "Continuation in chat" findings + reached `PROTOCOL.md`/`DESKRESEARCH_FINDINGS.md` with the right labels; check that ADR-064's text matches what the session actually established and that the + maintainer's chat answers were applied as given. +- `0091`: re-derive the reference frames of §A.1 byte for byte from the captures named; check the design (§A.2–§A.6) against ADR-064/065 and the code as built + (§C, `file:line`); re-run the gate; verify the removal list (§A.5) left no dead code, string, test, setting key or document sentence behind; verify the + Known issues of `CHANGELOG.md` `[1.3.0]` against `CAP-076`'s findings; verify the mutation claims of §D are reproducible from the test files (do **not** run + the mutations). +- `0092`: check the timeline and counts of `CAP-076-FINDINGS.md` against the logs it cites (frames, log lines, film times); check the release verdict chain + (`RELEASING.md` C3/C4 → the tag `v1.3.0` → `RELEASING.md` §13 → `CHANGELOG.md` → `README.md`); check that its proposals P1 … P6 reached `TODO.md` §5 and that + its `Status`/INDEX row are consistent with what is committed. + +## 3. Areas in scope + +- **Guardrail / reference documents:** every Markdown file in the repository root (including `AGENTS.md`, `PROJECT_RULES.md`, `PROJECT.md`, `ARCHITECTURE.md`, + `PROTOCOL.md`, `DECISIONS.md`, `TODO.md`, `CHANGELOG.md`, `README.md`, `RELEASING.md`, `REVERSE_ENGINEERING.md`, `APK_REVERSE_ENGINEERING_PROCEDURE.md`, + `CAPTURE_BLUETOOTH_HCI_SNOOP.md`, `TESTPLAN_BLUETOOTH_HCI_SNOOP.md`, `APP_TESTPLAN.md`, `DESKRESEARCH_FINDINGS.md`, `MAINTAINING_DOCS_SITE.md`, `SECURITY.md`, + `CONTRIBUTING.md`, `WORKSTATION_PREPARATIONS.md`, `AI_SESSION_LOG_PROCEDURE.md`, `SCREENSHOTS_*.md`, `_sidebar.md`), plus `id_registry.csv`, + `ai-sessions/INDEX.md`, `reverse-engineering/APK_VERSIONS.md` and `reverse-engineering/tools/BACKLOG.md`. +- **Every `ai-sessions/*_PROMPT_*.md` / `*_RESULT_*.md`** — in full for `0069` … `0092` (§2); for `0001` … `0068` at least a structural and cross-reference pass + (header block, status, pairing, `INDEX.md` row, registered IDs, links that resolve). +- **Every `captures/CAP-NNN-*/CAP-NNN-EVENT-NOTES.md` and `CAP-NNN-FINDINGS.md`** (`CAP-001` … `CAP-076`) — in full. The raw capture files (`.log`, `.mp4`, + exports, logcats) are opened as needed to check a specific claim and for the §2 item 8a investigation; that is the only place where "as needed" replaces + "every". Some logs are local `btsnooz` files not in git LFS (`CAPTURE_BLUETOOTH_HCI_SNOOP.md`); say so when one is missing instead of reporting a "0 frames". +- **The full Android source tree** under `android/` (main and test sources, Gradle files, the version catalog, manifests, `proguard`/`lint` configuration) — + every Kotlin file. +- **`reverse-engineering/`** — the documentation, the tools (`reverse-engineering/tools/*`: README, SPEC, tests), and the decompiled tree + `reverse-engineering/apk/v1.0.955078536-10253511/` as the object of the Phase 9 pass. The newer `v1.0.990706425-10260911/` only as the maintainer decides (§9). +- **`scripts/`** — every script (incl. `scripts/release.sh`, `scripts/release_notes.template`, `scripts/third_party_notices.py`, `scripts/readme_media.sh`, + `scripts/stale_capture_status.py`, `scripts/generate_sitemap.py`, the capture decoders): does each do what the document that cites it says it does? +- **CI, site and release tooling** (`.github/`, `scripts/lint_docs.py`, `scripts/ensure_footers.py`, the sitemap, the docs site configuration) — does what they + check match what the documents say they check? Does the docs site render every page the sidebar links? +- **The published releases** — read-only: `gh release view v1.3.0` (and `v1.2.2`, `v1.2.1`, `v1.2.0`, `v1.1.0`, `v1.0.0` as needed: notes, assets, digests) + against `RELEASING.md` §13, `CHANGELOG.md`'s blocks and `README.md`. Never edit, re-upload or re-tag anything. + +## 4. Strict execution rules + +- **No assumptions.** Verify everything, including project claims about standard protocols (Bluetooth RFCOMM, HFP, AVRCP, SSP, Fast Pair, pw_rpc / pw_hdlc, + protobuf wire format, Android APIs, Android app signing and installation, CompanionDeviceManager, foreground services). +- **Validate externally.** Use the search / fetch tools to check claims against official documentation — Android Developers docs and API reference, AOSP sources + (`android.googlesource.com/…?format=TEXT`, decoded with `base64 -d`, when a developer.android.com page returns only navigation), the Bluetooth Core + Specification and profile specifications (RFCOMM, HFP), the Google Fast Pair specification (developers.google.com/nearby/fast-pair), the Pigweed documentation + (pw_rpc, pw_hdlc), the protobuf encoding documentation, Material 3 / Compose documentation, GrapheneOS documentation, GitHub's documentation (releases, LFS), + and `qzed/pbpctrl` at the pinned commits the documents cite. Cite the URL and the exact sentence (or section number) you rely on; check any fetch-tool summary + against the raw text before quoting it. +- **No sampling.** Do not spot-check. Perform a 100 % complete review of every file in scope (§3); check every detail. If you cannot finish a file, say so in + the Progress block and finish it after resuming — never silently skip. +- **Best practices.** Apply industry-standard technical review methods: evidence traceability (every claim links back to a specific frame / capture / + `file:line` / ADR sentence / APK class), structural integrity (headers and sections nest, tables match their own column definitions, cross-references resolve, + IDs are registered, footers present), and consistency (the same fact stated the same way everywhere). +- **Run, don't guess.** Where a claim is checkable by running something read-only, run it and record the command and output: the build and test suite + (`cd android && ./gradlew --offline --max-workers=2 assembleDebug testDebugUnitTest test lint`, once, in Phase 0), `python3 scripts/lint_docs.py`, `tshark`, + the project's decoders and RE tools, `git log` / `git blame` / `git show`, `gh release view`. Build outputs under `android/**/build/` are not project files; + generating them does not count as changing files. **Do not** run mutation tests, `scripts/release.sh`, `scripts/ensure_footers.py` (it writes), + `scripts/generate_sitemap.py` (it writes) or anything that edits a tracked file, even temporarily. +- **No file changes.** Only assess. The only file this session creates or edits is ai-sessions/0093_AUDIT_RESULT_2026_10_15.md. Do not edit + `ai-sessions/INDEX.md` either — its `0093` row is updated when the audit is accepted (by the processing session, as `0069` did for `0068`). Scratch work + goes into the session's scratchpad directory, never into the repository. No commits, no pushes, no tags, no GitHub changes, no decompilation output written + into a tracked path. +- **Subagents** may only read and report; every finding they report is re-verified in the main session before it goes into the RESULT, and carries the **M**/**S** + mark of `AI_SESSION_LOG_PROCEDURE.md` §4b item 4. A sub-review claim that was not re-derived is reported as an OPEN QUESTION with its check, not as a finding. + +## 5. Resumability (you must be able to continue automatically after a token or rate limit) + +Create ai-sessions/0093_AUDIT_RESULT_2026_10_15.md **at the very start**, with the header block of `AI_SESSION_LOG_PROCEDURE.md` §4 (add a +`**Prompt:** ai-sessions/0093_AUDIT_PROMPT_2026_10_15.md` line as `0091`/`0092` did), `**Status:** partial — resumed`, and a **Progress** block at the top. +Update the Progress block at the end of **every phase** and after every large file or file group within a phase: which phases and files are done, which finding +IDs were assigned (so numbering never collides after a resume), the last command run, and where intermediate results live (the scratchpad directory — re-create +anything that is gone; a restart wipes `/tmp`). Append each phase's findings as soon as the phase is done; do not hold findings in context. + +A resumed session (`AI_SESSION_LOG_PROCEDURE.md` §5 — the same number, the same file, never a new session number) re-reads this prompt, then the RESULT's +Progress block, re-reads the files the unfinished step touched, and continues from the first unfinished step. It never redoes a finished, recorded step and never +assumes an unrecorded step was done. Set `**Status:** complete` only when every phase in §6 is finished. + +## 6. Phases (strictly sequential; do not start a phase before the previous one is finished and recorded) + +0. **Set-up** — create the RESULT (§5); record `git log -1`, `git status --short`, `git fetch && git log --oneline HEAD..origin/main`, `git tag`; the mandatory + reading (§0); Checkpoint 0 (§9) if the maintainer is present, otherwise record the defaults; run the build/test/lint gate (once) and `scripts/lint_docs.py` + as the factual baseline for later phases; record the test counts per module and the lint result. +1. **Governance / compliance** — `AGENTS.md`, `PROJECT_RULES.md`, `PROJECT.md`, `AI_SESSION_LOG_PROCEDURE.md`: internal consistency, and whether the rest of + the project complies with them (ADR sign-off provenance per `AGENTS.md` §6; the minimal permission set and the manifests per §2 — including the merged + manifest of the release build; Zero-GMS per §1, incl. the removal of the Play-services query since 1.3.0; dependency policy per §10 against the version + catalog and every `build.gradle.kts`; logging rules per §9; AGPL headers per §12; evidence labels per `PROJECT_RULES.md` §1; the Definition of done in + `PROJECT.md` against its evidence; every dated pointer note in `AGENTS.md` against the ADR it cites). +2. **Protocol** — `PROTOCOL.md` end to end: internal consistency (does a later Update contradict an earlier FACT without flagging it? did the 2026-10-14 Updates + for setting 13 and the settings stream land in every section they affect?), label correctness, every cited frame verified **for every FACT** (not a sample), + §8's changelog against the body, then the §2 item 8a investigation of the 🔴 / 🟡 entries of §6. +3. **Captures** — every `CAP-NNN-EVENT-NOTES.md` / `CAP-NNN-FINDINGS.md`, `CAP-001` … `CAP-076`: traceability of each finding to frames, consistency with + `PROTOCOL.md`, test-plan traceability (`AGENTS.md` §13 item 7), `PROJECT_RULES.md` rule 9a (findings rewritten in place, not accumulating addenda), + registration and status in `id_registry.csv` and the Capture Index (`scripts/stale_capture_status.py` as a helper, read-only); folder names against the films' + times; the `btsnooz`/LFS state of every log. +4. **Architecture** — `ARCHITECTURE.md` end to end against the actual source tree: does the code implement what it claims, module by module, feature by feature + (§5a's table row by row, §3.1/§3.2/§6.0b for the 1.3.0 stream), and are the open questions of its §13/§15 still open? +5. **Decisions** — every ADR, ADR-001 … ADR-065 and the draft ADR-060: internal consistency, proper superseding (vs. silent contradiction — in particular + ADR-061/062/063 → ADR-065, ADR-035/039 → ADR-043, ADR-001 → ADR-048), whether each ADR's Consequences were followed through in code and documents, + `id_registry.csv` integrity, and whether every ADR has the sign-off provenance `AGENTS.md` §6 requires. +6. **Backlog, release and housekeeping** — `TODO.md`, `CHANGELOG.md`, `README.md`, `RELEASING.md` (against `scripts/release.sh` and the published releases), + `id_registry.csv`, `ai-sessions/INDEX.md`, `APP_TESTPLAN.md`, `TESTPLAN_BLUETOOTH_HCI_SNOOP.md`, `CAPTURE_BLUETOOTH_HCI_SNOOP.md`'s Capture Index: staleness + (§2 item 12), dangling references, items marked done that are not (and vice versa), duplicates, conflicts; every "Deferred documentation" item of `0069` … + `0092` present in `TODO.md`. +7. **Session history** — the whole `ai-sessions/` set structurally, `0069` … `0092` in full, and the deep re-review of `0090`, `0091`, `0092` (§2, last block). +8. **Application** — every Kotlin file (main and test), Gradle configuration and manifests: §2 items 9–10, code-versus-documentation fidelity, tests versus + `AGENTS.md` §11 (real capture bytes as fixtures with frame numbers, fuzz tests for every `FrameDecoder`, no hand-built fixture without a real one beside it), + the 1.3.0 changes and removals in particular. +9. **Reverse engineering of the official APK** — §2 item 8b, targets 1–7 in order; each target closed with its findings, leads and open questions recorded in + the RESULT before the next target starts; `REVERSE_ENGINEERING.md`'s citations re-derived as you pass them (the class exists, the method exists, the literal + is where the document says it is); the tools' own README/SPEC/tests checked against what they do. +10. **Document improvements** — §2 item 13, file by file for the 19 listed documents, plus any other root document where you found a reason. +11. **Synthesis** — the open-ended "search for the unknown" (§2 item 11); deduplicate findings that surfaced in more than one phase and cross-reference them; + assign final severities; write the executive summary, the decisions for the maintainer (with advice) and the prioritised action list (§8). + +## 7. Exclusions + +**Privacy is out of scope.** Do not scan for, flag or report privacy-related data such as leaked MAC addresses, e-mail addresses, names, serial numbers or other +personal identifiers — ignore this category entirely. Real identifiers in `captures/CAP-NNN-*` are the maintainer's own informed decision (`DECISIONS.md` +ADR-010). (This exclusion covers the audit's findings; it does not license breaking `AGENTS.md`'s operational rules — §9's logging rules included — during the +session.) + +## 8. Deliverables + +A comprehensive, clearly structured audit report in ai-sessions/0093_AUDIT_RESULT_2026_10_15.md (header and status per `AI_SESSION_LOG_PROCEDURE.md` §4), in +this order: + +1. **Progress** block (§5). +2. **Executive summary** in plain language: the state of the project after 1.3.0, the most important findings, what should be done first, and what the + maintainer must decide. +3. **Method:** what was read in full / partly (and why), every command run (with its result), every external source consulted (URL + exact sentence), every + subagent used and what it covered. +4. **Findings**, grouped by phase/area (`A93-GOV-nn`, `A93-PROT-nn`, `A93-CAP-nn`, `A93-ARCH-nn`, `A93-DEC-nn`, `A93-HK-nn`, `A93-SES-nn`, `A93-APP-nn`, + `A93-RE-nn`, `A93-DOC-nn`), each with a stable ID that never changes after a resume. **For every finding include:** + - the exact file and location (`file:line` or section; a frame number for capture-based findings; class/method/path for APK-based findings), with the quoted + text or bytes; + - why it is a problem — which §2 category (or categories) and the reasoning — and its evidence label (FACT / HYPOTHESIS / ASSUMPTION / OPEN QUESTION); + - **how you verified it** (the command, the file you opened, the frame you decoded, the class you read) and the **M**/**S** mark; + - a concrete, actionable recommendation for how to fix or resolve it — precise enough to act on without re-deriving your reasoning; where a fix needs a FACT + promotion, an ADR or a superseding ADR, say so and give the draft text, labelled as a proposal awaiting the maintainer's sign-off (`AGENTS.md` §6); + - where applicable, the external evidence or documentation that validates the finding (URL + section / exact sentence); + - a **severity**: *critical* (breaks correct operation, risks the hardware or violates a hard guardrail), *major* (wrong behaviour, a wrong FACT, a broken + decision chain, a misleading published statement), *minor* (inconsistency, staleness, missing reference), *nit* (wording, formatting). +5. **Undiscovered protocol leads** (§2 item 8a and 8b) in their own section, split into "from the captures" and "from the APK": per lead the frames / the + class and literal, the command, the raw hex or field number, the proposed reading, its label, the related `PROTOCOL.md` §6 or `TODO.md` §4 entry, and the + capture or experiment that would confirm or refute it. Say explicitly which `PROTOCOL.md` §6 🔴/🟡 entries the pass could **close** (with the proposal text), + which it **narrowed**, and which stay open. +6. **Document improvement proposals** (§2 item 13) in their own section, one subsection per document (19), each with: current role and size, what is out of + date, what to keep / shorten / split / archive / move (with the target file), what is missing, what `0068` proposed and what became of it, and the expected + benefit; proposals touching project law or an ADR are drafted and labelled as proposals. +7. **Decisions for the maintainer:** every point where the maintainer must choose — one entry per decision, with the options, their consequences, and **your + advice** with its reason. Do not decide for the maintainer; do not bury a decision inside a finding. +8. **Prioritised action list:** every finding ID in the order the maintainer should handle it, grouped by severity, with the kind of session each needs + (MAINTENANCE / FEATURE / CAPTURE / REVIEW / CROSSCHECK) and which ones need a maintainer decision first. +9. **Statistics:** findings per category and per severity, M versus S; files reviewed; APK targets covered; what could not be verified and why. +10. **Deferred documentation** and **Commits** (`AI_SESSION_LOG_PROCEDURE.md` §9 item 6). For this review-only session: "Deferred documentation: none added to + `TODO.md` by this session (review only) — the prioritised action list above is the hand-over; the processing session adds the `TODO.md` lines" and "Commits: + not committed — review only; the maintainer decides". + +When the report is complete, set `**Status:** complete`, then give the maintainer a short summary in chat (the number of findings per severity, the number of +protocol leads, the decisions waiting, and the top five actions). Do not commit or push; the maintainer decides what happens next. + +## 9. Checkpoints + +- **Checkpoint 0 (Phase 0, in chat, one question per decision, only if the maintainer is present — otherwise record the default and continue):** + 1. Decompile the newer APK `v1.0.990706425-10260911` (JADX + apktool, into its own gitignored folder) so Phase 9 can compare both versions? **Default: no** — + the maintainer's standing answer of 2026-10-05 (`TODO.md` §4) is "not yet"; the pass works on `v1.0.955078536-10253511` as this prompt names it. + 2. Run the full Gradle gate in Phase 0? **Default: yes**, once, `--max-workers=2`. + 3. Any area the maintainer wants excluded or weighted? **Default: none** — everything in §3. +- **The final checkpoint is the RESULT itself** (§8 items 7 and 8): the maintainer reads it and answers the decisions in the processing session that follows + (as `0069` processed `0068` and `0050` validated `0049`). Ask nothing else in chat during Phases 1–11; write open questions into the RESULT. + +--- +https://github.com/tedsluis/opencontrolpixelbudspro2/blob/main/ai-sessions/0093_AUDIT_PROMPT_2026_10_15.md - https://tedsluis.github.io/opencontrolpixelbudspro2/ai-sessions/0093_AUDIT_PROMPT_2026_10_15 diff --git a/ai-sessions/0093_AUDIT_RESULT_2026_10_15.md b/ai-sessions/0093_AUDIT_RESULT_2026_10_15.md new file mode 100644 index 0000000..7491465 --- /dev/null +++ b/ai-sessions/0093_AUDIT_RESULT_2026_10_15.md @@ -0,0 +1,1910 @@ +# 0093_AUDIT_RESULT_2026_10_15.md — Exhaustive end-to-end audit of the whole project after 1.3.0, with a deep re-review of `ai-sessions/0090` … `0092`, a reverse-engineering pass over the official APK, and improvement proposals for 19 documents + +**Number:** 0093 +**Category:** AUDIT +**Date:** 2026-10-15 +**Prompt:** ai-sessions/0093_AUDIT_PROMPT_2026_10_15.md +**Title:** Comprehensive end-to-end technical audit of OpenControl for Pixel Buds Pro 2 after the publication of 1.3.0 — documentation, protocol, captures, +architecture, decisions, backlog, release process and app code; deep re-review of `0069` … `0092` (`0090`, `0091`, `0092` in depth); a reverse-engineering pass +over the official APK `v1.0.955078536-10253511`; improvement proposals for 19 documents; decisions for the maintainer with advice. Review only. +**Status:** complete + +## 1. Progress + +| Phase | State | Notes | +|---|---|---| +| 0 Set-up | done | git facts, Checkpoint 0 and the gate below; every mandatory document read in full (§3) | +| 1 Governance | done 2026-10-15 ≈ 16:00 | A93-GOV-01 … 11 | +| 2 Protocol | done 2026-10-15 ≈ 17:00 | A93-PROT-01 … 09; leads L93-1 … 3 | +| 3 Captures | done 2026-10-15 | A93-CAP-01 … 09 (all three capture sub-reviews in; items re-verified as marked) | +| 4 Architecture | done 2026-10-15 ≈ 17:30 | A93-ARCH-01 … 05 | +| 5 Decisions | done 2026-10-15 ≈ 17:40 | A93-DEC-01 … 04 | +| 6 Backlog/release/housekeeping | done 2026-10-15 ≈ 17:55 | A93-HK-01 … 08 | +| 7 Session history | done 2026-10-15 ≈ 19:20 | A93-SES-01 … 09 | +| 8 Application | done 2026-10-15 ≈ 18:40 | A93-APP-01 … 09 | +| 9 APK reverse engineering | done 2026-10-15 ≈ 18:20 (before 7/8 — see Method) | A93-RE-01 … 06; leads L93-4, L93-5 | +| 10 Document improvements | done 2026-10-15 ≈ 19:00 | §6, 19 documents | +| 11 Synthesis | done 2026-10-15 | §2, §3, §7–§10 | + +Finding IDs assigned so far: A93-GOV-01 … 11, A93-PROT-01 … 09, leads L93-1 … 3 (A93-SES-01 reserved). Scratch directory: /tmp/claude-1000/-home-tedsluis-git-opencontrolpixelbudspro2/72b53c95-2f24-4d91-8de4-765645f6970a/scratchpad/a93 +(re-create if gone). + +### Phase 0 — recorded facts (2026-10-15 09:34 CEST) + +- `git log -1`: `b9828ba` 2026-10-15 09:17:07 +0200 "Merge pull request #59 from tedsluis/bot/sitemap". Branch `main`; `git fetch` → 0 ahead, 0 behind `origin/main`. +- `git status --short`: ` M ai-sessions/INDEX.md` (the 0093 row added by the prompt-writing session, uncommitted), `?? ai-sessions/0093_AUDIT_PROMPT_2026_10_15.md` + (uncommitted), `?? android/domain/bin/` (build output, never staged). +- `git tag`: `v1.0.0 v1.0.1 v1.1.0 v1.1.1 v1.2.0 v1.2.1 v1.2.2 v1.3.0` — **eight** tags. The prompt's §0 context says "since the previous full audit the project + released 1.1.0, 1.2.0, 1.2.1, 1.2.2 and 1.3.0" and omits `v1.0.1` and `v1.1.1` — recorded as a finding against the prompt (A93-SES-01, Phase 7). +- **Checkpoint 0 (chat, 2026-10-15 09:36, `AskUserQuestion`, the maintainer's answers verbatim):** (1) decompile the newer APK `v1.0.990706425-10260911`? — + *"Nee"* (the default; Phase 9 works on `v1.0.955078536-10253511` only); (2) run the full Gradle gate once in Phase 0? — *"Ja"*; (3) areas to exclude or + weight? — *"Geen"* (everything in §3, equal weight). +- **Gate (10:05, `cd android && ./gradlew --offline --max-workers=2 assembleDebug testDebugUnitTest test lint`):** BUILD SUCCESSFUL in 2m 07s, exit 0, 334 tasks. + JUnit XML counts (0 failures, 0 errors): `:data` 1672 per variant (debug and release, 24 files each), `:hardware` 64 per variant, `:domain` 42 (JVM, one + variant), `:ui` 92 per variant, `:app` none. Lint (`build/reports/lint-results-debug.xml`): `:app` 6 warnings (3 `AndroidGradlePluginVersion`, 2 + `NewerVersionAvailable`, 1 `OldTargetApi`), `:ui` 1 (`ModifierParameter`), `:data`/`:hardware` none with a severity. `scripts/__pycache__` unchanged. +- `python3 scripts/lint_docs.py` (09:33, by the prompt-writing session; re-run below): exit 0, "clean — no dead filenames, no unregistered IDs, no stale project + name, no missing footers, no ADR/table/status mismatches"; 14 informational "dead filename reference" lines for historical session logs (`0065`, `0067`, `0074`, + `0079`, `0082`, `0084`, `0091` — placeholder capture-folder names and scratch files), reported as information, not failures, by design (lint_docs.py line 456–462). + +### Resume point 2 (2026-10-15 ≈ 19:40, usage limit): phases 0–10 written except the CAP-001…025 part of phase 3; next: fold in scratch file sub_caps_001_025.md (and copy all sub_*.md notes into an Appendix B), then reorder §4 into phase order (sections were written out of order), then phase 11 (§2 Executive summary, §3 Method — note phases 4–9 ran while the phase-3 sub-reviews ran, §7 decisions incl. ADR-060 heading rule, early-ADR provenance, targetSdk 34, the newer APK decompile; §8 action list; §9 statistics; §10 deferred/commits = not committed), Status → complete. + +### Resume point 1 (2026-10-15 15:30 — the session stopped on the Fable usage limit; the maintainer switched to Opus 5.5) + +- Phase 0 is still in progress. Reading done in full: the 14 files in the Progress row above; `REVERSE_ENGINEERING.md` read to line 2520 of 4606 (continue at line 2521, chunks of ≤ 420 lines). Still to read in full: `REVERSE_ENGINEERING.md` 2521–4606, `DESKRESEARCH_FINDINGS.md`, `CHANGELOG.md`, `README.md`, `SECURITY.md`, `CONTRIBUTING.md`, `WORKSTATION_PREPARATIONS.md`, `APK_REVERSE_ENGINEERING_PROCEDURE.md`, `ai-sessions/INDEX.md`, `id_registry.csv`; then `0090`/`0091`/`0092` PROMPT+RESULT and the `CAP-076` notes/findings; then Phases 1–11. +- The seven read-only sub-reviews (captures 001–025 / 026–050 / 051–076; sessions 0069–0079 / 0080–0089; `:data`; `:hardware`+`:domain`+`:app`+`:ui`) all stopped on the usage limit a second time and returned **no report**. A resumed session relaunches them (read-only, no sub-agents of their own, full markdown report as the final message) or does the work itself; nothing from them is used yet. One partial remark came back: `CAP-053`'s status line says 18.7 s for the second release-only window while its timeline and FINDINGS say 18.5 s (to verify in Phase 3). +- Finding IDs assigned: none yet (the candidate list below has no IDs; A93-SES-01 is reserved for the tag omission recorded in Phase 0). +- Last command run: the Phase 0 gate (10:05) and reads of `REVERSE_ENGINEERING.md`. Gate output: scratch file gate.log (re-run is not needed; the counts are above). + +## 2. Executive summary + +The project is in good shape where it matters most: no critical defect, the release 1.3.0 is what its record says (tag, hash and checksum checked against +GitHub), the app's hard rules hold (no `INTERNET`, no GMS, minimal permissions, AGPL headers, no scanning), every timing constant matches the code, and every +census number of `ai-sessions/0090` reproduced exactly when re-run. What needs work is **accuracy of the record** and three **app defects**. + +**69 findings** (0 critical, **7 major**, 50 minor, 12 nit) plus 5 protocol leads. The seven major ones: + +1. **A93-PROT-01** — the 🟢 FACT on setting 13 (`PROTOCOL.md` §4.1) states "73 of 73 … for every mode change" and "12 of 77"; re-derived: 75 of 89 Buds-side + changes were pushed, 12 of 89 lost to the Buds' `DISC`. The wrong ratio reached ADR-064 and the 1.3.0 release notes. +2. **A93-CAP-05** — `PROTOCOL.md` and `CAP-058` call the link keys P-256; the HCI event says P-192 (`CAP-058` 2235). +3. **A93-APP-01** — "Use different Buds" clears the choice on a second, Activity-owned `BudsCompanionPairing`; the repository's singleton still connects to the old + Buds by name. +4. **A93-ARCH-01** — `ARCHITECTURE.md` §3.1's ANC row still describes the removed 1.2.x hold and the cause reset at Connect as current. +5. **A93-CAP-06** — Test-IDs swapped in five capture notes (e.g. `ANC-002`/`ANC-004` in `CAP-068`) — the Test-ID → capture → frame chain points to the wrong evidence. +6. **A93-CAP-08 / A93-CAP-09** — capture FINDINGS with conclusions resting on false premises (`CAP-007`'s "184 s silence on DLCI 0x08", `CAP-029`'s invented + factory-reset precedent, `CAP-040`'s dock column, `CAP-021`'s "re-affirm" that is a real write, `CAP-002`/`CAP-004`'s uniqueness claim). + +The two `CAP-076` defects are confirmed in the code with exact lines and fixes (A93-APP-02, A93-APP-03), plus a related wait that a stream push can end +(A93-APP-04). Recurring patterns: arithmetic slips repeated across documents ("thirteen requests" in six files, A93-CAP-01), status lines and INDEX cells that +lag their RESULTs (A93-HK-05, A93-SES-06/07), and the big documents (`PROTOCOL.md` 3,771 lines, `REVERSE_ENGINEERING.md` 4,606) whose current truth is spread +over dated Updates. New on the protocol side: `CAP-021`'s audio waves are assistant sessions (L93-1), the SASS codes are defined by the official page (A93-PROT-03/04 +— and "in-use" is in `0x41`, not `0x42`), the ATT `0x0044` item can be closed (A93-PROT-07), and setting 23 carries a field this app version's schema does not have (L93-4). + +## 3. Method + +- **Reading.** In full: the 19 documents of the prompt plus `README.md`, `DECISIONS.md`, `ai-sessions/INDEX.md`, `id_registry.csv`, `reverse-engineering/APK_VERSIONS.md`, + `reverse-engineering/tools/BACKLOG.md`, the RESULTs of `0090`, `0091`, `0092`, the PROMPT of `0091`, `CAP-076-FINDINGS.md`. The PROMPTs of `0090`/`0092` and + `CAP-076-EVENT-NOTES.md` were read for scope only (their RESULT/FINDINGS were the subject). Not read by the main session: the 150 other capture notes and + sessions `0069` … `0089` (sub-reviews, below); the Kotlin sources outside the lines quoted in §4 (sub-reviews). +- **Re-derivations (M).** The `0090` census (`scripts/anc_setting13_crosscheck.py --census`, 54 logs); a scripted check of every hex citation with a frame number in + `PROTOCOL.md`/`DECISIONS.md`/`ARCHITECTURE.md` (47); an ATT `0x0044` scan over all 103 logs; the Capture Index ↔ registry ↔ folder check (76 rows); ADR statuses vs + the registry (65); §3.2's constants vs the code; the release facts via `gh` (tag, PR, assets, the published `.sha256`); targeted `tshark`/`pwrpc_decode.py` checks + quoted in each finding. `scripts/__pycache__` stayed unchanged (`git status --short scripts` empty after every run). +- **Gate (Phase 0).** One run, green — the counts are in Phase 0 above. +- **External sources (fetched 2026-10-15).** developers.google.com/nearby/fast-pair/specifications/extensions/sass; code.claude.com/docs/en/setup. Two further + references are named in findings as "not fetched this session". +- **Sub-reviews.** Seven read-only sub-reviews started 11:10 were all cut off by usage limits and returned nothing usable. Six new ones (16:00–19:45) returned full + reports, written progressively to scratch files: captures 001–025, 026–050, 051–075; sessions 0069–0089; `:data`; `:hardware`/`:domain`/`:app`/`:ui`/CI. Their raw + notes are Appendix B. Every item used in §4 was re-checked by the main session (marked M) or taken over after a check of its quoted lines/command (S); items not + re-checked one by one are grouped and said to be so. +- **Deviations from the prompt.** The phases were not strictly sequential: phases 4–9 ran while the phase-3 and phase-7 sub-reviews were running, and §4 was + reordered afterwards; the session moved from Fable 5.1 to Opus 5.5 after a usage limit (no loss — the state was in this file). The newer APK was not decompiled + (Checkpoint 0). No file other than this RESULT was changed; the INDEX row of 0093 was left as the prompt-writing session wrote it. + +## 4. Findings + +Every finding: **Location** (file:line, quote) · **Category** · **Label** · **Verified** (how; M = this main session, S = a sub-review re-checked here) · +**Recommendation** (a proposal; nothing in this audit changes a file other than this RESULT) · **External evidence** where it applies · **Severity** +(critical / major / minor / nit). Line numbers are as of `b9828ba`. + +### Phase 1 — Governance (`AGENTS.md`, `PROJECT_RULES.md`, `PROJECT.md`, `README.md`, `CONTRIBUTING.md`, `SECURITY.md`, `WORKSTATION_PREPARATIONS.md`) + +**A93-GOV-01 — `PROJECT.md` says 1.3.0 is "not released".** +- Location: `PROJECT.md:151` — "**1.3.0** (prepared in `ai-sessions/0091`, 2026-10-14; not released): …" (the same line later reports `CAP-076`'s verdict). +- Category: stale document. Label: 🟢 FACT. Verified (M): `git tag` lists `v1.3.0`; `RELEASING.md` §13 and `CHANGELOG.md` header: published 2026-10-15. +- Recommendation: "released 2026-10-15 (tag `v1.3.0`, build `ff27ee1`)"; add `PROJECT.md` to `RELEASING.md` E1's list of files to update after a release. +- Severity: minor. + +**A93-GOV-02 — `PROJECT.md`'s status stamp and status paragraph lag the releases.** +- Location: `PROJECT.md:39` "**Status (2026-09-24; updated 2026-10-06 for 1.1.0)**" above rows that cite 1.2.0 (lines 46–49); `PROJECT.md:136` "Status after 1.0.x + (… 1.1.0 added 2026-10-06 …)" heads one run-on paragraph (lines 138–151) that narrates every release up to 1.3.0. +- Category: stale / readability. Label: 🟢 FACT (read in full). Verified (M). +- Recommendation: one dated stamp per edit; replace the narrative by a five-line "current release + where its evidence is" box and a pointer to + `CHANGELOG.md` (the history is already there). Severity: minor. + +**A93-GOV-03 — `PROJECT.md` phase 6 names only the protocol test plan.** +- Location: `PROJECT.md:26–27` "Test & validation — … see `TESTPLAN_BLUETOOTH_HCI_SNOOP.md`." `APP_TESTPLAN.md` (the app's own hardware plan, used for every + release run since `CAP-062`) is not named. Category: incompleteness. Label: 🟢. Verified (M). Recommendation: name both. Severity: nit. + +**A93-GOV-04 — `PROJECT_RULES.md` rule 13's reading list differs from `AGENTS.md` §0.1.** +- Location: `PROJECT_RULES.md:163–166` ("reads `AGENTS.md`, `PROJECT_RULES.md`, `ARCHITECTURE.md`, and the relevant sections of `PROTOCOL.md`") vs `AGENTS.md` + §0.1 (seven documents including `PROJECT.md`, `DECISIONS.md` in full, `TODO.md`) and `AI_SESSION_LOG_PROCEDURE.md` §8. +- Category: inconsistency. Label: 🟢. Verified (M). Recommendation: rule 13 points to `AGENTS.md` §0.1 instead of repeating a shorter list. Severity: minor. + +**A93-GOV-05 — `AGENTS.md` §6's framing paragraph still opens "Framing is not yet settled".** +- Location: `AGENTS.md:198` "**Framing is not yet settled — do not implement against a placeholder.**" followed by the 2026-08-14 text. Since then every + channel the app uses has a 🟢 framing and an ADR (DLCI 0x02 ADR-034, DLCI 0x04 ADR-009/011/031, DLCI 0x08 out of use since ADR-043). +- Category: stale project law (rule text vs current state). Label: 🟢. Verified (M, `PROTOCOL.md` §2.2/§2.2a/§2.3 read in full). +- Recommendation (maintainer edit of project law): keep the per-channel/per-feature gate, retitle it "Framing is settled per channel — the gate stays per + feature", and move the 2026-08 three-channel narrative to a pointer at `PROTOCOL.md` §2.3. Severity: minor. + +**A93-GOV-06 — `AGENTS.md` §10's preferred stack names ViewModel-Compose; ADR-048 decided "no ViewModel".** +- Location: `AGENTS.md:317` "AndroidX Lifecycle/ViewModel-Compose". The dependency exists only to align a transitive version + (`android/ui/build.gradle.kts:45–48`, comment "Not referenced by this module's code"); no class uses a ViewModel (`grep -rn ViewModel android --include=*.kt` + → two comments that say there is none). +- Category: inconsistency (law vs ADR). Label: 🟢. Verified (M). Recommendation: "AndroidX Lifecycle (runtime-compose; `lifecycle-viewmodel-compose` only as a + version-alignment dependency — ADR-048)". Severity: nit. + +**A93-GOV-07 — `README.md` "Current state" and contributor section are out of date in four places.** +- Location: `README.md:178` "## Current state (2026-10-14)" (the section reports `CAP-076` of 2026-10-15); `README.md:186` "**63 accepted ADRs**"; + `README.md:195` "draft ADR-060, a 1.3.0 candidate"; `README.md:247–250` "the newest analysed run (`CAP-070-FINDINGS.md`, the 1.1.0 release APK)". +- Category: stale / wrong count. Label: 🟢. Verified (M): `id_registry.csv` → 58 `adr,accepted`, 6 `superseded` (ADR-003, 024, 038, 061, 062, 063), 1 + `proposed` (ADR-060) = 65 (script count; `lint_docs.py` confirms registry = `DECISIONS.md` headings); the newest analysed own-app run is `CAP-076` + (`ai-sessions/0092`); 1.3.0 shipped without ADR-060. +- Recommendation: "58 accepted, 6 superseded, 1 draft"; date the section 2026-10-15; "a later candidate"; point to `CAP-076-FINDINGS.md`. A `lint_docs.py` + check that compares the README's ADR count with the registry would stop the drift. Severity: minor. + +**A93-GOV-08 — `README.md`'s Status section repeats nine release blocks.** +- Location: `README.md:96–176` — 1.0.1 … 1.3.0 each with its own known issues, including the 1.2.1/1.2.2 settings that 1.3.0 removed. +- Category: readability / misplaced content (release history belongs in `CHANGELOG.md`). Label: 🟢. Verified (M). +- Recommendation: keep the latest release and its known issues; one line "older releases: `CHANGELOG.md`". Severity: minor. + +**A93-GOV-09 — `CONTRIBUTING.md` describes a pre-release project and omits the repository rules.** +- Location: `CONTRIBUTING.md:3` "This project is currently a solo reverse-engineering effort"; no mention that `main` accepts changes only by pull request, + which CI checks run (`.github/workflows/android.yml`, `.github/workflows/lint-docs.yml`) or that `scripts/lint_docs.py` must pass (`RELEASING.md` "Repository rules"). +- Category: stale / incompleteness. Label: 🟢. Verified (M). Recommendation: one paragraph "How a change gets in" (fork → PR → the two CI jobs green → + maintainer review). Severity: minor. + +**A93-GOV-10 — `WORKSTATION_PREPARATIONS.md` installs tools against their vendors' current guidance.** +- Location: `WORKSTATION_PREPARATIONS.md:38` `sudo chown -R $(whoami):$(whoami) /usr/local/lib/node_modules /usr/local/bin /usr/local/share`; `:48` + `npm install -g @anthropic-ai/claude-code`; `:73` `gpgcheck=0` in the Antigravity repository file. +- Category: best practice (workstation security; not the app). Label: 🟢 for the text; ⚪ whether the Antigravity vendor publishes a repository key (not checked). +- Verified (M) + external: code.claude.com/docs/en/setup (fetched 2026-10-15): "Native Install (Recommended)" `curl -fsSL https://claude.ai/install.sh | bash`, + a signed dnf repository (`gpgcheck=1`, `gpgkey=…/claude-code.asc`, fingerprint `31DD DE24 DDFA B679 F42D 7BD2 BAA9 29FF 1A7E CACE`), and "Do NOT use `sudo npm + install -g` as this can lead to permission issues and security risks". +- Recommendation: use the native installer or the signed dnf repository; drop the `chown` of `/usr/local`; set `gpgcheck=1` with the vendor key if one exists, + else say why not. Severity: minor. + +**A93-GOV-11 — `WORKSTATION_PREPARATIONS.md` small defects.** +- Location: `:135` `code-insiders … || warn …` (`warn` is defined only in the later RE-tools snippet; `git config core.editor "code --wait"` uses `code`); + `:172` "`git lfs version` # the capture logs are LFS files" — correct (all 278 capture binaries are LFS, checked), but the check list omits `capinfos` + (used by `CAPTURE_BLUETOOTH_HCI_SNOOP.md` §3 and `DESKRESEARCH_FINDINGS.md`) and `uv` (the `reverse-engineering/tools/*/.venv` set-ups). +- Category: incompleteness. Label: 🟢. Verified (M). Recommendation: define `warn` first, one editor, add the two tools. Severity: nit. + +**Checked and not a finding (Phase 1):** `PROJECT_RULES.md` rule 18 (LFS) holds — `git lfs ls-files | wc -l` = 278 = every capture binary +(`git ls-files captures | grep -E '\.(log|last|mp4|txt|png|jpg|pcapng|zip)$' | wc -l`); `CONTRIBUTING.md`'s rule citations (`PROJECT_RULES.md` §7 Version +control, §8 Scope) resolve; `SECURITY.md`'s signing fingerprint equals `README.md`'s; the manifests declare exactly `BLUETOOTH_CONNECT`, `POST_NOTIFICATIONS`, +`FOREGROUND_SERVICE`, `FOREGROUND_SERVICE_CONNECTED_DEVICE` (+ AndroidX Core's private receiver permission in the merged manifest), no `INTERNET`, no +`BLUETOOTH_SCAN`, no location (`android/app/src/main/AndroidManifest.xml:18–24`, `android/hardware/src/main/AndroidManifest.xml:16–17`) — `AGENTS.md` §1/§2 hold. + +### Phase 2 — Protocol (`PROTOCOL.md` read in full; the 0090 census re-run; every hex citation checked) + +**A93-PROT-01 — The 2026-10-14 FACT on setting 13 counts a class against itself ("73 of 73") and uses a wrong denominator ("12 of 77").** +- Location: `PROTOCOL.md:856–862` — "receives a `SERVER_STREAM` `4:{13:n}` **for every mode change the Buds report** on the Message Stream: 73 of 73 Buds-side + changes … Not pushed: a change the Buds make together with their `DISC` of MAESTRO (12 of 77 …)"; the same figures in `PROTOCOL.md:3767` (§8 row), + `ai-sessions/0090_CROSSCHECK_RESULT_2026_10_14.md:14–16`, `CHANGELOG.md` `[1.3.0]` Known issues ("12 of 77 such changes"), `CAP-076-FINDINGS.md` §6.1 + ("the 12-of-77 `DISC` case"). +- Category: error (arithmetic) / ambiguity in a 🟢 FACT. Label: 🟢 FACT for the counts below. +- Verified (M): re-run `python3 scripts/anc_setting13_crosscheck.py --census` over the 54 `btsnoop` logs of the 46 subscribing official-app captures + (`CAP-049`'s combined log left out, as 0090 did): `39 notify-set: stream-same`, `73 notify-unprovoked: stream-same`, `2 … stream-other`, `2 … none, other`, + `12 … none, Buds close MAESTRO within 0.1 s`, `100 stream: with a Notify within 1 s`, `3 stream: no Notify, no Message Stream open`, `359 / 17 / 6` for + `ReadSetting 4:13` — every published number reproduced exactly (`scripts/__pycache__` unchanged). The Buds-side ("unprovoked") Notifies are **89** = 73 + 2 + 2 + + 12. "73 of 73" is the class that was *defined* as "a stream packet followed" (circular); "12 of 77" divides by the 77 changes that were **not** missed. +- Recommendation (a proposal for the maintainer; the FACT itself — setting 13 is the mode and is pushed to a subscriber — is not in doubt): "… 75 of 89 + Buds-side changes were pushed within 1 s (73 with the predicted value, 2 as two close changes matched crosswise); 12 of 89 were not, because the Buds closed + MAESTRO 2–30 ms after the Notify; 2 of 89 fell on a re-open or a fresh Message Stream open". Correct the same figure in the §8 row and the `[1.3.0]` + known issue (by a dated note; the release text itself is history). Severity: **major** (a 🟢 FACT's headline count misstates its own evidence, and the + wrong ratio reached the user-facing release notes). + +**A93-PROT-02 — "41 of 41 holds … while Play held the Message Stream" conflates the two users.** +- Location: `PROTOCOL.md:868–870` ("the Buds pushed every reported change of setting 13 to it, also while Google Play services held the Message Stream and + received the `Notify` (41 of 41 holds shown …)"), `PROTOCOL.md:3768` (§8 row "41 of 41 holds pushed while Play held the Message Stream"); origin + `CAP-076-FINDINGS.md` §13's proposal text. +- Category: error (conflation). Label: 🟢. Verified (M): `CAP-076-FINDINGS.md` §4.2 — 29 holds in part 1 (Owner, Play held DLCI 0x04) and 12 in part 2 + (secondary user, no Play — no client held the Message Stream; `CAP-076` claims table: Play 0 in H2L/H2). +- Recommendation: "29 of 29 while Play held the Message Stream (part 1), 12 of 12 without Play (part 2)". Severity: minor. + +**A93-PROT-03 — The SASS burst's "in-use" string is attributed to Code `0x42`; it is in `0x41`.** +- Location: `PROTOCOL.md:1706–1708` — "Group `0x07` (Codes `0x11`/`0x21`/`0x34`/`0x40`/`0x41`/`0x42`, the last containing the ASCII string `"in-use"`)". +- Category: error. Label: 🟢 FACT (wire + spec). Verified (M): `tshark -r CAP-019-btsnoop_hci.log -Y 'btrfcomm.len>0 && btrfcomm.dlci==4 && + frame.number>=780 && frame.number<=810' -T fields -e frame.number -e frame.p2p_dir -e data.data` (exit 0) → 804 (phone → Buds) `07 41 00 16 69 6e 2d 75 73 + 65 …` (`696e2d757365` = "in-use"), 806 `07 42 00 11 00 64 …`, 810 the Buds' ACK `ff 01 00 08 07 41 69 6e 2d 75 73 65`. +- External evidence: developers.google.com/nearby/fast-pair/specifications/extensions/sass (fetched 2026-10-15): 0x41 "Indicate in use account key", + Seeker → Provider, UTF-8 "in-use" at octets 4–9 + nonce + MAC — the string appears only in this message; 0x42 "Send custom data", one custom byte + nonce + MAC. +- Recommendation: correct by a dated note (rule 9a). Severity: minor. + +**A93-PROT-04 — The open §6 item on SASS Codes `0x11`/`0x21`/`0x40`/`0x42`/`0x34` is answerable from the official page.** +- Location: `PROTOCOL.md:2696–2699` ("what do Fast Pair SASS … Codes `0x11`/`0x21`/`0x40`/`0x42` encode beyond their raw bytes? Is Code `0x34` … a + periodic/keepalive SASS code …?"). +- Category: undiscovered/closable finding. Label: 🟢 for the names and layouts (spec + matching lengths on the wire); 🟡 for any claim beyond the spec. +- Verified (M): the SASS page (fetched 2026-10-15) defines 0x10/0x11 capability (Provider length 4: version `0x0102` + 2 flag bytes — `CAP-019` 799 `07 11 00 04 01 + 02 98 00`; Seeker length 20 with nonce + MAC — 786), 0x21 Get switching preference (length 0; answer 0x22), 0x34 Notify connection status (Provider → Seeker, + sent on a connection-status change and as the answer to 0x33; octet 4 active flag 0/1/2, then encrypted status and nonce, no MAC), 0x40 Notify Audio switch + initiated connection, 0x41 in-use account key, 0x42 custom data. +- Recommendation: record the names and layouts as a dated Update (🟢 for the spec's definitions and the matching lengths); keep 🔴 only for the encrypted + contents; drop "periodic/keepalive" (the spec says 0x34 is event-driven). This is out of the app's scope (ADR-008 leaves SASS out), so no code follows. + Severity: minor. + +**A93-PROT-05 — The BLE Battery Notification is still labelled "confirmed as what the Buds Pro 2 send".** +- Location: `PROTOCOL.md:153` (§1 table: "🟡 HYPOTHESIS (confirmed as what the Buds Pro 2 send)"), `PROTOCOL.md:1078–1079` (Option A **Status** "🟡 + HYPOTHESIS (confirmed as used by the Buds Pro 2 specifically)") vs `PROTOCOL.md:1170` (2026-10-06, 🟢 FACT: no Battery Notification field in clear in + `CAP-054`'s case-open advertisements) and `README.md:195` ("never matched on the wire"). +- Category: stale label (`PROJECT_RULES.md` rule 4: relabel once contradicted). Label: 🟢. Verified (M, read in full). +- Recommendation: "🟢 mechanism (spec); 🔴 not observed from the Pixel Buds Pro 2 (`CAP-011`/`043`/`054`)". Severity: minor. + +**A93-PROT-06 — Two 🟢 promotions are not reflected in their entries' own Status lines.** +- Location: `PROTOCOL.md:1779–1783` (§4.5.3 Status: "🟡 HYPOTHESIS for the rotation-checklist's own field-order/on-screen-mapping reading and for whether it's + the same feature as `qht`'s 'ANC gesture loop'") — both 🟢 since 2026-09-28 (`PROTOCOL.md:1806`, ADR-046); `PROTOCOL.md:1830–1834` (§4.5.4 "🟡 + HYPOTHESIS — `field 29` = Head gestures … **Status**: 🟡 HYPOTHESIS") — 🟢 since 2026-10-04 (`PROTOCOL.md:1846`, ADR-052). No pointer at either line. +- Category: stale status. Label: 🟢. Verified (M). Recommendation: a one-line pointer at each Status line (as §4.5.5 already has). Severity: minor. + +**A93-PROT-07 — The ATT `0x0044` notification burst item can be closed.** +- Location: `PROTOCOL.md:2777–2794` ("Every occurrence examined so far is another device's traffic … The item stays listed only because no check was run on + every capture."). +- Category: closable open question. Label: 🟢 FACT (the scan below). +- Verified (M): over all 103 `btsnoop`/`btsnooz` logs (scratch script att44.sh: `btatt.opcode==0x1b && btatt.handle==0x0044` per connection handle, and + `btatt.uuid16==0x180d` per handle) — notifications in 76 logs; every carrying handle shows the Heart Rate service in the same log, except in `CAP-060`…`066` + and `CAP-072` `hci1.log.last`, where the carrier `0x0040`/`0x0041` is the LE peer `c8:cc:a8:e7:48:93` (LE Connection Complete in all 12 logs), which exposes + Heart Rate in `CAP-072-btsnoop_hci1.log`. In `CAP-059` the Buds' LE address held `0x0041` earlier (frame 538), but the notifications (5529–5702) come after the + handle's reuse by another peer (5237) with Heart Rate at 5305. The Buds' attribute table has no Heart Rate service (`CAP-034-FINDINGS.md` §4). +- Recommendation: tick the item with this command and result (positive control: `CAP-016` handle `0x0002`, 73 notifications, Heart Rate frame 760). + Severity: minor. + +**A93-PROT-08 — One frame number off by one.** +- Location: `PROTOCOL.md:111–112` — "`CAP-059` … frame 1509 `03 0a 00 08 4c b5 93 ed 88 3f 5a ef`". +- Category: error (citation). Label: 🟢. Verified (M): `tshark -r CAP-059-btsnoop_hci.log -Y 'frame contains 03:0a:00:08:4c:b5:93:ed' -T fields -e + frame.number` → 1508. Method note: a script extracted every backticked hex string of ≥ 5 bytes that has a `CAP-NNN` and a frame number in the preceding 220 + characters (38 in `PROTOCOL.md`, 9 in `DECISIONS.md`/`ARCHITECTURE.md`) and checked each with `frame.number==N && frame contains ` over every log of the + capture: 36 matched (directly or, for 10 extraction artefacts, at the adjacent frame the sentence names), 1 real error (this one). +- Recommendation: 1509 → 1508. Severity: nit. + +**A93-PROT-09 — §6's open items lag findings made elsewhere in the same document.** +- Location and evidence (M, read in full): `PROTOCOL.md:2738–2776` (the `CAP-021` DLCI 0x0a burst item) has no pointer to §2.3's 2026-10-04 note + (`PROTOCOL.md:604`, the GSND assistant-session opener) — see lead L93-1, which links them; `PROTOCOL.md:2814–2820`/`2874–2913` (the DLCI 0x08 zero-length + `04 04`/`04 15` Sent frames, "inconclusive" 2026-09-16) has no pointer to `CAP-069`'s Code `0x05` (wear) and `0x16` (head gestures) readings + (`PROTOCOL.md:604–606`, 🟡); `PROTOCOL.md:2940–2988` (the "two Notify without Get/Set" item, answered by `CAP-051`/0090 — press-and-holds on the settings + stream) and `PROTOCOL.md:3651` (the field-13 mirror item, 🟢 since 2026-10-14 by its own pointer) are still `[ ]`. +- Category: stale / structure. Label: 🟢. Recommendation: tick or point each; see §6 (document proposals) for §6's structure. Severity: minor. + +**Checked and not a finding (Phase 2):** every count of the 0090 Update besides A93-PROT-01's ratios reproduced exactly (above); the DECISIONS/ARCHITECTURE hex +citations all resolve; ADR-064 item 1's bytes equal `CAP-051` 1177 (`PROTOCOL.md:859`, checked OK by the script); `scripts/__pycache__` untouched by the runs. + +### Phase 3 — Captures (Capture Index, registry, folders; three read-only capture sub-reviews; CAPTURE/TESTPLAN/APP_TESTPLAN read in full) + +**A93-CAP-01 — "Thirteen MAESTRO requests at Connect" (1.2.x) is a miscount in six documents.** +- Location: `APP_TESTPLAN.md:27` ("thirteen MAESTRO requests at Connect"), `:371`, `:384`, `:407` (V16/W1/X2 "the thirteen requests"), `:37` ("fifteen MAESTRO requests" for + 1.3.0, contradicted by its own Y2 row `:431` "seventeen requests per full session, not fifteen"); `CAPTURE_BLUETOOTH_HCI_SNOOP.md:1783` (Group BH "thirteen MAESTRO + requests (the twelve settings reads, `SubscribeRuntimeInfo`, then one `GetHardwareInfo`)" — itself 14), `:1800`, `:1812`, the `CAP-072`/`CAP-073` Index rows + (`:2291–2292`); `CHANGELOG.md:143` (`[1.2.0]` "thirteen requests … instead of twelve settings reads plus the EQ read and the subscription"). +- Evidence: `CAP-076-FINDINGS.md` §2 counts the 1.2.2 session S0 as "15 (13 reads, SRI, GHI)" and every 1.3.0 session as 17; the test asserts `5 + + settingReads.size` = 17 for 1.3.0 (`BudsRepositoryImplTest.kt:467–471`); "thirteen" is the number of `ReadSetting` requests only (the EQ's `4:16` + 12 settings). +- Category: error (arithmetic) repeated across documents. Label: 🟢. Verified (M; the CAP-073 count is the sub-review's, below). Recommendation: "fifteen + (thirteen reads, `SubscribeRuntimeInfo`, `GetHardwareInfo`)" for 1.2.x and "seventeen" for 1.3.0, by dated notes where the text is history. Severity: minor. + +**A93-CAP-02 — `CAPTURE_BLUETOOTH_HCI_SNOOP.md` stale statements.** +- `:1865` Group BK "**Status:** 🔲 planned (skeleton written 2026-10-12 …)" below its own result paragraph (run 2026-10-12/13, analysed `0088`) and the Index row + (analyzed); `:1164` Group AH "a `ReadSetting` sweep of fields 1–32" (the sweep is 1–38 without 6, 8, 9, 10, 14, 20, 33 — `PROTOCOL.md` §6, 2026-10-03 correction; + `0073`); `:2302` the Group column note "(Z, A–Q, R, S …)" (groups now run to BL); `:5`, `:2031`, `:2160`, `:2175` "`PROTOCOL.md` §4.1's opcode table … rows filled in" + (§4.1 is the ANC entry; there is no opcode table — 2026-08 framing); `:2319` the Index legend defines a status `promoted` that no row uses although dozens of + captures fed promotions; the §4.1 tip layers (≈ `:220–249`) justify keeping superseded tips by `PROJECT_RULES.md` rule 9a, whose keep-history scope is + `DECISIONS.md`/`PROTOCOL.md`/`REVERSE_ENGINEERING.md`/FINDINGS, not this guide. +- Category: stale / inconsistency. Label: 🟢. Verified (M). Recommendation: fix in place; drop or define `promoted`. Severity: minor. + +**A93-CAP-03 — `TESTPLAN_BLUETOOTH_HCI_SNOOP.md` breaks its own Evidence-column rule and has stale lines.** +- Location: §0.3/§7 item 3 ("Evidence: a pointer only, never the finding") vs 22 of 103 Test-ID rows with an Evidence cell over 300 characters (script over the + table; `ANC-001` `:118` 1,099 characters); `:25` "Group A–Z, then AA–BE as of 2026-10-03" (groups run to BL); `:357` (§9) lists `SWITCH-001`/`WELL-001` as "no capture + scenario" while their rows (`:284–285`) give scenario AT and evidence. +- Category: inconsistency (rule vs practice; known in `TODO.md` §6 "Evidence cells to pointers", still open). Label: 🟢. Verified (M). Recommendation: move each long + cell to its capture's FINDINGS and leave the pointer; update `:25` and `:357`. Severity: minor. + +**A93-CAP-04 — `APP_TESTPLAN.md` section Y has its columns shifted.** +- Location: `APP_TESTPLAN.md:427` header "| ID | Steps | Expected on screen | Expected on the wire | Result | Notes |" vs `:431` (Y2) "| Y2 | Disconnect, Connect. | ready | ✅ + | | BL-3 …" — the result mark sits in "Expected on the wire", the Result cell is empty, the notes follow; the same in every Y row (`lint_docs.py` (f) counts cells only). +- Category: structure. Label: 🟢. Verified (M). Recommendation: insert the missing "Expected on the wire" cell per row (or drop the column for §Y). Severity: minor. + +**A93-CAP-05 — `PROTOCOL.md` and `CAP-058` name the link-key type wrongly: P-192, not P-256.** (S, re-verified M) +- Location: `PROTOCOL.md:2188–2189` ("the link key is type **0x05** (authenticated P-256) … type **0x04** (unauthenticated P-256)"); `CAP-058-FINDINGS.md:89/93/96` + and its EVENT-NOTES key-type cells. `CAP-067-FINDINGS.md:233` has it right. +- Evidence: `tshark -r CAP-058-btsnoop_hci.log -Y 'frame.number==2235' -V | grep -i 'key type'` → "Key Type: Authenticated Combination Key, P-192 (0x05)". + External: Bluetooth Core Specification, HCI Link Key Notification event — key types 0x04/0x05 are the P-192 combination keys, 0x07/0x08 the P-256 ones (the + Wireshark dissector prints the spec's names; the spec itself not fetched this session). +- Category: error in a recorded protocol fact (a statement about the pairing's security level). Label: 🟢. Recommendation: a dated correction note (maintainer + approval, `AGENTS.md` §6) in `PROTOCOL.md` and `CAP-058-FINDINGS.md`. Severity: **major**. + +**A93-CAP-06 — Test-IDs swapped or misused in five EVENT-NOTES (traceability, `AGENTS.md` §13 step 7).** (S, ANC IDs re-verified M against `TESTPLAN_BLUETOOTH_HCI_SNOOP.md:119/121`) +- `CAP-068-EVENT-NOTES.md`: Transparency tagged `ANC-002` (:87, :92, :96, :147) and Noise cancellation `ANC-004` (:90, :95, :104, the traceability line :220) — TESTPLAN: + `ANC-002` = Noise Cancellation, `ANC-004` = Transparency. `CAP-067-EVENT-NOTES.md:116/119/202` (INEAR-002/003 shifted by one; the Left bud into the ear untagged at :117). + `CAP-072-EVENT-NOTES.md` (:146/:209/:276 `CASE-004` for buds into the case — `CASE-006`; :199/:201 `HOLD-005` for touch controls and holds; :153/:193 `ANC-001` for NC; + :219/:272 `INEAR-004` for the in-ear switch — `INEAR-001`). `CAP-065-EVENT-NOTES.md:89/116/130/138` (`CASE-005` for the Left out — `CASE-004`). + `CAP-070-EVENT-NOTES.md:93`, `CAP-073-EVENT-NOTES.md:190` (INEAR misuse). +- Category: error (traceability). Label: 🟢. Recommendation: correct the tags; a lint check that each tag's TESTPLAN title shares a keyword with the line is cheap. + Severity: **major** (Test-ID → capture → frame is the project's evidence chain; swapped IDs send a reader to the wrong evidence). + +**A93-CAP-07 — Counts in the 1.x capture FINDINGS that do not add up.** (S, each with its tshark re-count) +- `CAP-060-FINDINGS.md:25` "seven non-user session or link ends (… four ACL drops)" — drop #3 is the user's Settings tap: 6 (as EVENT-NOTES:12 and the Index); + `CAP-062-FINDINGS.md:80–81` "11 NAKed, all NAKs in `CAP-062`" — tshark finds 10 (`PROTOCOL.md:810` says 10); `CAP-065-FINDINGS.md:182` "7 of 7" one-bud-out + announcements — its own §4 table has 8 qualifying rows (repeated in `PROTOCOL.md:411/3754` — maintainer); `CAP-075-FINDINGS.md:72–73` per-log claims H2L 7/5, H2 + 19/19 — tshark 6/6 and 20/18 (agrees with its own lists :85–86 and "24 claims" :217), `:322` "49 app claims" vs 37 in §1, `:18`/EVENT-NOTES:22 "8 + 2 + 16" of 31 omits + "3 overdrawn" (and 2 late); `CAP-074-FINDINGS.md:260` "14 of 16 failed, 2 free" vs 3 free in §4, `:165` "12 times" with 13 timestamps; `CAP-067` "7 of 7 announcements" + (6); `CAP-068-EVENT-NOTES.md:213` "11 Sets" (12); `CAP-069-FINDINGS.md:135` "Five Default taps, five writes" with four frames; `CAP-070-FINDINGS.md:19` "all 19 ACLs" (17), + `CAP-071-FINDINGS.md:17` "all 7 ACLs" (6); `CAP-061-FINDINGS.md:146` "(12)" Notifies (13); `CAP-062` "14 announcements" (15); `CAP-063-EVENT-NOTES.md:182` "≈ 93 s" for a 79-s window. +- Category: arithmetic. Label: 🟢 (sub-review re-counts; four re-checked M by the commands quoted in its notes file). Recommendation: one dated correction + line per file. Severity: minor. + +**A93-CAP-08 — Wrong or stale statements in `CAP-026` … `CAP-051` files.** (S; three re-verified M) +- `CAP-028-FINDINGS.md:82–85` shows a query with output "(0 rows)"; re-run it returns 1442 1443 1444 1448 (M — the conclusion still holds once the window starts at + 07:16:25.5; add a positive control). `CAP-029-FINDINGS.md:86–88` "matches the already-documented factory-reset pattern from `CAP-001`/`CAP-002`'s own Group P + precedent" — `CAP-001` is Group Z, `CAP-002` Group A, and neither file mentions a factory reset (`grep -il 'factory reset'` → 0, M): `CAP-029` is the first wire record. + `CAP-041-EVENT-NOTES.md:4–7, 195–203` still says "(b) clean negative … content diff still pending" (M) although FINDINGS §3/§8 (rewritten 2026-10-03) did the diff — + no banner. `CAP-047-FINDINGS.md:304–309` "reads 'both docked' identically in all three swapped-dock instances" — no Notify during swap #2, swap #3's frames are + counter-examples. `CAP-050-FINDINGS.md:204–242` classes one configuration three ways (counter-example / expected / correct) and undercounts the anomalies. + `CAP-040-EVENT-NOTES.md:121–228` ~15 "docked / case closed" rows contradicted by the wire (Left 95 % not charging until 07:53:53; no disconnect until 07:55:41). + `CAP-051-FINDINGS.md:98` "no preceding Set or Get anywhere in the log" — the log has Set 1527 and Get 1072 ("nearby" is meant). +- Plus (S, not re-checked one by one, listed in the scratch notes): ≈ 90 minor/nit slips in `CAP-026` … `CAP-075` — times off by seconds, leftover template text, + unticked next-step boxes in analysed files, negatives without command or positive control (`CAP-026`, `029`, `031`, `053`), rows out of time order, stale + "awaiting sign-off" (`CAP-051` ANC-005, declined 2026-09-18), the `CAP-053` "18.7 s" vs 18.5 s. +- Category: error / stale. Label: 🟢 for the three M items. Recommendation: a capture-notes clean-up session working through the scratch list (copied into + Appendix B of this RESULT). Severity: major for `CAP-029`'s invented precedent and `CAP-040`'s contradicted dock column (each could mislead a later reading); + minor for the rest. + +**A93-CAP-09 — Wrong or stale statements in `CAP-001` … `CAP-025`.** (S; three re-verified M) +- `CAP-007-FINDINGS.md:160–161, 285–297` (and `CAP-007-EVENT-NOTES.md:55`, the Index row) "~184 s silence on DLCI 0x08 / fully idle connection" — DLCI 0x08 carries + `0e 02`/`0e 01`/`04 03` push groups at 09:16:02 (1632/1633/1642), 09:16:23 (1686/1687/1695) and later (M: `tshark … -Y 'btrfcomm.dlci==8 && btrfcomm.len>0 && + frame.number>=1600 && frame.number<=1900'`); only Code `0x12` is absent — and §5's 🟢 rests on the wording. `CAP-007-FINDINGS.md:250–256` "zero RFCOMM/L2CAP data + frames" in the stated window — the re-run returns 32 (S). +- `CAP-004-FINDINGS.md:376–378` and `CAP-002-FINDINGS.md:143–144` "groups 0x01/0x02 only surface in `CAP-002`" — `CAP-004` 2360/2365 carry `02 05 00 02` (M). +- `CAP-021-EVENT-NOTES.md:56` frame 1626 "a re-affirm (touch controls was already ON, per `CAP-020`)" — the connect read 1140 is `4:{4:0}` and 1626 writes `4:{4:1}`: + a real OFF→ON write (M, `pwrpc_decode.py`). +- (S) `CAP-004-FINDINGS.md:710–714` still lists "Group 0x04 = Device action" as ready for 🟢 though `:187–189` (2026-10-08) says the DLCI 0x08 group identity is open; + `CAP-011-FINDINGS.md:181–185` "L/R on the wire match the screen at every sampled point" — its own table shows Left 92 at 09:49:28 while the screen showed 93 until + 09:52:25.8; `CAP-012-FINDINGS.md:365–369` §10 🟢 "CTKD caused by nRF Connect's early BLE connection" vs its own 2026-10-03 rewrite (`:162–167`, 🟡 the LE SMP pairing), + repeated in `CAP-012-EVENT-NOTES.md:11–15`, `CAP-004-FINDINGS.md:669–671`, `CAP-014-FINDINGS.md:296–299`; `CAP-001-FINDINGS.md:385–387` §6 "DLCI 0x02/0x04 identity + unconfirmed — AVRCP/A2DP candidates" against the same file's resolutions. +- Plus ≈ 120 minor/nit items (S; Appendix B): Index Test-ID columns missing IDs the notes use (`CAP-002`, `003`, `006`, `007`, `010`), stale 🟡 status text in the + `CAP-019` … `CAP-024` rows, negatives without command or positive control (`CAP-001`, `002`, `003`, `004`, `005`, `007`, `010`, `018`, `023` — the sub-review re-ran + several and they hold), BIEV "every 6–7 s" in `CAP-001` (gaps 0.07–22.7 s), arithmetic slips (`CAP-016` HID string 23 not 24 bytes; `CAP-009` 68 not 69/73 BIEV + pushes), a mistyped UUID `109b8b21-…` in `CAP-004` (everywhere else `109b862f-…`). +- Category: error / stale. Label: 🟢 for the M items. Recommendation: the same clean-up session as A93-CAP-08. Severity: **major** for `CAP-007` (a 🟢 resting on a false + premise), `CAP-004`/`CAP-002` (a false uniqueness claim) and `CAP-021` (a misread write); minor for the rest. + +**Checked (Phase 3):** the 76 Capture Index rows, the registry and the 72 capture folders agree on ID, date, group and status (script, M); every Test-ID used in +`CAP-051` … `CAP-075` exists in TESTPLAN except the non-ID "LOG-001" (`CAP-059-FINDINGS.md:87`); 1.2.0 sends 15 MAESTRO requests per Connect — `CAP-073`, 11 of 11 +sessions (`python3 -I scripts/pwrpc_decode.py CAP-073-btsnoop_hci.log | grep "dir=Sent REQUEST"` → 171 = 11 × 15 + 6 writes; S, consistent with `CAP-076` S0, M); +"8 of 19" (holds during the take-it-back chains) and "8 of 31" (all holds of `CAP-075` set 2) are both right — different denominators (S); `CAP-036`'s app version +`1.0.955078535` is what its own notes recorded from the screen (no frame given — ❓ a transcription slip, check the film). + +### Phase 4 — Architecture (`ARCHITECTURE.md` read in full; every §3.2 constant and named class checked against the code) + +**A93-ARCH-01 — §3.1's ANC reconciliation row describes 1.2.x behaviour as current.** +- Location: `ARCHITECTURE.md:329` (§3.1 table, ANC row) — "every `ancMode` carries its cause … **reset at Connect** … **Since the 1.2.0 rebuild (ADR-061):** the claim + is held while the noise-control tab is on screen, so such a `Notify` (a press-and-hold on a bud) reaches the app there". Since 1.3.0 the cause is **not** reset + at Connect (ADR-065 Q4, `ai-sessions/0091` §B) and there is no hold (ADR-065); the mode also comes from the settings stream, which the row never mentions — + the 1.3.0 rule sits in a separate §3.1 bullet (lines 286–291) that the table does not point to. +- Category: stale / internal inconsistency in the document the code is built from. Label: 🟢. Verified (M): `BudsRepositoryImpl.kt` (`causeOfStream`, + `causeOfReadAnswer`; no `setAncTabShown`/`HOLD_RECLAIM_*` left — `grep -rn` → 0 in `android/*/src`). +- Recommendation: rewrite the ANC row in place (`ARCHITECTURE.md` is not under rule 9a's keep-history scope) with the 1.3.0 sources (Notify on a claim, the + settings stream, the `4:13` read) and the cause rules as built; move the 1.2.x hold into §6.0b's history paragraph. Severity: **major**. + +**A93-ARCH-02 — §2.4's navigation tree and §9's persistence line still list removed 1.2.x settings.** +- Location: `ARCHITECTURE.md:153` (§2.4 tree: Settings tab "since 1.2.1 the 'Noise-control channel' choice, ADR-062 … since 1.2.2 'Noise control with Google Play + services', ADR-062 Update 2 and ADR-063") vs `ARCHITECTURE.md:207` ("gear → Settings is Dark mode · Case sounds · Use different Buds"); `ARCHITECTURE.md:922–923` + (§9 "Local state persistence: … since 1.2.1 … the 'Noise-control channel' choice are stored") vs `DarkModeSettingsStore.kt:34` ("the key `hold_reclaim_mode` … is + ignored since `ai-sessions/0091`"). +- Category: stale / contradiction. Label: 🟢. Verified (M). Recommendation: drop the removed items (one history clause each). Severity: minor. + +**A93-ARCH-03 — `BluetoothGatt` is named as owned by the hardware layer; no GATT client exists.** +- Location: `ARCHITECTURE.md:128` ("Owns raw `BluetoothSocket`/`BluetoothGatt` objects"), `:247` (`BudsTransport` "where applicable, `BluetoothGatt` (secondary — + case/charging characteristics)"), `:24`; vs `ARCHITECTURE.md` §2 line 91 / §13 (no GATT client) and `grep -rn BluetoothGatt android --include=*.kt` → one KDoc + line in `BudsError.kt:24`, no use. +- Category: inconsistency / ambiguity. Label: 🟢. Verified (M). Recommendation: "GATT: none as built (Case battery from DLCI 0x02, ADR-043); a GATT client would + need a new ADR". Severity: minor. + +**A93-ARCH-04 — §8.1 keeps the 2026-08 design text below the as-built note.** +- Location: `ARCHITECTURE.md:829–850` — "the app reads the firmware version string first (via DLCI 0x02's Rcvd block or DLCI 0x04's Device Information, whichever + is confirmed to carry it …)"; as built the gate is the announcement's `release_5.203` plus the Fast Pair Model ID (ADR-042, `BudsRepositoryImpl.writeGate`), + stated in the blockquote above it. +- Category: stale design text (the reader must reconcile two versions). Label: 🟢. Verified (M). Recommendation: replace the design paragraph by the as-built rule; + keep the rationale bullets. Severity: minor. + +**A93-ARCH-05 — §15 says no architecture question is open; §6.0b keeps one open.** +- Location: `ARCHITECTURE.md:1053` ("no architecture question is open") vs `ARCHITECTURE.md:703–704` ("(b) remains a deferred proposal that needs a maintainer + decision and an ADR" — automatic connecting in the background). +- Category: inconsistency. Label: 🟢. Verified (M). Recommendation: list (b) in §15 (or record its rejection in an ADR). Severity: minor. + +**Checked and not a finding (Phase 4, M):** all 25 constants of §3.2 equal the code (`grep -rn "const val .*_MS" android/*/src/main` and the two counts: +`DEFAULT_CONNECT_ATTEMPTS = 3`, `RfcommBudsTransport`); no literal `delay(…)`/`withTimeout(…)` in main sources; `BleLogger` exists (`:hardware`), ring buffer +`RING_BUFFER_CAPACITY = 20_000`; `BudsForegroundService` returns `START_NOT_STICKY`; the CDM filter is `Pattern.compile("(?i).*Pixel\\s*Buds.*")` +(`BudsCompanionPairing.kt:364`); frame caps `MAX_MESSAGE_STREAM_DATA_BYTES = 1024`, `MAX_HDLC_FRAME_BYTES = 4096` (`CodecRouter.kt:406–409`); toolchain as +documented (Gradle 9.7.1 with `distributionSha256Sum`, AGP 9.3.3, Kotlin 2.4.20, KSP 2.3.12, Hilt 2.60.1, Compose BOM 2026.09.00, JUnit 6.1.3, compileSdk 37, +min/targetSdk 34, 1.3.0 / 10300); a field-18 EQ frame from the stream is not applied (`BudsRepositoryImpl.kt:735–741`), as ADR-065 item 2 says; the 1.2.x +hold/re-claim code is gone (0 hits for `HOLD_RECLAIM`, `HoldReclaim`, `setAncTabShown`, `reclaimAnc`, `PlayServices` in `android/*/src`). + +### Phase 5 — Decisions (`DECISIONS.md` ADR-001 … ADR-065 + draft ADR-060 read in full; statuses scripted against `id_registry.csv`) + +**A93-DEC-01 — ADR-064's Status still says "not yet built".** +- Location: `DECISIONS.md:2841` — "**Status**: Accepted (maintainer, chat 2026-10-14, `ai-sessions/0090`) — not yet built; its hardware run comes with the build". + ADR-064 was built in 1.3.0 (`ai-sessions/0091`, ADR-065 item 1 "as built"), hardware-run in `CAP-076` and released 2026-10-15; ADR-065's Status line (2876) says so. +- Category: stale. Label: 🟢. Verified (M). Recommendation: append "— built in 1.3.0 (`ai-sessions/0091`), hardware run `CAP-076` (`ai-sessions/0092`); items 2 + and 6 amended by ADR-065" (a pointer, as ADR-044's was). Severity: minor. + +**A93-DEC-02 — ADR-064's Context repeats the circular "73 of 73".** +- Location: `DECISIONS.md:2848–2849` ("the stream carries `4:{13:n}` for 73 of 73 Buds-side and 39 of 39 Set-driven changes"). Same defect as A93-PROT-01 (75 of + 89 pushed, 12 lost to the Buds' `DISC`, 2 at re-opens). Category: error (count framing). Label: 🟢 (census re-run). Verified (M). +- Recommendation: a dated note with the corrected ratio (the decision stands — ADR-065 already handles the `DISC` case by the re-open's read). Severity: minor. + +**A93-DEC-03 — Superseded ADRs use two Status conventions.** +- Location: ADR-003/024/038 open their Status with "Superseded by …" (`DECISIONS.md`, e.g. ADR-038), ADR-061/062/063 open with "Accepted (maintainer …) — built …" + and state the supersession further on (`DECISIONS.md:2700–2701`, `2736–2741`, `2812–2814`); `id_registry.csv` says `superseded` for all six. +- Category: inconsistency (a reader scanning first words, and any future lint on the first word, sees "Accepted"). Label: 🟢. Verified (M, script over all 65 + Status lines). Recommendation: one convention — "Superseded by ADR-NNN (date) — was: Accepted (…)" — and a `lint_docs.py` check that the Status's first word + matches the registry. Severity: nit. + +**A93-DEC-04 — Four early ADRs record no decision-maker.** +- Location: ADR-002, ADR-004, ADR-005, ADR-007 — no "maintainer" anywhere in the entry (script: `re.search('maintainer', entry)` false for exactly these four; + ADR-009's missing record is already stated in ADR-009 and `README.md:186`). +- Category: governance traceability (`AGENTS.md` §6 binds agent-written ADRs; these predate the AI-session log). Label: ⚪ ASSUMPTION that they were written by the + maintainer at project start (no record says so). Recommendation: one dated line each — "decided by the maintainer at project start (no chat record)" — if that + is what happened; that is the maintainer's statement to make. Severity: nit. **Decision for the maintainer** (§7). + +**Checked and not a finding (Phase 5, M):** every ADR heading 001–065 has a registry row with a matching status (`lint_docs.py` "ADR/table/status mismatches": +clean); ADR-065's Update of 2026-10-15 states the two users separately (29/29 with Play, 12/12 without) — correct, unlike the PROTOCOL note (A93-PROT-02); ADR-065 +item 2 (field 18 read but not shown) holds in the code; ADR-062's "8 of 31" and `CAP-076-FINDINGS.md` §9's "8 of 31" agree (31 holds in `CAP-075` set 2). + +### Phase 6 — Backlog, release process and housekeeping (`TODO.md`, `RELEASING.md`, `CHANGELOG.md`, `MAINTAINING_DOCS_SITE.md`, `ai-sessions/INDEX.md`, `reverse-engineering/APK_VERSIONS.md`, `reverse-engineering/tools/BACKLOG.md`) + +**A93-HK-01 — `TODO.md` keeps finished items although its own rule and `RELEASING.md` E3 say to delete them.** +- Location: `TODO.md:5–6` ("a finished item is deleted here"); `RELEASING.md:76` (E3: "`TODO.md` §1 emptied"); yet `TODO.md:16` and `:21` (`[x]` 1.2.2 and 1.3.0 + published), `:47` (`[x]` "moot since 1.3.0"), `:205–212` (`[x]` "Done 2026-10-14 … **Next:** the maintainer's push, PR and CI; then `RELEASING.md` B1–B3 …" — all + done), `:243` (`[x]` 1.2.1 release steps). +- Category: inconsistency (rule vs practice) / stale. Label: 🟢. Verified (M; `gh release view v1.3.0` → published 2026-10-15T07:09:03Z; PR #56 merged + 2026-10-15T07:07:29Z as `aec17a5`). Recommendation: delete the five `[x]` items (their content is in `CHANGELOG.md`, `RELEASING.md` §13 and the session RESULTs). + Severity: minor. + +**A93-HK-02 — Three open `TODO.md` items describe a world before 1.3.0.** +- Location: `TODO.md:28–29` ("The Owner user's stored 'Noise-control channel' choice is 'Take it back' … the default 'Show a hint' is not restored") — the key is + ignored since 1.3.0 (ADR-065 item 5; `DarkModeSettingsStore.kt:34`); `TODO.md:245` ("**1.3.0 candidate (M):** volume-level notifications") — 1.3.0 shipped without it; + `CHANGELOG.md` `[1.2.0]` "A 1.3.0 candidate" (history; leave). +- Category: stale. Label: 🟢. Verified (M). Recommendation: drop the first (nothing to restore any more), relabel the second "candidate for a later release". + Severity: minor. + +**A93-HK-03 — `reverse-engineering/APK_VERSIONS.md` says the second APK was "Not pulled".** +- Location: `reverse-engineering/APK_VERSIONS.md:48–50` ("**1.0.990706425** … Not pulled or decompiled; a follow-up session is planned"); the folder + `reverse-engineering/apk/v1.0.990706425-10260911/` holds `base.apk` and two splits since 2026-10-05 (`ls -la`); the version table has no row for it. + `TODO.md:121–123` already records the gap ("1.0.990706425 paragraph still says 'Not pulled'"); the document's own rule ("Register a new version here before + starting analysis") and `APK_REVERSE_ENGINEERING_PROCEDURE.md` §7 step 1 are not met for ten days. +- Category: stale / incompleteness. Label: 🟢. Verified (M). Recommendation: add the row (files, SHA-256, versionName/Code, pull date, provenance) now, even with + the decompile still pending (the maintainer declined it for this audit, Checkpoint 0). Severity: minor. + +**A93-HK-04 — `MAINTAINING_DOCS_SITE.md` still describes pushing to `main` and bots that commit to it.** +- Location: `MAINTAINING_DOCS_SITE.md:9` ("Commit and push to `main` as normal"), `:37` ("commits the result automatically on every push to `main`"), `:65`; vs + `RELEASING.md:12` "Repository rules" (re-derived 2026-10-08: `main` takes changes only by pull request; the sidebar/sitemap bots open PRs from `bot/…` + branches — e.g. PR #57 `bot/sitemap`, merged, `gh pr view 57`). +- Category: stale process document. Label: 🟢. Verified (M). Recommendation: "Open a PR to `main`; after the merge the two workflows open their own PRs + (`bot/docs-sidebar`, `bot/sitemap`) — merge those too". Severity: minor. + +**A93-HK-05 — `ai-sessions/INDEX.md` status cells drift from the RESULT headers; nothing checks them.** +- Location: `ai-sessions/INDEX.md` rows 0089 (cell starts "awaiting maintainer sign-off"; its RESULT: "**Status:** complete (closed 2026-10-14 …)"), 0091 + ("done 2026-10-14 … PR #56 open" — not one of `AI_SESSION_LOG_PROCEDURE.md` §4's three values; its RESULT: "complete (… released as 1.3.0 …)"), 0069 ("complete (closed + 2026-10-04 …) — awaiting maintainer sign-off — … **nothing committed, tagged or published**" — two states in one cell). +- Category: inconsistency. Label: 🟢. Verified (M; script over all 93 rows: every row has its two files with the matching category). Recommendation: align the three + cells with their RESULTs; extend `lint_docs.py` (g) to INDEX cells (first words ∈ the three values, equal to the RESULT's). Severity: minor. + +**A93-HK-06 — `CHANGELOG.md` `[1.1.1]` contradicts itself on hardware testing.** +- Location: `CHANGELOG.md:168` ("tested on hardware before it is published") vs `:176` ("not yet hardware-tested (with the next release's run)"); the header (line 7) + names the run `CAP-071`. Category: inconsistency in a released block. Label: 🟢. Verified (M). Recommendation: a dated note "(hardware run `CAP-071`, `ai-sessions/0080`, + 2026-10-08: passed)" after line 176. Severity: nit. + +**A93-HK-07 — IDE build output in `android/domain/bin/` is not ignored.** +- Location: `android/domain/bin/` (25 files, incl. copies of Kotlin sources such as `bin/test/…/AncTileTest.kt`); no `bin/` pattern in `.gitignore`; it appears as + `?? android/domain/bin/` in this audit's and every recent session's `git status` (0083, 0084, 0088, 0090, 0091, 0092) and duplicates hits in `grep -rn … android`. +- Category: housekeeping. Label: 🟢. Verified (M). Recommendation: add `android/**/bin/` to `.gitignore` (and delete the folder locally — the maintainer's act). + Severity: minor. + +**A93-HK-08 — Small leftovers.** +- Location: an empty, untracked directory `scratch_0024/` in the repository root (created 2026-09-16; git does not show empty directories); + `reverse-engineering/tools/BACKLOG.md:261/263` two consecutive `---` rules before the footer. +- Category: housekeeping. Label: 🟢. Verified (M, `ls -la scratch_0024` empty). Recommendation: remove both (maintainer). Severity: nit. + +**Checked and not a finding (Phase 6, M):** the 1.3.0 release record in `RELEASING.md:335` matches GitHub — tag `v1.3.0` → `ff27ee1`, published +2026-10-15T07:09:03Z, assets APK + `.sha256` + `THIRD_PARTY_NOTICES.txt`, the published `.sha256` = `03272c6f…a96c` (downloaded to scratch), PR #56 merged as +`aec17a5`, bot PR #57 merged; the latest CI runs on `main` are green (Lint docs, sidebar, sitemap, Pages); the CAP-076 file list in `TODO.md` §3 exists +(`CAP-076-adb-checks-0092.log` is tracked). + +### Phase 7 — Session history (`0069` … `0092`; `0090`, `0091`, `0092` in depth) + +**A93-SES-01 — This audit's own prompt omits two releases.** +- Location: `ai-sessions/0093_AUDIT_PROMPT_2026_10_15.md` §0 context ("since the previous full audit the project released 1.1.0, 1.2.0, 1.2.1, 1.2.2 and + 1.3.0"); `git tag` → `v1.0.0 v1.0.1 v1.1.0 v1.1.1 v1.2.0 v1.2.1 v1.2.2 v1.3.0` — 1.0.1 and 1.1.1 are missing. Category: incompleteness (prompt). Label: 🟢. Verified (M). Recommendation: none for the code; noted so a later reader does not take the list + as complete. Severity: nit. + +**A93-SES-02 — `0091` did not finish the `ARCHITECTURE.md` update its prompt required, and its RESULT says it did.** +- Location: `ai-sessions/0091_FEATURE_PROMPT_2026_10_14.md` Phase F item 10 ("`ARCHITECTURE.md` (§2.4, §3.1, §3.2, §5a, §6.0b — the hold and re-claim paragraphs + become history with a pointer …)"); `ai-sessions/0091_FEATURE_RESULT_2026_10_14.md` §F ("§2.4 (the (i) and cause texts; the 1.2.x card as history), §3.1 (the stream + rule)"); the document still states the 1.2.x card and hold as current (A93-ARCH-01, A93-ARCH-02). +- Category: silent partial completion. Label: 🟢. Verified (M). Recommendation: fix the two places (A93-ARCH-01/02); add "every §3.1 table row the change + touches" to the FEATURE prompt template's documentation step. Severity: minor. + +**A93-SES-03 — `0091`'s compliance record lists a permission the app does not declare, and a wrong request count.** +- Location: `ai-sessions/0091_FEATURE_RESULT_2026_10_14.md` §D ("the manifest's permissions unchanged (`BLUETOOTH_CONNECT`, `BLUETOOTH_SCAN`, `POST_NOTIFICATIONS`, …)"); + §C ("the Connect-sequence counts (15 requests)"). +- Evidence (M): `BLUETOOTH_SCAN` is not declared — `android/app/src/main/AndroidManifest.xml:10` and `android/hardware/src/main/AndroidManifest.xml:17` say it was + removed 2026-09-24; the test asserts `5 + settingReads.size` = 17 requests (`BudsRepositoryImplTest.kt:467–471`), `CAP-076` counts 17 on the wire. +- Category: error in a session record (a compliance line that was written, not checked). Label: 🟢. Recommendation: a dated correction line in the RESULT + (`AI_SESSION_LOG_PROCEDURE.md` §4a); compliance lines should quote the command output (`aapt dump permissions` / merged manifest grep). Severity: minor. + +**A93-SES-04 — `0090`'s headline ratios** — see A93-PROT-01 (circular "73 of 73", "12 of 77"); the session's own census is otherwise exact (re-run, M). + +**A93-SES-05 — `0090`/`0091`/`0092` small record defects.** +- `ai-sessions/0090_CROSSCHECK_RESULT_2026_10_14.md` "Commits" ends with a placeholder bullet "(the PR number: the follow-up commit that records it)"; + `ai-sessions/0091_FEATURE_RESULT_2026_10_14.md` §G "The maintainer's next steps … push `feature/settings-stream` and open the PR" stands below the paragraph that + records the push and PR #56; `CAP-076-FINDINGS.md` "In short" gives the overwrite delay as "0.2–0.7 s", the `0092` summary "0.1–1.5 s", §6.2 "107–1,459 ms" (12 + sessions). Category: inconsistency. Label: 🟢. Verified (M). Recommendation: harmonise (one dated line each). Severity: nit. + +**Re-review of `0090` / `0091` / `0092` — what holds (M):** `0090`: every census count reproduced exactly (A93-PROT-01's run); its APK claims (the single writer +`fxb.smali:1237`, the stream path `fxg`→`fvo`→`fxb`) are recorded with file:line in `REVERSE_ENGINEERING.md` (spot-checked in Phase 9). `0091`: the two requests are +byte-identical to `CAP-051` 1177/1178 and `CAP-056` 1451/1450 (asserted by `SettingsStreamCodecTest`; on the wire in `CAP-076` 12/12); the removal is complete in +code (Phase 4); the gate's counts after the build (`:ui` 92 per variant) equal this audit's gate — the earlier suspicion of a `:ui` miscount is withdrawn. `0092`: +the arithmetic of `CAP-076-FINDINGS.md` §1/§2/§4/§5/§10 re-added by hand (33 claims = 14 snapshots + 19 user actions; 109 = 52 × 2 + 5; 34 = 16 × 2 + 2; 12 claims = 8 + +2 + 1 + 1) — consistent; the release facts match GitHub (Phase 6). + +**A93-SES-06 — Session records that disagree with the repository (sub-review of `0069` … `0089`, each re-verified).** (S) +- `ai-sessions/0073_CROSSCHECK_RESULT_2026_10_06.md:155` "968 citations … (850 of them in `REVERSE_ENGINEERING.md`)" vs its own appendix heading `:756` + "`REVERSE_ENGINEERING.md` (758)" (the appendix sums to 968 with 758) — M: both lines read. +- `ai-sessions/0079_MAINTENANCE_RESULT_2026_10_08.md:7` Status "pull request open, not merged" — PR #15 was merged and 1.1.1 released (tag `v1.1.1` on `86a6fb3`, + `RELEASING.md:331`); `0080` never updated it (§4a) — M: Status line read. +- `ai-sessions/0081_REVIEW_RESULT_2026_10_08.md:22` "26 Test-ID rows" / "26 of 26 TESTPLAN rows" — commit `d336884` changed **22** rows + (`git show d336884 -- TESTPLAN_BLUETOOTH_HCI_SNOOP.md | grep -c '^+|'` → 22; 26 = capture↔ID pairs); its Status "commit/push awaiting the maintainer's word" while §G + records the commits — M. +- Smaller (S): `0069` Summary "Nothing is committed, tagged or published" against its own Status; `0069` "59 short rows" vs "68"; `0078` "13 of 13" Kotlin files with the + AGPL header — the diff has 14; `0079` "31 without merges" — `git log --no-merges v1.1.0..7101d9c` gives 30; `0082` `:ui` "+14" vs per-phase sum 15; `0089` "+8" vs 7 new tests. +- Category: errors in records. Label: 🟢. Recommendation: one dated correction line per RESULT (§4a). Severity: minor (the first three), nit (the rest). + +**A93-SES-07 — `AI_SESSION_LOG_PROCEDURE.md` §4b is not followed consistently.** (S) +- "(this commit)" lines never back-filled with the hash (§4b.2) in 16 of the 19 sessions `0069` … `0088` that have one (only `0074`, `0083`, `0084` were); Deferred items + marked "This RESULT only — no task in `TODO.md`" (§4b.1) in `0074:438–440`, `0075:131–133`, `0077:160`, `0083:138–140`, `0086:138–139` — and `0081` A81-D-12 states + every Deferred item of `0070` … `0080` has a `TODO.md` line, which is false for three of them; stale "Next:", "PR opened / not merged" lines in the Progress of `0072`, + `0076`, `0077`, `0078`, `0083`, `0084`, `0085`, `0087`, `0088`, `0089`. +- Category: process drift. Label: 🟢 (sub-review, spot-checked M on `0079`). Recommendation: a closing step for the *next* session — back-fill the hash, close the + Progress lines, update INDEX and Status (`AI_SESSION_LOG_PROCEDURE.md` §4a/§4b) — and a lint check for "(this commit)" left unfilled after the next session. Severity: minor. + +**A93-SES-08 — ADR-060 entered `DECISIONS.md` as a "Proposed" heading, against the prompt; the lint forced it.** (S) +- Location: `DECISIONS.md:2667` "## ADR-060 — … (DRAFT — proposed, not accepted)", `id_registry.csv` (`ADR-060,adr,proposed`); `ai-sessions/0082` RESULT ≈ 330–336 records the + deviation (its prompt kept a draft in the RESULT and `TODO.md`); the maintainer later kept it as a draft. Cause: `lint_docs.py` requires a `DECISIONS.md` heading for + every ADR number cited anywhere. +- Category: governance (`AGENTS.md` §6 — "never write an ADR … may draft it, clearly labelled"). Label: 🟢. Recommendation: **decision for the maintainer** (§7): + allow a labelled "Proposed" heading in `DECISIONS.md` by a note in `AGENTS.md` §6, or let the lint accept draft ADR numbers from a list. Severity: minor. + +**A93-SES-09 — 1.2.2 was released without its hardware run and without the exception note `RELEASING.md` uses for 1.0.0.** (S) +- Location: `RELEASING.md:334` (1.2.2 row: "— (CAP-076 not run)"), checklist C3/C4 (`RELEASING.md:55–57`); the only recorded exception is for 1.0.0 (`RELEASING.md:139`). +- Category: process (an unrecorded deviation). Label: 🟢 (M: `grep -n Exception RELEASING.md` → 1 hit, line 139). Recommendation: a dated "Exception recorded for 1.2.2" + note with the maintainer's chat decision of 2026-10-14. Severity: minor. + +**Checked (sub-review, re-checked on samples M):** every 🟢 promotion and every ADR or ADR Update in `0069` … `0089` has a verbatim maintainer chat answer recorded, +except ADR-060's heading (A93-SES-08); the arithmetic of `0069` (173 ledger rows), `0073` (968), `0077`, `0081`, `0086` (47 = 9 + 35 + 3) holds. + +### Phase 8 — Application (`android/`; two read-only sub-reviews — `:data`, and `:hardware`/`:domain`/`:app`/`:ui`/CI — every item below re-checked in the code by the main session; marks: M = found by the main session, S = found by a sub-review and re-verified here) + +Paths: `data/…` = `android/data/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/`, likewise `hardware/…`, `app/…`, `ui/…`. + +**A93-APP-01 — "Use different Buds" acts on a second `BudsCompanionPairing`; the repository keeps connecting to the old Buds by name.** (S) +- Location: `app/…/MainActivity.kt:143` `private val companionPairing by lazy { BudsCompanionPairing(this) }` and `:273` `companionPairing.forgetAssociations()`; the + repository uses the `@Singleton` from `app/…/di/RepositoryModule.kt:72–73` (`bondedDeviceProvider = companionPairing::bondedDevice`, `:86`); + `hardware/…/BudsCompanionPairing.kt:67/97/120` — `chooseAgainRequested` is an instance field that turns the name fallback off. +- Problem: the flag is set on the Activity's instance only; the singleton's `bondedDevice()` still falls back to "the single bonded Pixel Buds" by name, so a + Connect or pull right after "Use different Buds" reaches the old Buds — contrary to `ARCHITECTURE.md` §9 ("until a new association exists no device is taken by its + name"); a rotation also creates a fresh Activity instance with the flag false. +- Category: application defect. Label: 🟢 (code reading; not observed on hardware — `CAP-068` tested "Use different Buds" with one pair only). Recommendation: inject + the singleton into `MainActivity` (the Activity still launches the `IntentSender`), or keep the flag in one shared holder; a JVM test with two lookups. Severity: + **major** (a user-facing function does not do what it says; can talk to the wrong device). + +**A93-APP-02 — A re-open's "Changed since the app last saw it" is overwritten by the equal `4:13` answer (CAP-076 defect (a)).** (S, confirmed in code) +- Location: `data/…/BudsRepositoryImpl.kt:385–386` `if (_ancMode.value != null && mode != _ancMode.value) AncModeCause.CHANGED_SINCE_LAST_READ else + AncModeCause.READ` — used for both the snapshot `Get`'s Notify and the `ReadSetting 4:13` answer; the second, equal answer returns `READ` and replaces the cause. +- Evidence: `CAP-076-FINDINGS.md` §6.2 (12 of 12 sessions: the Notify first, then the read); the test "the automatic re-open subscribes … again" + (`BudsRepositoryImplTest.kt` ≈ 3244–3280) answers the `Get` and the read with **different** modes, which never happens on hardware, so the defect passes it. +- Recommendation (`CAP-076` P1): return `null` (keep the cause) for an equal read, as `causeOfStream` does; fix the test to answer both with the same mode. + Severity: minor (an (i) line). + +**A93-APP-03 — A tap's `Set` can take the `Get`'s own Notify as its answer (CAP-076 defect (b), the silent tile tap).** (S, code path confirmed; 🟡 as the cause) +- Location: `data/…/BudsRepositoryImpl.kt:695–707` — every Notify is emitted to `_ancNotifies` (the `Get`'s wait), then to `_ancOutcomes` (the `Set`'s wait) even + when `answersGet`; `:973–993` — `ancSetOnClaim` subscribes to `_ancOutcomes` (`sendAndAwaitOnClaim`, `CoroutineStart.UNDISPATCHED`, `:1693`) and returns + `Success` on `AncOutcome.Notified`. The repository scope is `SupervisorJob() + Dispatchers.Default` (`app/…/di/RepositoryModule.kt:57`): the `Get` waiter can + resume on another thread, send the `Set` and subscribe before the inbound thread emits `Notified` for the `Get`'s answer. +- Evidence: `CAP-076-FINDINGS.md` §5.3 (06:46:08.96: no `Set` on the wire, no failure line, no toast). Recommendation (`CAP-076` P2): do not emit + `AncOutcome.Notified` for a Notify that answers a `Get`; a unit test for that rule (the interleaving itself cannot happen under `StandardTestDispatcher`). + Severity: minor. + +**A93-APP-04 — Settings-stream pushes can end a pending read's wait.** (S) +- Location: `data/…/BudsRepositoryImpl.kt:770–773` — `RoutedFrame.Setting` (RESPONSE **and** `SERVER_STREAM`) → `_maestroReplies.tryEmit(MaestroReply.Setting(value))` + without the `stream` flag; the reads' `accept` predicates match any `Setting` of the field (≈ `:1555`, `:1625`); the same for EQ (`MaestroReply.Value`). +- Problem: a push of the field being read (e.g. a `4:{13:n}` right after the subscription) completes the read as if it were the answer; the RESPONSE then + arrives with nothing waiting. Harmless while both carry the same value; wrong if they differ. +- Recommendation: carry `stream` in the reply and accept only RESPONSEs; likewise `_ancModeFresh` (`applyAncState`) should not end a Message Stream `Get`'s wait + (`refreshAncMode` waits on it — a stream packet can satisfy the Refresh and leave the real Notify classed as unsolicited). Severity: minor. + +**A93-APP-05 — Narrow error-model gaps (`AGENTS.md` §3/§8).** (S) +- Location: `hardware/…/BudsCompanionPairing.kt:177` `manager.associate(…)` without `try/catch` (a `RuntimeException` leaves `associationInFlight = true`, every later + tap reports "already in progress"); `hardware/…/RfcommBudsTransport.kt:336–341` — "still the current, open connection?" and `connection.auxSockets[channelId] = …` + are not atomic: a loss in between leaves a DLCI 0x04 socket open after `closeAll()` (the one-connection-per-channel collision of `ai-sessions/0039`); the + application scope has no `CoroutineExceptionHandler`, and `DebugSettingsStore`/`DarkModeSettingsStore` flows have no `.catch` for a corrupt preferences file. +- Recommendation: catch and reset; do the check-and-put under the transport's lock and re-check after; a scope handler that logs; `.catch { emit(emptyPreferences()) }`. + Severity: minor. + +**A93-APP-06 — Tests that do not test what they say.** (S, verified) +- `android/data/src/test/…/codec/SettingsStreamFixtures.kt:119` `SET_TRANSPARENT_CAP051_1527 = "0812001401e8e880" + 16 × "00"` — the real frame is + `0812001401e8e880e4e68882be02fffe63abde07ef49dae8` (`tshark -r CAP-051-btsnoop_hci.log -Y 'frame.number==1527' -T fields -e data.data`): not real bytes (`AGENTS.md` + §11), and unused. The fuzz tests (`SettingsStreamCodecTest` "fuzzed stream packets never throw", `OversizedLengthTest`, `CodecRouterTest` "random chunks never throw") + run through `CodecRouter.decodeGuarded`, which converts a thrown exception into `onMalformed`; none passes `onDecoderFault` (`grep -rn onDecoderFault + android/data/src/test` → 0), so a decoder that throws still passes the "never throw" test. `BudsRepositoryImplTest` ≈ 3097–3170 still names "ADR-062 Update 2 … a + re-claim's Get answer" / "Notify on the held claim". +- Recommendation: real bytes or delete; count `onDecoderFault` calls and assert 0; rename the stale tests. Severity: minor. + +**A93-APP-07 — Accessibility.** (S) +- `ui/…/SettingsUi.kt:151–164`, `ui/…/ControlsScreen.kt:212–219`, `ui/…/DebugScreen.kt:61–64`: the label is a separate `Text`, so TalkBack reads "Switch, on" with no + name; `ui/…/EqScreen.kt:356–365`: the five band sliders have no `contentDescription`/`stateDescription` (the balance slider has one, `:292`); + `ui/…/OpenControlNavHost.kt:322`: the tab icon repeats the visible label. +- Recommendation: `Modifier.toggleable(…, role = Role.Switch)` on the row with `onCheckedChange = null`; semantics on each slider; `contentDescription = null` on + the tab icon. Severity: minor. External evidence: developer.android.com, "Make apps more accessible" (merge a control with its label) — not fetched this session. + +**A93-APP-08 — UI wording and small code issues.** (S unless marked) +- The mode has four labels: "ANC mode: ACTIVE" (`ui/…/AncScreen.kt:160–162`), "Noise cancellation" (the buttons), "Active" (the tile, `domain/…/AncTile.kt:42`), + "Connected — ANC: ACTIVE" (`app/…/OpenControlApplication.kt:96`); enum and class names on screen (`AncScreen.kt:186` `connectionState::class.simpleName`, + `EqScreen.kt:207` "HEAVY BASS"); "Use different Buds" removes the association without a confirmation (`ui/…/SettingsMenu.kt:201`); lint `ModifierParameter` at + `ui/…/SettingsMenu.kt:96` (M: confirmed in `ui/build/reports/lint-results-debug.xml`); KDoc of removed 1.2.x parameters (`SettingsMenu.kt:78–80`, `:107–108`) and the + `MESSAGE_STREAM_LINGER_MS` KDoc ("also after the noise-control tab … is left (ADR-061)"); the log line of a same-mode push prints the kept cause + (`data/…/BudsRepositoryImpl.kt:419`, `CAP-076` defect (c)); `MainActivity.kt:217` a `Toast` on the Activity from `applicationScope`; `BudsForegroundService.kt:224` + `getLaunchIntentForPackage()` may be null; stale `TODO(verify)` in `BudsCompanionPairing.kt:51–53, 144–145` (hardware-confirmed since `0041`/`0043`). +- Recommendation: one label function from `ANC_MODE_LIST_ORDER`; human words for states and presets; a confirm dialog; the trivial fixes. Severity: nit (each). + +**A93-APP-09 — `.github/workflows/lint-docs.yml` runs with the default token permissions.** (S, verified: no `permissions:` key) +- Location: `.github/workflows/lint-docs.yml`. `.github/workflows/android.yml` sets `contents: read` and pins actions by SHA. Recommendation: `permissions: contents: read`. External + evidence: GitHub Docs, "Automatic token authentication — Modifying the permissions for the GITHUB_TOKEN" (least privilege) — not fetched this session. Severity: minor. + +**Checked and not a finding (Phase 8):** `AGENTS.md` §1/§2/§3/§7/§12 hold (sub-review, spot-checked M: manifests, AGPL header grep, `START_NOT_STICKY`, CDM pattern, +ring buffer, `FLAG_IMMUTABLE`, every receiver unregistered in `awaitClose`, MAC redaction in `BleLogger.describe`); the §3.2 constants (Phase 4); the 1.3.0 removal is +complete in the code; field 18 not applied; two `TODO(verify)` in `:data` (`EqFrame.kt:39`, `CaseBatteryFrame.kt:53`) are still valid open hypotheses. Rejected from the +sub-reviews: "`AGENTS.md` §2 still lists `BLUETOOTH_SCAN`" — §2 lists what *may* be requested (with `neverForLocation`); the app requesting less is compliant. +`targetSdk = 34` (lint `OldTargetApi`) is a decision (ADR-029), listed in §7. + +### Phase 9 — APK reverse engineering (`v1.0.955078536-10253511` only — Checkpoint 0: the newer APK is not decompiled; `REVERSE_ENGINEERING.md` read in full) + +Mechanical work in this phase (ADR-017 boundary — search, list, explain; no relevance decision, no promotion): the single-writer claim of `0090` +re-checked (`grep -rn 'Lgea;->I(Ljava/lang/String;Lgcq;)V\|Lgck;->I(…)V' apktool-output/smali*/` → 1 site, `fxb.smali:1237`, ✔); the field-23 path traced +end to end; a "Revision 6" search; the users of the marker types `qhq`/`qjf`/`qis`/`qiq` (settings 23/31/37/35) listed (`grep -rlw` → only `qhr.java` and their own +files, plus `qhq` in `fxb`/`gea`/`gck`). Findings on the document follow; the new leads are in §5b. + +**A93-RE-01 — Self-references by line number have drifted.** +- Location: `REVERSE_ENGINEERING.md:988` "(line 894 above)" (the bullet is now at ≈ 946), `:1234` "field register (line 856)" (now ≈ 1021–1029), `:1247` "line 999 + above", `:1433` "bonus register (line ~1068)" (now ≈ 1150–1176). +- Category: stale cross-references (the file grew ~3,000 lines since). Label: 🟢. Verified (M). Recommendation: refer by entry and heading ("the `qhr` register + above"), never by line. Severity: minor. + +**A93-RE-02 — A broken edit and a duplicated discovery in the `frb`/`fuh`/`glk`/`gjv` entry.** +- Location: `REVERSE_ENGINEERING.md:3374` starts mid-sentence ("decoded shape) is now the more promising route — pursued this same session …") — the start of the + sentence is missing; the `ai-sessions/0013` Update (dated 2026-09-13) sits after the `0023` Update (2026-09-15), and both report finding `gjv.p()`'s caller + (`ftw.smali:337`) "via a different strategy than the prior (failed) passes" — `0023`'s "missed by both prior passes" is wrong, `0013` had found it two days earlier; + `:3284` "6 classes implement it" lists seven (`fxm`, `fvp`, `fwc`, `fwx`, `fxg`, `fyq`, `geg`). +- Category: corruption / chronology / arithmetic. Label: 🟢. Verified (M). Recommendation: restore the sentence from git history (`git log -S 'decoded shape) is now + the more promising' -- REVERSE_ENGINEERING.md`), add a correction note on `0023`'s claim, "seven classes". Severity: minor. + +**A93-RE-03 — The "Correlation status with `PROTOCOL.md`" table stops on 2026-10-06.** +- Location: `REVERSE_ENGINEERING.md:4541–4580` (last row `ai-sessions/0073`, then an empty template row). Missing: field 21 = "Volume level notifications" (🟢, + 2026-10-06, `0072`, `PROTOCOL.md` §4.5.8a); `GetHardwareInfo` field 7 read by position (🟢, 2026-10-09, `0082`/ADR-058); the wall-clock field 2 (🟡 recorded, + 2026-10-07, `0077`); setting 13 = the app's only ANC-state source (🟢 §4.1, 2026-10-14, `0090`). +- Category: incompleteness (the table exists so a finding is not "rediscovered"). Label: 🟢. Verified (M). Recommendation: add the four rows; drop the empty row. + Severity: minor. + +**A93-RE-04 — The call-graph block still shows four `GetSoftwareInfo` calls.** +- Location: `REVERSE_ENGINEERING.md:4512` — "`fxm.i()` → `fxm.c(goq, goq)` x4 [`maestro_pw.Maestro/GetSoftwareInfo` pw_rpc unary …]". `0073` established (the `qjb` + entry's Update of 2026-10-06, `fxm.java:53`, `npy.java:82–90`, `fxk.java:22–36`) that the app opens pending unary calls and sends **no** `GetSoftwareInfo` request — + the Buds' announcement completes them (0 requests in 80 logs; `0090` E-17: 0 in 96). +- Category: stale. Label: 🟢. Verified (M). Recommendation: "opens 4 pending unary calls (no request on the wire); the Buds' unsolicited announcement completes + them". Severity: minor. + +**A93-RE-05 — The UUID register keeps a superseded 🟡.** +- Location: `REVERSE_ENGINEERING.md:4077` (row `25e97ff7-…` "pigweed internal rfcomm socket": "🟡 HYPOTHESIS (strong) that Sent-direction payload content specifically + carries `libmaestro`'s settings commands") — 🟢 since ADR-019 (2026-08-30) and ADR-034 (pw_rpc `maestro_pw.Maestro`/`WriteSetting`, 2026-09-20). +- Category: stale label. Label: 🟢. Verified (M). Recommendation: "🟢 (ADR-019, ADR-034)". Severity: minor. + +**A93-RE-06 — Smaller document defects.** +- `REVERSE_ENGINEERING.md:2506–2508` — an `ai-sessions/0069` pointer inserted inside a sentence ("Included here only to keep this *(pointer …)* register complete"); + `:4144` "If the Message Stream framing hypothesis (`PROTOCOL.md` §2.1) is confirmed" (🟢 since ADR-009); `:4485` the native-libraries note still offers + "`pbtk-from-binary`" (ADR-041: `pbtk` does not apply; same in `APK_REVERSE_ENGINEERING_PROCEDURE.md`'s intro, "running `pbtk`"); `:4595` "treat static findings as + 🟡 HYPOTHESIS until correlated" while the document labels code existence 🟢 (code) — say "a protocol reading from code is 🟡 until a capture shows it"; the `qhr` + register rows 5 and 14 (`:1155`, `:1164`) still read "not independently named" / "write-only" with no pointer to the `0073` names (5 = diagnostics, 14 = eartip + seal test, `:1449–1451`); the `MaestroDeviceSettingsProviderService` entry's "Open questions" still lists field 5's and field 6's caller (answered by `0073`/`0025`). +- Category: stale / wording. Label: 🟢. Verified (M). Recommendation: fix each in place or by a one-line pointer. Severity: nit. + +**Checked and not a finding (Phase 9, M):** field 23 — the read handler stores it and nothing reads it back (`fxb.java:355–357` → `gck.H` → `gcg` case 1 writes the +raw bytes into `gct.c` → `gdw.y`; `grep -rn 'Lgdw;->y:Lgct;' apktool-output/smali*/` → one `iput` in `gcg.smali`, no `iget`) — so the register's "case 23 → `H`" and +`0073`'s "stored as raw bytes and never read" agree; "Revision 6" (the DIS firmware-revision string) is not in the APK (`grep -rIl 'Revision 6\|FirmwareRevision'` over +sources and `res/` → 0; positive control "pigweed internal rfcomm" → 1 file) — it is GATT, served to Play services, consistent with ADR-025; the GSND literals stay +absent (as `0073`). + +## 5. Undiscovered protocol leads + +### 5a. From the captures + +**L93-1 — `CAP-021`'s four GSND AUDIO waves are assistant sessions (the opener is on the wire).** 🟡 HYPOTHESIS (strong), 🟢 for the frames. +- Evidence (M): `tshark -r CAP-021-btsnoop_hci.log -Y 'btrfcomm.len>0 && (btrfcomm.dlci==8 || btrfcomm.dlci==9) && frame contains 01:09:00:03' -T fields -e + frame.number -e frame.time -e frame.p2p_dir -e data.data` (exit 0): the Buds' GSND CONTROL `01 09 00 03 0a 01 03` at 2092 (08:02:29.503), 2963 + (08:02:50.108), 3645 (08:03:27.109) and 4340 (08:03:58.307) — the four wave starts of `DESKRESEARCH_FINDINGS.md` 2026-10-03 item 4 — each followed within + 1–2 ms by the first DLCI 0x0a frame (2093 at 08:02:29.504; 1123 DLCI 0x0a payload frames in all = the burst of `PROTOCOL.md` §6). Every wave also carries + `01 09 00 03 0a 01 05`, `… 01 01` and `… 01 21` (e.g. 2297, 2340, 2341). `CAP-069` showed the same opener at 3 of 3 filmed assistant sessions + (`PROTOCOL.md:3658`, 🟢 for that capture); `CAP-021`'s press-and-hold was set to the digital assistant during the capture (`CAP-021-FINDINGS.md` §3). +- Not yet recorded anywhere (`grep -rn '2092\|0a 01 03'` over `PROTOCOL.md`, `DESKRESEARCH_FINDINGS.md` and `CAP-021`'s files → 0; positive control + `CAP-069-FINDINGS.md:93`). +- Proposal: a dated note on `PROTOCOL.md` §6's DLCI 0x0a item and `DESKRESEARCH_FINDINGS.md` 2026-10-03 item 4 — "the four waves begin with the assistant-session + opener (🟡 assistant sessions started by press-and-holds)"; the codes `0a 01 05`/`01`/`21` as an open sub-question (session states?). Test: a filmed + assistant hold with the app's GSND channels logged (already the shape of `CAP-069` section III). + +**L93-2 — The SASS Group `0x07` codes are defined by the official page** — see A93-PROT-03/04 (spec fetched 2026-10-15; `CAP-019` 786–810). + +**L93-3 — ATT `0x0044` is never a Buds link** — closed by scan, A93-PROT-07. + +### 5b. From the APK + +**L93-4 — Setting 23's content is unknown to this app version: the Buds send a field the schema does not have.** 🟢 for the code and wire facts, 🔴 for the meaning. +- Evidence (M): `qhq` (the setting-23 type) decodes to **0 fields** (`python3 -I scripts/decode_rawmessageinfo.py jadx-output/sources/defpackage/qhq.java` → "0 + field(s)"); the Buds answer `23:{4:{1:0}}` in every log (`DESKRESEARCH_FINDINGS.md` 2026-10-06 item 3, "constant in every log"); the app keeps the unknown bytes + (`gcg` case 1 → `gct.c` → `gdw.y`) and never reads them (one `iput`, no `iget`). The field is therefore defined by firmware newer than this app's schema, or by + another product family. +- Proposal: when the maintainer decides to decompile `v1.0.990706425-10260911` (`TODO.md` §4), check `qhq`'s successor for a field 4 and the reader of setting + **39** (asked only by that version, `CAP-069` 1333/7261/8465 → `UNKNOWN`) — both with `schema_batch_extractor` and `citation_checker`, as `APK_REVERSE_ENGINEERING_PROCEDURE.md` §7 says. + +**L93-5 — Settings 24, 26, 30, 31, 32 stay unnameable from this version** (no write site, no reader case in `fxb`; 31 = the 0-field marker `qjf`, used only by +`qhr.java`): a checked negative of this pass, recorded so the next pass starts from the newer APK rather than this one. + +## 6. Document improvement proposals + +Per document: what a reader needs from it now (after 1.3.0), what stands in the way (finding IDs of §4), what `ai-sessions/0068` §6 proposed and what of +that is still open, and one concrete proposal. All are proposals for a MAINTENANCE session; project law (`AGENTS.md`, `PROJECT_RULES.md`) and FACT/ADR texts +need the maintainer. + +| # | Document | Problems now (§4) | `0068` §6 proposal — state | Proposal (this audit) | +|---|---|---|---|---| +| 1 | `ARCHITECTURE.md` | ARCH-01 (major), ARCH-02 … 05 | "rewrite as built" — done for §3.2 and the permissions; §2.4, §3.1's table, §8.1, §9 still mix 1.2.x and 1.3.0 | Not under rule 9a: rewrite §2.4/§3.1/§8.1/§9 in place as "as built at 1.3.0"; one "History" subsection at the end for the 1.2.x hold/re-claim; §15 lists every open item (incl. §6.0b (b)). | +| 2 | `DESKRESEARCH_FINDINGS.md` | PROT-01 (its 2026-10-14 §2 is the source), DR template drift (Appendix A DR-c5), a wrong "Option A" in the 2026-09-04 round 2 "Promoted to", "12 of 17" left uncorrected next to "14 of 19" | "add the later cross-capture analyses" — done (10-03, 10-04, 10-06, 10-14) | Relax the template to "Trigger · Method (command) · Logs · Result (label) · Promoted to" and apply it to the five free-form entries; fix the two slips. | +| 3 | `AGENTS.md` | GOV-05 (§6 "framing not yet settled"), GOV-06 (§10 ViewModel) | §5/§6 history paragraphs — still long; DI bullet, §13 hygiene — done | Project law: each section = the current rule (5–10 lines) + "History: `DECISIONS.md` ADR-…" — the dated notes move to the ADRs they cite. | +| 4 | `CAPTURE_BLUETOOTH_HCI_SNOOP.md` | CAP-01, CAP-02 | "index one line per capture" — not done (rows are paragraphs) | Generate §9 from `id_registry.csv` + one summary line per FINDINGS; delete the superseded tip layers of §4.1 (rule 9a does not cover this guide); fix the stale status lines. | +| 5 | `MAINTAINING_DOCS_SITE.md` | HK-04 (push-to-main text) | "add the lint checks; `dist/` excluded" — done | Rewrite the flow for the PR-only `main` and the two bot PRs. | +| 6 | `REVERSE_ENGINEERING.md` | RE-01 … 06 | "split into current reference / archive of checked negatives / history" — not done (4,606 lines, ≈ 433 KB) | Do the split now: (a) the current register (classes that matter, the `qhr` register as one table with type, write site, reader, wire evidence, ADR, app status), (b) "checked negatives" (KPI/Primes/GNP/WorkManager/endpoint service ≈ 1,500 lines), (c) dated trace history; no line-number self-references. | +| 7 | `AI_SESSION_LOG_PROCEDURE.md` | HK-05 (INDEX status drift), SES-02/03 (claims written, not checked) | "closing checklist, lint on Status" — Status lint done for RESULTs | Extend the lint to INDEX cells; the closing checklist gets "every compliance line quotes its command output" and "every document the prompt names as changed was re-read after the change". | +| 8 | `PROJECT.md` | GOV-01, GOV-02, GOV-03 | "Status after 1.0.0 paragraph" — done, then grew into a run-on history | Replace the status narrative by a five-line "current release" box + pointer to `CHANGELOG.md`; name `APP_TESTPLAN.md` in phase 6. | +| 9 | `TESTPLAN_BLUETOOTH_HCI_SNOOP.md` | CAP-03 | "strip Evidence cells to pointers" — not done (22 of 103 cells > 300 characters) | Do it mechanically (a script that moves each long cell to the named FINDINGS and leaves "→ `CAP-NNN-FINDINGS.md` §x"). | +| 10 | `CHANGELOG.md` | HK-06, CAP-01 (the 1.2.0 count), PROT-01 (the 1.3.0 "12 of 77") | "Keep a Changelog form for releases" — done | Dated corrections for the three slips; nothing else. | +| 11 | `PROJECT_RULES.md` | GOV-04 | 9a/4a amendments — done (9a scope, 4a scripts) | Rule 13 → "read what `AGENTS.md` §0.1 lists". | +| 12 | `TODO.md` | HK-01, HK-02 | "open items only" — done 2026-10-03, now drifting back (five `[x]` items) | A `lint_docs.py` check: no `[x]` in `TODO.md`. | +| 13 | `PROTOCOL.md` | PROT-01 … 09 | "per entry a Current block + collapsed History; §6 split open/answered" — not done (3,771 lines; e.g. §2.2a's L-1 thread has 8 dated Updates, §6 holds ~25 `[x]` items beside a 2-item "Resolved" subsection) | Start with the five most-read entries (§2.2a, §4.1, §4.2, §4.3, §4.5): a "Current" box on top, history below; move every `[x]` of §6 into "Resolved" (or delete it — the answer lives in its entry). | +| 14 | `WORKSTATION_PREPARATIONS.md` | GOV-10, GOV-11 | "verified package list with a check command; move Disaster Recovery" — check list done; DR still here (also in README) | Use the vendors' recommended installers (native Claude Code installer or its signed dnf repo); signed repositories only; one editor; add `capinfos`, `uv`. | +| 15 | `APK_REVERSE_ENGINEERING_PROCEDURE.md` | RE-06 (intro still names `pbtk`), HK-03 (§7 checklist not applied to the second APK) | "rewrite as the procedure actually used" — done (§3, §7) | Drop `pbtk` from the intro; add "register the APK in `reverse-engineering/APK_VERSIONS.md` the day it is pulled" as step 0 of §7. | +| 16 | `CONTRIBUTING.md` | GOV-09 | "firmware report section; test rules pointer" — done | Add "How a change gets in" (PR-only `main`, the two CI jobs, `lint_docs.py`). | +| 17 | `RELEASING.md` | HK-01 (E3 not followed) | "release log, hotfix path, firmware runbook" — done | E1 lists every file to update after a release (README status, `PROJECT.md`, `TODO.md` §1 emptied, `ARCHITECTURE.md` §5a) and a grep for the old version string. | +| 18 | `SECURITY.md` | — (1.3.0's new inbound path, the settings stream, is fuzzed — but see APP-06 on what the fuzz tests assert) | "supported versions, fingerprint, private reporting" — done | Name the settings stream among the parsed inputs, and say that the fuzz tests assert the decoder-fault count once APP-06 is fixed. | +| 19 | `APP_TESTPLAN.md` | CAP-01, CAP-04 | (new since `0068`) | Fix §Y's columns; one line per release at the top saying how many MAESTRO requests a Connect sends and how that is checked (count the decoder's `dir=Sent REQUEST` lines). | + +## 7. Decisions for the maintainer + +| # | Decision | Advice | +|---|---|---| +| D1 | Correct the ratios of the 2026-10-14 FACT (A93-PROT-01) in `PROTOCOL.md` §4.1, §8, ADR-064's Context and the `[1.3.0]` known issue — a FACT text edit needs your approval (`AGENTS.md` §6). | **Yes**, by dated notes: "75 of 89 pushed; 12 of 89 lost to the Buds' `DISC`; 2 at re-opens". The FACT itself stands. | +| D2 | Correct "P-256" to "P-192" in `PROTOCOL.md:2188–2189` and `CAP-058` (A93-CAP-05). | **Yes** — the HCI log names it; it changes the stated security level of the pairing. | +| D3 | Close two `PROTOCOL.md` §6 items (SASS codes, A93-PROT-04; ATT `0x0044`, A93-PROT-07), fix the "in-use" code (A93-PROT-03), and record lead L93-1 (assistant sessions in `CAP-021`) as 🟡. | **Yes** — each rests on a fetched spec page or a full scan; no code follows (SASS stays out of scope, ADR-008). | +| D4 | A fix release for A93-APP-01 … 04 (the wrong pairing instance, the overwritten "changed since" line, the silent tile tap, stream pushes ending reads). | **Yes, a 1.3.1** from one FEATURE session (all four are small and testable with `FakeBudsTransport`), with a short capture (the next free CAP number) in the user without Play (Use different Buds, a reconnect with a changed mode, ten tile taps). | +| D5 | Draft ADRs in `DECISIONS.md` (A93-SES-08): ADR-060 sits there as "Proposed" because the lint needs a heading for every cited ADR. | **Allow it explicitly** with a one-line `AGENTS.md` §6 note ("a draft may stand in `DECISIONS.md` only with the maintainer's consent, headed DRAFT — proposed"), rather than special-casing the lint. | +| D6 | Provenance of ADR-002/004/005/007 (A93-DEC-04). | If you decided them at project start, add one dated line each saying so; otherwise leave them and accept the gap. | +| D7 | `targetSdk = 34` (lint `OldTargetApi`, ADR-029). | **Keep 34 for 1.3.x**; plan the step to the current target in its own FEATURE session with an ADR-029 Update and a hardware run (foreground-service and broadcast behaviour changes need testing on GrapheneOS). | +| D8 | Decompile the official app 1.0.990706425 (`TODO.md` §4; L93-4: setting 23's unknown field 4, setting 39). | **Yes, in a CROSSCHECK session**, after registering it in `reverse-engineering/APK_VERSIONS.md` (A93-HK-03). | +| D9 | The two big restructurings (§6 rows 6 and 13: split `REVERSE_ENGINEERING.md`; "Current" blocks in `PROTOCOL.md`). | **`REVERSE_ENGINEERING.md` first** (mechanical, no FACT changes); `PROTOCOL.md` afterwards, entry by entry, because every "Current" block restates FACTs and needs your review. | +| D10 | A recorded exception for 1.2.2's release without its hardware run (A93-SES-09). | **Yes**, one dated note in `RELEASING.md` next to the 1.0.0 exception. | + +## 8. Prioritised action list + +| # | Action | Findings | Who / session | Size | +|---|---|---|---|---| +| 1 | 1.3.1: one `BudsCompanionPairing` (inject the singleton), keep the cause on an equal read, no `Notified` for a `Get`'s answer, stream pushes do not end reads; tests with real bytes; a short capture | APP-01 … 04 | FEATURE + CAPTURE | M | +| 2 | Dated corrections of the 🟢 texts (ratios, P-192, "in-use") and the §6 closures — after D1–D3 | PROT-01 … 07, CAP-05, DEC-02 | MAINTENANCE (maintainer approvals) | S | +| 3 | `ARCHITECTURE.md` §2.4, §3.1 (ANC row), §8.1, §9, §15 rewritten as built at 1.3.0 | ARCH-01 … 05, SES-02 | MAINTENANCE | S | +| 4 | Test-ID swaps and the false-premise conclusions in the capture notes | CAP-06, CAP-08, CAP-09 | MAINTENANCE (capture clean-up, Appendix B as worklist) | M | +| 5 | "Thirteen → fifteen/seventeen requests" in six documents; `APP_TESTPLAN.md` §Y columns | CAP-01, CAP-04 | MAINTENANCE | S | +| 6 | Status/INDEX/back-fill hygiene, with a lint on INDEX statuses and `TODO.md` `[x]` | HK-01, HK-05, SES-06, SES-07 | MAINTENANCE + `lint_docs.py` | S | +| 7 | Fuzz tests assert `onDecoderFault == 0`; the fake fixture replaced by `CAP-051` 1527's real bytes | APP-06 | FEATURE (with 1) | S | +| 8 | `README.md`/`PROJECT.md`/`TODO.md` after-release updates (ADR count 58, "released", dates) and an E1 file list in `RELEASING.md` | GOV-01/02/07, HK-02, §6 row 17 | MAINTENANCE | S | +| 9 | Register 1.0.990706425 in `reverse-engineering/APK_VERSIONS.md`; decompile it (D8) | HK-03, L93-4 | CROSSCHECK | M | +| 10 | Split `REVERSE_ENGINEERING.md`; fix its broken sentence, stale line references and the correlation table | RE-01 … 06 | MAINTENANCE | M | +| 11 | Accessibility of switches, checkboxes and EQ sliders; one mode label | APP-07, APP-08 | FEATURE | S | +| 12 | Workstation and repository hygiene (`android/**/bin/` ignored, signed installers, `.github/workflows/lint-docs.yml` permissions, `MAINTAINING_DOCS_SITE.md` PR flow) | GOV-10/11, HK-04, HK-07, HK-08, APP-09 | MAINTENANCE | S | + +## 9. Statistics + +| Area | Findings | Major | Minor | Nit | +|---|---|---|---|---| +| Governance (GOV) | 11 | 0 | 8 | 3 | +| Protocol (PROT) | 9 | 1 | 7 | 1 | +| Captures (CAP) | 9 | 4 | 5 | 0 | +| Architecture (ARCH) | 5 | 1 | 4 | 0 | +| Decisions (DEC) | 4 | 0 | 2 | 2 | +| Backlog/release/housekeeping (HK) | 8 | 0 | 6 | 2 | +| Sessions (SES) | 9 (one is a pointer to PROT-01) | 0 | 6 | 2 | +| Application (APP) | 9 | 1 | 7 | 1 | +| Reverse engineering (RE) | 6 | 0 | 5 | 1 | +| **Total** | **70 entries = 69 findings + 1 pointer** | **7** | **50** | **12** | + +Leads: 5 (L93-1 … L93-5). Sub-review raw items behind the grouped findings: ≈ 400 (Appendix B). Commands re-run by the main session: the census over 54 logs, 47 +citation checks, a 103-log ATT scan, ≈ 40 targeted `tshark`/`pwrpc_decode.py` checks. External pages fetched: 2. + +## 10. Deferred documentation and commits + +- **Deferred documentation:** none written by this session — it is review only; every proposal is in §4, §6, §7 and §8 for a MAINTENANCE/FEATURE session. + `TODO.md` §6's "reading owed by `ai-sessions/0090`/`0092`" (`PROTOCOL.md`, `ARCHITECTURE.md`, `REVERSE_ENGINEERING.md` in full) is done by this audit — the next + MAINTENANCE session can delete that item. +- **Commits:** not committed — review only. Uncommitted: this RESULT, the 0093 PROMPT and the 0093 row of `ai-sessions/INDEX.md` (status still "planned"; the next + session updates it to "complete" when it commits). + + +## Appendix A — candidate findings (scratch notes copied; unverified until their phase; IDs assigned in §4 only) + +## Candidates from PROJECT_RULES.md (read in full 09:40) +- PR-c1 rule 18 (line 200): "Captures and large binary files go through Git LFS, never directly into the main repository" vs practice: several logs/films are gitignored/local-only (btsnooz, films) — check CAPTURE doc + .gitignore + CONTRIBUTING; possibly rule needs "or are kept local (listed in .gitignore)". +- PR-c2 rule 13 (line 163-166) reading list omits DECISIONS.md/PROJECT.md/TODO.md while AGENTS §0.1 and AI_SESSION_LOG_PROCEDURE §8 require them — inconsistency (minor). +- PR-c3 line 53 cites retired AUDIT_REPORT_2026-08-22.md — ok (marked retired). +## Candidates from PROJECT.md (read in full 09:41) +- PJ-c1 line 151: "**1.3.0** (prepared in `ai-sessions/0091`, 2026-10-14; not released)" — 1.3.0 released 2026-10-15 (tag v1.3.0, RELEASING §13 row). Stale. Also 1.2.2 "(...; not released)" then "released 2026-10-14" in same sentence — confusing. +- PJ-c2 line 39: status stamp "updated 2026-10-06 for 1.1.0" but rows cite 1.2.0 (lines 47, 49) — stale stamp. +- PJ-c3 heading line 136 "Status after 1.0.x (added ...; 1.1.0 added 2026-10-06)" — paragraph covers up to 1.3.0 as one run-on paragraph; doc-improvement (split per release or point to CHANGELOG). +- PJ-c4 line 26-27: phase 6 cites only TESTPLAN_BLUETOOTH_HCI_SNOOP.md; APP_TESTPLAN.md (the app's own plan) not named. +- PJ-c5 line 42: battery "Case and per-bud charging in the case: DLCI 0x02 SubscribeRuntimeInfo, ADR-043 and its Update" — verify vs ARCHITECTURE §5a. +## Candidates from ARCHITECTURE.md (read in full 09:50, 1098 lines) +- AR-c1 §2.4 line 153: Settings tab still lists "since 1.2.1 the Noise-control channel choice, ADR-062 … since 1.2.2 Noise control with Google Play services, ADR-063" as current; line 207 says gear→Settings is Dark mode · Case sounds · Use different Buds (1.3.0). Internal contradiction / stale. +- AR-c2 §9 line 922-923: "Local state persistence: … since 1.2.1 (0087, ADR-062) the Noise-control channel choice are stored" — stale since 1.3.0 (ADR-065; §2 line 122 says the key is no longer read). +- AR-c3 §3.1 table ANC row line 329: "reset at Connect" + "Since the 1.2.0 rebuild (ADR-061): the claim is held while the noise-control tab is on screen" presented as current; contradicts §3.1 bullet line 291 (cause not reset at Connect since 1.3.0) and §6.0b (hold gone). +- AR-c5/c6 §2.1 BudsTransport row (line 247) and §2 Hardware layer (line 128): "BluetoothGatt (secondary — case/charging characteristics)" / "Owns raw BluetoothSocket/BluetoothGatt objects" while §2 line 91 and §13 say no GATT client exists. Minor/ambiguity. +- AR-c7 §8.1 lines 829-849: design text "reads the firmware version string first (via DLCI 0x02's Rcvd block or DLCI 0x04's Device Information, whichever is confirmed…)" kept below the as-built blockquote — reader must reconcile; doc-improvement. +- AR-c9 §12 line 979 / §1 diagram line 72: `BleLogger` — verify the class exists in :hardware. +- AR-c10 §3.2 timing table: verify every constant against code (grep const val *_MS) in Phase 4. +- AR-c11 §5a "Changed by the Buds" row (line 510) and the settings row (507): accumulating history cells; ARCHITECTURE.md is not under rule 9a's keep-history convention → may be rewritten in place; propose (Phase 10). +- AR-c12 §9 permissions table + DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION claim: verify manifests + merged manifest (Phase 1). +- AR-c13 §13 line 1013 "JUnit 6 since 0078"; AR-c14 §14 toolchain versions (Gradle 9.7.1, AGP 9.3.3, Kotlin 2.4.20, KSP 2.3.12, Hilt 2.60.1, Compose BOM 2026.09.00, material3 1.4.0); AR-c15 compileSdk 37 — verify against gradle files (Phase 1/8). +- AR-c16 §3.1 line 286 stream fields list includes 16 but not 18; check applySetting handles 16/18 stream packets (Phase 8). +- AR-c17 §15 line 1053 "no architecture question is open" vs §6.0b (b) "remains a deferred proposal that needs a maintainer decision and an ADR" (line 704) — an open architecture item not listed in §15. +- AR-c22 §6.0b line 646 CAP-076 counts "13 taps with Play present — 8 refused at attempt 1 … 4 cut by Play's connect, one of them … without a message" — check against CAP-076-FINDINGS §5 (8+4=12 vs 13). +- AR-c25 §3.1 line 329 ANC row: "the connect-time Get/Notify pair (ADR-021/ADR-022)" + "`CAP-062` 10/10" etc. — verify frames in Phase 3 where cited. +- AR-c26 §2 line 100: pointer "ai-sessions/0033_FEATURE_RESULT_2026_09_18.md Phase 6" fine. +- AR-c27 §9.0a line 884: CDM name pattern `(?i).*Pixel\s*Buds.*` — verify in BudsCompanionPairing.kt. +- AR-c28 §6.0a line 572: START_NOT_STICKY — verify in the service. +- AR-c29 §12 line 981: ring buffer 20,000 lines — verify. +- AR-c30 §5 line 455-456: buffer max 1024 (0x04/0x08) / 4096 (0x02) — verify in CodecRouter. +## Candidates from PROTOCOL.md lines 1-1320 (read 10:00) +- PT-c1 §1 table line 153 + Option A status line 1078: BLE advertisement "🟡 HYPOTHESIS (confirmed as used by the Buds Pro 2)" — after CAP-011/043/059/054 (0 battery fields in 610 FE2C reports, 🟢 2026-10-06) the label is stale per rule 4 (relabel once contradicted). Check CAP-054-FINDINGS §1-2. +- PT-c2 §2.2a line 283-307 service list (Maestro, HeadGesture, EartipFitTest, Dosimeter, JitterBuffer, Multipoint, DynamicServerConfigService) vs "15 service descriptors" (REVERSE_ENGINEERING) — reconcile in Phase 9. +- PT-c3 §4.1 line 851 CAP-074 counts (82 Notify: 34 app / 48 Play; holds 9/35) — verify Phase 3. +- PT-c4 §4.1 line 856-864 (0090's FACT): 73/73, 39/39, 12 of 77, 359 of 382, 0 WriteSetting 4:{13} in 96 logs — re-derive with scripts/anc_setting13_crosscheck.py --census in Phase 2 (deep re-review of 0090). +- PT-c5 §4.1 line 869-875 CAP-076 note: 12/12 sessions, 41/41 holds, 68/68 twice (21 & 19) — verify Phase 3 against CAP-076-FINDINGS §4/§4.3. +- PT-c6 §2.2a L-1 thread: 7 dated Updates each "status unchanged" (0061,0062,0063,0067,0069,0075,0080,0083) — readability; propose consolidation table in §6 (Phase 10). Also §6 may duplicate. +- PT-c7 Plan for Phase 2: script to verify every (CAP, frame, hex) citation in PROTOCOL.md FACT entries against the logs (tshark frame.number==N data.data contains hex). +- PT-c8 §4.3 intro line 1036 "Five candidate mechanisms (seven options are listed today…)" — stale wording with pointer; doc improvement. +- PT-c9 §4.3 Option A lines 1104-1126 the 8s/20s timing claim: "internally-observed-only detail… until a citation is found" — AGENTS §7 (corrected 2026-09-24) already says it's a project choice; PROTOCOL still carries the unresolved claim; align (minor). +- PT-c10 §3 table (lines 634-646) four placeholder .proto rows 🟡 with "history" pointers — propose delete/archival (Phase 10). +- PT-c11 §4.1 line 708-711 "Open sub-question: CAP-001's first two ANC taps have no 0x12 frame" — never closed? check §6 / CAP-006 (4/4) — if CAP-006 answered it, stale. +## Phase 0 gate (10:05): BUILD SUCCESSFUL 2m07s, exit 0. JUnit XML: :data 1672 per variant (debug+release), :hardware 64, :domain 42 (JVM, test-results/test), :ui 92 per variant, :app none. 0 failures. Lint: app 6 warnings (3 AndroidGradlePluginVersion, 2 NewerVersionAvailable, 1 OldTargetApi), ui 1 (ModifierParameter), data/hardware 0 issues with severity. +- SES-c1: 0091 RESULT line 12/215 says ":ui 204 (summed)" = 102 per variant; the gate on the same commit ff27ee1 (no ui test change since) gives 92 per variant and `git grep -c @Test ff27ee1 -- android/ui/src/test` = 92. Check what 0091 line 215 says exactly; maybe ui dropped 10 tests in the removal and the "after" count was copied from "before". → Phase 7. +## Candidates from PROTOCOL.md 1321-3460 +- PT-c12 §4.3 Option D line 1331: "Status: 🟡 HYPOTHESIS — the service's existence is now confirmed" then "🟢 FACT handle range resolved" — status line stale-ish (allowed). §1 row says 🟢 existence. OK. +- PT-c13 §4.3 Option E line 1374: "DECISIONS.md ADR-035 unblocks a receive-only on-demand claim" — superseded by ADR-043 (pointer exists elsewhere). fine. +- PT-c14 §4.3 Implementation priority para (line 1539-1550): a maze of pointers; doc improvement (rewrite in place is not allowed by 9a for PROTOCOL… but 9a's convention is "keep old entry + dated Update"; a consolidated "current state" box exists at the top of §4.3). Propose moving history to an appendix (Phase 10). +- PT-c15 §4.4 line 1562-1564: "Every Sent frame is retransmitted once … two ACK variants" left standing with a correction blockquote below — compliant with 9a (PROTOCOL keeps history) but §6 line 2706 item marked [x] answered; ok. +- PT-c16 §4.5 preamble line 1627-1653: 2026-08 framing "field 5 { field 4 {…}}" + "13-byte constant prefix" with a pointer; fine. +- PT-c17 §4.5.3 line 1766-1777 rotation checklist "🟡 HYPOTHESIS field order = on-screen order" — refuted 2026-09-28 (line 1806) but the bullet's own status line 1779-1783 still says "🟡 HYPOTHESIS for the rotation-checklist's own field-order … and for whether it's the same feature as qht's ANC gesture loop" — both 🟢 FACT since 2026-09-28; stale status line without pointer (rule 4). minor. +- PT-c18 §4.5.4 Head gestures line 1830-1834: "🟡 HYPOTHESIS … Status: 🟡 HYPOTHESIS" — 🟢 FACT since 2026-10-04 (line 1846) — the bullet's Status line has no pointer; stale (rule 4). minor. +- PT-c19 §4.5.2 Status line 1731-1733 "🟡 for the OFF-direction wire value (the OFF write is on the wire … the label was not changed)" — deliberately left; odd but acknowledged. nit. +- PT-c20 §4.5.5 Status line 1873-1874 "🟡 HYPOTHESIS for the specific In-ear detection UI-label equivalence" — 🟢 since 2026-09-28 (line 1885 says "the 🟡 in the Status line above is 🟢 FACT") — pointer present. fine. +- PT-c21 §4.5.9 line 2044-2050 "Loud Noise Protection (firmware 4.467+) … 🔴 unconfirmed" + note 2026-10-06; fine. +- PT-c22 §5 line 2099-2111 step list still ⚪ ASSUMPTION for steps 3-6 while §5.2 is 🟡 and the 2026-09-24 pointer says parts of 4–6 are 🟢 — acceptable with pointer. +- PT-c23 §5 line 2320-2324 Status: "🟢 FACT for step 5's specific behavioral outcome (battery notification on reconnect), per TESTPLAN §3" — a FACT resting on a testplan UI observation, not a frame — evidence rule 3 (traceable to capture/code/hypothesis test). Check TESTPLAN §3. Candidate minor. +- PT-c24 §6 "Framing" first item line 2341: "[x] Resolved … Case battery on DLCI 0x08 (ADR-035) (pointer: moved to DLCI 0x02 ADR-043)" fine. +- PT-c25 §6 line 2397-2408 DLCI 0x08 groups 0x01/0x02/0x05/0x09 open — Phase 9 target 3. +- PT-c26 §6 line 2417: trigger of 0e 01 burst open; line 2422 DLCI 0x08 identity open (GSND) — Phase 9 target 3 (grep "gsnd"/"gsound" again + the UUID f8d1fbe4 in the APK). +- PT-c27 §6 line 2696: SASS codes 0x11/0x21/0x40/0x42 open — Phase 9 target 5 (SASS page) / capture pass. +- PT-c28 §6 line 2738-2776 DLCI 0x0a burst: open; CAP-069 §3 found assistant sessions `01 09 … 0a 01 03` — the §6 item not updated with the CAP-069 finding (TODO §4 mentions). Check whether §6 item has a 2026-10-04 pointer → it does not in the text read (ends with CAP-047 update). Candidate: §6 DLCI 0x0a item stale vs §2.3 GSND CONTROL in CAP-069 paragraph (line 602-606: "01 09 00 03 0a 01 03 at the start of every assistant session"). minor. +- PT-c29 §6 line 2777-2794 ATT 0x0044 burst: "stays listed only because no check was run on every capture" — could be closed by a script over all captures (Phase 2 lead: run `btatt.opcode==0x1b && btatt.handle==0x0044` + 0x180d check across all logs). +- PT-c30 §6 line 2814-2820 and 2874-2913: DLCI 0x08 zero-length Sent frames (05 0c, 04 02, 04 04, 04 11, 04 13, 04 15, 0e 04) — Phase 9 target 3; note CAP-069 identified Code 0x05 wear state, 0x16 head gestures, 0x14 hold action; "04 04"→Code 0x05 neighbor, "04 15"→Code 0x16 neighbor — so the 2026-09-16 "inconclusive" for 04 04/04 15 is partly answered by CAP-069 (0x05 = wear state, 0x16 = head gestures active) — the §6 item lacks that pointer. Candidate minor. +- PT-c31 §6 line 2846-2863 Settable byte item [ ] still open with pointer to ADR-049 — could be [x]. nit. +- PT-c32 §6 line 2940-2988 "two Notify without Get/Set" item: resolved by CAP-051 (press-and-hold) but checkbox still [ ]; and the 2026-10-06 note refutes the ftj.p lead. Candidate nit: mark [x]. +- PT-c33 §6 line 3132-3149 CAP-042 idle push item: open — Phase 2 lead? low. +- PT-c34 §6 line 3172-3230 MaestroDeviceSettingsProviderService: "2113→field 5 (still unnamed)", "2116→field 32 (CATEGORY_RV_BLOCK_AUTO_TEST)", "2115 Feature A" — Phase 9 target 1 (settings 5, 32). +- PT-c35 §6 line 3303-3354 BluetoothPriorityReceiver — resolved mostly; checkbox [ ] with residual. fine. +- PT-c36 §6 Behavior line 3360-3365: 🔴 why GSND follows "Media audio" — Phase 9 target 3 lead (GSND = part of A2DP sink stack? "GSOUND" = Google Sound? cross-vendor). +- PT-c37 §6 line 3403-3410: Loud Noise Protection/Adaptive Audio items duplicate §4.5.9; "press duration for pairing mode" item — never addressed; low. +## PROTOCOL.md read in full (3771 lines, 10:20). More candidates: +- PT-c39 §8 row 2026-10-15 + §4.1 note line 869-875: "41 of 41 holds pushed while Play held the Message Stream" — 41 = 29 (Owner, Play) + 12 (secondary, no Play) per PROJECT.md:151 → the §8 row conflates users. Verify CAP-076-FINDINGS §4 (Phase 3). minor. +- PT-c40 §6 line 3651: field-13 mirror item still "[ ]" though 🟢 since 2026-10-14 (pointer present) → tick. nit. Same for line 3656 item's embedded 🟡 field 13 (pointer present). +- PT-c42 §6 structure: "### Resolved" subsection holds 2 items while ~25 resolved [x] items sit in the lists above; the intro says "check items off with a date and a pointer" — inconsistent practice; propose: move [x] items to Resolved or drop the subsection (Phase 10). +- PT-c43 §7 "Buds out of range: ⚪ ASSUMPTION, evidence —": CAP-062/065 observed losses with `read return: -1` and ACL drops; could be updated (minor). +- PT-c47 LEAD (8a): CAP-021's DLCI 0x0a 1123-frame burst (§6 line 2738) vs CAP-069 §3's assistant sessions on GSND AUDIO opened by `01 09 00 03 0a 01 03` on DLCI 0x08 — CAP-021 was Group G press-and-hold (HOLD-001…004 assign "Digital assistant"); the burst (100 % Buds→phone, 215-byte frames, repeating `6d b6 db`) is plausibly assistant audio after a hold set to assistant. Check: is there a `01 09 00 03 0a 01 03` on DLCI 0x08 right before frame 2093 and does CAP-021-EVENT-NOTES put an assistant hold at ~179 s? (Phase 2.) +- PT-c48 LEAD (8a): §6 line 2696 SASS codes 0x11/0x21/0x40/0x42 and 0x34 — the official SASS extension page defines the code table (Get/Notify capability 0x10/0x11, Set multipoint 0x20, Set switching preference 0x21(?), Notify connection status 0x34, Notify SASS initiated connection 0x40, Indicate in-use account key 0x41, Send custom data 0x42) — fetch the page and close the item by external validation (Phase 2). +- PT-c49 LEAD (8a): §6 line 2777-2794 ATT 0x0044 burst "stays listed only because no check was run on every capture" — run `btatt.opcode==0x1b && btatt.handle==0x0044` and `btatt.uuid16==0x180d` over every log (Phase 2) to close it. +- PT-c50 LEAD (8b): "Revision 6" (DIS Firmware Revision String 0x2A26, also DI code 0x09) meaning open since 2026-08 — Phase 9: grep APK for the DI 0x09 handler / "Revision". +- PT-c51 §6 line 3659-3662: Device Information `03 0b` len 25 "Current FHN ephemeral identifier" 🔴 — scope-limited (ADR-008/027); fine. +## DECISIONS.md read in full (2934 lines, ADR-001…065 + draft ADR-060) 10:45 +- DEC-c1 ADR-064 Status line (2841): "— not yet built; its hardware run comes with the build" — stale (built in 1.3.0, released; the 2026-10-14 Update says built). Status line should be updated like ADR-044's was by a pointer; minor. +- DEC-c7 ADR-065 item 2 (2897-2899): field list excludes 18 but says "field 18 is read but not shown, as today" — check whether a stream packet 4:{18:…} is decoded/applied (Phase 8, applySetting). +- DEC-c12 ADR-056: "Untested: APP_TESTPLAN.md S9 (two Buds)" — check TODO §2 lists it. +- DEC-c16 ADR-064 item 1 bytes `7e 00 4b 03 10 15 1d ea 71 de 7d 5e 25 f5 ad 21 28 01 97 54 b0 7e` = CAP-051 1177 — verify in Phase 2 script. +- DEC-c18 ADR-062 Update 2 item (1): "HOLD_RECLAIM_STEP_MS, HOLD_RECLAIM_CAP" — removed in 1.3.0; grep for remnants (Phase 8). +- DEC-c19 Template (line 25-33) lists Status values Proposed/Accepted/Rejected/Superseded; practice uses long free-text statuses — fine, but lint (g) does not check ADR statuses; doc-improvement: add "Amended" / "Accepted (built …)" convention? low. +- DEC-c20 ADR-009: provenance not recorded (known, A58-GOV-04) — only ADR without maintainer provenance? Check all ADRs have a "maintainer" mention: ADR-001 (project start, none), 002 (none), 004, 005, 007 (none explicit), 008 (none), 013 "maintainer reviewed", 014, 015 … — Phase 5: list ADRs lacking explicit sign-off provenance: 001, 002, 004, 005, 007, 008 (project-start/early ones). AGENTS §6 requirement applies to agent-written ADRs; early ones may be maintainer-written. Report as information with a check. +## TODO.md / RELEASING.md / MAINTAINING_DOCS_SITE.md / APK_VERSIONS.md / tools/BACKLOG.md read in full (11:00) +- TD-c1 TODO §1 keeps [x] release rows for 1.2.2 and 1.3.0 while RELEASING E3 says "TODO.md §1 emptied" and TODO's own rule (line 5-6) deletes finished items → E3 not done for 1.3.0 (and 1.2.2); inconsistency between RELEASING checklist and practice. minor. +- TD-c2 TODO §1 line 28-29: the stored "Noise-control channel" choice ("Take it back") — moot since 1.3.0 (key ignored, ADR-065 item 5); stale. +- TD-c4 TODO §2 line 47-53 a [x] item with 7 lines of history in an "open items only" file; §5 line 205-212 [x] "Done 2026-10-14 … Next: the maintainer's push, PR and CI; then RELEASING B1–B3 … C3/C4" — all done (released 2026-10-15): stale; §5 line 243 [x] 1.2.1 steps — done item kept. nit/minor. +- TD-c5 TODO §3 line 99 names CAP-076-adb-checks-0092.log (no backticks) — check it exists in the CAP-076 folder (Phase 3). +- TD-c6/AV-c1 APK_VERSIONS.md line 48-50 "Not pulled or decompiled" + no table rows for v1.0.990706425-10260911, while the folder (base + 2 splits, 79 MB) exists since 2026-10-05 and TODO §4 says pulled; the doc's own rule "Register a new version here before starting analysis" — stale/incomplete (minor; known in TODO §4 but unfixed for 10 days). +- TD-c9 TODO §5 line 245 "**1.3.0 candidate (M):** volume-level notifications" — 1.3.0 shipped without it; relabel. minor. +- TD-c14 TODO §6 line 329-334 reading owed by 0089/0090 — this audit's full reading pass can close them (say so in the deliverable). +- RL-c1 RELEASING §13 1.3.0 row "published 2026-10-15T07:09:03Z", PR #56 merge aec17a5, bot PR #57 — verify with gh/git (Phase 6). +- MD-c1 MAINTAINING_DOCS_SITE.md line 9 "Commit and push to `main` as normal" and line 36-38 "commits the result automatically on every push to `main`" and line 64-66 — contradict RELEASING "Repository rules" (main accepts changes only by PR; the bots open PRs from bot/sitemap and bot/docs-sidebar, they no longer push to main). Stale since 2026-10-08. minor (process doc). +- BL-c1 BACKLOG.md line 261-263: two consecutive `---` rules before the footer. nit. +- BL-c2 BACKLOG.md "Still open: the wire-payload-vs-schema decoder and the tshark/DLCI-reassignment helper" + "APK version-diff tool (not built — no second decompiled version exists yet)" — consistent with TODO §4. +## CAPTURE_BLUETOOTH_HCI_SNOOP.md lines 1-1860 read (11:20); 7 subagents launched 11:10 (caps 001-025 / 026-050 / 051-076; sessions 0069-0079 / 0080-0089; :data; hardware+domain+app+ui) +- CB-c2 §4.1 lines 220-249: three stacked "superseded … kept per PROJECT_RULES §3" tip layers; rule 9a limits the keep-history convention to DECISIONS/PROTOCOL/RE, so this document may be rewritten in place — the doc's own justification cites the wrong rule scope (minor; doc improvement §6.4). +- CB-c6 line 1163-1164 Group AH outcome: "a ReadSetting sweep of fields 1–32" — PROTOCOL §6 (2026-10-03 correction) says fields 1–38 without 6, 8, 9, 10, 14, 20, 33; stale. minor. +- CB-c5 line 1243: "TODO.md's own 'Recommended priority order' §5" pre-restructure pointer (TODO header acknowledges such pointers). nit. +## CAPTURE_BLUETOOTH_HCI_SNOOP.md read in full (2329 lines, 11:40) +- CB-c10 line 1865 Group BK "**Status:** 🔲 planned (skeleton written 2026-10-12…)" contradicts the Result paragraph above it (run 2026-10-12/13, analysed 0088) and the Index row (analyzed). Stale. minor. +- CB-c11 line 2255 CAP-036 row App version "1.0.955078535" (every other row: 1.0.955078536) — typo or a real different version? Check CAP-036-EVENT-NOTES (Phase 3). nit/minor. +- CB-c12 Index legend (2315-2323) defines `promoted` status; no row uses it although dozens of captures fed 🟢 promotions — legend vs practice; propose dropping `promoted` or defining when to use it (Phase 10). minor. +- CB-c14 Column note 2302-2308 "the letter(s) from §4 (Z, A–Q, R, S …)" — groups run to BL now; stale. nit. +- CB-c15 §7 FAQ (2159-2164) and §8 (2175) speak of "PROTOCOL.md §4.1's opcode table … rows filled in" — §4.1 has no opcode table in that sense (it is the ANC entry); stale framing from 2026-08. nit. +- CB-c18 ARITHMETIC CHECK (Phase 3): Group BH text (1783) "thirteen MAESTRO requests (the twelve settings reads, SubscribeRuntimeInfo, then one GetHardwareInfo)" and the CAP-072/073 rows (2291/2292 "thirteen requests") vs ARCHITECTURE §3.1 (12 settings fields + EQ 4:16 = 13 ReadSettings) + SubscribeRuntimeInfo + GetHardwareInfo = 15; Group BL (1874) says 1.3.0 has seventeen (= 15 + 2) and "the skeleton said fifteen". So either "thirteen" is wrong by two or "twelve settings reads" includes EQ and one field is not read. Re-derive from CAP-073's log with pwrpc_decode (one session) in Phase 3. +- CB-c17 CAP-076 row lists `CAP-076-adb-checks-0092.log` as a file → check existence (TD-c5). +- CB-c19 CAP-074 row: "9 holds shown, 35 reached Play services" vs PROTOCOL §4.1 line 851 "press-and-holds 9 to the app and 35 to Play" — consistent. CAP-075 row "41 of 41 … 0 of 9 … 8 of 19" vs ADR-062 Update "8 of 31 holds shown within a second … 16 never" — 8 of 19 (take-it-back chains) vs 8 of 31 (all Play-user holds incl. hint-only 9 + 3 extra?) — 19 + 9 = 28 ≠ 31; check CAP-075-FINDINGS §4 (Phase 3; agent C reports numbers). +## TESTPLAN_BLUETOOTH_HCI_SNOOP.md + APP_TESTPLAN.md read in full (11:55). Usage limit hit ~12:00; all 7 subagents terminated early (work saved) — resumed after the reset. +- TP-c1 TESTPLAN §0.1 line 25 "Group A–Z, then AA–BE as of 2026-10-03" — groups run to BL; stale. nit. +- TP-c3 TESTPLAN §0.3 Evidence column rule ("pointer only, never the finding") and §7 item 3 are violated by most rows (ANC-001 … BATT-004 carry paragraphs of frames and results) — the doc's own rule vs practice; known in TODO §6 ("Evidence cells to pointers") but still live. minor (doc improvement §6.9). +- TP-c4 ANC-001 Evidence "Planned: `CAP-068` (Group BD) — a `Set` by the release build." next to the CAP-068 result — stale wording. nit. +- TP-c8 TESTPLAN §9 lists SWITCH-001/WELL-001 as "no capture scenario" while their §4c rows give scenario AT and evidence CAP-058 — inconsistency. minor. +- AT-c1 APP_TESTPLAN header line 27 "thirteen MAESTRO requests at Connect" (1.2.0) and line 37 "fifteen MAESTRO requests: O8" (1.3.0) vs its own Y2 note "seventeen requests per full session, not fifteen (CAP-076-FINDINGS §2)"; O8 row says Sound pull = ReadSetting 4:16 then exactly twelve reads → 13 reads + SubscribeRuntimeInfo + GetHardwareInfo = 15 (1.2.x), 17 (1.3.0). "thirteen" = the ReadSettings only. Same slip in CAPTURE doc Group BH/BI + rows (CB-c18) and APP_TESTPLAN V16/W1/X1/X2/X9 ("the thirteen requests"). Verify on CAP-073's log (Phase 3). minor (arithmetic/consistency, 6+ places). +- AT-c3 APP_TESTPLAN section Y (lines 430-451): the "Expected on the wire" column holds the result mark (✅/⚠️/not run) and the Result column is empty — columns shifted for every Y row (lint (f) counts cells only). minor (structure). +- AT-c6 APP_TESTPLAN Summary table rows A–S have no counts; fine (filled per run). +## REVERSE_ENGINEERING.md 841-3150 read (resumed session, Opus 5.5) +- RE-c1 stale self-references to line numbers inside REVERSE_ENGINEERING.md: line 988 "(line 894 above)" (the default-branch bullet is at ~946), 1234 "field register (line 856)" (register at ~1021-1029), 1247 "line 999 above", 1433 "bonus register (line ~1068)" (table at ~1150-1176). Line numbers drift as the doc grows; replace with section/anchor names. minor (doc improvement). +- RE-c2 line 2506-2508: an A68 pointer "*(pointer 2026-10-03 …)*" was inserted mid-sentence ("Included here only to keep this *(pointer…)* register complete") — breaks the sentence. nit. +- RE-c3 qhr register line 1175 row 29 and the later 0073 Update (1449-1453) name fields 5/14/21/3/6 — the register rows 5/14/21 still say "not independently named"/"HearingWellnessFragment-scoped toggle, not further named" without pointer to the 0073/0072 Updates (row 21 has the 0072 Update right below; rows 5 and 14 have none). minor. +- RE-c4 line 1172 row 23 "not found" write site and 1453 "Field 23 is stored as raw bytes and never read" — row text "case 23 calls geaVar.H" vs 0073 "never read" — the two statements disagree on the read side (fxb case 23 exists per row). Verify fxb.java case 23 in Phase 9. +- RE-c5 line 1203-1208 "Open questions: what fields 24/26/29's enum types actually are" — field 29 later resolved (0069 Update, true→2/false→1); open-questions bullet not pointed. nit. +- RE-c6 frb/fuh/glk/gjv entry: orphaned sentence fragment "decoded shape) is now the more promising route — pursued this same session …" directly after the 0023 sign-off sentence (broken edit; the start of the sentence is missing). minor. +- RE-c7 same entry: the 0013 Update (dated 2026-09-13) is placed AFTER the 0023 Update (2026-09-15) and both claim to have found gjv.p()'s caller (ftw.smali:337) "by a different strategy than the prior (failed) passes"; 0023's "missed by both prior passes" is wrong (0013 found it two days earlier). Chronology/duplicate-discovery inconsistency. minor. +- RE-c8 same entry: "6 classes implement it: fxm, fvp, fwc, fwx, fxg, fyq, geg" — seven names listed. nit (arithmetic). +- RE-c9 MaestroDeviceSettingsProviderService entry: "Open questions: field 5's and field 32's semantic identity" — field 5 named by 0073 ("diagnostics", gck.java:582/503), field 6 caller found (guy, 0025); open-questions bullet not pointed. nit. +- RE-c10 UUID register row 25e97ff7 (pigweed): "🟡 HYPOTHESIS (strong) that Sent-direction payload content specifically carries libmaestro's settings commands" — stale: WriteSetting/ReadSetting on DLCI 0x02 decoded and 🟢 (ADR-019, ADR-034, PROTOCOL §4.5). minor. +- RE-c11 line ~4095 "this companion app's own decompiled source contains no trace of DLCI 0x04/0x08's GATT/Fast-Pair-service handling" — mixes RFCOMM DLCIs with GATT; wording. nit. +## REVERSE_ENGINEERING.md read in full (4606 lines) — end +- RE-c12 "Correlation status with PROTOCOL.md" table stops at 2026-10-06 (+ an empty template row) — missing the later promotions/records: 0072 field 21 = "Volume level notifications" (🟢, PROTOCOL §4.5.8a), 0082/ADR-058 GetHardwareInfo field 7 by position (🟢 2026-10-09, PROTOCOL §6), 0077 wall-clock field 2 (🟡 recorded), 0090 field 13 = only ANC-state source (PROTOCOL §4.1 Update 2026-10-14), 0055 qht order is there. Incomplete. minor. +- RE-c13 "Call graph notes" block: "fxm.i() -> fxm.c(goq, goq) x4 [GetSoftwareInfo pw_rpc unary …]" reads as 4 requests; 0073 (qjb entry Update) established the app never sends GetSoftwareInfo — it opens pending unary calls the Buds' announcement completes. Block not pointed. minor. +- RE-c14 "Message Group / Code register" intro "If the Message Stream framing hypothesis (§2.1) is confirmed" — confirmed long ago (ADR-009); and the empty table row. nit. +- RE-c15 "Known limitations": "treat static findings as 🟡 HYPOTHESIS until correlated with a capture" vs the document's own (and PROTOCOL's) practice of labelling code-existence facts 🟢 (code). Clarify: 🟡 applies to the protocol-behaviour reading, not to code existence. nit. +- RE-c16 call-graph "ClassicBTReceiver -> ??? (not traced this pass — next entry point to follow)" open since 2026-08; 0073's connecting note partially covers it. Open lead (Phase 9 optional). +- RE-c17 Native-libraries note: ADR-017 paragraph mentions "pbtk-from-binary" — pbtk cannot read this APK (ADR-041); stale tool mention. nit. +- HK-c1 repo root holds an empty untracked directory scratch_0024 (created 2026-09-16, session 0024 leftover; git does not show empty dirs). nit housekeeping. +## DESKRESEARCH_FINDINGS.md 1-560 +- DR-c1 2026-09-04 round 2 "Promoted to: … PROTOCOL.md §4.3 (Option A GrapheneOS confirmation, …)" — Result 1 of that entry is Option C (HFP) on GrapheneOS, not Option A. Slip. nit/minor. +- DR-c2 same entry: the 0069 correction fixed "17 … 12" to "19 … 14" in one sentence, but the next paragraph still says "it is not connect-time as such; 12 of 17 connect/reopen-time samples show 0xe8" — uncorrected arithmetic (should be 14 of 19). minor. +- DR-c3 Option C (HFP AT+BIEV) entries 2026-09-04 rounds 1/2 call Option C "confirmed working … 🟢 FACT" for app use; ADR-040 (2026-09-24) says not consumable by an app on Android 14+ — check for a pointer in DESKRESEARCH (wire-level FACT stays true; app-consumability not claimed) → likely fine; verify no "implementable" claim. (Phase 2.) +- DR-c4 DLCI 0x08 battery triple "Option E" results (CAP-004/007/027/035) — ADR-043 moved Case battery to DLCI 0x02 and withdrew the DLCI 0x08 Case claim of ADR-035/039; check whether DESKRESEARCH entries carry a pointer (Phase 2). +- DR-c4 checked: Option E remains a FACT source (PROTOCOL §4.3 line 1030); not a finding. +## DESKRESEARCH_FINDINGS.md read in full (1124 lines) +- DR-c5 Template drift: the template's five fields (Trigger/Method/Captures examined/Result/Promoted to) are used by 2026-08-17 … 2026-09-15 and 2026-10-04 only; 2026-09-19 (both), 2026-09-20, 2026-10-03, 2026-10-06 (partly), 2026-10-14 use free headings; 10 "Promoted to" lines for 14 entries. minor (doc improvement: relax template or apply it). +- DR-c6 2026-10-06 correction: "Whether other .log.last files overlap a neighbouring capture in the same way was not checked. The recount is in TODO §6" — check TODO §6 still lists it and whether 0090 (96 logs) handled duplicates beyond CAP-042 (0090 §5 mentions only CAP-042). Phase 2/3 lead: script to detect identical (time_epoch, bytes) frames across all logs. +- DR-c7 2026-10-14 §2: counts 73 + 2 + 12 + 1 + 1 Buds-side Notify classes; PROTOCOL §4.1 0090 FACT cites "73/73, 39/39, 12 of 77 …" — re-derive with scripts/anc_setting13_crosscheck.py --census (Phase 2). +- DR-c8 2026-08-17 entry: "Promoted to PROTOCOL §6" for the 0x1e80/0x2680/0xe980 "address instability" finding; superseded by the varint correction (pointer present) — but its "Reading" paragraph ("FrameDecoder cannot hardcode 0x0000/0xD180 …") stays as a design implication; ok with pointer. nit. +## CHANGELOG.md 1-200 +- CL-c1 [1.1.1] block: intro "tested on hardware before it is published" vs its Changed bullet "not yet hardware-tested (with the next release's run)"; the header paragraph says hardware run CAP-071 (0080). Internal contradiction in a released block; a dated note would fix it. minor. +- CL-c2 [1.2.0] Changed: "The app sends thirteen requests at connect instead of twelve settings reads plus the EQ read and the subscription: the one GetHardwareInfo above" — 12 + 1 + 1 = 14 before, 15 after (same arithmetic slip as CB-c18/AT-c1). Verify on CAP-073 (Phase 3). minor. +- CL-c3 [1.3.0] Known issues "12 of 77 such changes in the captures" — check 77 against DESKRESEARCH 2026-10-14 §2 (73+2+12+1+1 = 89 Buds-side Notify events; 12+…=77?) (Phase 2). +- CL-c4 [1.2.1] Known issues "8 of 19 holds were shown within a second, 6 never" vs ADR-062 Update "8 of 31" — reconcile with CAP-075-FINDINGS (Phase 3). +## README.md read in full (355) +- RM-c1 "## Current state (2026-10-14)" — the section cites CAP-076 of 2026-10-15 and the 1.3.0 release; date stamp stale. nit. +- RM-c2 "Protocol-known but not built: … draft ADR-060, a 1.3.0 candidate, needs WELL-002 first" — 1.3.0 shipped without it; stale (same as TD-c9). minor. +- RM-c3 "Before testing against real hardware … the newest analysed run (CAP-070-FINDINGS.md, the 1.1.0 release APK)" — newest analysed own-app run is CAP-076 (1.3.0, 0092); and "CAP-070-EVENT-NOTES.md shows how the last run followed it". Stale. minor. +- RM-c4 "Captures: 76 registered … 72 analyzed, 2 planned, 2 withdrawn" and "Decisions: 63 accepted ADRs … one labelled draft" — verify counts against id_registry.csv / DECISIONS (Phase 3/5). +- RM-c5 Status section: nine release paragraphs (1.0.1 … 1.3.0) duplicating CHANGELOG, each with its own known issues; a user reads 1.2.1/1.2.2 details about removed features. Doc improvement: keep the latest release + pointer to CHANGELOG. minor. +- RM-c6 Building: "platform android-37.0 and build-tools 36.0.0" — verify against android/ gradle files (Phase 8). +## SECURITY.md / CONTRIBUTING.md read in full +- SE-c1 SECURITY "What the code does": lists decoders and write gate; 1.3.0 added a new inbound path (the settings stream SERVER_STREAM 4:{N} decoded and applied for 12 fields + 13) — check its fuzz coverage (Phase 8) and whether SECURITY should name it. Verify caps 1024/4096 (AR-c30). +- CO-c1 CONTRIBUTING "This project is currently a solo reverse-engineering effort" — since 1.0.0 an app with releases; and it does not say that main accepts changes by PR only and which CI checks run (android.yml, lint-docs). minor doc improvement. +- CO-c2 CONTRIBUTING cites "PROJECT_RULES.md §7" for Conventional Commits and "§8" for the scope guardrail — verify section numbers (Phase 1). +- CO-c2 checked: PROJECT_RULES §7 = Version control, §8 = Scope guardrails — fine. +## WORKSTATION_PREPARATIONS.md read in full (372) +- WS-c1 Antigravity repo file sets gpgcheck=0 (no package signature check) — best practice: gpgcheck=1 with the vendor key, if the vendor publishes one (external check Phase 1). minor (security hygiene of the workstation, not the app). +- WS-c2 npm global-install recipe: `sudo chown -R $(whoami) /usr/local/lib/node_modules /usr/local/bin /usr/local/share` — npm's docs recommend a user prefix (~/.npm-global) instead of taking ownership of /usr/local; and Claude Code's docs now recommend the native installer over npm (external check Phase 1). minor. +- WS-c3 VSCode block uses `warn` before it is defined (defined only later in the RE-tools snippet) and `code-insiders` while git core.editor is `code --wait`. nit. +- WS-c4 Check list: "git lfs version # the capture logs are LFS files" — several capture logs (btsnooz CAP-012/013/031, and others) are local-only files, not LFS (memory: capture_logs_btsnooz_not_lfs); and the list omits `capinfos` (used by DESKRESEARCH/CAPTURE §3) and `uv` (reverse-engineering/tools .venv). nit. +- WS-c5 JADX 1.5.1 / apktool 3.0.3 pinned here — check APK_VERSIONS.md tool-version table agrees (Phase 6). +## APK_REVERSE_ENGINEERING_PROCEDURE.md read in full (278) +- AP-c1 intro still lists "running pbtk" among the mechanical steps; §3 says pbtk does not apply (ADR-041). nit. +- AP-c2 §7 checklist (2026-10-03) not applied to the second APK pulled 2026-10-05 (v1.0.990706425-10260911): no APK_VERSIONS row (AV-c1), no record of steps 2–8. The maintainer declined decompiling it in this audit (Checkpoint 0), so this is a backlog/documentation gap only. minor. +## ai-sessions/INDEX.md read in full +- IX-c1 INDEX Status cells drift from the RESULT headers: 0089 INDEX starts "awaiting maintainer sign-off" (RESULT: "complete (closed 2026-10-14 … 1.2.2 published)"); 0091 INDEX "done 2026-10-14 … PR #56 open" (RESULT: "complete (… released as 1.3.0 …)"; "done" is not one of procedure §4's three values); 0069 INDEX "complete (closed …) — awaiting maintainer sign-off — … nothing committed, tagged or published" (two states in one cell). lint_docs (g) checks RESULT Status lines only, not INDEX. minor; propose lint check: INDEX status must start with one of the three values and agree with the RESULT. +- IX-c2 Every INDEX row 0001–0093 has ≥2 files with matching category (script check) — positive result. +## id_registry.csv read (246 rows) +- RG-c1 README "Decisions: 63 accepted ADRs … and one labelled draft" — id_registry: 58 accepted, 6 superseded (ADR-003, 024, 038, 061, 062, 063), 1 proposed (ADR-060) = 65. "63 accepted" is wrong (58). minor. (Check DECISIONS headings agree — lint (ADR/table/status) passes, so registry = DECISIONS.) +- RG-c2 captures: 72 analyzed, 2 planned (CAP-030, CAP-055), 2 withdrawn (CAP-052, CAP-057); 76 folders; no duplicate ids — README capture line correct. +- TD-c5 checked: CAP-076-adb-checks-0092.log exists in the CAP-076 folder (git-tracked? see list). +## 0090 RESULT read in full +- S90-c1 (major candidate) PROTOCOL §4.1 0090 FACT bullet: "receives a SERVER_STREAM 4:{13:n} for every mode change the Buds report … 73 of 73 Buds-side changes" — the 73 is the class *defined* as "Notify with a stream packet within 1 s" (DESKRESEARCH 2026-10-14 §2 table), so 73/73 is circular; the same entry's next bullet says 12 Buds-side changes were NOT pushed ("12 of 77"). Buds-side Notify events in the table: 73 + 2(pairs) + 12 + 1 + 1 = 89 (or 91 counting the pairs' Notifies). "every mode change" contradicts "Not pushed … 12". And "12 of 77": 77 is not any sum in the table (73+2+1+1 = 77 excludes the 12 themselves, so the ratio should be 12 of 89). Propagated: 0090 Summary, PROTOCOL §8 row, CHANGELOG 1.3.0 Known issues ("12 of 77"), README? Re-derive with --census in Phase 2. +- S90-c2 0090 Commits list ends with a placeholder bullet "(the PR number: the follow-up commit that records it)". nit. +- S90-c3 0090 read PROTOCOL/ARCHITECTURE/RE only in part (its own "Files read"; TODO §6 "reading owed") — this audit read them in full; closable. +## 0091 RESULT read in full +- SES-c1 WITHDRAWN: 0091 §D gives :ui 184 summed = 92 per variant after the removal (102 before) — matches today's gate. +- S91-c1 0091 §D Compliance: "the manifest's permissions unchanged (BLUETOOTH_CONNECT, BLUETOOTH_SCAN, POST_NOTIFICATIONS, FOREGROUND_SERVICE, FOREGROUND_SERVICE_CONNECTED_DEVICE …)" — BLUETOOTH_SCAN is NOT declared (removed 2026-09-24; app and hardware manifests say so: android/app/src/main/AndroidManifest.xml:10, android/hardware/src/main/AndroidManifest.xml:17). The compliance record lists a permission that does not exist → the check was not actually performed as described. minor. +- S91-c2 0091 "Tests … the Connect-sequence counts (15 requests)" vs 17 requests per full session in 1.3.0 (CAP-076-FINDINGS §2 per APP_TESTPLAN Y2 note) — check the test's count/name in Phase 8. +- S91-c3 0091 §C "field 18 an EQ frame with persist" from the stream — ADR-065 item 2 says field 18 "read but not shown, as today"; check what `persist` does (Phase 8, DEC-c7). +- S91-c4 0091 §G "The maintainer's next steps: push feature/settings-stream and open the PR" while the Commits paragraph above says pushed and PR #56 opened. nit (stale tail). +## 0092 RESULT read in full +- S92-c1 confirms PT-c39: PROTOCOL §4.1 note (0092) "the Buds pushed every reported change of setting 13 to it, also while Google Play services held the Message Stream and received the Notify (41 of 41 holds shown)" — 41 = 29 (Owner, with Play) + 12 (secondary user, no Play — nobody held the Message Stream). Should read "29 of 29 while Play held it; 12 of 12 without Play". Same conflation in PROTOCOL §8 2026-10-15 row (check). minor. +- S92-c2 0092 "Not read": DESKRESEARCH 2026-10-14, CodecRouter.kt, SettingFrame.kt, Maestro.kt, SessionReopener.kt, AncScreen.kt, SettingsMenu.kt, MainActivity.kt … — a reading gap for a release-verdict session; check TODO §6 carries it (Phase 6/7). +## CAP-076-FINDINGS.md read in full (414); arithmetic re-checked by hand: §10 109 = 52×2 + 5, 117 = 109 + 8; part 2 34 = 16×2 + 2, 37 = 34 + 3; claims 33 = 14 snapshots + 19 user actions; §5.2 12 claims = 8 + 2 + 1 + 1 — all consistent. +- C76-c1 CB-c18/AT-c1 confirmed by CAP-076 §2 table: S0 (1.2.2) sent "15 (13 reads, SRI, GHI)" → 1.2.x = 15 MAESTRO requests per Connect, 1.3.0 = 17. "Thirteen requests at Connect" (CHANGELOG 1.2.0, APP_TESTPLAN header/rows, CAPTURE Group BH/BI and CAP-072/073 rows) is wrong (13 = the ReadSettings only). Also "the skeleton's fifteen" for 1.3.0. Verify once on CAP-073 (1.2.0) in Phase 3. +- C76-c2 The "overwritten" delay is stated three ways: FINDINGS "In short" "0.2–0.7 s", 0092 RESULT summary "0.1–1.5 s", FINDINGS §6.2 "107–1,459 ms" (12 sessions) vs the two exhibited cases 747/190 ms. nit (harmonise). +- C76-c3 FINDINGS §13 PROTOCOL-note proposal is the origin of the "41 of 41 … while Play held" conflation (S92-c1). +- AR-c22 RESOLVED: ARCHITECTURE §6.0b "13 taps … 8 refused … 4 cut" = 12 claims for 13 taps (FINDINGS §5.2) — consistent. +## Phase 1 work (governance) + external checks (2026-10-15 ~15:50) +- PR-c1 WITHDRAWN: all 278 capture binaries (incl. the three btsnooz logs) are git-tracked through LFS (`git lfs ls-files | wc -l` = 278 = `git ls-files captures | grep -E '\.(log|last|mp4|txt|png|jpg|pcapng|zip)$' | wc -l`); rule 18 holds. +- EXT-1 (WS-c2 evidence) code.claude.com/docs/en/setup (fetched 2026-10-15): "Native Install (Recommended)" `curl -fsSL https://claude.ai/install.sh | bash`; a signed dnf repo (gpgcheck=1, gpgkey, fingerprint 31DD DE24 …); npm "Do NOT use `sudo npm install -g`". WORKSTATION's npm recipe takes ownership of /usr/local instead. Also a contrast for WS-c1: a vendor dnf repo with gpgcheck=1 is the documented norm. +- EXT-2 (PT-c48 lead) developers.google.com/nearby/fast-pair/specifications/extensions/sass (fetched 2026-10-15): Group 0x07 "Audio switch": 0x10 Get capability, 0x11 Notify capability (both directions; version 0x0102, Provider length 4, Seeker 20 with nonce+MAC), 0x12 Set multipoint state, 0x20 Set switching preference, 0x21 Get switching preference (Seeker→Provider, length 0; answer 0x22), 0x22 Notify switching preference, 0x30–0x33, 0x34 Notify connection status (Provider→Seeker, on connection change on multipoint Providers and as answer to 0x33; octet 4 active flag 0/1/2, then encrypted status, nonce, no MAC), 0x40 Notify Audio switch initiated connection, 0x41 Indicate in use account key (Seeker→Provider, UTF-8 "in-use" octets 4–9 + nonce + MAC — "in-use" appears ONLY in 0x41), 0x42 Send custom data (Seeker→Provider, 1 custom byte + nonce + MAC; Provider then updates its advertisement), 0x43 Set drop connection target. ⇒ PROTOCOL §6 item (line ~2696, "what do 0x11/0x21/0x40/0x42 encode … is 0x34 periodic/keepalive") is answerable by spec at the naming level; and PROTOCOL §4.5.2 (line ~1706) "Codes 0x11/0x21/0x34/0x40/0x41/0x42, the last containing the ASCII string in-use" contradicts the spec (in-use is in 0x41) — verify on CAP-019 bytes (Phase 2). +- HK-c2 android/domain/bin/ (25 files: IDE build output incl. copies of Kotlin test sources, e.g. domain/bin/test/…/AncTileTest.kt) is not in any .gitignore; it appears as ?? in every session's git status since ≥0083 and pollutes greps over android/. Add bin/ to .gitignore (or android/.gitignore). minor. +- PT-c48 VERIFIED (M): CAP-019 804 phone→Buds 07 41 00 16 696e2d757365 ('in-use') … = spec 0x41 'Indicate in use account key'; 806 07 42 00 11 00 64 … = 0x42 'Send custom data' (custom byte 0x00 then nonce/MAC per spec layout? len 0x11=17 ✓); 799 Buds 07 11 00 04 01 02 98 00 = Provider 'Notify capability' version 0x0102 flags 0x9800 (len 4 ✓); 786 phone 07 11 00 14 … len 20 ✓ (Seeker with nonce+MAC); 805 07 40 00 11 …. PROTOCOL §4.5.2 (line ~1706) 'the last [0x42] containing the ASCII string in-use' is wrong — it is 0x41 (frame 804). Command: tshark -r CAP-019-btsnoop_hci.log -Y 'btrfcomm.len>0 && btrfcomm.dlci==4 && frame.number>=780 && frame.number<=810' -T fields -e frame.number -e frame.p2p_dir -e data.data (exit 0). +- LEAD-A (new, M): CAP-021's four GSND AUDIO (DLCI 0x0a) waves each start with the Buds' GSND CONTROL 01 09 00 03 0a 01 03 1–2 ms before the first audio frame: 2092 08:02:29.503 → 0x0a 2093 29.504; 2963 08:02:50.108; 3645 08:03:27.109; 4340 08:03:58.307 (= the four wave starts of DESKRESEARCH 2026-10-03 item 4) — the same opener CAP-069 §3 found 3/3 for filmed assistant sessions (PROTOCOL.md:3658). Not recorded in PROTOCOL/DESKRESEARCH/CAP-021. ⇒ the 🔴 'trigger not on film' narrows to 🟡 strong: assistant sessions (press-and-hold set to assistant in that capture). Also 0a 01 05 / 0a 01 01 / 0a 01 21 follow in each wave (2297–2341 …): candidate session-state codes. Command: tshark -r CAP-021-btsnoop_hci.log -Y 'btrfcomm.len>0 && (btrfcomm.dlci==8||btrfcomm.dlci==9) && frame contains 01:09:00:03' -T fields -e frame.number -e frame.time -e frame.p2p_dir -e data.data (exit 0); DLCI 10 frames: 1123 (positive control = the burst count in PROTOCOL §6). +- DEC-c7/S91-c3 checked: a field-18 EQ frame (stream or read) is not applied (BudsRepositoryImpl.kt:735–741, ) — consistent with ADR-065 item 2. Not a finding. +- PT-c49 VERIFIED/closable (M): ATT notification on handle 0x0044 (`btatt.opcode==0x1b && btatt.handle==0x0044`) occurs in 76 of 103 logs (scratch att44.txt; script att44.sh); in every one, every carrying connection handle also shows the Heart Rate service (`btatt.uuid16==0x180d`) in the same log, EXCEPT CAP-060…066 and CAP-072 hci1.log.last, where the carrier (0x0040/0x0041) is the LE peer c8:cc:a8:e7:48:93 (LE Connection Complete, all 12 such logs) — the same peer exposes Heart Rate in CAP-072-btsnoop_hci1.log (0x0040/0x0041). In CAP-059 the Buds' own LE address (04:00:6e:cf:6e:07, frame 538) held handle 0x0041 earlier, but the 0x0044 notifications (5529–5702) follow the handle's re-use by 54:b7:53:5f:61:0e (5237) whose HR service is at 5305. ⇒ no 0x0044 notification on a Buds link in any capture; PROTOCOL §6 item (line ~2777–2794, "stays listed only because no check was run on every capture") can be closed (proposal; maintainer). Note: CAP-064's 0x0044 values carry what looks like an IPv4 header (0x45 …) — another device's data, out of scope. +- PT-c7 DONE (M): scripted check of every PROTOCOL.md backticked hex string (≥5 bytes) with a CAP-NNN and a frame number in the 220 characters before it (scratch cites2.py/checkcites.py; `tshark -r -Y 'frame.number==N && frame contains '` over every log of that capture): 38 extracted, 31 OK at once; of the 7 mismatches, 6 are extraction artefacts (the hex belongs to the next frame named in the sentence — verified: CAP-069 3166, CAP-064 9927, CAP-068 A2036 (hci2.log.last) all contain the cited bytes; 1583 and 3659 are negative/frame-less phrases); ONE real error: PROTOCOL.md:111–112 cites CAP-059 "frame 1509 `03 0a 00 08 4c b5 93 ed 88 3f 5a ef`" — the bytes are in frame 1508 (`tshark -r CAP-059-btsnoop_hci.log -Y 'frame contains 03:0a:00:08:4c:b5:93:ed'` → 1508). nit (off-by-one). +- DECISIONS+ARCHITECTURE citation check: 9 extracted, 5 OK, 4 extraction artefacts (an md5, a list of bytes, a later frame) — no real error. +- S91-c2 refined: BudsRepositoryImplTest.kt:467–471 asserts `5 + settingReads.size` = 17 requests; the 0091 RESULT §C "the Connect-sequence counts (15 requests)" is wrong (nit). +- S91-c5: 0091 PROMPT Phase F item 10 asked ARCHITECTURE §2.4/§3.1/§6.0b to turn the hold/re-claim into history with a pointer; RESULT §F claims §2.4 "(the 1.2.x card as history)" and §3.1 "(the stream rule)" done — but §2.4's tree (line 153) and §3.1's ANC row (line 329) still state the 1.2.x behaviour as current → origin of A93-ARCH-01/02. + +## Appendix B — the six sub-review reports (raw notes, as written by the read-only sub-reviews; items used in §4 are re-verified there) + +### sub_caps_001_025 + +#### Sub-review CAP-001..CAP-025 (progressive) +Global checks: all Test-IDs used in CAP-001..025 .md files exist in TESTPLAN (only false hits AES-128/IEEE-802). Folder dates match id_registry.csv and §9 index dates for all 25. + +#### CAP-001 (read in full) +- SC1-01 minor CAP-001-FINDINGS.md:206-207 "AT+BIEV=2,100 → OK ← repeats at 08:51:14.106, 20.070, 20.136, 34.392, 34.860, 41.410, 08:52:04.148, 08:52:04.438 — roughly every 6–7s". Own listed times give gaps 6.0/0.07/14.3/0.5/6.5/22.7/0.3 s (tshark-verified frames 1303,1558,1572,1949,1968,2027,2245,2268). "roughly every 6–7s" contradicts the data and CAP-009 (AGENTS §5). Fix: replace with "irregular; see CAP-009-FINDINGS §2". +- SC1-02 minor CAP-001-FINDINGS.md:224-227 "Neither HFP indicator distinguishes Left/Right/Case — both appear to report a single aggregate value" — stale vs CAP-009 (BIEV tracks Right bud); no pointer. Fix: add dated pointer. +- SC1-03 major CAP-001-FINDINGS.md:385-387 §6 "Channel 1 (DLCI 0x02) and channel 2 (DLCI 0x04)'s exact protocol identity is still unconfirmed — candidates are AVRCP and/or A2DP" — contradicted by §2's Upgrade (pw_hdlc) and §5's Full resolution (Hearable Controls 🟢); no pointer. Fix: add pointer/strike. +- SC1-04 minor CAP-001-FINDINGS.md:418-424 §7 item 3 "channel 1/DLCI 0x02 ... exact protocol identity is still unconfirmed; channel 2's e8e8XX ... one-hot state byte" and item 1 (isolated Group B) — stale vs pw_hdlc upgrade, §5 Full resolution and CAP-006. Fix: pointer. +- SC1-05 minor CAP-001-FINDINGS.md:274-307 "Resolution (2026-08-12) ... closes to a 🟢 FACT characterization, but NOT as an ANC opcode ... this bitmask answers a different question than ANC state entirely" — directly contradicted by the following "Full resolution" (it IS ANC state); the "(superseded below)" note at 311 refers only to the pbpctrl pass. Also heading line 245 "§5 ... inconclusive (🔴 OPEN QUESTION)" and conclusion 266-272 left unmarked. Fix: mark the first Resolution block and the heading superseded. +- SC1-06 nit CAP-001-FINDINGS.md:82-85 "This document's own §2 already correctly flagged channel 2/DLCI 0x04 ... as unresolved (🔴 OPEN QUESTION" — §2 table (line 64) now says 🟢 FACT. Fix: date-qualify. +- SC1-07 minor CAP-001-FINDINGS.md:108 vs 156: "854 sub-frames checked across both files" (CAP-001+CAP-002) vs "640/640 (100%) match" across CAP-001+002+003 — more files, fewer sub-frames; unexplained. Fix: state what 640 counts (or recount). +- SC1-08 minor CAP-001-FINDINGS.md:393-409 "Zero matches in all four captures ... No SCO/eSCO connection is ever established" — negative without exact command/exit status or positive control (AGENTS §13.8); CAP-008 has eSCO frames usable as control. Fix: add command + CAP-008 positive control. +- SC1-09 nit CAP-001-FINDINGS.md:234 "frame 733/737" vs table line 38 and EVENT-NOTES:39 "732 → 737"/"frame 732" (732 = Create Connection, 733 = Command Status, tshark). Fix: use 732/737. +- SC1-10 nit CAP-001-FINDINGS.md:236 "RFCOMM channels 0, 2, 4, 5 opened (frames 984–1070)" vs EVENT-NOTES:44 "0, 1, 2, 4, 5 ... 1334" (frame 1334 = SABM Channel=1, verified). Fix: add channel 1/1334. +- SC1-11 nit CAP-001-FINDINGS.md:93 typo "`CAP-002-CAP-002`". +- SC1-12 minor CAP-001-EVENT-NOTES.md:48 "`INEAR-001`(partial)" for "Left ring shows not-charging state" — INEAR-001 in TESTPLAN:152 is "Toggle 'In-ear detection' on/off"; wrong Test-ID, and not in §9 index row. Fix: drop or use the correct case/bud Test-ID. +- SC1-13 nit CAP-001-EVENT-NOTES.md:68 "right-bud-removal and left-bud-removal timestamps were adjusted ... (08:51:20/36 instead of 08:51:24/36)" — order reversed vs timeline (left 20, right 36), and 36→36 isn't a change. Fix: reword. +- SC1-14 nit CAP-001-EVENT-NOTES.md:41 row "08:51:07–09" listed after the 08:51:11 row (out of order). + +#### CAP-002 (read in full) +- SC1-15 minor CAP-002-FINDINGS.md:20-27 rule "Every frame number cited below is in this original ~1,877-packet sliced numbering ... holds for frame numbers ≤ 1,877" vs :358 "frames 1004, 1826, 17610, ..." where 1004/1826 are explicitly full-file numbers ("from CAP-001's own capture window") — the header rule mis-translates them (+47984). Fix: mark 1004/1826 as full-file numbering or narrow the rule. +- SC1-16 minor CAP-002-FINDINGS.md:75 §2 table row "4 | 0x08 | ... not decoded in this pass | 🔴 OPEN QUESTION" while §2a :172-174 says "§2's table row above is updated in status from 🔴 OPEN QUESTION to 🟢 FACT" and §9 :586-588 repeats it — table never updated. Fix: update the row or add pointer. +- SC1-17 minor CAP-002-FINDINGS.md:368-387 Task 6 "actual purpose remains 🔴 OPEN QUESTION — Google's own spec table ... does not list code 0x0a" — superseded by :268 (session nonce 🟢, 2026-09-24) but no pointer in Task 6 (Task 4 has one at :405). Fix: add pointer. +- SC1-18 minor CAP-002-FINDINGS.md:602-606 and §8 item 1 :546-550 — "0x0c0X GATT handle cluster's identity — needs the UUID-resolving capture" / "Highest priority" — resolved by CAP-034 (id_registry CAP-014 row, §7 :541 already says "done since: CAP-034"); no pointer here. Fix: add pointer. +- SC1-19 minor CAP-002-FINDINGS.md:411-414 "Nothing at all was found on RFCOMM between 17:05:48 and the end of the sliced window" (and EVENT-NOTES:63/:85) — negative without command/positive control (AGENTS §13.8). Verified true: `-Y 'frame.time >= "2026-08-09 17:05:48" && frame.time <= "2026-08-09 17:07:05" && btrfcomm.len>0'` → 0 (exit 0); same filter 17:05:30–17:05:48 → 213. Fix: add these. +- SC1-20 nit CAP-002-FINDINGS.md:412 "17:05:34.56–17:05:38.92" first burst start vs §2a :115 burst "17:05:34.509" (frame 48917). Fix: 34.509. +- SC1-21 nit CAP-002-FINDINGS.md:607 HFP handshake listed under "Not ready yet" while marked 🟢 FACT; :329 "(§2a below)" (it is above); :344 "(line ~205)" (actual :279). +- SC1-22 minor CAP-002-EVENT-NOTES.md:92 "`APP-002` is not — expected, not observed in the notes" but :59 has row "Tap Continue | APP-002". Fix: correct the traceability line. +- SC1-23 minor CAP-002-EVENT-NOTES.md:53 "Save device to ... dialog | GFPS-001" (and :81) — GFPS-001 (TESTPLAN:301) is "Message Stream traffic present/absent with app uninstalled/GMS disabled", not the Save dialog (that is GFPS-002). Fix: drop GFPS-001 here. +- SC1-24 minor CAPTURE_BLUETOOTH_HCI_SNOOP.md:2221 CAP-002 row Test-IDs "`PAIR-001`, `CASE-003`" vs EVENT-NOTES using GFPS-002, APP-001, APP-002 (+BATT-004 per traceability); and row says Device Information "tentatively identified" while FINDINGS §3 is 🟢 spec-verified. Fix: sync row. +- SC1-25 nit CAP-002-EVENT-NOTES.md:18 "starts 08:04:53(=17:04:53 local)" — garbled time. Fix: "17:04:53". + +#### CAP-003 (read in full) +- SC1-26 minor CAP-003-FINDINGS.md:42-49 "Zero `Read By Group Type` packets exist anywhere in the log ... zero discovery-opcode traffic anywhere" — negative without exact command/exit status or positive control (§13.8; CAP-017 has 137 such frames as control). Fix: add command + control. +- SC1-27 nit CAP-003-FINDINGS.md:153-155 "`0x0f28` (eight reads total, four request/response pairs ... 2506/2508, 2510/2512, ... 2804/2806)" — lists 8 request/response pairs (4 polling instances × 2); tshark confirms 8 requests + 8 responses. Fix: "eight request/response pairs in four polling instances". +- SC1-28 minor CAP-003-FINDINGS.md:332-334 "The libmaestro/ANC-EQ control channel identity — still completely unaddressed" — stale (ADR-009/034), and unlike CAP-002 the CAP-003 status banner (:3) does not cover it. Fix: pointer. +- SC1-29 nit CAP-003-FINDINGS.md:217 "confirming this gap persists in all three captures with this goal" vs :320 "four-for-four negative result". Fix: align. +- SC1-30 minor CAPTURE_BLUETOOTH_HCI_SNOOP.md:2222 CAP-003 row Test-IDs "`PAIR-001`" only, but EVENT-NOTES:48/56/58 use CASE-003, APP-001, APP-002. Fix: sync row. + +#### CAP-004 (read in full) +- SC1-31 major CAP-004-FINDINGS.md:376-378 "groups 0x01, 0x02, 0x03, 0x04, 0x05, 0x09, 0x0e all appear on DLCI 0x08 in CAP-001, CAP-002, and CAP-004 alike (0x01 and 0x02 additionally only surface in CAP-002's richer ... session" — self-contradictory, and the "only CAP-002" half is false: this same file :278-279 decodes CAP-004 frames [2360,2361]/[2365,2366] as Group 0x02 Code 0x05 (tshark: 2360 `02050002`+2361 `0800`, 2365 `02050002`+2366 `0801`, DLCI 0x08). Same error in CAP-002-FINDINGS.md:143-144 "content not seen in CAP-001 or CAP-004's captured windows at all — new groups 0x01 and 0x02". Fix: state which groups appear in which capture per actual decode. +- SC1-32 major CAP-004-FINDINGS.md:710-714 §10 "Ready to promote now (🟢 FACT): Message Stream Group 0x04 = "Device action", spec-confirmed" — contradicted by the 2026-10-08 label correction at :187-189 ("this table is DLCI 0x08's private envelope ... the identity of this envelope's group 0x04 is open"); §10 has no pointer. Also :196 "group identity confirmed" in the same paragraph as the correction. Fix: strike/annotate both. +- SC1-33 minor CAP-004-FINDINGS.md:749-751 garbled: "recorded in PROTOCOL.md §5.1 as a proposal; promoted to 🟢 FACT 2026-09-18 (DECISIONS.md ADR-030, ...) for promotion to 🟢 FACT, not committed as settled" and still listed under "Not ready yet ... 🟡 HYPOTHESIS" (:738) although banner :3 says CTKD is 🟢. Fix: rewrite sentence; move item or mark resolved. +- SC1-34 minor CAP-004-FINDINGS.md:315 "confirmed via `tshark -r CAP-004-btsnoop_hci.log -Y 'data.data contains "Europe/Amsterdam"' ...` against CAP-001's log" — command names the wrong log file for a CAP-001 claim. Fix: CAP-001-btsnoop_hci.log. +- SC1-35 minor CAP-004-FINDINGS.md:624-627 "Cross-checked against CAP-002's own full ... log for the same opcode/address combination — also zero results" — negative filtered by Buds address with no command or positive control; CAP-002-FINDINGS §7 (:534-536) documents that `bluetooth.addr == ` matches nothing in that log even when the Buds are connected (filter artefact risk, AGENTS §13.1/§13.8). Fix: redo by chandle, show command + control. +- SC1-36 nit CAP-004-FINDINGS.md:205-207 and :717-719 "Recommend CAP-002 §3 be updated to 🟢 FACT ... (not done in this file" — stale; CAP-002-FINDINGS.md:343-354 Task 3 did it. Fix: pointer. +- SC1-37 nit CAP-004-FINDINGS.md:371 "all three captures that have this channel (CAP-001, CAP-002, CAP-004 — CAP-003 also has it" and :457 "in all three captures" vs Task 3/5 four captures. Fix: "all four". +- SC1-38 minor CAP-004-EVENT-NOTES.md:47-48 table row broken across two physical lines ("(corrected 2026-08-28 — | — | — | — |" then a line starting "originally transcribed ...") — breaks the Markdown table. Fix: join into one row. +- SC1-39 minor CAP-004-EVENT-NOTES.md (whole file) — no "Traceability check" section (present in CAP-001/002/003) and GFPS-001, the capture's core Test-ID (index row :2223), appears in no timeline row (AGENTS §13.7). Fix: add. +(Revision to SC1-39: only CAP-001/002/003/013 have a "Traceability check" section, so its absence is not CAP-004-specific — keep only the "GFPS-001 not in any timeline row" part, nit.) + +#### CAP-005 (read in full) +- SC1-40 minor CAP-005-FINDINGS.md:107-109 "DLCI 0x04 ... and DLCI 0x08 ... are completely silent in this window (checked explicitly, zero rows — see §4's identical check reused for both windows)" — §4's commands (:167-168) cover only 15:03:20–15:03:30, not the §3 window 15:03:10–16; and neither 0-row command has a positive control (§13.8). Claim is true (re-run: 15:03:10–16 → only 1245/1249/1250, DLCI 0x02). Fix: add the §3-window command and a control (same filter matching e.g. DLCI 0x08 frames elsewhere in the log). +- SC1-41 nit CAP-005-FINDINGS.md:13-14 "nothing here is promoted past 🟡, per this session's own task instructions" vs :141-147 "🟢 match ... All three CRCs verified ... 🟢 FACT". Fix: qualify the header rule (CRC/framing is FACT, content is 🟡). +- SC1-42 nit CAP-005-FINDINGS.md:198 "All four `Sent` payloads (1245, 1321, 1338, and 1321≡1338's shared value)" — there are three Sent payloads. Fix: "All three". +- SC1-43 minor CAP-005-FINDINGS.md:431-433 §7 "Not ready: the field-to-band mapping ... all explicitly flagged 🔴 in §6" and :370 "🔴 Field-to-band mapping" — resolved by CAP-015 (🟢, noted at :373-375) but §7 and the 🔴 marker were not updated. Fix: mark resolved. + +#### CAP-006 (read in full) +- SC1-44 minor CAP-006-FINDINGS.md:24 video row "container index corrupted ..., recovered up to 17:25:05 via VLC's scene filter" — stale vs CAP-006-EVENT-NOTES.md:20-22 (file re-pulled 2026-09-05, opens cleanly, 17:23:49–~17:25:08). Also EVENT-NOTES:70 "last independently recovered frame is 17:25:05". Fix: update metadata row. +- SC1-45 minor CAP-006-FINDINGS.md:187-188 and EVENT-NOTES.md:69/:92-93 "Not attributable ... since the video decode is truncated in this exact window" (17:24:56–17:25:02) — contradicts the recovery note (EVENT-NOTES:19-20: only 17:25:05–17:25:07 was unrecovered; now the full file decodes). Fix: re-check the video for that window and drop the truncation claim. +- SC1-46 minor CAP-006-FINDINGS.md:81/:86 table "+1.3s" for ANC-002 vs "All four offsets are within this capture's own ±1s video-sampling tolerance" — +1.3 s is outside ±1 s. Fix: reword ("within ~1.3 s"). +- SC1-47 minor CAPTURE_BLUETOOTH_HCI_SNOOP.md:2225 CAP-006 row Test-IDs list only ANC-001..004, but EVENT-NOTES:61 tags PAIR-003, which TESTPLAN:295 (corrected 2026-08-28) names as the first Pixel 7a PAIR-003 occurrence; meanwhile the CAP-012 row (:2231) still says "bonus `PAIR-003` (first Pixel 7a occurrence)". Fix: add PAIR-003 to CAP-006 row, drop "first" from CAP-012 row. +- SC1-48 nit CAP-006-FINDINGS.md:157-171 "unblocked, pending the maintainer's own review and a DECISIONS.md entry ... Recommended next step: the maintainer records a DECISIONS.md update" — stale (index row: "ADR-009 blocker resolved"); file has no status banner unlike CAP-001..005. Fix: add banner/pointer. +- SC1-49 nit CAP-006-FINDINGS.md:192-196 "this is the second capture in a row (after none previously reported) where the screen-recording .mp4 itself was corrupted" — self-contradictory and superseded by the re-pull. Fix: reword/pointer. + +#### CAP-007 (read in full) +- SC1-50 major CAP-007-FINDINGS.md:160-161 "on a channel that had otherwise been completely silent for the preceding ~184s", :285-289 "autonomously during an otherwise fully idle connection ... a true free-running counter would not go fully silent for ~184s", :296-297 "goes completely silent for ~184s during genuine idle time", and EVENT-NOTES.md:55 "uninterrupted ~184s (~3m 4s) silence on DLCI 0x08 (09:15:39.285 → 09:18:43.428, frames 1444 → 2074)". False: tshark `-Y 'btrfcomm.dlci==0x08 && btrfcomm.len>0'` shows DLCI 0x08 push groups (`0e 02`, `0e 01`, `04 03 00 04 10 05 18 64`) at 09:16:02 (1632/1633/1642), 09:16:23 (1686/1687/1695), 09:17:02 (1767/1769/1775), 09:17:52 (1870/1871/1878), plus DLCI 0x04/0x0c/0x02 traffic at the same moments. Only Code 0x12 is absent for 184 s. The §5 🟢 conclusion and the index row (:2226 "after a ~184s idle gap with no channel churn") rest on this wording. Fix: "no Code 0x12 for ~184 s (DLCI 0x08 carried the periodic 0e/04 03 push groups)"; re-evaluate the "idle" argument. +- SC1-51 major CAP-007-FINDINGS.md:250-256 command `-Y "frame.time_relative>=95 && frame.time_relative<=108"` with result "zero RFCOMM/L2CAP data frames of any kind" — re-running that window with `btrfcomm.len>0` returns 32 RFCOMM data frames (1545–1607, 09:15:43.738–09:15:45.125: DLCI 0x04 re-open burst incl. ANC Notify 1572, DLCI 0x02 1603/1604/1607). The claim only holds for ~09:15:45.2–09:16:02. Fix: correct the window (e.g. rel ≥ 97) or the result; EVENT-NOTES:52 "within many seconds" ok but cite the 09:15:45.125 / 09:16:02.052 neighbours. +- SC1-52 minor CAP-007-FINDINGS.md:277-279 (§4) "This capture found no byte, on any DLCI, that changed value in response to the one physical bud-removal event" and :309-312 (§5 item 2) "produced no value change on any byte, on any DLCI" — contradicted by the 2026-10-03 addition :220-224 (Code 0x05 `08 06`→`08 05`, frames 717→1345, "the earlier statement ... is withdrawn"); no pointer in §4/§5. Fix: add pointer/strike. +- SC1-53 minor CAP-007-FINDINGS.md:221-222 "frame 717 (09:14:17, both buds worn)" — contradicts §3.1 :85-88 / EVENT-NOTES:77-78 ("no earbud is ever seen inserted in an ear ... both earbuds sit in the open case"). Fix: "both buds in the open case". +- SC1-54 minor CAP-007-FINDINGS.md:334-336 "Only Code 0x12 (and empty 0x11/0x13 placeholders) were observed on this DLCI in this capture" — Codes 0x05 (:221), 0x14/0x16 (frames 1438/1439), 0x03 (1642…), 0x02/0x04 groups etc. are on DLCI 0x08 in this log. Fix: correct the list. +- SC1-55 nit CAP-007-FINDINGS.md:171-172 command has two `-Y` options (only the last one applies). Fix: combine with `&&`. +- SC1-56 nit CAP-007-FINDINGS.md:233-237 §3.4 "Zero results — 🟢 FACT" has no positive control (§13.8). Verified: same filter without the time bound → 310 frames; with it → 0 (exit 0). Fix: add these. +- SC1-57 minor CAPTURE_BLUETOOTH_HCI_SNOOP.md:2226 CAP-007 row Test-IDs "`INEAR-004`(partial), `CASE-004`, `OBS-003`" — EVENT-NOTES:45-46 tags PAIR-003 (reconnect, 2026-08-16, also earlier than CAP-012's claimed "first Pixel 7a occurrence"). Fix: add PAIR-003 (see SC1-47). + +#### CAP-008 (read in full) +- SC1-58 minor CAP-008-FINDINGS.md:151-152 "No second full handshake precedes call 2 — only a codec renegotiation (`AT+BCS`, §5)" and :243-245 "mSBC was selected via a standard Codec Connection Setup exchange just before each eSCO setup: Sent +BCS: 2 → Rcvd AT+BCS=2 → OK (frames 1641/1644/1645 for call 1)". `tshark -Y 'frame contains "BCS"'` → only 1641/1644 (call 1); call 2's eSCO command (2274, 09:40:23.467) follows `+CIEV: 2,1` (2273) with no BCS exchange. Fix: state that call 2 reused the codec with no `+BCS`. +- SC1-59 minor CAP-008-FINDINGS.md:206-207 "Call 1: frames 1646 (cmd) / 1656 (complete), 09:39:19.697/.821 — 7.6ms after +CIEV: 2,1 and 76ms before the first RING" — RING 1658 is 09:39:19.822: the command is 126 ms and the complete 1 ms before it; 76 ms matches neither. Fix: correct the number. +- SC1-60 minor CAP-008-FINDINGS.md:78 and :84-86 "reconfirmed here with a third distinct channel-number assignment for the same profile" / "the third distinct RFCOMM channel number ... (channel 4/DLCI 0x09 in CAP-001 ...; channel 6/DLCI 0x0c here)" — only two numbers are listed, and CAP-002-FINDINGS.md:74 already has HFP on channel 6/DLCI 0x0c (CAP-003/CAP-001 channel 4). Same claim in the §9 index row :2227. Fix: "same channel 6 as CAP-002; differs from CAP-001/CAP-003's channel 4". +- SC1-61 nit CAP-008-FINDINGS.md:253/:257 "14th consecutive silent result ... 13 prior captures" vs :380-381 "on top of the existing eleven silent captures"; and "consecutive" while CAP-021 is named as an exception. Fix: align counts. +- SC1-62 minor CAP-008-EVENT-NOTES.md:8-10 "CAP-002-FINDINGS.md §5 found zero `AT+` traffic anywhere outside CAP-001's own pairing-time handshake across a full 8+ hour log" — stale; CAP-002 §5 was rewritten 2026-10-03 (filter artefact, three more handshakes). FINDINGS :146-147 has the correction, EVENT-NOTES has no pointer (also FINDINGS :27-29 scope note repeats the old premise). Fix: add pointer. +- SC1-63 nit CAP-008-FINDINGS.md:45/:347 "switched on by the user at 09:38:48 (video)" vs EVENT-NOTES:40 "09:38:49", while `Create Connection` is at 09:38:47.919 (frame 230) — wire precedes the video-visible tap; worth flagging (same pattern as CAP-005) rather than "matches within 1 s". Also :164 "Finger taps red hang-up at 09:39:49–50 ... (Δ≈0s)" — Δ to the wire event is ~1.7–2.7 s. +- SC1-64 nit CAP-008-FINDINGS.md:237-241 "Input/Output Bandwidth: 32000 — an exact match to this section's 'Tx/Rx bandwidth 8000 B/s' summary" — 32000 ≠ 8000 (different fields). Fix: cite the Transmit/Receive Bandwidth fields (0x1f40 = 8000). + +#### CAP-009 (read in full) +- SC1-65 minor CAP-009-FINDINGS.md:146 "Every AT+BIEV transition lands at or extremely close to the on-screen change for R (within ~1s)" — contradicted by the table directly below (:155-156: R 89 lags ~2 min 52 s–3 min, R 88 lags ~9 min 32 s) and by EVENT-NOTES:230. Fix: "the first transition within ~1 s; later ones minutes before the on-screen change". +- SC1-66 minor CAP-009-EVENT-NOTES.md:247-248 "69 pushes (73 total `AT+BIEV`/`AT+CIND` frames combined)" — stale vs FINDINGS.md:99-101 (2026-10-03 correction: 68 `AT+BIEV=2` pushes; the 69th row is `AT+BIEV=1,1`). Re-count: Info column on DLCI 0x0c → 68 `AT+BIEV=2` (88×26, 89×19, 90×5, 92×6, 93×12), 1 `AT+CIND=?`, 1 `AT+CIND?`; `frame contains "AT+BIEV" or "AT+CIND"` → 71 frames, not 73. Fix: update both numbers. +- SC1-67 nit CAP-009-FINDINGS.md:120-121 "Gap statistics across all 68 consecutive-frame intervals ... computed from the full 69-row extraction" — after the correction there are 68 pushes = 67 intervals; the stats include the BIEV=1,1 row. Fix: recompute or state the basis. +- SC1-68 minor CAP-009-EVENT-NOTES.md:76-79 "`tshark -Y 'frame contains "google-pixel-buds-pro-v1"'` returned 0 matches for reasons not fully diagnosed" — not reproducible: the same filter now returns 75 (exit 0), equal to the raw byte count. Fix: replace the note with the working tshark result. +- SC1-69 minor CAP-009-EVENT-NOTES.md:126-128 "Two of the maintainer's own timestamps are refined by minutes" and :111 "off by minutes in more than one case" — the table (:137-147) shows refinements of ~0–5 s, and the coarse-pass misreads (:150-152) were ~35–45 s and ~9 s. Fix: "by seconds" / correct magnitudes. +- SC1-70 nit CAP-009-EVENT-NOTES.md:58 "cross-checked four independent ways" — the list has 5 items (FINDINGS:35 says 5). Fix: "five". +- SC1-71 nit CAP-009-FINDINGS.md:325-327 "L and R track the maintainer's on-screen Left/Right values exactly, at every transition" — §3's own table (:157-158) shows the on-screen L lagging Option E by ~9 and ~13 min. Fix: "match in value and order". +- SC1-72 nit CAP-009-FINDINGS.md:381 "14 transitions across the whole session" — the table has 14 rows including the "first frame" row = 13 transitions. :440 labels `030a` as the "Revision 6 device-info string, cf. CAP-002-FINDINGS.md §2a" — 03 0a is the session nonce (Revision 6 is code 0x09) and the reference is CAP-002 §3. :283 "as AGENTS.md §5 currently states" — stale (AGENTS §5 now cites CAP-009). + +#### CAP-010 (read in full) +- SC1-73 minor CAP-010-EVENT-NOTES.md:11-12 "a standard "Forget Buds, re-pair via the official app" flow" vs the same file :30-31 and FINDINGS §1 :45-58 ("forget-and-re-pair via system Bluetooth settings", system "Pair new device", standard SSP dialog). Fix: "re-pair via system Settings, then the official app's setup". +- SC1-74 nit CAP-010-FINDINGS.md:96-100 "Ten Read/Write/Handle Value Notification exchanges" vs the §3 table (:139-151) with 13 rows (16 ATT packets); 35 ATT packets total verified (`-Y btatt` → 35) = 6 Read-By-Type + 16 cluster + others unaccounted. Fix: recount categories so they sum to 35. +- SC1-75 nit CAP-010-FINDINGS.md:70-75 "zero matches" for discovery opcodes — no exit status / positive control (§13.8; e.g. CAP-017's 137 discovery frames with the same filter). Same for CAP-010's claim reused in the index row. +- SC1-76 nit CAP-010-FINDINGS.md:249-252 §7 item 3 "Update ... Capture Index row for CAP-010 (currently *planned*/TBD)" — stale, row :2229 is "analyzed". Fix: mark done. +- SC1-77 nit CAP-010-EVENT-NOTES.md:55 "reports the same 100% aggregate battery indicator" — "aggregate" stale vs CAP-009 (BIEV is per-earbud). EVENT-NOTES:65 row labelled "(after video ends)" contains the 11:43:49 LE disconnect "inside the video window". +- SC1-78 minor CAPTURE_BLUETOOTH_HCI_SNOOP.md:2229 CAP-010 row Test-IDs "`PAIR-001`" only; EVENT-NOTES uses CASE-003 (:47) and GATT-001 (:53, the Group W target). Fix: sync. + +#### CAP-011 (read in full) +- SC1-79 major CAP-011-FINDINGS.md:181-185 "Entries idx=1 and idx=2 match the on-screen Left/Right percentages exactly at every sampled point ... the 09:52:24→09:52:25.8 transition (92/87 ...) wire event ~0.86s before the UI visibly updates" — the file's own table (:177) shows L=92 already at 09:49:28.326 (frame 4222, `0x5c`), while the screen shows Left 93% until 09:52:25.8 (EVENT-NOTES:51-52). So at 09:49:28 wire and screen disagree, and Left's wire change precedes the UI by ~3 min, not 0.86 s (only Right 88→87 is ~0.86 s). Same error in EVENT-NOTES.md:52 ("first two entries (92, index 1) and (87, index 2) match these exact new values, ~0.86s before the UI updates"). Fix: state L changed on the wire at 09:49:28 (UI lag ~3 min, cf. CAP-009 §3's lag hypothesis). +- SC1-80 minor CAP-011-EVENT-NOTES.md:38 / FINDINGS.md:23 "Classic peer not confirmed this session (no `bthci_evt.code==0x03` Connection Complete checked)" — the §7b DLCI 0x08 analysis is therefore not scoped to the Buds' connection (AGENTS §13.1). Easily closed: `-Y 'bthci_evt.code==0x03'` → single Connection Complete, frame 728, 09:45:24.958, handle 0x0002; all 338 RFCOMM payload frames are on chandle 0x0002. Fix: add this. +- SC1-81 nit CAP-011-FINDINGS.md:249-250 "4:02, 2:56, 8:21 apart — not a fixed cadence like HFP's ~6–7s" — last gap 09:52:24.925→10:00:45.105 = 8:20.2; "HFP's ~6–7s" is stale (CAP-009: irregular). +- SC1-82 nit CAP-011-FINDINGS.md:209-226 "18 sessions checked ..." but the result reports only CAP-001/CAP-002 ("exact 3-for-3 match in both independent sessions"); the outcome for the other 16 is not stated. Fix: state per-session outcome or narrow the claim. + +#### CAP-012 (read in full) +- SC1-83 major CAP-012-FINDINGS.md:365-369 §10 "Conclusion: 🟢 FACT — hypothesis confirmed. CAP-004's CTKD bonding mechanism was specifically caused by nRF Connect's early BLE connection existing before classic pairing began" and :158-161 "This directly confirms ... CTKD was an artifact of nRF Connect's early BLE connection specifically" — contradicted by the 2026-10-03 rewrite in the same §2 (:162-167: CAP-003 had an early LE link and still used SSP; the gate is an SMP pairing on the LE link, 🟡 HYPOTHESIS, ADR-030 Update). Same stale wording in CAP-012-EVENT-NOTES.md:11-15 and CAP-004-FINDINGS.md:669-671. Fix: rewrite §10's conclusion to "classic SSP without an LE SMP pairing" and mark the causal claim 🟡. +- SC1-84 minor CAP-012-FINDINGS.md:267-270, :325-327, :343-344 and EVENT-NOTES.md:104-105 "first Pixel-7a `PAIR-003` data point" / "previously captured only on the Pixel 9a" — superseded by TESTPLAN:295 (corrected 2026-08-28: first Pixel 7a occurrence is CAP-006; CAP-007 too). Fix: pointer. +- SC1-85 minor CAP-012-EVENT-NOTES.md:60-62 vs :49 "All timestamps below are video-confirmed to within 1s" / "directly comparable": the wire runs ahead of the video by ~2 s — `Delete Stored Link Key` 15:31:05.955 before the 15:31:08 tap, IO-capability exchange done 15:31:06.466 before the dialog at 15:31:09, `Simple Pairing Complete` 15:31:18.872 before the 15:31:20 Pair tap (SPC cannot precede the confirmation). FINDINGS:134 repeats "tapped 15:31:20" without noting it. Fix: measure and state the video↔log clock offset (cf. CAP-008 SC1-63). +- SC1-86 nit CAP-012-FINDINGS.md:214-216 "13 header pairs, in order" — 18 pairs are listed. Fix: "18". +- SC1-87 nit CAP-012-FINDINGS.md:339-342 §9 item 2 "a dedicated look at whether CAP-002's original silent session ever tore down its ACL link" — obsolete per :294-296 (CAP-002 "silence" was a filter artefact). Fix: mark done/obsolete. + +#### CAP-013 (read in full) +- SC1-88 minor CAP-013-EVENT-NOTES.md:28-29 "The log's first frame lands ... (a passive BLE advertising report at 17:11:45.799, immediately followed by `Delete Stored Link Key`" vs the same file :79 "First frame ... `Sent Read By Type Request` (ATT, handle 0x0003): an LE/ATT link already existed at log start (frame 2 is the first LE Extended Advertising Report)". tshark: frame 1 = "Sent Read By Type Request", frame 2 = LE Extended Advertising Report. Fix: correct :28-29 (and note the pre-existing LE/ATT link there too). +- SC1-89 minor CAP-013-FINDINGS.md:157-158 "Unlike CAP-001–CAP-004/CAP-006 (where DLCI 0x02 opens in the same initial multiplexer burst" — CAP-004 never opens DLCI 0x02 (CAP-004-FINDINGS.md:83-84 "Channels 1 and 2 are never opened at all"). Fix: drop CAP-004. +- SC1-90 minor CAP-013-EVENT-NOTES.md:117-120 "Group A's associated Test-IDs are PAIR-001 (primary) and incidental BATT-004 ... APP-001/APP-002 ... are not Group A's own assigned Test-IDs" — TESTPLAN:297-298 assigns APP-001/APP-002 to Group A (and GFPS-002 to "A (incidental)"); the second traceability section (:122-124) treats GFPS-002 as expected for Group A. Fix: reconcile the two sections with TESTPLAN. +- SC1-91 nit CAP-013-FINDINGS.md:219-220 §8 "DLCI 0x02's delayed opening ... Needs a repeat" — already tested/not reproduced by CAP-031 (§5 update :169-173). Fix: pointer. Also :119 "silence reproduced a 5th time" (CAP-008 counts 13 prior silent captures). +- SC1-92 nit CAP-013-FINDINGS.md:141-144 / EVENT-NOTES.md:100 battery push "frames 1423–1425 ... near this window" for the 17:13:47 reading — frames are at 17:14:20.259, 33 s later and after the video ended (17:14:04). Fix: state the gap. + +#### CAP-014 (read in full) +- SC1-93 minor CAP-014-FINDINGS.md:375-379 §9 "🔴 Handle 0x0034's real identity and the "missing first 3 bytes" of its device-name-like read value ... cross-connection-contaminated handle from a different peer device ... still open" — contradicted by the 2026-10-03 correction in §4c (:258-269: frame 3353 is a complete Device Name read; handle 0x0034 = GAP Device Name on the Buds' own table, as CAP-034 shows). Fix: close the §9 bullet with a pointer. +- SC1-94 minor CAP-014-FINDINGS.md:296-299 "consistent with CAP-012's already-established explanation ("CTKD when a BLE tool connects first, classic SSP when only system settings is used") ... CAP-012-FINDINGS.md §2 already carries this as 🟢 FACT" — CAP-012 §2 was rewritten 2026-10-03 (gate = SMP pairing on the LE link, 🟡 HYPOTHESIS, ADR-030 Update). Same in EVENT-NOTES.md:130-131 ("BLE-tool-first → CTKD"). Fix: pointer. +- SC1-95 minor CAP-014-EVENT-NOTES.md:17-23 and :139-141 "the `0x0c0X`/`0x0f2X` handle↔UUID mapping is still 🔴 OPEN" — no pointer to CAP-034's resolution (the FINDINGS has a banner :3, the EVENT-NOTES does not). Also :155-156 unchecked "[ ] Update this session's row in §9 Capture Index" although the row (:2233) is filled/analyzed. Fix: add banner/pointer, tick the box. +- SC1-96 nit CAP-014-FINDINGS.md:161-164 "three other LE connections exist ... on chandles 0x0002/0x0006/0x0009" — the table below (:169-174) lists five (0x0002, 0x0006, 0x0007, 0x0008, 0x0009). Fix: "five other LE connections (three carry discovery bursts)". +- SC1-97 nit CAP-014-FINDINGS.md:238 "3rd session now confirming this exact string on this exact handle" — CAP-002, CAP-003, CAP-010 already gave three (CAP-010-FINDINGS.md:164-165 "now four independent sessions"); CAP-014 is the 4th/5th. Fix: recount. + +#### CAP-015 (read in full) +- SC1-98 minor CAP-015-FINDINGS.md:304-305 "the quintet's 5 elements change in a strict, repeating order — index 4, then 3, then 2, then 1, then 0 — across three full passes" and :319 "the same order, independently, 3 times"; also :31-32 / :152 "(3 passes)", EVENT-NOTES.md:31 "in three repeating passes" and :87 "three passes total". The §4 table (:182-237) and EVENT-NOTES:74 ("Second full pass begins") show two passes of the 4→0 order: pass 1 = two extreme cycles per band, pass 2 = one near-zero cycle per band (3 cycles per band, 2 passes). Fix: "two passes (3 save cycles per band)". +- SC1-99 minor CAP-015-FINDINGS.md:349-352 after the 2026-10-08 correction "field 18 fires 1.1–8.9 s after the last field-16 preview ... — far too fast for a deliberate, separate tap on a UI element" — 8.9 s (Treble cycle 1, 2785→2819) is not "far too fast"; the argument no longer follows from the corrected range (and banner :3 says CAP-053 shows field 18 = Save). Fix: drop the "too fast" argument / point to CAP-053. +- SC1-100 minor CAP-015-EVENT-NOTES.md:54-78 Test-ID column uses non-existent placeholders "`EQP` (other)" and "`EQS` (Upper treble)"/(Treble)/(Mid)/(Low bass) — TESTPLAN:127-137 defines EQP-003 (Light bass), EQP-004 (Balanced), EQP-005 (Vocal boost), EQP-006 (Clarity), EQP-007 (Last saved), EQS-001/002/003/005, each marked "Captured 2026-08-18 (CAP-015)". The §9 index row (:2234) lists only `EQP-002`, `EQS-004`. Fix: use the real IDs in both places. +- SC1-101 nit CAP-015-FINDINGS.md:113-116 "Six presets tapped in sequence ... (Default/`Last saved` baseline first" — the baseline frame 2111 is the page opening, not a tap (EVENT-NOTES:52), and Default (EQP-001) ≠ Last saved (EQP-007). Fix: "five presets tapped plus the page-open baseline". +- SC1-102 nit CAP-015-EVENT-NOTES.md:46-47 `Create Connection` at 06:11:45.417 (frame 1376) is listed before the 06:11:46 Connect tap — wire precedes the video by ≥0.6 s; not commented (same clock-offset pattern as CAP-008/CAP-012). + +#### CAP-016 (read in full) +- SC1-103 minor CAP-016-EVENT-NOTES.md:67 "🟡 HYPOTHESIS this is the Buds' BLE/GATT link (address differs from the classic BD_ADDR ...)" for `4f:25:00:85:9a:b1`/handle 0x0002 — contradicts the same file's :51 ("BLE link to another device (a heart-rate wearable ...) — not the Buds") and FINDINGS §2 (2026-10-03 correction, Heart Rate service frame 760). Also FINDINGS.md:299-301 §9 still asks "Does 4f:25:00:85:9a:b1 ... belong to the same physical Buds unit?" Fix: update :67 and close the §9 bullet. +- SC1-104 minor CAP-016-FINDINGS.md:333-336 "the HID payload is `a3 02` ... followed by 45 bytes that decode ... as: ASCII `#AndroidHeadTracker#1.0` (24 bytes) + exactly 8 0x00 ... + `BT` + 6-byte BD_ADDR" — from the hex shown: string = 23 bytes (`23 41…23 31 2e 30`), payload after `a3 02` = 23+8+2+6 = 39 bytes (L2CAP length 0x29 = 41 = 2+39; 0x2d = 45 is the ACL length incl. the 4-byte L2CAP header). Fix: 23 bytes / 39 bytes. +- SC1-105 minor CAP-016-FINDINGS.md:180 frame 1521 context "both buds still docked at this instant (first removed ~06:32:00–02, connect completes 06:32:02.749 ..." — self-contradictory: the notify is at 06:32:03.567, after the first bud's removal (≈06:32:00–02) that triggered the connect (§1). The battery table :238 also shows only Right charging (Left out) from 06:32:03.557. Fix: "one bud out (Left), Right still docked". +- SC1-106 nit CAP-016-FINDINGS.md:214 "`AT+BIEV`-shaped HFP heartbeat frames at their ordinary ~6–7s cadence" — stale vs CAP-009 (irregular). :126 "re-opened all four with SABM 2815/2843/2904" lists three frames. EVENT-NOTES.md:98 "the complete 20-row event timeline" — the table has 23 rows. + +#### CAP-017 (read in full) +- SC1-107 minor (cross-file, found here) CAP-004-FINDINGS.md:575 and CAP-004-EVENT-NOTES.md:48 transcribe the unnamed service as "`Unknown Service (109b8b21-50e3-45cc-8ea1-ac62de4846d1)`" — CAP-017-FINDINGS.md:151 (video + nRF text log), CAP-014-nrf-connection.log:95 and CAP-014-FINDINGS give `109b862f-50e3-45cc-8ea1-ac62de4846d1`. CAP-004's 2026-08-28 correction fixed only the `13190001`→`15190001` digit, not this one; `109b8b21` occurs nowhere else in the repo. Fix: same correction note for this UUID. +- SC1-108 minor CAP-017-FINDINGS.md:161-164 "both 128-bit UUIDs are new to this project as of this capture" — CAP-004 (2026-08-11, five days earlier) already recorded both services from nRF Connect's UI (CAP-004-FINDINGS §6); the grep only covered DESKRESEARCH/REVERSE_ENGINEERING/PROTOCOL. Fix: "first wire-session discovery; already UI-recorded in CAP-004". +- SC1-109 minor CAP-017-FINDINGS.md:76 "→ 137 matches (vs. zero in the 11:42 CAP-010, CAP-002, CAP-003)" — the filter includes opcodes 0x08/0x09 (Read By Type); the same filter on CAP-010's log returns 6 (verified), and CAP-003 has 1649/1657. The "zero" applies only to 0x04/0x05/0x10/0x11. Fix: restrict the comparison. (137 itself verified: 52+22+5+4+28+26.) +- SC1-110 nit CAP-017-FINDINGS.md:191 "0x0f28 ... Same value as CAP-002, CAP-003, the 11:42 CAP-010 — 5th confirming session" — CAP-002 never read 0x0f28 (CAP-003-FINDINGS.md:152 Task 12: "CAP-002: only 0x0f2a"); this is the 3rd session (CAP-003, CAP-010, CAP-017). Fix: recount. +- SC1-111 nit CAP-017-FINDINGS.md:368-369 "five standard LE Audio services (BASS/ASCS/PACS/VCS/MICS/AICS/CAS/TMAS-family)" — eight acronyms (and eight LE Audio rows in §3). :393-395 "Update Capture Index with this second CAP-017 session — the existing CAP-010 row currently only describes the 11:42 attempt" — stale (row :2236 exists). :6-7 / EVENT-NOTES:5 "a second, independent `CAP-017` capture" — garbled (it is the second Group W session, CAP-017 vs CAP-010). +- SC1-112 nit CAP-017-EVENT-NOTES.md:58-59 classic `Connect Complete` 18:32:58.425 (frame 994) vs the next row "18:32:58.42–58.72 ... Frames 960–1090, 18:32:58.150–58.719" — start time inconsistent (58.42 vs 58.150) and frame 960 precedes 994. + +#### CAP-018 (read in full) +- SC1-113 minor CAP-018-FINDINGS.md:166-169 "Does not resolve: whether CAP-016's original burst ... was itself from the Buds or from a similarly-unrelated device — ... no cross-capture check of CAP-016-btsnoop_hci.log was performed" and §9 :228-230 (🔴) — stale: CAP-016-FINDINGS.md §2/§11 (corrected 2026-10-03) put all 73 frames on handle 0x0002, a Heart-Rate wearable, not the Buds. Also EVENT-NOTES.md:10-15 purpose text still says CAP-016's burst came with "a bud removal/insertion happening nearby" without pointer. Fix: close with pointer. +- SC1-114 minor CAP-018-FINDINGS.md:107-108 "an unnamed/vendor UUID, same "Unknown Service" outcome CAP-016-FINDINGS.md §11 itself reported for this handle range" — CAP-016 §11 (:397-399) says no discovery response resolving handle 0x0044's UUID was captured; it reported no "Unknown Service". Fix: drop the cross-reference. +- SC1-115 minor CAP-018-FINDINGS.md:55-56 "video-overlay time and log wall-clock time agree closely in this capture, consistent with every other capture in this project that has checked this" — contradicted by CAP-016-FINDINGS.md §6 (3–5 s film/wire offset, not constant) and the ~2 s offsets in CAP-012/CAP-008 (SC1-63/SC1-85). Fix: drop "consistent with every other capture". +- SC1-116 nit CAP-018-FINDINGS.md:180-182 the gap check is filtered to `bthci_acl.chandle==0x0003` yet its stated output mentions "background BLE advertising reports and periodic Number-of-Completed-Packets", which that filter cannot return; no exit status/positive control (§13.8). EVENT-NOTES.md:73 "20s after the video ends" vs FINDINGS:93 "~18s" (06:10:10.008 − 06:09:52 = 18 s). EVENT-NOTES.md:69 "Settable=0x00=docked" has no ADR-049 pointer (FINDINGS has the banner). + +#### CAP-019 (read in full) +- SC1-117 minor CAP-019-FINDINGS.md:213-214 "OFF writes were captured for conversation detection (this capture, frame 1720 `4:{22:0}`) and touch controls (CAP-020 frame 1995 `4:{4:0}`); not for Multipoint or head gestures" — contradicted by this file's own §4 (:167-178: Multipoint OFF write frame 2482 `4:{11:0}`, on film) and by CAP-020 (index row :2239 "head gestures OFF: frame 2038"). Fix: list 2482 and CAP-020 2038. +- SC1-118 minor CAP-019-EVENT-NOTES.md:35 "Multipoint was found OFF and switched ON once" and the timeline (:46-48) — omit the Multipoint ON→OFF write at 07:39:13.783 (frame 2482, on film 07:39:14, FINDINGS §4, rewritten 2026-10-03) and the SASS round that follows it (2487–2496). Fix: add the row; AGENTS §13.7. +- SC1-119 minor CAPTURE_BLUETOOTH_HCI_SNOOP.md:2238 CAP-019 row "both toggles isolate to a single DLCI 0x02 `Sent` frame each (🟡 HYPOTHESIS ...)" — stale: CONV-001 is 🟢 FACT with two writes (1720 OFF, 1808 ON; FINDINGS §3, PROTOCOL §4.5.1), Multipoint has two writes (2293, 2482) and field 11 is 🟢. Fix: update the row. +- SC1-120 nit CAP-019-FINDINGS.md:221-224 "New, higher-confidence finding: Multipoint's DLCI 0x02 write is immediately followed by a DLCI 0x04 Group 0x07 (SASS) negotiation burst containing an ASCII "in-use" string — the first content-level, action-correlated data" (also EVENT-NOTES:128-131 "Multipoint activates SASS") — §4 (:159-165) itself shows 0x11/0x40/0x41("in-use")/0x42 also run at connect; only 0x21 and the flags bit are Multipoint-specific (and CAP-002 already had "in-use" at connect). Also "confirms ... may trigger an SDP/connection update" — no SDP traffic is shown. Fix: narrow the claim. +- SC1-121 nit CAP-019-FINDINGS.md:209 / EVENT-NOTES.md:107 "a fourth and fifth data point" while the table has four rows. + +#### CAP-020 (read in full) +- SC1-122 minor CAP-020-FINDINGS.md:193-195 §6 "TOUCH-001 and HEAD-001 both isolate cleanly to a single Sent frame on DLCI 0x02 ... 🟡 HYPOTHESIS level" — contradicted by §3 :110-111 (TOUCH-001 field 4 = 🟢 FACT in both directions, 1741 and 1995, ADR-019) and by the two writes per toggle documented in §3/§4. Same stale wording in the §9 index row :2239 ("both toggles isolate to a single DLCI 0x02 Sent frame each (🟡 HYPOTHESIS"). Fix: update §6 and the row. +- SC1-123 minor CAP-020-EVENT-NOTES.md:33-35 "Both toggles were found already OFF ... switched ON once each; "Use touch controls" was then switched OFF again" — omits the head-gestures OFF write (07:47:35, frame 2038) that the same file adds at :11 and :47. Fix: add it to the session-shape paragraph. +- SC1-124 nit CAP-020-EVENT-NOTES.md:46 "07:46:56–07:47:19 | Screen remains ... with both toggles ON" — head gestures was tapped ON only at 07:47:00 (confirmed 07:47:04); row :47 (07:47:35) is placed before row :48 (07:47:20) — out of order. +- SC1-125 nit CAP-020-FINDINGS.md:288-289 "`qhr`'s own oneof field numbers (the same 38-field schema PROTOCOL.md §4.1's ANC command and §4.2's EQ quintet also live inside)" — PROTOCOL §4.1's ANC command is the DLCI 0x04 Message Stream Group 0x08 Set (ADR-009), not a `qhr` WriteSetting. Fix: drop §4.1 or name the qhr ANC field if one exists. Also :210-211 second open question keeps 🔴 though §8 answers it. + +#### CAP-021 (read in full) +- SC1-126 major CAP-021-EVENT-NOTES.md:56 "Frame 1626, 08:00:09.891252 — not a new toggle (touch controls was already ON, per CAP-020); a re-affirm" — wrong: CAP-020 ended with touch controls OFF (CAP-020 frame 1995 `4:{4:0}`), and this session's own connect-time read is OFF: `python3 scripts/pwrpc_decode.py CAP-021-btsnoop_hci.log` → 1140 `ReadSetting 4:{4:0}`, then 1626 `WriteSetting 4:{4:1}` mirrored 1633/1637. So 1626 is a real OFF→ON write (TOUCH-001), not a re-affirm. FINDINGS.md:216-218 repeats "the earlier TOUCH-001-shaped re-send". Fix: record it as a TOUCH-001 ON write and check the film at 08:00:09. +- SC1-127 minor CAP-021-EVENT-NOTES.md:141-144 and :147-150 decode "field7 { field1 { field4 wt=varint value=6 } }" / "innermost field 4 varint value encodes the selected action" — mechanically `22 02 08 06` is field 4 **length-delimited** containing field 1 = 6 (FINDINGS §8 :317-323 corrects this: qik.field4 → qho.field1). EVENT-NOTES not updated; index row :2240 still `field7{field1|2{field4=5|6}}`. Fix: correct notation in both. +- SC1-128 minor CAP-021-EVENT-NOTES.md:57 "Rcvd echo: frame 1903 ..., which uniquely also echoes the prior value (5 = Active noise control) alongside the new one (6)" — pwrpc decode: 1903 = `4:{7:{1:{4:{1:6}} 2:{4:{1:5}}}}`, i.e. Left=6 and Right=5 (Right's current value, not Left's prior value); 3623 likewise carries both sides (`1:…6, 2:…6`), so not "unique". Fix: "the mirror carries both sides' current values". +- SC1-129 minor CAP-021-FINDINGS.md:111-116 §3 "Status: 🟡 HYPOTHESIS ... Not 🟢 FACT: no official spec covers this" and §6 :235-238 "still 🟡 HYPOTHESIS overall" — contradicted by the table header :98 "Meaning (🟢, ADR-019)", banner :3 and §8 :338-343 (promoted 🟢 2026-08-30). Index row :2240 also still "(🟡 HYPOTHESIS ...)". Fix: pointer/update. +- SC1-130 nit CAP-021-FINDINGS.md:220-221 "TODO.md's CAP-008 item already earmarks DLCI 0x0a for a phone-call capture" — stale (CAP-008 done 2026-08-26, ruled out). :225-227 "HOLD-001–HOLD-004 are the first settings observed to nest a third level" — CAP-005's EQ write already nests field5{field4{field16{…}}}. + +#### CAP-022 (read in full) +- SC1-131 minor CAPTURE_BLUETOOTH_HCI_SNOOP.md:2241 CAP-022 row "all 3 isolate to DLCI 0x02 writes (🟡 HYPOTHESIS: field19=Mono audio, field15=Volume EQ, field17=Volume balance ...); Volume balance's persistent-write claim not tested (no disconnect/reconnect this session)" — stale: FINDINGS banner :3 / §3 / §5 (fields 15/17/19 🟢; persistence 🟢 via CAP-022 2099 → CAP-023 1047, PROTOCOL §4.5.7). Fix: update row. +- SC1-132 minor CAP-022-EVENT-NOTES.md:80-84 "a continuous drag emits many wire updates per second ... most likely just under-sampling on the video side" and FINDINGS.md:174-176/:218-220 "a single continuous drag gesture" — the 7 writes are spaced 6.6–8.8 s apart over 46 s (08:16:34.740 … 08:17:20.458) and run monotonically −100 → +100 then 5 (zigzag), which is not "many updates per second"; the video description stops at 08:16:50 while writes continue to 08:17:20. Fix: describe as repeated drags/settles; re-check film 08:16:50–08:17:20. +- SC1-133 nit CAP-022-FINDINGS.md:37-40 "a 34-frame sequentially-incrementing burst (0x2001→0x2026)" — 0x2001…0x2026 is 38 values (CAP-021-EVENT-NOTES:55 says 38 for the same pattern); 45 − 11 = 34. Fix: reconcile count vs range. +- SC1-134 nit CAP-022-FINDINGS.md:82-83 "toggle tapped ON at t≈33s (08:15:57) ... within a few seconds of frame 1621 (08:15:53.965)" — the write precedes the filmed tap by ~3 s while the OFF pair agrees to 0.4 s; EVENT-NOTES:44 calls it "video-confirmed". Fix: re-read the film at t≈29–33 s. §4 :104-105 and §6 :213-214 still say field 15 "stays 🟡" (banner says 🟢, ADR-025 Update). + +#### CAP-023 (read in full) +- SC1-135 nit CAP-023-FINDINGS.md:76-80 / EVENT-NOTES.md:49, :89-92 "zero RFCOMM traffic ... checked explicitly across the whole ±15s window around each action" — the command's epoch window 1787293445–1787293475 = 08:24:05–08:24:35, which is not ±15 s around 08:24:17 (FW-002) or 08:24:23 (FW-001); EVENT-NOTES quotes it with placeholders X/Y; no positive control (§13.8). The result holds (re-run 08:24:02–08:24:45 `btrfcomm.len>0` → 0 rows, exit 0; frames at 08:23:46–47 e.g. 849/1047 match the same filter outside the window). Fix: state the real window and add a control. +- SC1-136 nit CAP-023-FINDINGS.md:115-116 "FW-003 ... remain unattributed — no capture has visited the "About" page yet" — stale for FW-003 (TESTPLAN:150 lists Groups BH–BL and "Since 1.2.0 OpenControl reads ..."); FW-004 still a gap per TESTPLAN:151. Fix: pointer. + +#### CAP-024 (read in full) +- SC1-137 minor CAP-024-EVENT-NOTES.md:10-12 "alongside Multipoint — confirming CAP-019's Multipoint change persisted across sessions, both still showing ON at this session's start" — CAP-019 ended with Multipoint switched OFF (CAP-019 frame 2482 `4:{11:0}`, 07:39:13, on film). CAP-024's connect read is `4:{11:1}` (frame 1014, pwrpc_decode), so Multipoint was switched on again somewhere in between; it is not CAP-019's change persisting. Fix: reword. +- SC1-138 minor CAPTURE_BLUETOOTH_HCI_SNOOP.md:2243 CAP-024 row "(🟡 HYPOTHESIS: field2=In-ear detection, field28="Bud return"/CASE-001, field27="Other alerts"/CASE-002)" — stale vs FINDINGS banner :3 (field 2 🟢 ADR-019 Update 2026-09-28; field 28 🟢; field 27 category 🟢). Same in FINDINGS §3 :79 and §6 :185-186 ("field2 ... remains 🟡"). Fix: update. +- SC1-139 nit CAP-024-FINDINGS.md:182 "Now 9 distinct settings across CAP-019–CAP-024" — the captures in that range attribute 12 distinct qhr fields (2, 4, 7, 11, 12, 15, 17, 19, 22, 27, 28, 29). :193-195 "CASE-001 has one unambiguous and one weakly-correlated sample" — after the 2026-10-03 correction both are real changes (1096 read 1 → 1988 write 0). + +#### CAP-025 (read in full) +- SC1-140 minor CAP-025-EVENT-NOTES.md:124-125 unchecked "[ ] If confirmed, promote PROTOCOL.md §4.4 from 🟡 HYPOTHESIS to 🟢 FACT — not done here; maintainer sign-off required" — stale: FINDINGS §3/§4/§8 (:79-81, :106, :155-158) record 🟢 FACT promoted 2026-08-23 (ADR-011). Fix: tick with pointer. +- SC1-141 minor CAP-025-EVENT-NOTES.md:36 "Buds MAC ... Not independently confirmed this session" vs FINDINGS.md:26 "`04:00:6e:cf:6e:07` (confirmed via `bthci_evt.bd_addr` on the wire ...)". Fix: align (and cite the command/frame). +- SC1-142 minor CAP-025-FINDINGS.md:159-162/:172-174 and index row :2244 "Case/combined ringing goes through Find Hub's network-mediated path, not the direct local Message Stream command" — no pointer to the 2026-09-24 ADR-027 Update recorded in TESTPLAN:158 (FIND-004): the Device Action spec defines a local Ring value `0x03` = both, untested on this firmware. Fix: add pointer, scope the claim to "the official app uses Find Hub". +- SC1-143 nit CAP-025-FINDINGS.md:112 "0x00 ... 3 occurrences, all following a video-confirmed "Mute Left/Right" tap or immediately after" — the third (2202) has no video-visible tap (:86-88 "Stop (not ringing)"). EVENT-NOTES.md:55 still calls 2202 "likely a residual retry". Index row :2244 "matches the spec's worked ACK example exactly" — FINDINGS §6 :127 says the Buds' ACK carries one state byte vs the spec example's two. + +### sub_caps_026_050 + +#### Sub-review CAP-026..CAP-050 (progressive) + +#### CAP-026 (read in full: EVENT-NOTES 100 lines, FINDINGS 157 lines) +- SC2-01 minor: CAP-026-EVENT-NOTES.md:61 "~06:49:58 | Bluetooth toggled ON ... Video `t=8` (overlay `06:50:01`)" — time column contradicts its own overlay (06:49:53 + 8 s = 06:50:01; every other row uses start+t). Fix: time 06:50:01, or fix t. +- SC2-02 minor: CAP-026-EVENT-NOTES.md:69 "06:50:19–06:51:19 ... **Zero DLCI 0x02/0x04/0x08 traffic anywhere in this window** (log query, `06:51:00`→end, returns 0 rows)" — the cited query does not cover 06:50:19–06:51:00 (first 41 s of the window); FINDINGS.md:105-111 also starts at 06:51:00. Claim is true (my check: last DLCI 2/4/8 data frame is 1343 at 06:50:05.380, query `frame.time>="2026-09-12 06:50:05"` returns 1339–1343, exit 0), but no command/positive control for the stated window. Fix: cite the query from 06:50:06 and add positive control "same filter from 06:50:05 matches 1339/1340/1342/1343" (AGENTS §13 step 8). +- SC2-03 minor: CAP-026-FINDINGS.md:94-97 and :104-108 — negatives "(0 rows)" with no exit status and no positive control (AGENTS §13 step 8). Fix: add exit status and a positive control (e.g. `bthci_evt.code==0x03` returns 413; time filter from 06:50:05 returns 1339–1343). +- SC2-04 nit: CAP-026-EVENT-NOTES.md:63/65/80 still call `03 03` an "Option B candidate"; only FINDINGS has the 2026-09-30 banner ("🟢 Battery updated", ADR-031/033). Fix: add same one-line status pointer to EVENT-NOTES. +- SC2-05 nit: CAP-026-EVENT-NOTES.md:47/51 procedure numbered "42."/"43." (copied from Group numbering). Fix: renumber 1./2. or note source. + +#### CAP-027 (read in full: EVENT-NOTES 110, FINDINGS 263) +- SC2-06 minor: CAP-027-EVENT-NOTES.md:59 "15:45:59 | Double-tap ... frame 1909 @15:45:55.855 ... (offset 4.15s)" — 15:45:59 − 15:45:55.855 = 3.145 s, not 4.15. Repeated in CAP-027-FINDINGS.md:35 "one outlier at 4.15s, `TOUCH-003` @15:45:59". Fix: 3.15 s in both. +- SC2-07 minor: CAP-027-EVENT-NOTES.md:43 "run a consistent **~1.5–2.3s ahead of**" vs FINDINGS.md:35 "1.20–2.31s" and the notes' own rows :61 (1.20 s) and :68 (1.40 s) and :59 (3.15 s). Fix: notes header → "1.20–2.31 s, one outlier 3.15 s", matching FINDINGS. +- SC2-08 minor: CAPTURE_BLUETOOTH_HCI_SNOOP.md:2246 (CAP-027 index row) "all 13 instances 1:1-correlated" — the timeline labels 16 TOUCH-002..006 instances (TOUCH-002 ×5: 1580/2139/2251/2464/2642; -003 ×2; -004 ×5; -005 ×2; -006 ×2), FINDINGS §3.1 table lists 16 frames; FINDINGS.md:33 says "15 timestamped actions". Three different counts, none 13. Fix: state one count with its basis (16 labelled gestures + 2 "+tap once" PAUSE frames 2407/2587) in index and FINDINGS. +- SC2-09 nit: CAP-027-FINDINGS.md:19 and :71-77 cite "`TESTPLAN_BLUETOOTH_HCI_SNOOP.md` line 165–170" / "165: Play/pause" … "169" — those rows are now at lines 176–180 (`grep -n TOUCH-00 TESTPLAN…`). Fix: cite by Test-ID, not line number. +- SC2-10 nit: CAP-027-FINDINGS.md:137 "The bulk (398 of 511 payload frames) are exactly 30 bytes" vs the 2026-10-08 addendum at :147-149 "416 … of 426 stream packets … ai-sessions/0068 counted 420" — three counts of the same 30-byte stream with no reconciliation. Fix: one sentence saying which unit each counts (RFCOMM frames vs HDLC/pw_rpc packets) or correct 398. +- SC2-11 nit: CAP-027-FINDINGS.md:153-154 "both un-timestamped `TBD`/`TDB` in the original procedure notes" — EVENT-NOTES.md:72/75 now carry timestamps. Fix: "(were TBD in the draft; recovered from the wire)". +- SC2-12 nit: CAP-027-EVENT-NOTES.md:103-107 rename checkbox left `[ ]` with a contradictory explanation ("placeholder folder name already embeds the correct actual date"). Fix: tick it, "n/a — folder already named". + +#### CAP-028 (read in full: EVENT-NOTES 98, FINDINGS 150) +- SC2-13 major: CAP-028-FINDINGS.md:82-85 quotes the query `... frame.time>="2026-09-12 07:16:25" and frame.time<="2026-09-12 07:17:15"` with output "(0 rows)". Re-running that exact command returns frames 1442, 1443, 1444, 1448 (DLCI 0x02, 07:16:25.477–.488), exit 0. The text at :88 itself says the settling burst ends at 07:16:25.49 frame 1448, so the quoted output contradicts its own next paragraph. Same claim in CAP-028-EVENT-NOTES.md:65 "(log query `07:16:25`→`07:17:15` returns 0 rows)". The substantive conclusion (nothing between 07:16:25.488 and 07:17:15.549) holds. Fix: lower bound "2026-09-12 07:16:25.5" (0 rows) and add positive control (bound 07:16:24 → 1388…1448 and 1834…1847). +- SC2-14 minor: CAP-028-FINDINGS.md:27 heading "Precondition check — Head gestures already enabled (🟢 FACT, from `CAP-020`)" — the rewritten body (:29-31) says CAP-020 left it OFF (frame 2038) and the on-state comes from this session's own frame 1350, labelled 🟡. Heading stale/over-labelled. Fix: "(from this session's ReadSetting 1350 / DLCI 0x08 795; 🟡 value meaning)". +- SC2-15 minor: stale "enabled since CAP-020" text not updated with the 2026-10-03 rewrite: CAP-028-EVENT-NOTES.md:10-11 ("Requires 'Head gestures' enabled first (Group F, `CAP-020`)"), :23 and :38-39 ("already known working ... since `CAP-020`"), FINDINGS.md:18, and CAPTURE_BLUETOOTH_HCI_SNOOP.md:2247 index row "Head gestures already enabled from `CAP-020` (Group F)" — contradicts the CAP-020 index row :2239 ("head gestures OFF: frame 2038; it was on again in `CAP-028`"). Fix: pointer "CAP-020 left it off; on here per 1350". +- SC2-16 nit: CAP-028-EVENT-NOTES.md:61-62 rows out of time order (07:16:21.719 row before 07:16:21.689 row). Fix: swap. + +#### CAP-029 (read in full: EVENT-NOTES 128, FINDINGS 172) — spot-checked frames 1593, 2034, 2091, 2497, 2860, 3316: all match. +- SC2-17 major: CAP-029-FINDINGS.md:87-88 (§3, labelled 🟢 FACT) "This matches the already-documented factory-reset pattern from `CAP-001`/`CAP-002`'s own Group P precedent — a third/fourth confirming instance". CAP-001 is Group Z and CAP-002 Group A (folder names); `grep -i factory` finds no mention in any CAP-001/CAP-002 file, and the only other FINDINGS mentioning "factory reset" is CAP-012 (saying it was *not* one). Same unsupported claim in CAP-029-EVENT-NOTES.md:108-110 ("Confirmed — matches the established pattern"). No precedent exists to match. Fix: drop the precedent claim; record CAP-029 as the first wire record of a factory reset (FACT for frames 2091/2497/2860/3316 only). +- SC2-18 minor: CAP-029-EVENT-NOTES.md:105-107 "zero DLCI 0x02/0x04/0x08 traffic of any kind accompanies it" vs CAP-029-FINDINGS.md:60-62 "The only RFCOMM activity in the exact pause window ... is routine zero-length DLCI-0x02 flow-control `UIH` frames". Fix: "zero DLCI 0x02/0x04/0x08 payload frames". +- SC2-19 minor: CAP-029-FINDINGS.md:44-47 negatives "separately the entire pre-reset session `07:53:51`–`07:54:55`" and "no DLCI 0x02 write" — no command, exit status or positive control given (only the DLCI 4 07:54:00–:30 query is shown). Fix: show the commands (e.g. `btrfcomm.dlci==2 and btrfcomm.len>0` in the window) with exit status and a positive control (756/777 for DLCI 4). +- SC2-20 nit: CAP-029-EVENT-NOTES.md:30 "Record the decision here: TBD." while :53 records "**Run**". Fix: replace TBD with pointer to :53. +- SC2-21 nit: CAP-029-EVENT-NOTES.md:96 tags `APP-001`/`APP-002` (incidental); CAPTURE_BLUETOOTH_HCI_SNOOP.md:2248 index row Test-IDs list omits them. Fix: add "incidental `APP-001`/`APP-002`" to the index row (or drop the tag). +- SC2-22 nit: CAP-029-EVENT-NOTES.md:93 row time range "07:55:44.765–07:56:34.348" but lists a Delete Stored Link Key at 07:55:13 (frame 2091) outside it (already its own row :92). Fix: list only the 3 in-range events or widen the range. + +#### CAP-030 (planned skeleton; read in full: EVENT-NOTES 114, no FINDINGS — consistent with index "planned" and registry "planned") +- SC2-23 nit: CAP-030-EVENT-NOTES.md:9-10 "item #18 (passive BLE scan for the Battery Notification) is already planned separately as `CAP-011`" — CAP-011 is analyzed (2026-08-21) and repeated as CAP-043 (2026-09-13). Same stale wording in CAPTURE_BLUETOOTH_HCI_SNOOP.md:2249 ("item #18 already planned separately as `CAP-011`"). Fix: "covered by CAP-011 (analyzed) and its repeat CAP-043". +- SC2-24 nit: CAP-030-EVENT-NOTES.md:59-60 "explicitly out of scope per Google's own documentation (see Purpose above)" — the Purpose section (:9-14) says nothing about impulse sounds/Google docs. Fix: point to TESTPLAN `LOUD-001` row or procedure step 19. + +#### CAP-031 (read in full: EVENT-NOTES 102, FINDINGS 320) — spot-checked 529, 961 (cap_len 15/len 77), 1122 (DLCI 0x02, 06:07:19.203): match. +- SC2-25 minor: CAP-031-FINDINGS.md:306-308 Update says "(§8's proposed root-cause guess, 'extraction path vs. session-specific,' is now supported ...)" but §8 (:288-298) proposes only (a) file-size polling vs content freshness and (b) logging restart — no extraction-path guess; the same Update then says at :313-317 that §8's guess "is superseded". Self-contradictory. Fix: "§8's guesses are superseded by the extraction-path explanation (CAP-032 §0.1/§7 Test C)"; delete the "is now supported" clause. +- SC2-26 minor: CAP-031-FINDINGS.md:205-208 negatives ("→ 0 matches" for 43:8a:82:03:4b:f2; "Exactly one `LE Enhanced Connection Complete` event in the entire log") — no command for the count, no exit status, no positive control (AGENTS §13 step 8). Fix: give the LE-meta subevent filter with its output (529) and show the bd_addr filter matching 04:00:6e:cf:6e:07 as positive control. +- SC2-27 nit: CAP-031-FINDINGS.md:187 "all 5 DLCIs opened within ~6.85s" and :266 "all 5 channels" — §3's table lists 6 DLCIs (0x00, 0x0c, 0x0a, 0x08, 0x02, 0x04). Fix: "all 6 (5 + mux control)". +- SC2-28 nit: CAP-031-EVENT-NOTES.md:15-16 Video/Log rows end in "— " with no value (duration/packets missing; FINDINGS has 202.4 s / 1,747 packets, 227.98 s). Fix: fill from FINDINGS.md:13-16. + +#### CAP-032 (read in full: EVENT-NOTES 103, FINDINGS 358) — spot-checked 91, 768, 991, 1417 (hex matches), 1645: match. +- SC2-29 minor: "~58 s before the Forget tap" — CAP-032-FINDINGS.md:75, :233-234, :302; CAP-032-EVENT-NOTES.md:90; CAPTURE_BLUETOOTH_HCI_SNOOP.md:2251; CAP-031-FINDINGS.md:308. Log start 18:29:45.723 (frame 91 18:29:45.827) to the screenshot-confirmed tap 18:30:42 is 56.3 s (56.2 s); 58.2 s is to the HCI Delete Stored Link Key (frame 768, 18:30:43.928). Fix: "~56 s before the tap (~58 s before its HCI effect, frame 768)". +- SC2-30 nit: CAP-032-EVENT-NOTES.md:37 "begin at 18:31:14.998 (frame 991), ~3.6s later" after the 18:31:11 pair-button press — 14.998 − 11 = 4.0 s. Fix: "~4.0 s". +- SC2-31 minor: CAP-032-FINDINGS.md:248-250 "a total of 22 distinct addresses ... matching exactly the 22 distinct `bd_addr` values independently found elsewhere in this log (§0.3)" — §0.3 (:95-113) reports no count of distinct bd_addr values (it only says the Buds' address is absent before 768). Dangling cross-reference for a FACT-style claim. Fix: give the command that yields 22 distinct bd_addr values, or drop "(§0.3)". +- SC2-32 nit: CAP-032-FINDINGS.md:253 example "index `0x06`, frame 113: `01 00 06 40 00 11 11 01 80 00 00 00`" lists 12 bytes while the text says these entries have parameter length 0x12 (18) and frame 93 is quoted with all 18. Fix: quote all 18 bytes or mark "…". + +#### CAP-033 (read in full: EVENT-NOTES 143, FINDINGS 246) +- SC2-33 minor: CAP-033-FINDINGS.md:241-243 "Decode DLCI 0x0a in a session that actually opens it (this one didn't)" — DLCI 0x0a *was* opened this session: `tshark -r CAP-033-btsnoop_hci.log -Y 'btrfcomm.frame_type==0x2f'` → SABM frame 1536 (15:18:10.103, DLCI 0x0a), alongside 0x00/0x0c/0x08/0x04; it only carried no payload (`btrfcomm.len>0` per DLCI: 0x00 30, 0x04 53, 0x08 41, 0x0c 52). Fix: "opened (SABM 1536) but silent; decode in a session where it carries payload". +- SC2-34 minor: CAP-033-FINDINGS.md:146-147 describes the SDP protocol-descriptor list as "`[Group:1][Code:1][Length:2][RFCOMM channel:1]` protocol descriptor" — the quoted bytes (`35 0c 35 03 19 01 00 35 05 19 00 03 08 03`) are SDP data-element sequences (L2CAP UUID16 0x0100, RFCOMM UUID16 0x0003, uint8 channel), not the private Group/Code envelope; wrong structural reading (AGENTS §13 step 6). Fix: describe as "ProtocolDescriptorList: L2CAP, RFCOMM(channel N)". +- SC2-35 nit: CAP-033-FINDINGS.md:153/:157 "no traffic on this DLCI observed in this session" (DLCI 0x06, 0x12) — negative without command/positive control. Fix: cite the per-DLCI count command above (no 0x06/0x12 entries; 0x04 = 53 as control). +- SC2-36 nit: CAP-033-EVENT-NOTES.md:82 time column "15:17:23" for the Forget, while the row's own note and FINDINGS §1.1 table say tapped 15:17:24. Fix: 15:17:24. + +#### CAP-034 (read in full: EVENT-NOTES 156, FINDINGS 529) — spot-checked 3264, 3272, 3469, 3492, 3509, 3517, 3518, 3527, 3610; decoded hex of 3273/3446/3464/3435 re-derived: tables correct. +- SC2-37 minor: CAP-034-FINDINGS.md:141-142 "it confirms the client still trusts its already-resolved database (the one just declared 20s earlier in the same LE session)" — the discovery burst ended 06:47:45.471 (frame 3469) and the hash re-check is 06:51:22.158 (frame 3690): ~3 min 37 s, not 20 s. Fix: "~3.5 minutes earlier". +- SC2-38 nit: CAP-034-EVENT-NOTES.md:121 burst "06:47:42.147 - 06:47:45.490" vs FINDINGS.md:130/:163/:500 "06:47:42.142–45.471" (frames 3264 = 06:47:42.1417, 3469 = 06:47:45.4708 per tshark). Fix notes to .142/.471. +- SC2-39 nit: CAP-034-EVENT-NOTES.md:126 "unpaused by the 5.5s on-screen dialog gap ... (`Pairing Confirm` at 05.396 → `Pairing DHKey Check` at 08.900)" — that wire gap is 3.50 s; and :125 SMP range starts "06:51:05.069" while frame 3492 is 06:51:05.084; FINDINGS.md:472 gives SMP frames 3492–3527 vs notes 3492–3518. Fix: reconcile the gap figure and frame range. +- SC2-40 nit: CAP-034-FINDINGS.md:5 "All four promotions proposed in §9 ... have been applied" and :511 "all four applied" — §9 lists five numbered items (5 = no ADR). Fix: "items 1–4 applied; 5 = no ADR". +- SC2-41 nit: CAP-034-FINDINGS.md:457 "the official Fast Pair spec's own characteristics table (§4.2) lists these as 'Encrypted: No'" — §4.2 quotes no "Encrypted" column (only names/properties; the "No" quoted is from the FHN table for FE2C1238). Fix: quote the spec row or drop "(§4.2)". +- SC2-42 nit: CAP-034-FINDINGS.md:448-450 negative "(no output)" without exit status or positive control. Fix: add exit 0 and the same filter over 06:47:42–06:47:46 returning 3264…3469. +- SC2-43 nit: CAP-034-EVENT-NOTES.md:16-17 "Fill in each `[ ]`/`___` below as the session happens — do not pre-fill..." — leftover template instruction in an analyzed file. Fix: delete. + +#### CAP-035 (read in full: EVENT-NOTES 513 (dumpsys block skimmed), FINDINGS 360) — ATT list re-run (38 frames, matches count). +- SC2-44 minor: CAPTURE_BLUETOOTH_HCI_SNOOP.md:2254 (CAP-035 index row) "Two videos turned out to be sequential with only a ~7s recording-stop/restart gap" — corrected 2026-10-03 to ~3 s in CAP-035-EVENT-NOTES.md:29/:435 and FINDINGS.md:75 (06:50:53 + 67.69 s = 06:52:00.7 → 06:52:04). Index not updated. Fix: "~3 s". +- SC2-45 nit: CAP-035-FINDINGS.md:73-74 "separated by only a short (~3–7s) recording-stop/restart gap" left in the same sentence that corrects "~7s" to "~3 s". Fix: "~3 s". +- SC2-46 minor: CAP-035-FINDINGS.md:284-287 "38 ATT frames ... (Model Number String, Device Name, ... — all standard GAP/GATT service handles `0x0001`–`0x0009` — plus `0x0f32`/`0x0f33`" — tshark shows Model Number String at handle 0x0f26 (frames 577/728, Device Information) and Device Name at 0x0034 (711, GAP) / 0x0016 (590/729, phone-side GAP), outside 0x0001–0x0009. Conclusion (no 0x0c00–0x0c18 handle) still holds. Fix: list the actual handles (0x0001–0x0009, 0x0016, 0x0034, 0x0f26, 0x0f32/0x0f33). +- SC2-47 nit: CAP-035-EVENT-NOTES.md:455 "06:52:00 dialog appears ... video 1 stops ~1s later" vs FINDINGS.md:58 "Video 1's last captured frame (t=65s → 06:51:57) shows the system dialog ... already open". Fix: dialog visible by 06:51:57. +- SC2-48 nit: leftover template text in an analyzed file — CAP-035-EVENT-NOTES.md:16-18 ("Fill in each `[ ]`/`___` below"), :123 ("Record the exact output here, verbatim, even if empty: `___`"), :478-480 ("*(Fill in after capture ...)*"), :510 ("🔴 *(carry forward any that remain unresolved after this session)*"). Fix: delete or replace with pointer to FINDINGS §11. +- SC2-49 nit: CAP-035-FINDINGS.md:322 "DLCI 0x06 and 0x12 never open at all — first check of their existence under any condition" — CAP-033-FINDINGS.md:153/:157 (2026-08-30) already recorded "no traffic on this DLCI" for both. Fix: "first check under this Group's conditions" (as §7 :275 says). + +#### CAP-036 (read in full: EVENT-NOTES 575 (dumpsys block skimmed), FINDINGS 585) — spot-checked 1169/1182, 1392–1591, push frames 2009…2360, DLCI-2 total 99, Sent-DLCI-2 45, grep counts 0811=1/0813=1/0812=0: all match. +- App-version check (caller's question): CAP-036-EVENT-NOTES.md:116-117 records the on-screen app version as "`1.0.955078535`", and CAP-036-FINDINGS.md:34 repeats it; the index row (CAPTURE_BLUETOOTH_HCI_SNOOP.md:2255) therefore matches the capture's own notes. It is the only capture with …535 (all others …536, and AGENTS.md §0 names the analysed APK v1.0.955078536-…). No screenshot/frame is cited for the value, so a transcription slip cannot be excluded — see SC2-50. +- SC2-50 nit: CAP-036-EVENT-NOTES.md:117 app version `1.0.955078535` hand-entered without a video/screen reference, unlike the other metadata; differs by one digit from every neighbouring capture (…536). Fix: confirm from the video (Info/About screen if visible) and either keep with a frame/time reference or correct, then propagate to index/FINDINGS. +- SC2-51 minor: CAP-036-EVENT-NOTES.md:403 "Start video recording (Bluetooth off, Buds already out of case)" — contradicted by the wire and by FINDINGS: every DLCI 0x04 "Battery updated" frame from connect on reads `03 03 00 03 e4 e4 ff` (frames 1165/1167/1172 at 06:36:31.36, again 2002–2004 at 06:39:51) = 100 % with the charging bit set (ADR-031/033), the connect-time Notify reads Settable=0x00 (frame 1182), the UI shows "C:100%" (:412), and CAP-036-FINDINGS.md:553-555 concludes "both buds were in the case when this frame was sent". No docking event is in the timeline. Fix: correct :403 (buds in the open case, or record when they were docked) and add a pointer from FINDINGS §3/§12.6. +- SC2-52 minor: stale open question without pointer — CAP-036-EVENT-NOTES.md:533-537 ("Settable toggles ... `0x00` ... not reconciled") and :524-532 (DLCI 0x02 burst "not decoded"); FINDINGS has the 2026-09-30 banner (burst decoded, Settable = ADR-049) but EVENT-NOTES has none. Also CAP-036-FINDINGS.md:376-381 still lists both as 🔴 in §11 with no inline pointer (only the top banner). Fix: add "(answered: PROTOCOL §6 2026-09-24 / ADR-049)" to each item. +- SC2-53 minor: "~10s-ish cadence" for the periodic push — CAP-036-EVENT-NOTES.md:426, :476, :489 and CAP-036-FINDINGS.md:284 — contradicted by the file's own §12.1 (:414) gaps "10.1s, 70.1s, 10.1s, 9.6s, 50.4s, 9.6s" (my check on DLCI 0x02 frames 2009…2360 agrees). Fix: "irregular (10–70 s)". +- SC2-54 nit: video end time — CAP-036-EVENT-NOTES.md:65 and FINDINGS.md:39/:131 "06:41:16" (ffprobe 317.95 s from 06:35:58 = 06:41:15.95), but EVENT-NOTES.md:430 "06:41:18 | End video recording" and folder name …_06-41-18. Fix: 06:41:16 in :430 (folder name may stay, with a note). +- SC2-55 nit: CAP-036-EVENT-NOTES.md:99-101 and FINDINGS.md:77-78 say `bluetooth.addr` "is populated ... only for the HCI event layer" — but the same unrestricted filter returns 0 rows for every frame including HCI events (FINDINGS.md:73-74; my re-run: 0 rows, exit 0, while `bthci_evt.bd_addr==…` returns 906/948/951). Fix: "bluetooth.addr matches no frame in this log; bthci_evt.bd_addr does". +- SC2-56 nit: frame-range drift for the same bursts — DLCI 0x02 burst "1392–1591" (EVENT-NOTES:411, FINDINGS:220) vs "1404–1591" (EVENT-NOTES:473/529, FINDINGS:377); DLCI 0x08 burst "1186–1372" (EVENT-NOTES:410) vs "1273–1372" (EVENT-NOTES:482, FINDINGS:259). Fix: state SABM-inclusive vs first-payload ranges explicitly. +- SC2-57 nit: CAP-036-FINDINGS.md:139 "`CAP-036-EVENT-NOTES.md` line ~329's requirement" — the on-screen-values item is at line 345; and EVENT-NOTES.md:335 still has the template "Record the exact output verbatim ...: `___`". Fix: cite by section; delete template line. + +#### CAP-037 (read in full: EVENT-NOTES 239, FINDINGS 198) — spot-checked 8139, 11483, 11507, 12008, 12065: match. 16+10=26 and 31−2+2+3=34 recomputed OK. +- SC2-58 minor: CAP-037-EVENT-NOTES.md:218-236 "Next steps" — all seven boxes still `[ ]` ("Write `CAP-037-FINDINGS.md`", "Update ... Capture Index ... from `planned`", "Rename this capture's folder") although the status line :3-6 says FINDINGS written and folder renamed, and the index row is "analyzed". Fix: tick the done items. +- SC2-59 minor: CAP-037-EVENT-NOTES.md:206-211 item still labelled "🔴" and phrased as unresolved ("Not resolved by this capture") although its own inserted text says "answered by `CAP-048` §4" and FINDINGS.md:195 marks it 🟢 answered. Fix: relabel 🟢/answered. Likewise :183-191 reads the byte "per ADR-024" with no pointer to ADR-049 (FINDINGS has the banner at :3; EVENT-NOTES has none). Fix: add the same status banner. +- SC2-60 nit: CAP-037-FINDINGS.md:128-137 heading "DLCI 0x02 — silent throughout (🟢 FACT, clean negative)" — body says Sent frames do occur in every reconnect's settling burst; the command's output, exit status and a positive control are not given. Fix: retitle "no Sent DLCI 0x02 outside connect bursts" and show the per-burst frame ranges as output/positive control. +- SC2-61 nit: CAP-037-FINDINGS.md:142-147 presents frame 12065 "Rcvd Disconnect Complete" as output of `-Y "btrfcomm.dlci==4 and bthci_acl.chandle==0x0009"` — that filter cannot return an HCI event (12065 has no bthci_acl.chandle; verified). Fix: mark 12065 as from a separate `bthci_evt.code==0x05` query. +- SC2-62 nit: CAP-037-EVENT-NOTES.md:144 Test-ID column holds "🟢 (corrected 2026-10-03: the time given here was frame 11507's)" instead of a Test-ID. Fix: move to Notes column, Test-ID `OBS-004`. + +#### CAP-038 (read in full: EVENT-NOTES 224, FINDINGS 265) +- SC2-63 minor: CAP-038-FINDINGS.md:225-226 (§8) "no A2DP/GATT-battery traffic found on either connection (clean negative, §6)" — contradicts the rewritten §6 (:187-198, "An A2DP stream is configured and opened on both connections", 56 btavdtp frames) and §9 :245. Same stale text in CAP-038-EVENT-NOTES.md:178-180 "items 3–4 are clean negatives" although item 3 (:171-172) was rewritten to "AVDTP sets up an A2DP stream". Fix: "A2DP stream set up but not started (§6); no Buds GATT battery traffic". +- SC2-64 minor: CAP-038-FINDINGS.md:136 and :212 "DLCI `0x03`, 155 non-empty frames" — 155 is the all-frames census at :97 (includes SABM/UA/empty UIH); `btrfcomm.dlci==3 and btrfcomm.len>0` on chandle 0x0001 returns 132 (61 of them Sent, matching EVENT-NOTES :114). Fix: "155 frames (132 non-empty, 61 Sent)". +- SC2-65 minor: CAP-038-FINDINGS.md:169-176 "no `08 12` 'Set ANC state' frame anywhere in this entire log" backed by a query restricted to `bthci_acl.chandle==0x0004`, so chandle 0x0001's Message Stream (DLCI 0x05) is not covered; no exit status or positive control. (My whole-log check `btrfcomm.len>0 and data.data contains 08:12:00` → 0 rows, so the claim holds.) Fix: drop the chandle restriction and add a positive control (e.g. `contains 08:13:00` → 3282…4158 + 1154). +- SC2-66 minor: CAP-038-EVENT-NOTES.md:209-215 three boxes `[ ]` "(Owned centrally — not done by this pass.)" for Capture Index, TESTPLAN pointer and PROTOCOL §6 copy — the index row (CAPTURE_BLUETOOTH_HCI_SNOOP.md:2257) is "analyzed" and FINDINGS.md:235 says "done — the three 🔴 items are in `PROTOCOL.md` §6". Fix: tick them with pointers. +- SC2-67 nit: CAP-038-EVENT-NOTES.md:112/:136/:152-157/:193-197 and FINDINGS.md:149/:243-244 still read Settable 0x00 as "DOCKED" / "reconfirms ADR-024"; FINDINGS has the ADR-049 banner (:3) but EVENT-NOTES has none, and the 🔴 items :193-200 are listed as open although FINDINGS.md:235 says answered (press-and-hold confirmed by CAP-051, tension folded into ADR-049). Fix: add the banner/pointers to EVENT-NOTES. + +#### CAP-039 (read in full: EVENT-NOTES 182, FINDINGS 225) — verified 47-frame burst, frame 653 = failed page (0x03/0x04/0x01), disconnect reasons 4×0x16 + 1×0x13; 14 Notify frames (7×08, 7×40) recomputed OK. +- SC2-68 minor: "~300-frame" DLCI 0x02 burst survives in CAP-039-EVENT-NOTES.md:105 ("Rapid ~300-frame DLCI 0x02 write burst (consistent with an EQ slider drag"), FINDINGS.md:3 (banner), :200 and :221, while FINDINGS.md:134 corrected it to 47 Sent frames (re-run: 47). Fix: replace "~300-frame" everywhere with "47-frame (Sent)" and drop "EQ slider drag" in the notes (FINDINGS §4 rejects it; it is the ReadSetting sweep). +- SC2-69 minor: CAP-039-EVENT-NOTES.md:101 "chandle `0x0002` (BLE) @`07:07:34.091`" — FINDINGS.md:51/:59-61 (rewritten 2026-10-03) shows frame 653 is a failed classic page (status 0x04 Page Timeout, link type ACL), not a BLE connection. Fix notes row. +- SC2-70 minor: CAP-039-EVENT-NOTES.md:123-124 "6 reconnects (5 of them locally-initiated disconnect/reconnect cycles, reason `0x16`" and :156-157 "disconnect-and-immediately-reconnect 5 times ... (reason `0x16`, locally-initiated)" contradict the same file's :91 ("reason `0x16` ... on 4 of the 5 disconnects"), :117 (5184 = 0x13) and FINDINGS.md:62-64. Fix: "5 disconnects, 4 locally-initiated (0x16), 1 remote (0x13)". +- SC2-71 minor: CAP-039-EVENT-NOTES.md:40 video "05:03s, `07:07:17`–`07:12:20`" contradicts the same file's :120 ("ffprobe duration 265.62s", ends ~07:11:43) and FINDINGS.md:20-21. Fix metadata row: 265.62 s, 07:07:17–~07:11:43. +- SC2-72 minor: CAP-039-EVENT-NOTES.md:168-179 all "Next steps" `[ ]` although FINDINGS exists, folder renamed (status :3-5) and the index row is "analyzed". Fix: tick. +- SC2-73 nit: CAP-039-EVENT-NOTES.md:141-151 and FINDINGS.md:125-130/:204-206 "ADR-024 does not need revisiting" — no pointer in EVENT-NOTES to ADR-049 (FINDINGS banner :3 has it, but :206 still asserts the opposite). Fix: add banner to notes; annotate :206. +- SC2-74 nit: CAP-039-EVENT-NOTES.md:105 row (~07:07:43) placed after the 07:07:57 rows — out of time order. + +#### CAP-040 (read in full: EVENT-NOTES 311, FINDINGS 298) — 22 `0e 01` occurrences (14+1+7) and 27-byte `03 01` recomputed OK. +- SC2-75 major: the hand-written timeline's dock-state column is contradicted by the wire and never reconciled. CAP-040-EVENT-NOTES.md:121-228 records ~15 "Buds placed in case | Docked" and "Case closed (auto disconnect)" cycles between 07:28:36 and 07:52:09, but (a) DLCI 0x04 "Battery updated" (`tshark -Y 'btrfcomm.len>0 and data.data[0:2]==03:03'`) reads `03 03 00 03 5f 64 ff` (Left 95, Right 100, **no charging bit**) 36× from 07:28:17 until 07:53:53, and only then `df 64` (Left charging); Right gains the charging bit (`64 e4`) only at 07:55:25; (b) there is no Disconnection Complete until 07:55:41 although a closed case with both buds docked disconnects (ADR-016); (c) FINDINGS.md:150 film frame at 07:52:28 shows "both earbuds visibly loose beside the case (undocked)" 19 s after the timeline's "07:52:09 Case closed ... Docked". FINDINGS §1/§2 attribute the missing wire signal only to the app buttons. Fix: add a correction stating the buds were not docked (no charging) between 07:28:17 and 07:53:53 per the wire, mark the Dock State column as unverified, and re-check the film for the "Docked" rows. +- SC2-76 minor: CAP-040-FINDINGS.md:214-247 (§4) times the "physical docking" by DLCI 0x08 frame 6243 (07:54:28, Left `0xff`) and calls it a single occurrence "at the exact moment of physical docking", but DLCI 0x04 already reports Left charging (`df 64`) at 07:53:53 and Left `ff 64` (unknown+charging) three times at 07:54:14, 14 s before 6243. The docking instant is ~07:53:53, and the sentinel is not single-channel/single-occurrence. Fix: cite the DLCI 0x04 frames and re-time the correlation. +- SC2-77 minor: CAP-040-EVENT-NOTES.md:120 "`07:28:28` | Initial connection established | ... | no discrete frame (channel stayed open ...)" — the same file (:72-73) and FINDINGS.md:60-66 give the Buds' Connection Complete as frame 646 at 07:28:12.077. Fix: time 07:28:12, evidence frame 646. +- SC2-78 minor: CAP-040-EVENT-NOTES.md:87 "These values remain exactly the same throughout the entire 24-minute video" (and :114 "remained static (95/85/100) for the entire duration") — video is 1745 s (~29 min) and Left 96→95→100, Case 85→84 (FINDINGS.md:159-162 corrects it, notes unannotated). Fix: pointer to FINDINGS §2. +- SC2-79 minor: CAP-040-EVENT-NOTES.md:242-277 analysis checkboxes and :296-304 next steps all `[ ]` despite filled results, FINDINGS written and index "analyzed". Also :242 "For each of the 6 unmapped codes" lists 7. Fix: tick; "7". +- SC2-80 nit: CAP-040-FINDINGS.md:135 and EVENT-NOTES.md:233 "~30 individual ... rows" — the timeline has 83 rows marked "no discrete frame" (grep -c). Fix: "~80". +- SC2-81 nit: CAP-040-FINDINGS.md:120-127 negative "(0 rows)" for 07:29:44–07:29:53 with no exit status/positive control. Fix: show the same filter over 07:28:10–07:28:14 returning 646…980. + +#### CAP-041 (read in full: EVENT-NOTES 240, FINDINGS 267) +- SC2-82 major: CAP-041-EVENT-NOTES.md states the opposite of the rewritten FINDINGS and has no pointer: :4-7 "a scoped negative at the length level — byte-for-byte content diff still pending", :195-203 "Outcome classification: (b) — clean negative", :214-217 🔴 "Whether the connect-time burst's payload *content* ... differs with settings state remains untested", :185-194/:210-213 the 79-valued sub-message as an open question. FINDINGS.md:3/§3/§6/§8 (2026-10-03) say the burst *is* a settings read-back (Buds' ReadSetting answers carry every setting, frames 799…3279), the content diff was done (§8, 184 sub-frames, CRC-checked) and the 79 field is Option F entry 6.1 (🟢). The index row (CAPTURE_BLUETOOTH_HCI_SNOOP.md:2260) follows FINDINGS. Fix: add the 2026-10-03 status banner to EVENT-NOTES and re-label those items answered. +- SC2-83 minor: CAP-041-FINDINGS.md:136-142 (§2 "Settings states compared (from the Event Timeline, all four genuinely different ...)") is not reconciled with §3's decoded answers (:150-158): Windows A and B return the identical EQ `[6.00 × 5]` although the timeline logs an EQ change ("Treble high, Bass low", EVENT-NOTES.md:108) between them, and A's EQ `[6.00 × 5]` (all bands equal) does not match the logged "Bass high, Treble low" (:100); Window C returns mono `0` and Volume EQ `0` though no mono-off or Volume-EQ change is logged anywhere (mono ON at :101). The timeline times are already declared placeholders (:124-126). Fix: state that the per-window settings states are taken from the wire answers (§3), and mark the timeline's settings rows as unverified. +- SC2-84 minor: CAP-041-EVENT-NOTES.md:152-153 Contamination log "four separate Bluetooth toggles ... we now have four distinct connect-time bursts" — contradicts the 3-reconnect correction directly above it (:124-150) and FINDINGS §1. Fix: "three". +- SC2-85 nit: CAP-041-EVENT-NOTES.md:47 and :122 video end "17:17:48" (also folder name) vs :63/FINDINGS.md:18 ffprobe 421.23 s from 17:10:39 = 17:17:40. Fix: 17:17:40 or note the discrepancy. +- SC2-86 nit: CAP-041-FINDINGS.md:164 §4 heading "🟡 HYPOTHESIS ... does not resolve" and :183-191 "does **not** confirm" vs §6 :210 "🟢 ... is `SubscribeRuntimeInfo` entry 6.1, the Case %"; the banner (:3) lists §1/§2/§5 as "as written" but not §4. Fix: add "(answered: Option F, ADR-043)" to the §4 heading. +- SC2-87 nit: CAP-041-EVENT-NOTES.md:221-232 all Next-steps boxes `[ ]` although FINDINGS exists and the folder is renamed (:7-8). + +#### CAP-042 (read in full: EVENT-NOTES 170, FINDINGS 280) — gap arithmetic recomputed OK (967.5 s, 1123.7 s end→start, 154.6 s; 38+8+6=52; t≈974.08 s = 17:46:16.95). +- SC2-88 minor: CAP-042-EVENT-NOTES.md:155-167 all Next-steps boxes `[ ]` although status (:3-7) says decoded/folder renamed, open questions "copied to PROTOCOL.md §6" (:149/:151/:153), and the index row is "analyzed". Fix: tick. +- SC2-89 minor: stale reading of the DLCI 0x02 idle push — CAP-042-FINDINGS.md:173-183 (§3) and :205-218 (§5) treat the two idle DLCI 0x02 clusters as the same cross-channel push as CAP-036 §12.5 (and build the app-foreground hypothesis on it), but the 2026-10-03 banner (:3) says those packets are `Dosimeter/SubscribeToLiveDb` stream packets, not the runtime-info stream that CAP-036's push is (CAP-036-FINDINGS.md §12.6). The body sections carry no pointer, and EVENT-NOTES.md:4-6/:99-100/:130-132 have no banner at all. Fix: add an inline note in §3/§5 and a banner in EVENT-NOTES. +- SC2-90 nit: CAP-042-FINDINGS.md:169 gives DLCI 0x04's connect burst as "2.596–6.53s" but §1 (:104) shows the Get on DLCI 0x04 at 2.345 s (frame 598). Fix: "2.345–6.53 s". +- SC2-91 nit: CAP-042-EVENT-NOTES.md:70 "Fixed dock state: **undocked/worn** (both earbuds resting loose beside the open, empty case …)" — the buds were not worn; this matters because ADR-049 cites CAP-042 frame 602 as an `e8`-with-no-bud-worn sample. Fix: "undocked, not worn". +- SC2-92 nit: CAP-042-EVENT-NOTES.md:49 / FINDINGS.md:14-15 "Android ⚪ ASSUMPTION (carried from `CAP-036`)" — CAP-036 itself records the Android version as "not recorded". Fix: "not recorded (17 assumed, as neighbouring captures)". + +#### CAP-043 (read in full: EVENT-NOTES 100, FINDINGS 205) +- SC2-93 minor: CAP-043-EVENT-NOTES.md:27 "Android 17 (🟢 FACT, MP4 container tag)" — contradicted by CAP-043-FINDINGS.md:19 (corrected 2026-10-03): the MP4 tag is the camera phone's, Android version is ⚪ ASSUMPTION. The index row (CAPTURE_BLUETOOTH_HCI_SNOOP.md:2262) also gives a bare "17". Fix: notes and index → "17 (⚪ assumed)". +- SC2-94 minor: CAP-043-FINDINGS.md:160-163 "isolation check (§2/§3) confirming **no** classic connection, **no** RFCOMM, and **no** GATT session to the Buds ever formed" — the isolation filter (:46) covers only classic Connection Complete/RFCOMM/SDP, not LE. The log does contain an LE connection (`bthci_evt.le_meta_subevent==0x0a or 0x01` → frame 317, 09:50:36.133, random address 45:73:fe:c2:04:47, chandle 0x0002) carrying all 345 ATT frames (GAP/GATT/Device Information + unknown services) — that is the "btatt" branch §2 :52-53 mentions. It is probably an unrelated device (no Buds-like service map), but the file never attributes it. Fix: add the LE-connection query and attribute chandle 0x0002 (e.g. by its service list vs CAP-034 §4) before claiming "no GATT session to the Buds". +- SC2-95 nit: CAP-043-EVENT-NOTES.md:65 row time "~09:50:29–09:50:36 (video t≈5s)" mixes the first advertisement (log 09:50:29.06, before video t=0 when the screen still shows "Bluetooth is off") with the on-screen toggle at t≈5 s (09:50:36). Fix: split into two rows and point to FINDINGS §2's timing note. + +#### CAP-044 (read in full: EVENT-NOTES 123, FINDINGS 299) — 9 Connection Completes (6 pre-video + 3), 2m8s window, 2m12s tail recomputed OK. +- SC2-96 minor: "Force-stop precedes Forget ... that specific ordering issue from `CAP-033` §1.1 does not reproduce" (CAP-044-EVENT-NOTES.md:32-34; FINDINGS.md:72-75, :200-202 "This session fixes both of `CAP-033`'s specific gaps"; index row :2263 "Force-stop-before-Forget order fixed") — the same files record that the app was relaunched from recents at 13:05:55 and "Forget" was tapped *inside the relaunched app* at 13:05:59 (EVENT-NOTES.md:67-68 "Deviation: App is woken up again", FINDINGS.md:39-40/:60-61). So at the Forget the app was alive — the CAP-033 condition (app alive when forgetting) recurs; the off-camera force-stop before 13:05:51 is moot. Fix: say the order was nominally followed but nullified by the relaunch; drop "fixes both gaps" / "order fixed". +- SC2-97 minor: CAP-044-FINDINGS.md:226-231 "Forget only being reachable *from inside* the already-bonded device's own companion-app screen, on this OS/hardware combination ... every attempt so far has needed to *open* the app" — contradicted by CAP-033 (Forget "via system Bluetooth settings", CAP-033-EVENT-NOTES.md:82) and CAP-031/CAP-032 (per-device Forget in the system "Device details" page). Fix: drop the claim; the procedure fix is simply "Forget from system settings with the app stopped". +- SC2-98 minor: CAP-044-EVENT-NOTES.md:26 and FINDINGS.md:12-13 "Android 17 (🟢 FACT, MP4 container tag)" — CAP-043-FINDINGS.md:19 (2026-10-03) established that this MP4 tag belongs to the camera phone, not the test phone; CAP-044 was not corrected. Fix: "17 (⚪ assumed)". +- SC2-99 nit: CAP-044-FINDINGS.md:137-138 "now a 5th/6th confirming instance, counting `CAP-001`/`CAP-002`/`CAP-032`/`CAP-033`/this capture" — five listed. Fix: "5th". + +#### CAP-045 (read in full: EVENT-NOTES 103, FINDINGS 138) +- SC2-100 minor: Left/Right gesture counts are inconsistent with the file's own timeline. CAP-045-FINDINGS.md:106-107 "`TOUCH-007` ... exercised 8 times (4 per earbud)", :73-75 "8 of them clean ANC-mode-cycle transitions ... matching the physical long-press gestures", :86-87 "All 5 Left-attributed and 5 Right-attributed Notify frames", :72-73 "All 10 transitions correlate to a video-confirmed action within ≤1s". The timeline (EVENT-NOTES.md:53-64 and FINDINGS §3 list) shows Left gestures 1583/1755/1818 (3) + removal 1849; Right gestures 2168/2218/2237/2254 (4) + removal 2273; and 2122 with "no video-visible discrete gesture" (EVENT-NOTES.md:59) — i.e. 7 gestures, 4 Left-side vs 6 Right-side Notify-only frames, and one transition with no video action. Same "10-sample" wording in EVENT-NOTES.md:84 and the index row :2264. Fix: 7 gestures (3 L + 4 R), 2 removals, 1 unattributed (2122). +- SC2-101 minor: CAP-045-FINDINGS.md:107-110 "the first to show it holds for the Right earbud specifically (`CAP-027`'s own Right-side sample was ambiguous between 'one long hold' and 'two presses'...)" — CAP-027-FINDINGS.md §4.3 (:204-216) has the Right bud as a single clean frame (2930) and the *Left* bud as the ambiguous one (3056/3091). Fix: swap sides ("first multi-sample Left replication; CAP-027's Left was ambiguous"). +- SC2-102 nit: CAP-045-EVENT-NOTES.md:48 "Left bud is in the case, right bud is out (loose)" then Left-bud long-presses from 08:23:21 and "Left earbud removed from ear" at :56 — no row records the Left bud being taken out and inserted. Fix: add the missing event (or "not visible on video"). + +#### CAP-046 (read in full: EVENT-NOTES 103, FINDINGS 146) — zigzag values recomputed (199→−100, 200→+100, 4→2, 2→1) OK. +- SC2-103 minor: confirmation count — CAP-046-FINDINGS.md:27 heading "3× video-confirmed", :62 "3/3 extreme-position video confirmations", :122 "3 extreme positions video-confirmed", EVENT-NOTES.md:84 "3 extreme-position video confirmations (+ a 4th confirming the 'no change' gap)", index row :2265 "(3/3 video-confirmed)" — but the same files cite four extreme-position screenshots, one per extreme write (w_43.1 → 1181, w_76.4 → 1746, w_112.3 → 1834, w_144.0 → 1908; FINDINGS.md:53-59 and :93-94; EVENT-NOTES.md:55/59/63/67, where :63 says "2nd confirmation" and :67 "3rd confirmation" although they are the 3rd and 4th). Fix: "4/4" everywhere, or state which one is not counted and why. + +#### CAP-047 (read in full: EVENT-NOTES 111, FINDINGS 471) — re-ran Notify list for recording 2 (all DLCIs). +- SC2-104 major: CAP-047-FINDINGS.md:304-309 (§4) "This session's own dock-state-byte evidence (§5 below) confirms the physical-presence sensor reads 'both docked' identically in **all three** swapped-dock instances (`06:10:20.78`, and both of Recording 2's)" — repeated :459-461 (§10). §5 shows no such reading for Recording 2: `tshark -r CAP-047-btsnoop_hci-2.log -Y "btrfcomm.len>0 and data.data[0:2]==08:13"` returns 952, 2673, 3048, 3364, 3834, 3996, 4339 — **no Notify at all during swap #2 (06:33:41–06:33:59)**, and swap #3's `0x00` frames (3048 empty case, 3364 one bud) are the file's own counter-examples, not both-seated readings. Only frame 2803 (Recording 1) is a both-seated `0x00`. The §4 open question ("why no disconnect in Recording 1 when the sensor reads identically in all three") therefore rests on an unsupported premise. Fix: restrict the claim to Recording 1 (2803) and rephrase the §4/§10 question. +- SC2-105 minor: CAP-047-FINDINGS.md:337-345 (§5) scans only `btrfcomm.dlci==0x05`, so Connection 1's Message Stream (DLCI 0x04, chandle 0x0005) is skipped; it carries frame 952 (06:33:12.44) `08 13 00 04 01 e8 00 20` — Settable `0x00` while, per CAP-047-EVENT-NOTES.md:61-63, the case was closed/empty and both buds outside. Under the file's own ADR-024 framing this is a fourth counter-example, unreported (under ADR-049's 🟡 "00 ⇒ no bud worn" it is consistent). Fix: include DLCI 0x04 of Connection 1 in §5 and list 952. +- SC2-106 minor: CAP-047-FINDINGS.md:298-300 "at `06:33:59.19` (frame 2033, ~2s after swap #2's both-docked moment)" — per EVENT-NOTES.md:64-66 both buds were seated by 06:33:41–51; 06:33:57 is the on-screen *disconnect*, not the docking. So the wire disconnect is ~8–18 s after both-docked, and 2 s *after* the screen already showed "+ Connect". Fix: correct the interval and note the screen-vs-wire ordering. +- SC2-107 minor: CAP-047-FINDINGS.md:76-78 "its own fresh Buds Connection Complete (frame 611, 05:51:39.91) lands 11s after Recording 1's own documented start (05:51:38" — 05:51:39.91 − 05:51:38 = 1.9 s (it is 9.2 s after the main log's first frame). Fix: "~2 s". +- SC2-108 nit: CAP-047-FINDINGS.md:379-380 "it adds three more single-session counter-examples (one video-time-clean, two with a plausible settling explanation, one without)" — 1+2+1 = 4 ≠ 3. Fix wording (two with settling explanation, one without). +- SC2-109 nit: CAP-047-EVENT-NOTES.md:3 uses "⚪" (the ASSUMPTION marker) as the status icon of an analysed capture; index/registry say "analyzed". Fix: ✅. + +#### CAP-048 (read in full: EVENT-NOTES 135, FINDINGS 217) — re-ran §2 query: 31 rows, 15 Gets. +- SC2-110 minor: CAP-048-FINDINGS.md:57-58 quoted output "(16 rows — 15 distinct Get(`08110000`)/Notify(`0813...`) pairs across 13 chandles, plus 1 extra Notify-only anomaly" — 15 pairs + 1 = 31 rows; re-running the exact command gives 31 rows (15 `0811` Gets). Fix: "31 rows". +- SC2-111 minor: CAP-048-EVENT-NOTES.md:81 "Connection-retry burst: 7 `Create Connection`/`Connect Complete` cycles on the same chandle (`0x000b`) ... culminating in a stable connection" — FINDINGS.md:163-168 (rewritten 2026-10-03) shows six are failed pages (status 0x04, frames 7705…7753) and only 7781 is a connection; notes not updated. FINDINGS.md:160-162 still prints the pre-correction output line "(7 'Rcvd Connect Complete' events ... each preceded by its own 'Sent Create Connection')" under a `bthci_acl.chandle==0x000b` filter that cannot match HCI events. Fix both. +- SC2-112 nit: CAP-048-EVENT-NOTES.md:95-100 "Of those 13(+2 same-chandle reopens), 8 were individually video-cross-checked ... A future pass could individually verify the remaining ~5" — 15 − 8 = 7 unchecked readings (0x0006 reopen, 0x0008, 0x0009, 0x000b, 0x000e, 0x000f, 0x0012). Fix: "remaining 7". +- SC2-113 nit: CAP-048-EVENT-NOTES.md:58 heading "(the draft's per-reconnect table above did not survive verification)" — no draft table remains above. Fix: "(replaces the draft's table)". + +#### CAP-049 (read in full: EVENT-NOTES 102, FINDINGS 182) — capinfos re-run: 1,866 + 2,526 = 4,392 OK; frame 691 status 0x04 confirmed. +- SC2-114 minor: CAP-049-FINDINGS.md:41 quoted `capinfos` output "Earliest: 2026-09-12 18:13:55.523844" — actual `capinfos -a CAP-049-btsnoop_hci.log.last` → 18:13:55.218251 (as the same file's :23 and :43 say). The ".523844" microseconds are identical to CAP-048's `.log.last` earliest time (CAP-048-FINDINGS.md:31, 17:38:51.523844) — a copy-paste error inside a quoted command output. Fix: 18:13:55.218251. +- SC2-115 minor: CAP-049-EVENT-NOTES.md:53 "18:14:05.361 / 18:14:09.395 | Classic `Connect Complete` (two chandles, `0x0002` then `0x0005` — a brief reconnect settling" — FINDINGS.md:80/:86 (annotated 2026-10-08) shows frame 691 (18:14:05.361) is status 0x04 Page Timeout, no connection; only 736 is a connection. Notes not updated. Fix: "failed page (691) then connection (736, 0x0005)". +- (SC2-116 withdrawn — not a defect.) + +#### CAP-050 (read in full: EVENT-NOTES 148, FINDINGS 335) — re-ran Connection Complete list: 16 events, all status 0x00 (matches §1). +- SC2-117 major: CAP-050-FINDINGS.md §5 table contradicts itself on the same physical configuration (Left docked, Right out): :222 "21:03:04 | `0x00` | Left docked, Right out — **a counter-example**"; :225 "21:04:07 ... | `0x00` | 21:04:04: Left docked, Right out — ... this is expected, not a counter-example (ADR-024 only ever claimed a both-vs-not-both binary)"; :228 "21:08:19 | `0xe8` | Left docked, Right out — consistent ... ('Left only' correctly reads as not-both, 0xe8)". Under the both-vs-not-both binary the file invokes, 0x00 with only Left docked *is* a counter-example (as :222 says), so :225 is wrong, and the heading :204 "dock-state reading correct at every other reconnect" and :242/:245 "this session's own single anomaly" undercount. Also EVENT-NOTES.md:90 (21:02:04 `0x00`, video Left docked/Right out) and EVENT-NOTES.md:87 (21:01:13 both buds outside) vs FINDINGS.md:220 "21:01:14 `0x00` ... not individually checked" are further unflagged mismatches under that reading. Fix: classify 21:01:14, 21:02:04, 21:03:04, 21:04:07 consistently (≥3–4 counter-examples under ADR-024; under ADR-049's 🟡 "00 ⇒ no bud worn" they are consistent) and correct the §5 heading/§8 summary. +- SC2-118 minor: CAP-050-EVENT-NOTES.md:94-95 video times inconsistent with the wire and with their own references: row :94 "21:04:16 – 21:04:20 | Both earbuds again placed ... 'Connect' (disconnected)" says it "falls between chandle `0x0001` (ends 21:02:37.728) and chandle `0x0002`'s 2nd instance (starts 21:03:00.091)" — 21:04:16 is not between those; row :95 "21:04:48 – 21:04:52 | Right earbud removed ... reconnected" cites the wire reconnect at 21:04:03.465 (45 s *before* the row's own time and before row :94's disconnect) and video "t=183s" (= 21:04:04). Fix: re-time both rows (≈21:03:15–21:04:04) from the film. +- SC2-119 minor: CAP-050-FINDINGS.md:147-148 "`04.05`: `08 03` in 11 of 14 bursts; `08 04` once ...; `08 05` once" = 13 of 14; "`04.16`: `08 02` in 11 of 14; `08 01` twice" = 13 of 14. One burst unaccounted for. Fix: recount and state the 14th value. +- SC2-120 minor: CAP-050-FINDINGS.md:204 heading "16/16 `Get`/`Notify` pairs" vs body :210 "19 `08 11 00 00` 'Get ANC state' `Sent` frames total ... each answered", while the table (:218-237) lists 23 Notify times. Fix: one consistent count (16 reconnects, 19 Gets, 23 Notifies) in heading and text. +- SC2-121 nit: CAP-050-EVENT-NOTES.md:96 chandle list for the 10 Connection Completes ("0x0006,0x0007,0x0008,0x0009,0x000a,0x000b/0x000c,0x000d,0x000c-2nd") has 9 entries — misses 0x000d-2nd (frame 17862, 21:12:29). Also :97 time cell "21:11:41 – 21:12:44... 21:11:41 - 21:12:53" mixes video-relative 00:11:41 into wall-clock (21:01:01 + 11:41 = 21:12:42). Fix both. +- SC2-122 nit: CAP-050-EVENT-NOTES.md:41-43 "Whether each of these physical actions actually reopened DLCI 0x08 on the wire is explicitly deferred to the separate, out-of-scope log-analysis session" — that analysis is done (FINDINGS §2, status :3). Also :24 Firmware shown as plain `release_5.203` while FINDINGS.md:14 and the index say ⚪ assumed. Fix: pointer; mark ⚪. + +### sub_caps_051_075 + +#### Sub-review CAP-051..CAP-075 (progressive) + +#### CAP-051 (read in full) +- SC3-01 major — CAP-051-FINDINGS.md:98 "only a spontaneous Notify(0x13) with no preceding Set or Get anywhere in the log" (also EVENT-NOTES:77 "no preceding Set(0x12) or Get(0x11) anywhere in the log"). Contradicts same file: Set at 1527 (line 69/81) and Get 08 11 at frame 1072 (line 57). Fix: "no Set/Get between the previous Notify and this one" (line 72's "anywhere nearby" wording). +- SC3-02 minor — CAP-051-FINDINGS.md:187 "TOUCH-007 gains a third confirming session (after CAP-027)" vs :216-217 "gains a second confirming session (CAP-051, in addition to CAP-027)". Fix: pick one count (only CAP-027 named → "second"). +- SC3-03 minor — CAP-051-FINDINGS.md:132 "1534 13:3 (the tap, 0.001 s after Notify 1533)" while §2 table (:71) lists the tap's Notify as 1530 and never mentions 1533. tshark: 1530 and 1533 are both `0813000401e8e880` (two Notifies), 1534 at 28.3759 is 0.0008 s after 1533. Fix: add 1533 to the §2 table (duplicate Notify) or say "0.077 s after Notify 1530 / 0.001 s after the repeat 1533". +- SC3-04 minor (stale) — CAP-051-EVENT-NOTES.md:34 Test-device cell still says raw-path "left open ... not marked confirmed", while :36 and FINDINGS §0 confirm raw path, untruncated. Fix: replace the note with a pointer to FINDINGS §0. +- SC3-05 minor (stale) — CAP-051-EVENT-NOTES.md:21-22 "stays flagged (not actioned)" and :123-124 "ANC-005 ... awaiting maintainer sign-off — not unilaterally added"; declined 2026-09-18 (ai-sessions/0031, FINDINGS:222, Capture Index row). Fix: add "declined 2026-09-18" pointer. +- SC3-06 nit — CAP-051-FINDINGS.md:129 runtime-info "every ~10 s: 1517/1565/1711/1796 at 21:43:23/33/43/44:03" — the last gap is 20 s, not ~10 s (a 21:43:53 push is missing or unlisted). Fix: list the 21:43:53 frame or drop "every ~10 s". + +#### CAP-052 (read in full; withdrawn skeleton) — 0 findings (Index row "withdrawn" consistent). + +#### CAP-053 (read in full) +- (known, confirmed) CAP-053-EVENT-NOTES.md:5 "(12.8 s and 18.7 s)" vs :68/:92/:102 and FINDINGS:41/:130 "18.5 s" (78.12→96.6 = 18.48 s). Fix: 18.5 s. +- SC3-07 minor — CAP-053-FINDINGS.md:60-61/:68 vs EVENT-NOTES.md:61 — connect sweep frame range given as "873–1082" (FINDINGS:68) vs "877–1077" (EVENT-NOTES:61), while FINDINGS:72 names 1083 (17:47:08.512) as "the end of the connect sweep" (tshark: 1083 is a MAESTRO frame, `7e80a3...0807 1013...`, i.e. still sweep). Fix: one range, ending at 1083. +- SC3-08 minor — CAP-053-FINDINGS.md:72-73 negative "no RFCOMM data frame on handle 0x0002 between 17:47:08.512 ... and 1577" and :100 "No `08 12`" have no command (AGENTS §13.8). Re-run here: `tshark -Y "bthci_acl.chandle==0x0002 && btrfcomm.len>0 && frame.number>1083 && frame.number<1577"` → empty (claim holds). Fix: add the command + positive control (same filter matches 1577). +- SC3-09 nit — CAP-053-FINDINGS.md:36 "1914 ... Bass drag 138.87–139.87 | during the drag": 17:48:52.515 = film 140.02 s, after the release at 139.87 s (±0.13 s clock). Fix: "0.15 s after release" (cf. 1727 row's "0.2 s" which by the same arithmetic is 0.09 s after 97.59). +- SC3-10 nit — CAP-053-EVENT-NOTES.md:114/:119 Appendix A is "unchanged" but its placeholder sentence was rewritten by the rename ("rename this folder from the placeholder `CAP-053-2026-10-05_17-46-34_17-49-39-Group_AO`") — now nonsensical; :206 still says yyyy placeholder. Fix: restore `yyyy-MM-dd_HH-mm-ss_HH-mm-ss` in the appendix. + +#### CAP-054 (read in full) +- SC3-11 minor — CAP-054-EVENT-NOTES.md:31 "The log starts 18:02:45.507 ... (13.7 s after the film starts at 18:02:31.4)" and :89 "log 13.7 s later": 45.507 − 31.4 = 14.1 s. Fix: 14.1 s. +- SC3-12 minor — CAP-054-FINDINGS.md:55 "not in the ≈ 44 s and ≈ 50 s after each lid opening": lid open #2 (18:05:36.5–37.0) to the first bud out (18:06:16.0) is ≈ 39 s; the show-UI advertiser runs 18:05:37.9–18:06:15.2 (37 s, :37). Fix: "≈ 44 s and ≈ 39 s". +- SC3-13 nit — CAP-054-EVENT-NOTES.md:57 / :90 "≈ 92 s from Bluetooth on to the opening": Bluetooth on 18:02:47.9 → lid open 18:04:18.2 = 90.3 s (film 16.59→106.85). Fix: ≈ 90 s. +- SC3-14 nit — CAP-054-EVENT-NOTES.md:66 tags `CASE-006` (lid part); neither the Capture Index row (CASE-003–CASE-005) nor FINDINGS §10 lists CASE-006. Fix: add it to both or drop the tag. + +#### CAP-055 (read in full; planned skeleton) — 0 findings (Test-IDs CALL-001/HEAD-002/HEAD-003 exist; Index "planned" consistent). +#### CAP-057 (read in full; withdrawn skeleton) — 0 findings. + +#### CAP-056 (read in full) +- SC3-15 minor — CAP-056-EVENT-NOTES.md:133 "every field-2 write against the filmed switch ... — 5 writes, 5 filmed taps": the session has 6 field-2 writes (2173, 2849, 3627, 4048, 4344 and the re-send 4361, :106 and FINDINGS:123). FINDINGS :136-137 "five filmed taps ... produced five WriteSetting" same. Fix: "5 taps, 6 writes (4344 cancelled and re-sent as 4361)". +- SC3-16 nit — CAP-056-FINDINGS.md:197 "the re-open `Get`s 3137 (17:34:37.601) and 3476" — tshark: 3137/3476 are the Buds' Notify `0813000401e8e808`; the Gets (`08110000`) are 3133/3472 (as :230 lists). Fix: "the re-open Gets 3133/3472 (answers 3137/3476)". +- SC3-17 minor (stale) — CAP-056-EVENT-NOTES.md:149 "[ ] Capture Index / id_registry.csv / TESTPLAN rows — after the checkpoint" still unchecked; the Capture Index row (analyzed) and id_registry CAP-056 row exist. Fix: tick with date. + +#### CAP-058 (read in full) +- SC3-18 nit (pattern, also CAP-053:119, CAP-054:118) — CAP-058-EVENT-NOTES.md:162-163 Appendix A "kept unchanged" but its placeholder sentence now reads "rename this folder from the placeholder `CAP-058-2026-10-05_21-39-51_21-46-22-Group_AT` to the actual session date" — the folder rename rewrote the skeleton text (a global replace). Fix: restore `CAP-0NN-yyyy-MM-dd_HH-mm-ss_HH-mm-ss-Group_XX` in the three appendices. +- SC3-19 nit — CAP-058-FINDINGS.md:48 "the process was present again 16 s after the force stop" mixes the laptop-clock dumpsys run (21:40:51) with the phone-clock force stop (21:40:32), although :17 says laptop↔phone is "not measured"; :31 says "≈ 7–10 s later". Fix: "present again at the next run (21:40:51 laptop clock, offset unmeasured)". +(Spot-checked 1422 Delete Stored Link Key 21:40:57.841, 3801 UCR reply 21:42:07.643, 5747 `4:{21:0}` — all match.) + +#### CAP-059 (read in full) +- SC3-20 minor — CAP-059-FINDINGS.md:87 "Same phenomenon as `LOG-001`'s originally-reported "89s deny-mode drop"?" — `LOG-001` is not a Test-ID (grep TESTPLAN: absent) and LOGS-001 is this very capture (EVENT-NOTES:7-10); the 89 s drop came from the prior session in `ai-sessions/0040` §2.1 (FINDINGS:55-56). Fix: "Same phenomenon as the 89 s drop of `ai-sessions/0040` §2.1?". +- SC3-21 nit — CAP-059-FINDINGS.md:71-72 "Frames 4586–4595 (`tshark -Y "frame.number>=4562 && frame.number<=4600"`): app claim `SABM` DLCI 4 frame 4552" — 4552 lies outside both the stated range and the filter. Fix: widen to 4552–4600. +- SC3-22 nit — buds-out time inconsistent: CAP-059-FINDINGS.md:168 "both buds out since ≈17:19:11" vs :233 "left the case at 17:19:06" vs :176 "bud removals (17:19:05.95–13.9)". Fix: one value (removals 17:19:06–14). +- SC3-23 nit (stale) — CAP-059-FINDINGS.md:218-222 "Parsed as flags + LLLLTTTT fields ... Option A's CAP-011 result (inconclusive) stands" — CAP-054-FINDINGS §6 later established `0x10` = SASS "Version and flags" and answered Option A (no clear field); no pointer here. Fix: add a pointer to CAP-054 §1/§6 in the status banner. +- SC3-24 nit — CAP-059-EVENT-NOTES.md:81-82 timeline out of order (17:18:18 EQ-write row placed after 17:20:35; "EQ screen viewed at some point" row has no time). Fix: move the 17:18:18 row before 17:18:33. +(Spot-check: Find frames 2301/2482/2504/2589 = `0401000102/00/01/00`, ACKs 2310/2491/2506/2600, Buds copies 2312/2494/2507/2604 — match EVENT-NOTES:74.) + +#### CAP-060 (read in full) +- SC3-25 major — CAP-060-FINDINGS.md:25 "seven non-user session or link ends (three Buds-side closes ..., four ACL drops)" — the four ACL drops include #3, which the same table (:35) and EVENT-NOTES:87-88 attribute to the user's Settings-panel tap ("User tapped the Buds' row"). Non-user ends = 3 Buds DISC + 3 ACL (#4–#6) = 6; EVENT-NOTES:12 says "six non-user connection drops"; Capture Index says "six connection drops". Fix: "six non-user ends (3 Buds-side RFCOMM closes, 3 ACL drops) plus one Settings-triggered ACL drop (#3) and one in-app Disconnect". +- SC3-26 minor — CAP-060-FINDINGS.md:124 "the EQ-preset window (17:57:45–18:06)" — the EQ writes end 17:58:06.476 (EVENT-NOTES:83) and the film ends 18:00:49. Fix: 17:57:45–17:58:06. +- SC3-27 minor — CAP-060-EVENT-NOTES.md:82 "Reconnect after PAIR-003-style drop (see Drop 2 below is later — ...)" placed at 17:57:38 while Drop 2 (:84) is at 17:57:29.912, i.e. earlier, not later; rows out of order. Fix: move Drop 2 before 17:57:38 and say "reconnect after Drop 2". +- SC3-28 minor — CAP-060-EVENT-NOTES.md:108 "`PAIR-003` (four reconnect cycles)" — the timeline tags five reconnects PAIR-003 (17:55:29, 17:58:41, 17:59:00, 17:59:40, 18:00:29). Fix: five. + +#### CAP-061 (read in full) +- SC3-29 minor — CAP-061-FINDINGS.md:146 "3272, 3346, 3604, 3996, 4309 … 5392 (12)" — tshark (`chandle==0x000b && dlci==0x04 && p2p_dir==1 && data contains 08:13:00:04`) lists 13 `e8 08` Notifies 3272–5392 (3272, 3346, 3604, 3996, 4309, 4398, 4580, 4682, 4781, 4969, 5187, 5277, 5392); only with 13 do the rows sum to the "All 20" of :139 and "19 of 20" of :196. Fix: (13). +- SC3-30 minor — CAP-061-FINDINGS.md:93-95 "8 were held ... (export "Case battery not read: Timeout", lines 169, 180, 195, 266, 294, 321, 369)" — 8 claims, 7 line numbers. Fix: add the missing export line (the 5766 claim after 17:31:05). +- SC3-31 nit — CAP-061-EVENT-NOTES.md:106 / FINDINGS:147 "Right bud seated ≈ 17:30:52.4–53 (film 2 fps: t=352.5–353.0)": with the file's own mapping overlay = 17:24:59.15 + t (EVENT-NOTES:26), t=352.5–353.0 is 17:30:51.65–52.15 (phone 0–0.2 s earlier), ≈ 0.8 s earlier than stated; every other row (e.g. t=357 → 17:30:56) fits the mapping. Fix: 17:30:51.5–52.2. + +#### CAP-062 (read in full) +- SC3-32 major — CAP-062-FINDINGS.md:80-81 "the full tally is 70 `Set`s in 13 captures, 59 ACKed, 11 NAKed, all NAKs in `CAP-062`" (and :81 "`CAP-062` holds the only NAKs") — this capture has 10 NAKs (:40, :68; tshark `frame contains ff:02:00:03:02:08:12` → 5998 6050 6107 6155 6243 8344 8448 8456 8505 10127 = 10), and PROTOCOL.md:810 says "11 NAKed ... (`CAP-062` ×10, `CAP-064` 3440)". Fix: "11 NAKed — 10 in CAP-062, 1 in CAP-064 (3440)". +- SC3-33 minor — CAP-062-FINDINGS.md:225 "14 Connect/Disconnect cycles" and :237 "14/14 announcements, 14/14 reads"; EVENT-NOTES.md:154 "PAIR-003 ✓ (×14 app sessions)". `python3 -I scripts/pwrpc_decode.py CAP-062-btsnoop_hci.log | grep -c GetSoftwareInfo` → 15 (2768 … 11040), and the timeline has 15 Connect taps (06:41:04.8 … 06:56:41.3); §3's 14 rows are session *ends* (the 15th session runs to the end). Fix: 15 sessions / 15 announcements (14 ended). +- SC3-34 nit — CAP-062-FINDINGS.md:144 "3760 (06:42:28.851) ... `64 e4` (4520)" — 4520 belongs to the 06:43:02 DLCI 0x05 claim (EVENT-NOTES:89), ≈ 34 s later, not a frame "within ±3 s" as the method (:155) requires. Fix: cite the battery frame nearest 3760 or mark it "next claim". + +#### CAP-063 (read in full; Appendix A skimmed as the unchanged skeleton) +- SC3-35 minor — CAP-063-EVENT-NOTES.md:239 "AY-13a | done, repeated (5×) | export 206, 257, 272, 535, 546" vs FINDINGS.md:67 "AY-13a ... 🟢 confirmed (8 Refreshes)"; and 535/546 are labelled AY-13b in the timeline (:159-160). Fix: one count (3 AY-13a + 2 AY-13b + 2 double-tap + 1 AY-14 = 8 Refreshes) and drop 535/546 from the AY-13a row. +- SC3-36 minor — CAP-063-EVENT-NOTES.md:182 "16:17:21–16:18:40 | Idle ... (≈ 93 s, not 2 min)": that window is 79 s; 93 s only from the 16:17:05.8 re-open (also FINDINGS:69 "idle 93 s", step map :243). Fix: state the start (16:17:06) or say 79 s. +- SC3-37 nit — CAP-063-EVENT-NOTES.md:90 "15:58:16.6–16.7 | ... Play services re-opens DLCI 0x04 (1708 ...)" — tshark: 1708 `SABM` DLCI 0x04 is at 15:58:20.321 (FINDINGS:94 says 15:58:20.32); only 1441/1470 (0x0a/0x08) are at 16.6–16.7. Fix: give 1708 its own time. +- SC3-38 nit — CAP-063-EVENT-NOTES.md:248 "7175 → export 650" vs timeline :171 "export 644" for the same Buds DISC (FINDINGS:64 uses 641/650 for observer-stopped/re-open). Fix: align the export line numbers. + +#### CAP-064 (read in full; Appendix A skimmed as the skeleton) +- SC3-39 nit — CAP-064-FINDINGS.md:94 "four automatic re-opens by LINK_BACK, each 6–8 ms after the reading turned `CONNECTED`" — the table's own times give C 10:05:31.007→.011 (4 ms), D 13.822→13.829 (7 ms), E 32.834→32.840 (6 ms), G 32.495→32.498 (3 ms). Fix: "3–7 ms". +(Arithmetic re-checked and consistent: 384 = 14+112+112+14+60+36+36 pw_rpc packets; 36 writes = 26+4+2+4; 14 ends = 9 Buds DISC + 3 ACL + 2 taps; 26 balance releases incl. Centre ×5; Settable `e8` 28 s on the table 10:06:15.0–10:06:43.2. The NAK 3440 matches PROTOCOL.md:810.) + +#### CAP-065 (read in full; Appendix A not re-read beyond its header — the skeleton) +- SC3-40 major — Test-ID mis-mapping in CAP-065-EVENT-NOTES.md: TESTPLAN:170-171 define `CASE-004` = "Remove Left earbud", `CASE-005` = "Remove Right earbud". EVENT-NOTES tags Left-bud removals `CASE-005` (:89 "Left-slot bud taken out ... [`CASE-005`, `INEAR-002`]", :116 "Left-slot bud out ... [`CASE-005`]", :130 "Left bud out of the slot ... [`INEAR-002`, `CASE-005`]", :138 "only the Left-slot bud out ... [`CASE-005`]"), and the traceability paragraph (:217-218) defines "`CASE-004` (lid closed with a bud inside, BA-7, BA-9), `CASE-005` (every bud out of the case)". Fix: Left removals → `CASE-004`, Right → `CASE-005`; lid close with buds inside → `CASE-006`. +- SC3-41 minor — CAP-065-FINDINGS.md:182 "19 when it was the Left and 21 when it was the Right — 7 of 7 (incl. #4's first-out)"; the §4 table (:171-179) has 8 one-bud-out sessions (#1, #2, #6, #7, #9 Left→19 = 5; #3, #4, #8 Right→21 = 3), as the 7×19/3×21 announcement count (:194) also implies. The same "7/7" is in the Capture Index row and PROTOCOL.md:411/:3754. Fix: 8 of 8 (PROTOCOL.md text change needs the maintainer). +- SC3-42 minor — CAP-065-FINDINGS.md:95 "the link came 3.5–7 s after the lid opened": from the table (:84-90) A 11:08:20.5→27.10 = 6.6 s, E 11:19:16.5→23.02 = 6.5 s, F 11:20:50.5→57.89 = 7.4 s, G 11:21:28.5→33.91 = 5.4 s. Fix: "5.4–7.4 s". +(Arithmetic consistent: 266 = 10+95+95+12+54; 107 requests; 28 `00` Notifies; 51 = 26 PS + 25 app claims; 7 ACKs.) + +#### CAP-066 (read in full; Appendix A skimmed — skeleton) +- SC3-43 minor (stale, missed by the 2026-10-03 correction) — CAP-066-EVENT-NOTES.md:179 step mapping "plus a variant (16:21:36, the Right from the ear into the case, on 19 → 19)" — the same file's timeline row :120 was corrected 2026-10-03 to "session on ch 21 (announcement A3267) ... announcement ch 19 (A5268)", and FINDINGS.md:176/:188-191 (row 4 rewritten, A68-CAP-02) say 21 → 19 (tshark 3267 = `7e00a503…` ch 21). Fix: "on 21 → 19 (a third switch)". +(Arithmetic re-checked: 32 balance writes per the zigzag list; 76 Notifies = 28 `00` + 48 `e8`; 80 reads = 64+16; 10 announcements; 14 BB-5 samples + 5 CAP-065 = 19; ad0c4ba 15:38:00 +0200 → install 13:42:56 UTC = 4 min 56 s ✓.) + +#### CAP-067 (read in full; Appendix A skimmed — skeleton) +- SC3-44 minor — CAP-067-EVENT-NOTES.md:21 "all 7 MAESTRO announcements of the HCI logs" and FINDINGS.md:14 "7 of 7 announcements": FINDINGS §4 table (:147-152) lists 6, §5 (:166-167) counts 3 + 3, §10 (:261) "6 session opens". tshark (Buds frames containing `release_5.203` on handle 0x000b): `.log.last` 253, 982, 1123; `.log` 399, 980, 1628 = 6. Fix: "6 of 6". +- SC3-45 major — Test-ID mis-tags against TESTPLAN:172-173/248 (`INEAR-002` = insert Left, `INEAR-003` = insert Right, `INEAR-004` = removed from the ear): CAP-067-EVENT-NOTES.md:116 "Right bud into the Right ear | ... [`INEAR-002`]" (should be `INEAR-003`); :119 "the Right bud out of the ear ... **BC-6a ✓** [`INEAR-003`]" (a removal → `INEAR-004`); :117 Left into the ear has no tag (`INEAR-002`); repeated in the summary :202 "[`INEAR-002`] 08:05:12 ✓; [`INEAR-003`] BC-6a ✓". Fix the tags (and the Capture Index's "INEAR-002–004" stays valid). +- (cross-capture, filed under CAP-058) see SC3-46. + +#### CAP-058 addendum +- SC3-46 minor — CAP-058-FINDINGS.md:89/:93/:96 "type **0x05** (authenticated P-256)" / "type **0x04** (unauthenticated P-256)" (also EVENT-NOTES:62/:84/:102 key types). HCI Link Key Notification key type 0x04 = Unauthenticated Combination Key generated from P-192 and 0x05 = Authenticated Combination Key from P-192 (P-256 would be 0x07/0x08); CAP-067-FINDINGS.md:233 correctly says "`0x04` Unauthenticated Combination Key, P-192". Fix CAP-058's "P-256" → "P-192" (and PROTOCOL.md §5.1 if the proposal was applied with that wording). + Verified: `tshark -r CAP-058-btsnoop_hci.log -Y "frame.number==3804 || frame.number==2235" -V | grep "Key Type"` → "Authenticated Combination Key, P-192 (0x05)" (2235), "Unauthenticated Combination Key, P-192 (0x04)" (3804). The same P-256 wording is in PROTOCOL.md:2188-2189 (needs maintainer approval to change). Severity raised to major (wrong in PROTOCOL.md). + +#### CAP-068 (read in full; Appendix A skimmed — skeleton) +- SC3-47 major — ANC Test-IDs swapped vs TESTPLAN:119/121 (`ANC-002` = Noise Cancellation, `ANC-004` = Transparency): CAP-068-EVENT-NOTES.md:87 "Tab **Transparency** … | `ANC-002`", :90 "Tab **Noise cancellation** … | `ANC-004`", :92 tile `Set 80` → `ANC-002`, :95 tile `Set 08` → `ANC-004`, :96 `Set 80` → `ANC-002`, :104 NC → `ANC-004`, :147 Transparency → `ANC-002`, and the traceability line :220 "`ANC-002` (BD-1, BD-5, BD-25), … `ANC-004` (BD-4, BD-5, BD-7′)". Fix: swap 002↔004 throughout (CAP-064/065/066 use the TESTPLAN mapping). +- SC3-48 minor — CAP-068-EVENT-NOTES.md:213 "I: 11 `Set`s on film with ACK and Notify (tab 4, tile 5, BD-25 1, BD-7′ 1)" vs FINDINGS.md:87 "**12 `Set`s, 12 ACKs**" (table :91-102 has 12; `message_stream_tally.py` set 11 + 1) and the Capture Index "12 ANC Set → 12 ACK". The tab had 5 `Set`s (BD-1, BD-3, BD-4, BD-8 ×2) besides BD-7′. Fix: "12 (tab 5, tile 5, BD-25 1, BD-7′ 1)". +- SC3-49 minor — CAP-068-FINDINGS.md:165 "the ring tone became audible 13 s after the Ring (07:48:44.8)": the Ring was 07:48:16.9 (EVENT-NOTES:138, B2142 07:48:16.927) → 27.9 s; 13 s is after loss #11 (07:48:31.65). Fix: "28 s after the Ring, 13 s after loss #11". +- SC3-50 nit — CAP-068-FINDINGS.md:19 "classic handle `0x000b` for all six ACLs" vs the §1 table (:65-72) numbering 5 ACLs plus one failed page (no ACL). Fix: "five ACLs (and one page timeout)". + +#### CAP-069 (read in full; Appendix A skimmed — skeleton) +- SC3-51 minor — CAP-069-FINDINGS.md:135 "Five Default taps, five writes `4:{16:{0.0…}}`" then lists four frames (1990, 5803, 5965, 6066) and says :138 "(Four writes for five filmed selections …)"; :309 (P-4) lists the same four. EVENT-NOTES.md:152 "BE-17 done early (16:07:17) and repeated four more times (16:17:36, 16:18:43, 16:19:07 …)" — three times listed. Fix: "four writes (five selections)" in :135 and "repeated three more times" in EVENT-NOTES:152. +- SC3-52 minor — CAP-069-FINDINGS.md:62-64 self-contradictory: "it is `08 01` after every ON and `08 02` after every OFF (6/6), i.e. **inverted** against the skeleton's prediction (… that matched; … matched; the skeleton therefore predicted it right …)". Fix: drop "inverted against the skeleton's prediction" → "inverted against field 29's numbering (the skeleton predicted it right)". +- SC3-53 nit — CAP-069-FINDINGS.md:218 "6/6 with the unsolicited Notifies (2270, 2367, 7327, 7419, 7459)" — five frames listed. Fix: list the sixth or say 5/5. +(Re-checked: 26 writes = 1+6+4+2+13; Code 0x05 samples 6 `03` + 2 `06` + 5 `04` + 2 `05` = 15; BE-12 negative has command, exit and positive control.) + +#### CAP-070 (read in full; Appendix A skimmed — skeleton) +- SC3-54 minor — CAP-070-FINDINGS.md:19 "classic handle `0x000b` in all 19 ACLs" — the §1 ACL table (:73-84) has Z1–Z8 + A1–A7 + B1–B2 = 17. Fix: 17 (or list the two missing). +- SC3-55 minor — CAP-070-FINDINGS.md:230 "The first LINK_BACK attempt fails on a multiplexer collision (#4, #6, #8, #11 is clean): E3 232/371/532 …" (three cited lines/collisions: A1252/A1266, A2241/A2274, A3351/A3385) vs §9 table :260 "collides … (4 times)". Fix: "3 times (#4, #6, #8; #11 clean)" in both. +- SC3-56 nit — CAP-070-EVENT-NOTES.md:93 tags "Left (upper slot) out of the case onto the table" with `INEAR-002` (= insert Left into the ear, TESTPLAN:172) — a case removal, no ear on film. Fix: drop `INEAR-002` there (`CASE-004` only). +(Re-checked: 89 writes = Z 12 + A 77 (5+1+4+2+5+14+3+39+2+2); 49 other balance values = 53 − 4 × `17:7`; 22 sessions = 8+11+3; 23 Notifies = 19 `00` + 4 `e8`.) + +#### CAP-071 (read in full; Appendix A skimmed — skeleton) +- SC3-57 minor — CAP-071-FINDINGS.md:17 "classic handle `0x000b` in all 7 ACLs" — the §1 table (:100-108) has 6 ACLs (Z1, A1–A4, B1; "A0" was two page timeouts, no ACL), matching the six Connection Complete frames named at :19 and EVENT-NOTES:42. Fix: 6. +- SC3-58 nit — CAP-071-EVENT-NOTES.md:253 "P0 | done (outputs below, exit statuses recorded)" — the outputs are above (:45-121). Fix: "above". +(Re-checked: 205 requests = 168 reads (11×13 + 12 + 13) + 13 subscriptions + 24 writes; 27 claims = 23 `08 11` + 4 Ring; five bud-into-case moments; ACL drop → retry page 0.42 s.) + +#### Check 6(a) — MAESTRO requests per Connect (1.2.0) +Command (repo root, read-only): `python3 -I scripts/pwrpc_decode.py captures/CAP-073-*/CAP-073-btsnoop_hci.log | grep "dir=Sent REQUEST"` (exit 0; 171 lines). Per session (split at each Buds `GetSoftwareInfo` announcement): 15 non-write requests in 11 of 11 sessions (announcements 258, 3060, 3299, 3800, 4238, 4834, 5054, 5496, 5857, 6422, 6821) + 6 user WriteSettings (4 after 3299, 1 after 5054, 1 after 6422) = 165 + 6 = 171 = FINDINGS:107's "171 phone→Buds frames". Session 1 list (frames 266–333, ch 21): ReadSetting 4:16 (266), 4:2 (296), 4:4 (299), 4:7 (302), 4:11 (305), 4:12 (308), 4:15 (311), 4:17 (314), 4:19 (317), 4:22 (320), 4:27 (323), 4:28 (326), 4:29 (329), SubscribeRuntimeInfo (332), GetHardwareInfo (333) = **13 ReadSetting + SubscribeRuntimeInfo + GetHardwareInfo = 15** (CAP-076's statement is right). +- SC3-59 major — "thirteen requests" is wrong/ambiguous wherever it denotes the Connect read: CAP-073-FINDINGS.md:101 heading "## 2. The Connect read — thirteen requests" (body :103-104 lists 13 ReadSettings + Subscribe + GetHardwareInfo, and :108 "15 per channel"), :231 "the thirteen, one `GetHardwareInfo`", :242 "13 requests in …", :273 "no request outside the thirteen"; CAP-072-FINDINGS.md:108 heading "thirteen requests" (body :110-111 again 13 reads + Subscribe + GHI = 15), :310 "thirteen requests"; CAP-072-EVENT-NOTES.md:119, :215, :240; and the skeleton text CAP-072-EVENT-NOTES.md:302-303 "**thirteen** MAESTRO requests plus the EQ read and the subscription — the twelve settings reads, `SubscribeRuntimeInfo`, then `GetHardwareInfo`" (which lists 14 and counts the subscription twice). Capture Index CAP-073 row: "11 sessions with the thirteen requests byte-identical to CAP-072". Fix: "fifteen requests (13 `ReadSetting` incl. 4:16, `SubscribeRuntimeInfo`, `GetHardwareInfo`)" or define "thirteen = the 12 settings reads + `GetHardwareInfo`" once and use it consistently. + +#### CAP-072 EVENT-NOTES (read in full; appendix skimmed) +- SC3-60 major — Test-ID mis-tags against TESTPLAN (ANC-001 Off, ANC-002 NC, ANC-003 Adaptive, ANC-004 Transparency; CASE-004/005 = Left/Right out of the case, CASE-006 = into the case; HOLD-005 = ANC-mode list; TOUCH-001 = touch controls; INEAR-001 = the in-ear-detection switch): CAP-072-EVENT-NOTES.md:153 "ANC tap **Noise cancellation** → … `Set` 08 … `ANC-001`" and :193 same (→ `ANC-002`); :158 tile OFF→Active→Transparent tagged "`ANC-001`…`003`" (→ `ANC-002`, `ANC-004`); traceability :273 "`ANC-001` (17:44:32, 18:06:35), `ANC-002`/`ANC-003` (17:48:46, 17:50:03/23 …), `ANC-004` (17:41:57–17:43:26)" (17:44:32/18:06:35 are NC, 17:48:46 is Off, 17:50:23 Transparent; Adaptive was never set); :146/:209 "both/Left into the case … `CASE-004`" and :276 "`CASE-004` (17:38:29, 17:51:03, 17:59:23, 18:13:15)" — all into-case events (→ `CASE-006`); :199 "Use touch controls OFF / ON … `HOLD-005`" (→ `TOUCH-001`); :201 press-and-hold Left/Right → `HOLD-005` (→ `HOLD-001`…`004`); :219 "In-ear detection OFF / ON … `INEAR-004`" and :272 (→ `INEAR-001`). Fix the tags and the traceability paragraph. + +#### CAP-072 FINDINGS (read in full) +- (SC3-59 applies: :108 heading "thirteen requests", :310.) +- SC3-61 minor — CAP-072-FINDINGS.md:283-284 "a tone was found at 2 of 2 clear Right entries … but at **none** of the five Left entries (E4, E7, E8, E10 and E2 faint)" — the table (:257) records E2 (Left, switch on) as "faint", so it is not "none". Fix: "at none of four Left entries clearly (E2 faint)". +- SC3-62 nit — CAP-072-FINDINGS.md:302-303 "the 10-s chain guard skipped 8 re-opens …, after which a Connect tap followed (17:41:28, 17:51:40, 18:00:16, 18:01:47, 18:12:22, 18:20:03)" — six taps for eight skips; say which two skips were followed by LINK_BACK instead. +(Re-checked: 60 writes = 4+4+2+1+2+30+2+2+3+4+2+2+2; 28 losses = 22+4+1+1; 52 claims = 49 + 3; Notify Settable 18 `00` + 31 `e8` claims.) + +#### CAP-073 (read in full; appendix skimmed) +- (SC3-59 applies: FINDINGS :101, :231, :242, :273; EVENT-NOTES :75, :103, :136, :167, :197.) +- SC3-63 minor — hold duration stated three ways: CAP-073-FINDINGS.md:154 "The channel stayed open **30.97 s** (08:27:59.763–08:28:30.890)", :247 "none in 30.5 s", EVENT-NOTES.md:172 "no `DISC` for 30.5 s (08:27:59.8–08:28:30.9)", FINDINGS :233/:261 "31 s". 08:28:30.890 − 08:27:59.763 = 31.127 s. Fix: 31.1 s everywhere. +- SC3-64 nit — CAP-073-EVENT-NOTES.md:190 "Expected but not observed: … `INEAR-004` (BI-19, in-ear detection not switched)" — switching in-ear detection is `INEAR-001` (TESTPLAN:152); `INEAR-004` is a bud removed from the ear. Fix: `INEAR-001`. +(Re-checked: 11 session ends = 1+1+2+7; 8 automatic re-opens (2 AFTER_LOSS + 6 LINK_BACK) + 3 user connects = 11 sessions; 12 claims = 11 snapshot + 1 hold; 6 writes + 4 Sets.) + +#### CAP-074 (read in full) +- SC3-65 minor — CAP-074-FINDINGS.md:260 (§7 table) "The app's first connect attempt on 0x04 … **14 of 16 failed** (56–363 ms), 2 connected into a free channel" vs §4 item 3 (:176-177) "the three claims opened on attempt 1 into a free channel — 1.03 s (15500), 1.61 s (16578), 0.38 s (14845)". From the §1 inventory, the run's 16 app claims are 13 collisions (E 84, 98, 119, 163, 191, 218, 280, 335, 396, 456, 513, 571, 626) + 3 free; §2's "14 such lines in the run" (:142) counts E 15, which is 06:38:10, before the film. Fix: "13 of 16 failed, 3 connected into a free channel" (and "13 lines in the run, 14 with 06:38"). +- SC3-66 minor — CAP-074-FINDINGS.md:165-166 "E logs 'Message Stream hold started …' **12 times in the run** (06:40:38.936, … 06:45:14.634 — thirteen with the pre-film 06:38:24.617)" — the list has 13 run timestamps, and :167 counts 13 "hold ended" (1 tab + 12 closes) for the run. Fix: "13 times in the run (14 with the pre-film one)". +- SC3-67 nit (verify) — CAP-074-FINDINGS.md:196 / EVENT-NOTES.md:227-228 "three of them [Play's Get answers] already carry a changed mode — 15618, 15957, 16270" → TOUCH-007 total "47": Play's claim 16977 read `08` ×2 (16999/17003, :107) right after the app's claim ended on `80` (16937), which is also a changed mode by the same rule → 4 and 48. Fix: add 16999 or state why it is excluded. +- SC3-68 nit — CAP-074-FINDINGS.md:179 / :262 "**20 of 20 recoveries of the run fall in 5.2–5.7 s**" — the item lists 11 cut intervals + 5 re-open intervals = 16. Fix: list the other 4 or say 16 of 16. + +#### CAP-075 (read in full; appendix skimmed) +#### Check 6(b) — "8 of 19" vs "8 of 31" +Both are correct, with different denominators, per CAP-075-FINDINGS.md §4 :166-171: 31 = every hold in set 2's film (8 within a second + 2 late + 3 overdrawn + 2 after a user action + 16 never = 31); 19 = the holds made during the three "Take it back" chains (8 + 2 + 3 + 6 = 19); "Show a hint" 9 holds (0 within a second, 1 after a user action, 8 never); the stopped period 3 (0). All 8 shown holds fall in the chains, so "8 of 31" (DECISIONS.md:2782 ADR-062 Update; CAP-076-FINDINGS.md:154/:326; CAP-075 FINDINGS:18/:294) is the whole-film figure and "8 of 19" (FINDINGS:257/:268/:327) the per-position figure. Only a wording risk: in the ADR-062 Update (DECISIONS.md:2782, copied in FINDINGS:294) "8 of 31 holds shown" stands in the sentence about mode 2, where the mode-2 figure is 8 of 19 → suggest "8 of the film's 31 holds (8 of the 19 made during the chains, 0 of the 9 under the hint)". +#### Defects +- SC3-69 minor — claim counts per log wrong: CAP-075-FINDINGS.md:72-73 (§1 table) H2L "12 | 7 (#1, #2, #4, #6, #8 …; #10) | 5 (#3, #5, #7, #9; #11 …; #12 …)" — the lists name 6 and 6; H2 "38 | 19 | 19". tshark on handle 0x000b: H2L 12 phone `SABM`s on DLCI 4, 6 Play markers `03 08 00 02 01 25` (949 1488 2089 2342 18730 21552) → app 6 / Play 6; H2 38 `SABM`s, 18 markers (910 … 6842) → app 20 / Play 18 — matching :85-86's own lists (Play #1, #3, #5, #7, #9, #11, #15, #18, #20, #22, #24, #26, #28, #30, #32, #34, #36, #38 = 18) and the "Closer" cell (15 cut + 4 released + 1 Disconnect = 20), and :217 "its 24 claims" (6 + 18). Also :34-35 "19 claims of its own on DLCI 0x04 in H2 and 3 + 2 in H2L" (→ 18; 4 + 2), :125 "one per claim: 1 / 10 / 7 / 19" (→ 1 / 10 / 6 / 20), EVENT-NOTES.md:42 "19 Message Stream claims of its own in the live HCI log" (→ 18) and :364 "12 + 38 (set 2: 6 + 19 the app's, 3 + 19 Play's)" (→ 6 + 20, 6 + 18). Fix all to app 6/20, Play 6/18. +- SC3-70 minor — CAP-075-EVENT-NOTES.md:22-23 "of the 31 holds made in set 2's film, 8 were on screen within a second …, 2 only after the maintainer tapped, and 16 never" sums to 26; FINDINGS:166-168 adds "2 shown late by a re-claim, 3 drawn for < 0.3 s or overdrawn". Same omission in FINDINGS:18 ("8 … 16 never"). Fix: list all five categories (8 + 2 + 3 + 2 + 16). +- SC3-71 minor — CAP-075-FINDINGS.md:322 (§11) "8 sessions, 49 app claims" — §1's inventory gives 1 + 10 + 6 + 20 = 37 app claims (or 36 for the 1.2.1 processes of the run); 49 is not derivable from any table. Fix: 37 (state the scope). +- (SC3-59 applies: "the thirteen requests" FINDINGS:27, :302; EVENT-NOTES:122, :181, :208, :246, :342-344, while §2 :107 itself says "fifteen frames each".) +- SC3-72 nit — CAP-075-EVENT-NOTES.md:335 "H4 (12 holds, 34 s, no `DISC`)" vs :149 "no `DISC` for 35.7 s" and FINDINGS:78 "#8 … 35.72 s" (34.1 s is hold-start→hold-end, 35.7 s SABM→DISC). Fix: name which interval. + +### sub_sessions + +#### Sub-review: ai-sessions 0069–0089 (progressive) + +#### 0069 (RESULT read in full 447 lines; PROMPT §3–§6 read) +- SS-01 (med) 0069 RESULT:18-19 Summary "Nothing is committed, tagged or published." vs :91 Progress "committed on branch maintenance/0069 (2026-10-04)" and :427 Commits list (8246ec8…cf44734) and :7 Status "committed … released as 1.0.1". Summary not updated when closed. Fix: dated note in Summary "(committed 2026-10-04, released 1.0.1 — see Status)". +- SS-02 (low) 0069 RESULT:438 Commits ends "the commit that records this list (it follows the ones above)" — never back-filled; it is e1fc886 (= tag v1.0.1). §4b.2 says next session completes it; 0070 did not. Fix: add "`e1fc886` — docs(sessions): record the 0069 commits in its RESULT (= v1.0.1 build)". +- SS-03 (low) 0069 RESULT:28 "59 short rows in five capture notes" vs ledger A68-HK-08 (:263) "the checks found 68 short rows". Two counts for the same lint output. Fix: re-run/state one number (maybe 59 in notes + 9 elsewhere — say so). +- SS-04 (low, known) INDEX.md:78 0069 cell holds "complete (closed …) — awaiting maintainer sign-off — … **nothing committed, tagged or published**". Fix: strike the stale tail, keep closed status. +- SS-05 (info) 0069 RESULT:337-349 gate table split by mutation table M1–M8; the rows "Phase 7 …" sit after the mutation table with a note "(The four rows above continue the gate table)". Cosmetic; merge tables. +- Arithmetic checked OK: ledger 173 rows = 134 processed + 26 narrowed + 7 TODO + 5 moved + 1 rejected (recounted by awk). 29 = 9+20 OK; 7+5=12 OK. +- Deferred → TODO.md: the A68 items (accessibility, string resources, instrumented tests, A68-APP-09/10/13, slips, folder names, templates, A68-SES-03) are present in TODO.md now; "ADR-040 seven times" removed by d336884 (0081 ADR-040 Update) OK; L68-9/UUID register/decode_qhr removed by b4405d8 (0073). "11 `00` answers" (A68-ARCH-03) removed by d336884 (0081). Not checked whether 0081/0073 each records the removal. +- §6 gate: no new ADR / no promotion; dated Updates each cite checkpoint answers (24 answers table) — OK. + +#### 0070 (RESULT read in full; PROMPT §4 tasks read) +- SS-06 (low) 0070 RESULT:147 Commits "this commit: docs(sessions): record the 0070 commits in its RESULT" — never back-filled (it is 9507d39); the after-publish commit 5a9de44 (branch release/1.0.1-after-publish, which edited this RESULT, :134-138) is not listed either. grep of ai-sessions/ for 9507d39/5a9de44: 0 hits. Fix: add both hashes (§4b.2). +- SS-07 (info) 0070 PROMPT task 18 forbade the RELEASING.md Release-log row and README "Latest release" before publication; RESULT:121-126 did both in advance, citing the maintainer's chat request (quoted). Deviation is recorded — OK, but RESULT never says "deviates from prompt task 18". Fix: one sentence. +- SS-08 (low) PROMPT task 17 asked for the L68-5 samples at the checkpoint; RESULT:45 says only "L68-5 got no samples here" under "Not read"; no checkpoint row / no FINDINGS pointer stating 0 samples with a positive control (AGENTS §13.8). Fix: state "0 `04 05`/`04 03` samples (DLCI 0x08 never opened, command + control)". +- Deferred → TODO: H5/S12 present (TODO.md:59); CDM picker 🔴 present (:196); P1 install-time item later removed by 0f142e8 (0075) OK; Ring-status item removed by b835866?/later — OK (CAP-069 answered it). +- §6: §4.1/§4.4 dated Updates approved in chat (rows "ANC-006", "Ring") — OK; no ADR. + +#### 0071 (RESULT read in full; PROMPT task headings read) +- SS-09 (low) 0071 RESULT:148 "The documentation commit that carries this file (its hash is completed by the next session …)" — never completed: 93a930c (analysis) and 874ec28 (sign-off record) appear in no ai-sessions file (grep 0 hits). 0072 did not back-fill. Fix: add both hashes + PR #3 merge 511f781. +- SS-10 (info) ADR-052/053 and PROTOCOL promotions (field 29, SASS bit 2) cite checkpoint answers "FACT + ADR-052/053 (Recommended)" — approval recorded OK. ADR-049 Update cites "Wear table" answer (DECISIONS.md ADR-049 Update 2026-10-04) OK. +- Arithmetic: 26 writes = 1+12+1+6+4+2 = 26 OK; correction 7→6 samples recorded. +- Status vs INDEX row 80: both "complete (signed off…)" consistent. + +#### 0072 (RESULT read in full; PROMPT task headings read) +- SS-11 (low) 0072 RESULT:16 Progress "**Next:** the maintainer's answer to the commit question; then commit/push/PR" — stale; Status (:7) and Commits (:171-178) say committed and pushed. Fix: "Next: none". +- SS-12 (low) 0072 RESULT:178 "this list's own commit … its hash is back-filled by the next session" — not back-filled (it is c809aa7); 0073 does not mention it. Same pattern as SS-02/06/09. +- §6: field 21 = Volume notif. 🟢, pairing IO caps 🟢, label 🟢 — approved in chat ("Extras" answer verbatim) OK. ADR-006/ADR-018 Updates approved ("Option A", "SDP-001") OK. +- Deferred → TODO: all 6 items present (TODO.md:83, 88, 94, 145, 148, 150) OK. + +#### 0073 (RESULT read in full 1115 lines — appendices tallied by script, not read row by row; PROMPT §5 read) +- SS-13 (med) 0073 RESULT:155 "968 citations in 19 documents (850 of them in `REVERSE_ENGINEERING.md`)" vs the appendix heading :756 "**`REVERSE_ENGINEERING.md`** (758)". Appendix per-document counts sum to 968 (758+91+46+2+1+1+16 capture files 110 = 968), so 850 is wrong. Fix: dated correction "758". +- SS-14 (low) 0073 RESULT:1110 "(this commit) | docs: record the commits of ai-sessions/0073" and :1112-1113 "The hash of the last commit above and the merge: to be completed by the next session's first step" — never completed (it is 4a709c8; PR #5 merge not recorded); 0074 RESULT does not mention it. Fix: back-fill. +- SS-15 (info) 0073 RESULT:29-31 process note: workflow runner used for 5 citation reviews without asking the maintainer — recorded honestly; 443 "confirmed (read)" verdicts not re-derived by main session (§4b.4) — recorded. No defect, but those 443 remain sub-review-only. +- SS-16 (info) prompt §0.1 reading list: ARCHITECTURE.md, CAPTURE_*, TESTPLAN_*, id_registry not read; declared at :733-738. Deviation declared, OK. Deferred item 8 carries it to TODO (TODO.md "ai-sessions/0073` owed" present). +- Arithmetic OK: 644+300+19+5=968; 201+443=644; 1067 = 234+302+469+43+19; 103 tests = 20+7+23+16+13+14+10; 75,538 = 38,085+37,453; 595 rows = 338+116+34+39+37+31; inventory status tally by script = 17/28/2/1/24 (A=72), B 103, C 90 (1 narrowed) — matches :63-66. 19 UUIDs in §4.10 count OK. +- §6: no promotion, no ADR; PROTOCOL notes approved ("All five notes") OK. +- Deferred → TODO: items 1–8 present (TODO.md:115, the 23/24/26 line, :178 setting-13 film item, 'primary' bud, empty-case test, Save button enabled, exported components, "ai-sessions/0073` owed"). + +#### 0074 (RESULT read in full 461 lines; PROMPT §4 tasks read) +- SS-17 (low) 0074 RESULT:438-440 Deferred item "Read only late or in part … (This RESULT only — no task in `TODO.md`.)" — breaks AI_SESSION_LOG_PROCEDURE.md §4b.1 ("Every item the session defers has a line in TODO.md") and the prompt task 23 ("each item also in TODO.md, in the same words"). Fix: add a TODO.md §6 "reading owed by 0074" line (as 0073 did) or drop it from "Deferred". +- SS-18 (low) PROMPT task 24 asked "one `feat(app)` per switch with its tests … each building and passing on its own"; RESULT:449 commits all four switches in one commit ac9eae5 and :455 says lint_docs was not run on 3 intermediate doc commits. The deviation is stated for lint but not for the per-switch split. Fix: one sentence recording the deviation and the reason. +- SS-19 (info) PROMPT task 8 asked for "an ASCII mock-up of the tab" in the checkpoint; RESULT §B does not say whether a mock-up was shown. Minor silent omission. +- Arithmetic OK: latency 19 samples, range 21–241 ms, median 58 ms (recomputed from §A.1 table + 6 extra samples); 12 reads = 7 + 5. +- Status / INDEX:83 consistent with git (tag v1.1.0 → 0323849; Commits back-filled by 0075). No ADR/promotion (none asked) OK. + +#### 0075 (RESULT read in full; PROMPT task headings read) +- SS-20 (low) 0075 RESULT:131-133 two Deferred items marked "(This RESULT only — no task in `TODO.md`.)" — breaks §4b.1 (same pattern as SS-17). The CAP-068 §0 sentence was later handled by 0081's status banner (CAP-068-FINDINGS.md:3), but with no pointer back. Fix: mark done-by-0081 here; avoid "RESULT only" deferrals. +- SS-21 (low) 0075 RESULT:144 "(this commit) | docs(session): ai-sessions/0075 commits back-filled, status complete" — hash 8256c74 not filled in; the E1–E4 commit 1390583 (which edited this RESULT, :160-161) is not listed. Fix: back-fill both. +- §6: L-1 dated Update approved ("L-1" answer); §4.5 notes approved ("Docs"). OK. Status/INDEX:84 consistent with git. + +#### 0076 (RESULT read in full; PROMPT task headings read) +- SS-22 (low) 0076 RESULT:7/:15 Status "pull request opened" / Progress "Next: the maintainer merges the pull request" — PR #9 merged as 6167843; never recorded; "(this commit)" (:127) = 2d39048 not back-filled. Fix: back-fill both. +- Deferred item (NavHost TODO(verify)) done by 0079 (OpenControlNavHost.kt:245 "reworded in ai-sessions/0079") and TODO.md line removed — OK. +- §6: ADR-055 Update approved in chat ("ADR-055" answer) OK. 12 of 12 frames = 6 requests + 6 answers OK. + +#### 0077 (RESULT read in full; PROMPT D1–D7 + tasks headings read) +- SS-23 (low) 0077 RESULT:7/:15 Status "pull request opened"/"Next: the maintainer merges" — PR #10 merged (42660fd); "(this commit)" (:170) not back-filled. Same back-fill pattern. +- SS-24 (low) 0077 RESULT:160 Deferred "Later session prompts can drop the `git checkout` .pyc line. (This RESULT only — no task in TODO.md.)" — §4b.1 again. (Practically harmless.) +- §6 OK: ADR-056/057 carry "Accepted (maintainer, chat 2026-10-07, AskUserQuestion "D5 ADRs" …)" in DECISIONS.md; PROTOCOL Updates each map to a verbatim answer. `.pyc` untracked: `git ls-files scripts/__pycache__` empty — claim holds. +- Arithmetic OK: 106 = 63 + 43; 106 + 12 = 118; 13 answers in table. + +#### 0078 (RESULT read in full 417 lines; PROMPT headings read) +- SS-25 (low) 0078 RESULT:~343 Compliance "Every changed Kotlin file keeps `SPDX-License-Identifier: AGPL-3.0-or-later` (13 of 13)" — `git diff --name-only 42660fd a4cfaf1 -- '*.kt'` lists **14** files (7 main: MainActivity, BudsRepositoryImpl, EqScreen, OpenControlIcons, OpenControlNavHost, PullRefresh, SettingsMenu; 7 :ui tests). The RESULT's own list (3 + 4 + 7) also gives 14. Fix: "14 of 14" (re-run the header grep). +- SS-26 (info) 0078 RESULT: tree audit "118 → 135 libraries" vs third_party_notices "136 libraries" (:~322). 118+25−8 = 135 is consistent; the 136 is unexplained (script counts differently?). Fix: one clause explaining the +1. +- SS-27 (low) 0078 RESULT:7/:14 Status "pull request opened"/"Next: … the maintainer merges"; PR #11 merged (188d19e) not recorded; "(this commit)" not back-filled. Same pattern. +- Arithmetic OK: 25 added artefacts recounted from the table; 118+25−8=135; mutations M1–M7 table consistent with INDEX:87. +- §6: ADR-029 Update approved ("37 + ADR-029 Update (Recommended)") OK. "🟢 FACT: generated by D8" is a build-artefact label, not a PROTOCOL promotion — OK. +- Deferred → TODO: §5 "After the toolchain upgrade of ai-sessions/0078" present (TODO.md:224) OK. + +#### 0079 (RESULT read in full 240 lines; PROMPT headings read) +- SS-28 (med) 0079 RESULT:7 Status "complete (committed and pushed to `release/1.1.1`, pull request open, not merged …)" and :227 "Not merged (D1 comes after CAP-071 …)" — stale: PR #15 merged (f359aa2), 1.1.1 released 2026-10-08 with tag v1.1.1 on 86a6fb3 (= this RESULT's own "(this commit)", :236, never back-filled). The file was last changed in 86a6fb3; 0080 did not update it (unlike 0075, which updated 0074's Status after 1.1.0). INDEX:88 also omits "released". Fix: §4a status update "(released as 1.1.1 on 2026-10-08, tag on 86a6fb3, PR #15 merged f359aa2)". +- SS-29 (low) 0079 RESULT:~52/:~60 "`git log --oneline v1.1.0..origin/main`: 38 commits (31 without merges)" / "(31 commits)" — recount with origin/main = 7101d9c (Phase 0 says HEAD..origin/main empty): 38 total, **30** without merges (`git log --oneline --no-merges v1.1.0..7101d9c | wc -l` → 30). Fix: 30. +- Arithmetic OK: 71 captures = 66 analyzed + 3 planned + 2 withdrawn. Gate counts equal to 0078 OK. +- §6: no ADR/promotion. Deferred → TODO §5 **M** repository settings present (TODO.md:234) OK. + +#### 0080 (RESULT read in full 194 lines; PROMPT headings read) +- SS-30 (info) 0080 RESULT:14 Progress "Next: the maintainer's release steps D1–E4 … Commits back-filled by the next session" — superseded by the "After the session" paragraph (:186-190) in the same file; harmless but the Progress block was not updated. 0080 does record PR #15 merge and the tag — but did not update 0079's Status (see SS-28). +- Arithmetic OK: U results 11 ✅ + 3 ⚠️ = 14 (= APP_TESTPLAN U1–U14); writes 24 + 9 + 4. +- §6: two PROTOCOL notes approved verbatim; no ADR. Deferred → TODO: §2 items present; SIGQUIT pointer done by 0081 (CAP-070-FINDINGS.md:3 banner) OK. + +#### 0081 (RESULT read in full 311 lines; PROMPT headings read) +- SS-31 (med) 0081 RESULT:22 "26 Test-ID rows of the test plan did not point back", :~279 "26 of 26 TESTPLAN rows carry the new pointer", ledger A81-A-08 (:~72) "24 rows lack a back-pointer", INDEX.md:90 "26 TESTPLAN back-pointers". Reality: commit d336884 changes **22** TESTPLAN rows (`git show d336884 -- TESTPLAN_BLUETOOTH_HCI_SNOOP.md | grep -c '^+|'` → 22: ANC-001…004, BATT-001/004, CASE-001…005/008, EQS-001…004, HOLD-005, PAIR-001/003/004, SWITCH-001, WELL-001). The A81-A-08 list itself has 26 capture↔ID pairs, i.e. 22 distinct rows (PAIR-003 ×3, BATT-004 ×3). Fix: "22 rows (26 capture pointers)" in the RESULT and INDEX. +- SS-32 (med) 0081 RESULT:9 Status "complete — … commit/push awaiting the maintainer's word (see "Commits")" vs §G (:300-305) "Approved in chat … `6d31902`, `d336884` … the session files follow in the third commit" — Status never updated; third commit (a8f69a6) never back-filled; PR #20 merged (9e08a0a) not recorded. Fix: Status "complete (committed a8f69a6, PR #20 merged 9e08a0a)". +- SS-33 (low) 0081 A81-D-12 (:~222) claims "every "Deferred documentation" item of `0060`, `0067`, `0070`…`0080` has a TODO line — checked by grep per item" — false for 0074 (:438-440), 0075 (:131-133) and 0077 (:160), which each defer items explicitly "This RESULT only — no task in TODO.md" (see SS-17/20/24). Fix: qualify the claim. +- Arithmetic OK: A81-C-32 4+0+4+4+1+12+6 = 31; A81-C-04 13+14+24+16 = 67; area C 27+5+7+7 = 46. +- §6 OK: M-1…M-5 (ADR-040 Update, three FACT-line count corrections, CAP-004 🟢→🟡) each approved verbatim in §C.8. +- Deferred → TODO: slip residue, S-1…S-4, 0061/0063 debt present (TODO.md:305-337, :327). + +#### 0082 (RESULT read in full 590 lines; PROMPT headings read) +- SS-34 (med, §6) 0082 wrote **ADR-060 into DECISIONS.md** (status "Proposed — draft, not accepted") and registered it `proposed` in id_registry.csv:234, against the prompt (task 21 / §5 "Approvals": an unaccepted ADR lives in the RESULT and TODO.md, not DECISIONS.md) and AGENTS.md §6 ("must never write a new DECISIONS.md ADR … without explicit maintainer approval"). RESULT:330-336 flags it as a deviation; the maintainer's chosen option was "ADR-060 als concept" and the Commits answer (:571-572) says "ADR-060 stays as the Proposed draft" — so there is after-the-fact maintainer acquiescence, but the cause (lint_docs requiring a heading for every cited ADR number) is a tooling conflict with §6 that is not registered anywhere. Fix: TODO/process item — let lint accept "draft ADR" citations without a DECISIONS.md heading, or record in AGENTS.md §6 that a Proposed heading with the maintainer's chat consent is allowed. +- SS-35 (low) 0082 RESULT:~452 "New tests: … `:ui` +14" (70 → 84) vs the per-phase tallies: AncScreenTest +4 (§C), SettingsMenuTest +3 (§D), BatteryCardTest +3 (§E), SettingsMenuTest +3, ControlsScreenTest +1, EqScreenTest +1 (§F) = **15**. One of the per-phase "+N" is off by one (or a test was removed silently). `:data` +17 = 5 + 6 + 5 + 1 OK; `:domain` +7 OK. Fix: recount from JUnit XML per class. +- SS-36 (low) 0082 RESULT:14 Status "complete (… commits pending the maintainer's answer, "Commits" below)" — Commits section shows the answer was given and five commits made; the "(next)" session commit (531c99d) never back-filled; PR #21 merged ec6d163 not recorded; 1.2.0 later rebuilt (0084) and released — 0082's Status never updated. INDEX:91 says only "complete". Fix: §4a update. +- Arithmetic OK: GetHardwareInfo census 49 + 88 + 1 = 138. +- §6 OK otherwise: ADR-058/059 accepted and PROTOCOL §6 serial attribution → 🟢 each with a verbatim chat answer (§B). +- Deferred → TODO: 1.3.0 candidate present (TODO.md:245); fixture swap done in 0084; CAP-064 wording item removed in 921e647 (0084) — OK. + +#### 0083 (RESULT read in full 150 lines; PROMPT headings read) +- SS-37 (low) 0083 RESULT:43 Progress "**Next:** finish the blurred camera film …, swap the blurred films in, the sha256sum file, footers and lint, then the commit question" — stale; Status (:7) and Commits (:143-148) say closed/merged. Fix: "Next: none". +- SS-38 (low) 0083 RESULT:138-140 Deferred: three items "Not done in this session and not in TODO.md because they are checks, not work items" (GrapheneOS release notes, AOSP user-switch source, base.apk pull) — §4b.1 has no such exception. Fix: one TODO line or drop from "Deferred". +- SS-39 (info) 0083 reading fell short of the prompt (ARCHITECTURE.md, RELEASING.md, 0082 RESULT not read) — declared at :63-67 OK. +- §6 OK: L-1 🟡→🟢 ("Promote both to 🟢"), ADR-061 ("Accept as ADR-061 now"), ADR-058/059 Updates — each a verbatim chat answer. Commits back-filled by 0084 (the one session that did it properly). INDEX:92 consistent. + +#### 0084 (RESULT read in full 332 lines; PROMPT headings read) +- SS-40 (low) 0084 RESULT:31 Progress "**Phase 0 — in progress.**" — stale (all phases done, Status closed). Commits (:327-328) "pull request #27 opened …, nothing merged" — PR #27 merged as 4586e2b and 1.2.0 published 2026-10-10 (tag v1.2.0 → 5b4d5db) — not recorded in this RESULT (0085 records the release). Fix: one line. +- SS-41 (info) 0084 RESULT:24-25/:51-53 records a guardrail breach honestly ("deleted BalanceSnapTest.kt with git rm before asking … restored … asked … deleted after yes"). Recorded correctly; no action. +- Arithmetic OK: :data 1644+11 = 1655; :domain 44−2 = 42; :ui 84+(1+1+4) = 90; INDEX:93 counts match. +- §6 OK: ADR-032/059 pointer Updates each with a chat answer ("ADR-032", "ADR-059"); no promotion. +- Deferred → TODO: release steps (done since), serials item present (TODO.md:298), shorter-runs item present; the TESTPLAN BI Group column was done (TESTPLAN.md:181 lists BI) OK. + +#### 0085 (RESULT read in full 122 lines; PROMPT headings read) +- SS-42 (low) 0085 RESULT:32/:52/:59 Progress keeps the running states "**Phase 0 — in progress.**", "**Next:** write CAP-073-EVENT-NOTES.md …", "**Phase D — checkpoint next.**" next to "Phase D done" — a resumable log never collapsed to its final state (cf. 0073's "Final state" note). Fix: mark the block "(final state)" and drop the superseded "Next" lines. +- Arithmetic OK: §W results 3 ✅ + 6 ⚠️ + 3 not run = 12 (W1–W12); 12 Test-IDs with BI in TESTPLAN. +- §6 OK: ADR-061 Update approved ("ADR-061" → "Add the Update"). Release steps D1–E4 each quoted with the maintainer's Dutch instruction. Deferred → TODO present (TODO.md:44/47; the hold item later marked moot by 0091). + +#### 0086 (RESULT read in full 154 lines; PROMPT headings read) +- SS-43 (low) 0086 RESULT:146-150 "the back-fill of these hashes … (this commit)" / "The hash of the back-fill commit itself is completed by the next session" — not done (it is c03faab); 0087 RESULT does not mention 0086's commits; PR #37 merge (8a0db34) not recorded. Same pattern as SS-02 etc. +- SS-44 (low) 0086 RESULT:138-139 Deferred "Open from this session, in CAP-074-FINDINGS.md §10 (not separately in TODO.md)" — four items (stack lines for the cuts, APK hash, the 06:31:07 ACL end, volume steps) — §4b.1 again. +- Arithmetic OK: 47 holds = 9 + 35 + 3; 47 claims = 25 + 22. +- §6 OK: root-cause 🟢/🟡 labelling and ADR-061/032 Updates each a verbatim answer; PROTOCOL §4.1 note "no status change". A read-only subagent's figures are marked S (§4b.4) — correctly. + +#### 0087 (RESULT read in full 341 lines; PROMPT headings read) +- SS-45 (low) 0087 RESULT:15 Summary "**What 1.2.1 does differently** (versionCode 10201; nothing published, no tag, no release — the maintainer's own acts)" vs Status (:10) "released … tag v1.2.1 on 31ee183 … published 2026-10-13" — the Summary was not revised when the Status was (cf. SS-01). Also :336 "(The hash of the back-fill commit … is completed by the next session.)" — 3950736 never filled in (0088 does not). Fix: back-fill; "(at the time of the session)" in the Summary. +- SS-46 (info) 0087 RESULT:~318 Deferred "TESTPLAN Group column for the BK Test-IDs — the analysing session of CAP-075" is not a TODO.md line (§4b.1) — but it was done (TESTPLAN.md:181 lists BK). OK in effect. +- Arithmetic OK: :data 1655 + (7+2) = 1664; :ui 90 + (2+3) = 95; INDEX:96 counts match; the eleven 5.22–5.71 s values listed = "11 following a collision". +- §6 OK: ADR-062 "Accept ADR-062 as drafted" and the ADR-061 pointer Update — verbatim answers (§B). + +#### 0088 (RESULT read in full 157 lines; PROMPT headings read) +- SS-47 (low) 0088 RESULT:132-136 Commits "the third commit (this RESULT, …)" has no hash (it is 4a8929c; then a57cbed) and says PR #44 "(not merged)", while :27 (Continuation) says #44 merged as 0596fc2. Fix: back-fill hashes, "(merged 0596fc2)". +- SS-48 (low) 0088 RESULT:140-157 the Progress block (placed after Commits) still reads "**Next:** Checkpoint 0 and the Phase D checkpoint …" and "Found: a leftover git worktree … **Not removed** (Checkpoint 0 asks)" — contradicted by the Checkpoint table row "Worktree → Remove it" and :93-95 "removed". Fix: mark the block as the pre-checkpoint state or update it. +- SS-49 (info) 0088 RESULT:6 "**Status:** complete" with no close note, but 1.2.1 was released in the same chat (Continuation :27) — fine; INDEX:97 consistent. +- Arithmetic OK: APP_TESTPLAN Summary "X 1.2.1 | 12 | 6 | 0 | 5 | 1" → 6+0+5+1 = 12. +- §6 OK: PROTOCOL §4.1 🟢 note ("Add as 🟢 FACT in §4.1 (Recommended)") and ADR-062 Update ("Approve the draft") — verbatim answers. + +#### 0089 (RESULT read in full 267 lines; PROMPT headings read) +- SS-50 (med, known) ai-sessions/INDEX.md:98 status cell starts "awaiting maintainer sign-off — built on `feature/channel-control` …" and ends "**1.2.2 published 2026-10-14**"; RESULT:9 Status "complete (closed 2026-10-14 …)". Two states in one cell. Fix: "complete (closed 2026-10-14 …)" as in the RESULT. +- SS-51 (low) 0089 RESULT:14 Summary "(versionCode 10202; prepared on branch `feature/channel-control`; nothing committed, no tag, no release)" vs Status/Commits (:259-266: committed, merged 5b204ea, tag v1.2.2 on bfd7891, published) — Summary not revised at close (same pattern as SS-01/45). Also Progress :34 "Baseline gate: running." left beside the finished gate line. +- SS-52 (low) 0089 RESULT:~40 Progress "tests: … `BudsRepositoryImplTest` (+8, one rewritten), `HoldReclaimSettingsStoreTest` (+1)" = +9 for :data, but the gate (:47, :55) says :data 1664 → 1672 = **+8**; §D lists **7** new BudsRepositoryImplTest tests + 1 rewritten + 1 store test = +8. Fix: "(+7 new, 1 rewritten)". +- SS-53 (med) 1.2.2 was published without its hardware run (`CAP-076` not run — the maintainer's choice, RESULT:263-265; RELEASING.md:334 Release-log cell "— (CAP-076 not run)"), skipping RELEASING.md checklist C3/C4 (:55-57). Unlike 1.0.0 (RELEASING.md:139-145 "Exception recorded for 1.0.0 …"), no dated exception note was added to RELEASING.md's rules, and ADR-062 Update 2 / ADR-063 shipped "not hardware-verified". Fix: add a dated "Exception recorded for 1.2.2 (maintainer, chat 2026-10-14)" note in RELEASING.md next to the 1.0.0 one. +- §6 OK: ADR-062 Update 2, ADR-063 and the AGENTS.md §1 note each a verbatim chat answer (§B rows "ADR form", "ADR-063", "AGENTS.md"); the "🟢 FACT (this Play version)" at :70 is a RESULT label, not a PROTOCOL promotion. +- Deferred → TODO: §6 "Reading owed by ai-sessions/0089" present (TODO.md:329); §5 P3/P5 present. + +#### Cross-session patterns +- P-1 Commit back-fill (§4b.2) missing for the "(this commit)" line in 0069, 0070, 0071, 0072, 0073, 0075, 0076, 0077, 0078, 0079, 0081, 0082, 0086, 0087, 0088; only 0074 (by 0075), 0083 (by 0084), 0084 (by 0085) were back-filled by the next session. +- P-2 "This RESULT only — no task in TODO.md" deferrals (0074, 0075, 0077, 0083, 0086) contradict §4b.1; 0081 A81-D-12 claimed all deferred items had TODO lines. +- P-3 Summary/Progress blocks not revised when Status was closed (0069, 0072, 0076, 0077, 0078, 0083, 0084, 0085, 0087, 0088, 0089). +DONE + +### sub_data + +#### sub_data — :data audit (1.3.0, b9828ba) — progressive findings + +#### BudsRepositoryImpl.kt lines 1–1480 (in progress) + +- D-01 major — BudsRepositoryImpl.kt:385-386 `causeOfReadAnswer`: `if (_ancMode.value != null && mode != _ancMode.value) CHANGED_SINCE_LAST_READ else READ`. + Confirmed CAP-076 §6.2: snapshot Get's Notify (answersGet → causeOfReadAnswer → CHANGED_SINCE_LAST_READ, emitAncMode sets _ancMode = new mode) then + ReadSetting 4:13 answer with same mode → mode == _ancMode → READ → emitAncMode overwrites cause. Fix (P1): return AncModeCause? and `null` when + mode == _ancMode.value && _ancMode.value != null (emitAncMode then keeps cause); return type nullable; test with CAP-076 H1 4094 → 4135. + Note: also affects Notify-answers-Get when the stream push already set the mode → READ overwrites CHANGED_BY_BUDS (same fix covers it). +- D-02 major — handleRoutedFrame Notify branch (~702-708): `_ancNotifies.tryEmit(...)` then `emitAncMode` then `_ancOutcomes.tryEmit(AncOutcome.Notified(it))` + unconditionally, also when answersGet. setAncModeAfterGet → ancSetOnClaim → sendAndAwaitOnClaim(_ancOutcomes,…) (~973). On a multi-threaded dispatcher the + Get waiter can resume and subscribe to _ancOutcomes before line ~708 → the Set takes the Get's Notify as Notified → Success (line ~993) without ACK. + Fix: do not emit Notified for a Notify that answersGet; and/or stamp outcomes with a sequence (outcome seq > seq at Set write) and filter. +- D-03 nit — applyAncState line 419 logs `(cause ?: _ancModeCause.value)` → a same-mode push logs the kept cause (CAP-076 §4.4). Fix: log `cause?.name ?: "unchanged"`. +- D-04 minor — readSettings/readAncSetting accept (~1555, ~1625): `(it is MaestroReply.Setting && it.value.field == field)`; handleRoutedFrame emits + MaestroReply.Setting also for stream pushes (frame.stream true). A stream push of the field being read (or a 4:{13:n} push between the subscribe and + readAncSetting, sent back-to-back) is taken as the read's answer; the real RESPONSE then arrives with nothing waiting; an error/unreadable RpcResult for + READ_SETTING of field N can then end field N+1's wait (accept matches any READ_SETTING Result). Fix: carry `stream` in MaestroReply.Setting and accept only + non-stream answers for reads (`!it.stream`). +- D-05 nit — companion MESSAGE_STREAM_LINGER_MS KDoc: "also after the noise-control tab or the app is left (ADR-061)" — describes ADR-061's hold, removed + by ADR-065 item 5. Fix: drop the clause. +- D-06 nit — GET_RESPONSE_TIMEOUT_MS KDoc "Wait for a fresh ANC Notify after a manual Refresh" — it also bounds every tap's Get (setAncModeAfterGet). Also + `launchInitialEqRead` now runs six steps (EQ, settings, runtime info, settings stream, 4:13, hardware info) — name misleads. +- D-07 nit — refreshBattery: `resubscribeJob = scope.launch {…}` overwrites a still-running earlier job without cancelling it (only the last is cancelled by + cancelClaimJobs). Fix: `resubscribeJob?.cancel()` first, or skip if active. +- D-08 nit — readAncSetting: `Maestro.readSettingRequest(...) ?: return@withLock` returns silently (no log) unlike every other failure in that function. +- D-09 minor — applyAncState → updateAncAvailability computes `_ancAvailabilityProvisional = clock() - messageStreamOpenedAt < 2 s`; for a settings-stream + push / 4:13 answer (MAESTRO channel, not the claim) the ADR-024 "stale within 2 s of the Message Stream open" rule does not apply, yet a stream packet + arriving within 2 s of a claim's open is flagged provisional. Fix: pass a `provisional` flag; false for MAESTRO-sourced readings. +- D-10 nit — handleRoutedFrame `is AncFrame.Set -> emitAncMode(anc.mode)` (inbound Set) sets the mode without a cause, so the old cause sticks to a new mode. + Either drop (the Buds never send Set) or surface as UnidentifiedFrame/log. + +#### BudsRepositoryImpl.kt fully read; codec/*, SafeModeGate, SessionDiagnostics, SessionReopener, settings/* read +- D-02 addendum: application scope = `CoroutineScope(SupervisorJob() + Dispatchers.Default)` (app/di/RepositoryModule.kt:57), tile scope Dispatchers.Main + (AncTileService.kt:79) → inbound collector and the tap's Get waiter run on different threads — the race is real. JVM tests with one test dispatcher cannot + reproduce it. Best fix: in handleRoutedFrame emit `AncOutcome.Notified` only when `!answersGet` (the Get's answer can never be the Set's). +- D-11 minor — application scope has no CoroutineExceptionHandler; handleRoutedFrame (outside decodeGuarded) and `debugModeEnabled.collect` run in it. + DataStore `.data` (DebugSettingsStore:43, DarkModeSettingsStore:38) has no `.catch { IOException -> emit(emptyPreferences()) }`: a corrupt prefs file + throws into BudsRepositoryImpl's init collector → uncaught on Default → process crash. setDebugModeEnabled/setDarkMode can throw IOException across the + module boundary (AGENTS §3 sealed results). Fix: add `.catch` on the data flows; wrap edits in BudsResult. +- D-12 nit — CodecRouter: SubscribeToSettingsChanges error packets (CLIENT_/SERVER_ERROR) are not in `answersUs` → reported as UnidentifiedFrame; the + "Settings stream subscribed" log is printed on send success only, so a Buds-side refusal of the subscription is never named. Fix: route an error for + METHOD_SUBSCRIBE_TO_SETTINGS_CHANGES to a log line "settings stream refused (status)". +- D-13 nit — MessageStreamFrameSplitter: `ArrayList` + `repeat(totalLength){ buffer.removeAt(0) }` is O(n²) per chunk (boxed bytes). Small frames, + so harmless; ByteArray + offset would be cleaner. +- D-14 nit (dead code, intentional) — DLCI 0x08 path (Dlci.GSND_CONTROL, gsndSplitter, CaseBatteryFrameDecoder, RoutedFrame.CaseBattery → Unit) never fed + (ADR-043); SoftwareInfo.firmwareStrings is unused in main (KDoc says "the Safe-Mode gate checks" it — the router calls entries().map{}.distinct()). Fix: + drop firmwareStrings or have routeMaestro use it; correct the KDoc. +- No leftovers of ADR-061/062/063 code in :data main (grep hold/reclaim: only comments saying they are gone; DarkModeSettingsStore documents the ignored + `hold_reclaim_mode` key). Constants match ARCHITECTURE.md §3.2 (all 15 BudsRepositoryImpl constants + SessionReopener's two). +- TODO(verify): EqFrame.kt:39 (16 vs 18 semantics, ADR-020 open item) and CaseBatteryFrame.kt:53 (fresh flag, ADR-014 🟡) — both still describe open + hypotheses; the second guards a decoder whose channel is never opened. + +#### Tests: SettingsStreamCodecTest, SettingsStreamFixtures, BudsRepositoryImplTest 3078–3592 read +- D-15 minor — SettingsStreamFixtures.kt `SET_TRANSPARENT_CAP051_1527 = "0812001401e8e880" + 16×"00"` is labelled as CAP-051 frame 1527 but the real frame + (tshark -r CAP-051-btsnoop_hci.log -Y frame.number==1527 → exit 0) is `0812001401e8e880e4e68882be02fffe63abde07ef49dae8` (the official app's + nonce+MAC). Not real bytes (AGENTS §11); it is also unused. Fix: replace with the real bytes or delete. +- D-16 minor — fuzz `fuzzed stream packets never throw` (SettingsStreamCodecTest) and CodecRouter-based fuzzing: `feedCounting` passes no `onDecoderFault`; + CodecRouter.decodeGuarded catches RuntimeException and reports it as malformed, so a decoder that throws still passes the test ("never throw" is not + asserted). Also `assertTrue(mode == null || mode in AncMode.entries)` is always true by type. Fix: count onDecoderFault and assert 0; drop the tautology. +- D-17 minor — BudsRepositoryImplTest `the automatic re-open subscribes to the settings stream again` (~3244–3280): the re-open's snapshot Get is answered + by autoAck's OFF (Cap065 6334) while the 4:13 read answers ADAPTIVE (1225) — two sources disagree in one session, which never happens on hardware + (CAP-076 §6.2: Notify before 4:13, same mode, 12/12). The CHANGED_SINCE_LAST_READ assertion passes only because of that, masking D-01. Fix: answer + the Get with an ADAPTIVE Notify (CAP-076 H1 4094 → 4135 order) and assert CHANGED_SINCE_LAST_READ survives the equal read. +- D-18 nit — `a differing read of 13 …` asserts `READ` for "the same mode read again" — will need to change with D-01's fix (keep cause on equal read). +- D-19 minor — no test for D-02's interleaving (the Get's Notify reaching _ancOutcomes after the Set's wait subscribed); with StandardTestDispatcher the + router finishes handleRoutedFrame before the waiter resumes, so it cannot occur. Fix: a test that emits the Get's Notify, then the Set's wait subscribes, + then emits an extra Notified/close — or unit-test the "skip Notified when answersGet" rule directly. +- D-20 nit — `the stream packet … set by this app`/same-mode tests never assert the log text of a same-mode push (D-03 uncaught). +- D-21 nit — ancSetOnClaim sets `lastSetMode/lastSetAt` before the send and never clears them on a NAK (Rejected) or SendFailed; a hold to that same mode + within 2 s after a refused Set is classed SET_BY_APP. Fix: clear lastSetMode on Rejected/SendFailed. +- Settings-stream decoding IS fuzz-covered (SettingsStreamCodecTest `fuzzed stream packets never throw`, 8 400 mutations + prefixes; field 13 0–255 sweep). + +#### OversizedLengthTest, CodecRouterTest, test-name sweep done +- D-16 also applies to OversizedLengthTest `mutated real frames with a valid CRC never throw` and CodecRouterTest `random chunks never throw` (router guard + swallows a throw into onMalformed; the tests do not pass onDecoderFault). CodecRouterTest random chunks (≤49 B, random) almost never pass the HDLC CRC, so + they exercise splitters only. +- D-22 minor — applyAncState does `_ancModeFresh.tryEmit(mode)` for stream packets and 4:13 answers; refreshAncMode (manual refresh, Refresh battery, + Connect snapshot) waits on _ancModeFresh, so a MAESTRO-channel packet can end the Get's wait; getPending is then cleared in finally, and the Get's real + Notify arriving later is classified as unsolicited (CHANGED_BY_BUDS if it differs). Fix: emit _ancModeFresh only from the DLCI 0x04 Notify. +- D-23 nit — BudsRepositoryImplTest 3097–3170: section/DisplayNames still say "ADR-062 Update 2 … a re-claim's Get answer", "Notify on the held claim" — + describe the removed re-claim/hold; rename to ADR-065 item 4 wording. +- D-24 nit — CodecRouterTest `field 18 is flagged as persisted` builds the frame with EqFrameEncoder (hand-built) without the "synthetic, labelled" note + required by AGENTS §11; SettingsStreamCodecTest `an unread field …` DisplayName lists (10, 20, 31) but loops over 8, 10, 20, 31. +DONE. + +### sub_hw_ui + +#### sub_hw_ui findings (in progress) +#### Read: AndroidManifest.xml, app/build.gradle.kts, AncTileService.kt, BudsForegroundService.kt, BluetoothStateObserver.kt +- Manifest: perms OK (CONNECT, POST_NOTIFICATIONS, FGS, FGS_CONNECTED_DEVICE); no INTERNET; no BLUETOOTH_SCAN (AGENTS §2 lists it but it was removed by 0044 APP-13 — doc drift in AGENTS.md §2 "Minimal permission set" still lists BLUETOOTH_SCAN). Note: not in app manifest -> check hardware manifest. +- AncTileService:147 `tile.label = "ANC"` hard-coded vs manifest @string/tile_anc_label (nit, i18n/consistency). +- AncTileService:136-141 toast only on BudsResult.Failure; CAP-076 §5.3 silent tap: success returned if Set's wait takes Get's Notify (root in :data BudsRepositoryImpl ~973/993). Tile cannot detect; fix in :data (ignore Notify received before Set written) — out of my files but tile has no fallback. +- AncTileService:118-127 tap when not Ready: startActivityAndCollapse(PendingIntent) fine on API34; no isLocked/unlockAndRun handling (on lock screen tap launches activity behind keyguard - system prompts; minor). +- BudsForegroundService:221-226 getLaunchIntentForPackage may return null -> PendingIntent.getActivity(null) NPE (nit, practically never). +- BluetoothStateObserver:304 registerReceiver without RECEIVER_* flag: OK for protected system broadcast on targetSdk 34 (lint UnspecifiedRegisterReceiverFlag may warn; nit). +#### Read: hardware manifest, BleLogger, BudsTransport, RfcommSocket, ProfileProxies, BluetoothPermissions, BudsSdpUuids, RfcommBudsTransport +- hardware manifest: FGS declared connectedDevice, exported=false. OK. +- BleLogger: 20_000 cap OK; Log.d for events (below INFO) OK; describe() redacts MAC. OK. +- RfcommBudsTransport:336-341 check-then-put race: closeAll() (reportLoss from another reader) between line 336 check and line 340 put leaves aux socket open forever (auxSockets.clear already ran; reader launched in cancelled scope never runs) -> leaked DLCI 0x04 port, the exact collision ai-sessions/0039 warns about. Fix: do the check+put under `lock`, and re-check isOpen after put (close if closed). +- RfcommBudsTransport:328 openChannel has no per-channel guard against two concurrent opens (both pass containsKey) -> second connect() collides & kills incumbent. Verify caller serializes (data layer mutex); else add a Mutex. +#### Read: BudsCompanionPairing, OsConnectionObserver, LinkEvaluation, ConnectionStateMachine, PairingLogic +- BudsCompanionPairing:177 manager.associate(...) not wrapped in try/catch (AGENTS §8): a RuntimeException (IllegalStateException e.g. missing feature / SecurityException) leaves associationInFlight=true forever (every later tap -> AlreadyInProgress) and propagates. Fix: try/catch RuntimeException -> associationInFlight.set(false); onFailure(AssociationFailed(describe(e))). +- BudsCompanionPairing:364 NAME_PATTERN `(?i).*Pixel\\s*Buds.*` matches ARCH §9.0a. OK. TODO(verify) comments at 51-53 and 144-145 now stale (verified on hardware since 0036/0041) - nit. +- OsConnectionObserver:130-139: proxies requested only if permission held at collection start. If BLUETOOTH_CONNECT is granted later while the flow keeps running, evaluate() has requested={} and bound={} -> LinkEvaluation.evaluate returns NOT_CONNECTED (containsAll(emptySet)=true) even when Android has the Buds connected, until the flow is re-collected. Verify whether MainActivity re-collects on permission grant. Fix: in evaluate, if requested is empty and permission now ok, request proxies (or return null/UNKNOWN when requested is empty). +#### Read: MainActivity, RepositoryModule, TransportModule, AppUiSession, OpenControlApplication +- RETRACT OsConnectionObserver permission item: MainActivity:367 re-creates the observer on a Bluetooth-grant change (remember(permissionState.bluetoothConnect.isGranted)). Only a nit: the dependency is implicit. +- **MAJOR** MainActivity:143 `private val companionPairing by lazy { BudsCompanionPairing(this) }` is a second instance besides the @Singleton one (RepositoryModule:72) that the repository (bondedDeviceProvider) and AppUiSession use. "Use different Buds" (MainActivity:273 forgetAssociations) sets `chooseAgainRequested` (in-memory, per instance, BudsCompanionPairing:67) only on the Activity's instance -> (a) repository.connect() via singleton still name-falls-back to the old single bonded "Pixel Buds"; (b) any Activity recreation (rotation, theme/locale/config change) creates a new instance with chooseAgainRequested=false, so the lookup finds the old Buds by name again and the "choose again" state is lost. Fix: inject the singleton BudsCompanionPairing in MainActivity (associate() works with app context + main executor; the IntentSender is still launched by the Activity) or move chooseAgainRequested to a shared/persisted place. +- MainActivity:217 Toast.makeText(this@MainActivity, …) from applicationScope after an async write: Activity may be destroyed (rotation) -> leaked Activity reference / toast on dead context. Use applicationContext. +- MainActivity:258-262 orphaned KDoc ("The pairing entry point behind Pair a device…") sits above bondedChanged's KDoc, not above startPairing (line 280). nit. +- MainActivity:482-486 ACTION_REQUEST_ENABLE: SecurityException only logged, no user-visible text (silent tap) when Nearby devices permission missing. minor. +- MainActivity:507 / AncTileService:147 tile label "ANC" hard-coded twice; manifest uses @string/tile_anc_label. nit. +- OpenControlApplication:96 notification "Connected — ANC: ${it.name}" exposes the enum constant name (e.g. NOISE_CANCELLATION) instead of the UI label. nit/minor. +- OpenControlApplication:71-86: text updates re-call startForegroundService while app is in background (ANC push changes text) — may throw ForegroundServiceStartNotAllowedException (caught) leaving stale notification text; acceptable but the log line will say "not started" though service is running. nit. +- BudsForegroundService/manifest: START_NOT_STICKY + connectedDevice OK (ARCH §6.0a). +#### Read: all domain/main (17 files). AGPL headers present in all .kt of hardware/domain/app/ui (grep). No .java sources. +- AncTile.kt:42 tile subtitle = enum name title-cased -> "Active" for AncMode.ACTIVE, while the class KDoc/AncTileService call it "Noise cancelling" and the app UI labels it differently (check AncScreen). minor UI consistency; use the same label function as the ANC screen. +- DeviceInfo.kt:65 vs :73: serial index 2=Right/3=Left but firmware entry 2=Left/3=Right — both cite the official app; flag for verification (nit), labels in :ui. +#### Read: AncScreen.kt, ControlsScreen.kt +- Label inconsistency for one mode: AncScreen:160-162 `"ANC mode: ${ancMode.name}"` -> "ANC mode: ACTIVE"/"TRANSPARENT" (raw enum) in the (i); buttons say "Noise cancellation"/"Transparency" (ControlsScreen:167-172); tile says "Active"/"Transparent" (AncTile.kt:42); notification "Connected — ANC: ACTIVE" (OpenControlApplication:96). Fix: one `ancModeLabel(mode)` from ANC_MODE_LIST_ORDER used by all four. minor. +- AncScreen:186 `"Connection: ${connectionState::class.simpleName}"` shows Kotlin class names ("Failed", "Discovering") to users. nit. +- ControlsScreen:212-219 a11y: Checkbox and its Text are separate siblings; the label is not merged into the checkbox's semantics and only the 48dp box is tappable. Fix: Row(Modifier.toggleable(value, enabled, role=Role.Checkbox){…}) + Checkbox(onCheckedChange=null). minor. +#### Read: ConnectionScreen.kt +- ConnectionScreen:514 BudsError.Unknown -> "Unexpected error: ${cause.message}" raw (not BleLogger.describe-redacted) — privacy is out of scope, note only. +- ConnectionScreen:642-643 ANSWER_CUT_OFF_TEXT "Tap Refresh to see the current mode" — also toasted by the tile (no Refresh there); since 1.3.0 the stream reports the mode anyway. nit wording. +- BluetoothOff panel (122-125) + MainActivity ACTION_REQUEST_ENABLE: AGENTS §2 satisfied. +#### Read: ConnectionBanner, DebugScreen, Details, FindMyBudsScreen +- DebugScreen:61-64 a11y: Switch without a label in semantics (Text sibling). Fix: Row(Modifier.toggleable(role=Role.Switch)) + Switch(onCheckedChange=null). minor. +- DebugScreen:69 text "up to the last 20,000 lines" matches BleLogger.RING_BUFFER_CAPACITY; better reference the constant (nit, drift risk). +#### Read: SettingsMenu.kt, SettingsUi.kt +- SettingsMenu.kt:96 lint ModifierParameter: `modifier: Modifier = Modifier` is not the first optional parameter (`onOpenUrl: (String) -> Unit = {}` at :95 precedes it). Fix: move `modifier` above onOpenUrl (call sites use named args). +- SettingsMenu.kt:78-80 KDoc of SettingsMenuScreen still lists the removed "Noise-control channel"/"Noise control with Google Play services" card as part of the Settings tab (stale since 1.3.0 / ADR-065); :107-108 two dangling KDoc comments for removed parameters (ADR-062 choice, ADR-063 Play-services line). Fix: delete; say "Dark mode · Case sounds · Use different Buds". +- SettingsMenu.kt:201 "Use different Buds" disconnects and forgets associations immediately — no confirmation dialog for a destructive action. minor UX. +- SettingsUi.kt:151-164 a11y: Switch has no label in its semantics (title Text is a sibling) — TalkBack reads "Switch, on". Fix: Row(Modifier.toggleable(value, enabled, role=Role.Switch, onValueChange=onChange)) and Switch(onCheckedChange=null). minor (affects every settings switch). +- SettingsMenu.kt:341 licence raw resource read on the main thread inside remember{} (small file; AGENTS §3 says I/O off Main). nit. +#### Read: OpenControlNavHost.kt +- OpenControlNavHost:322-323 a11y: NavigationBarItem icon contentDescription = destination.label AND a visible label Text -> TalkBack announces the label twice. Fix: contentDescription = null. nit. +- ModifierParameter confirmed by ui/build/reports/lint-results-debug.xml: SettingsMenu.kt line 96, "Modifier parameter should be the first optional parameter" (only :ui lint issue). +#### Read: EqScreen, PullRefresh, TimeFormat, OpenControlTheme, OpenControlIcons (header part) +- EqScreen:346-366 a11y: the five band Sliders have no label/stateDescription (BalanceSlider has one at :292) — TalkBack reads a bare percentage of an unnamed slider. Fix: Modifier.semantics { contentDescription = label; stateDescription = "%.1f".format(shown) }. minor. +- EqScreen:207 preset label `preset.name.replace('_',' ')` -> "HEAVY BASS" all caps from enum names (TalkBack may spell capitals; not localisable). nit. +#### Read: libs.versions.toml, all build.gradle.kts, settings.gradle.kts, 4 workflows +- lint-docs.yml has no `permissions:` block -> GITHUB_TOKEN gets the repo default (may be read/write). Fix: top-level `permissions: contents: read`. minor. +- update-docs-sidebar.yml / update-sitemap.yml: job-level contents:write + pull-requests:write (needed), SHA-pinned; no `concurrency:` group -> two quick pushes to main can race on `git push --force origin bot/...` (last wins; harmless). nit. +- android.yml: least privilege OK (contents: read), SHA-pinned, source + merged-manifest INTERNET checks OK. `lint` only fails on errors, so the ModifierParameter warning never fails CI (consider lint { warningsAsErrors } or a baseline). nit. +- ui/build.gradle.kts comment "aligns it with the rest of lifecycle 2.8.6" stale (lifecycle 2.11.0; partially corrected in same comment). nit. +#### Tests / misc +- :app has no src/test at all: AncTileService, MainActivity glue (incl. the BudsCompanionPairing instance split), OpenControlApplication FGS driver untested; 1.3.0 tests updated: AncScreenTest, SettingsMenuTest, TabRestoreTest (commit 152c5b3). AncScreenTest pins "ANC mode: OFF" enum text (would change with the label fix). +- No remaining user-visible 1.2.x strings (hold / re-claim / Take it back / Noise control with Google Play services) in main sources; only comments (SettingsMenu.kt:78-80,107-108). ARCHITECTURE.md §2.4 line 153 still lists the "Noise-control channel"/"Noise control with Google Play services" choice in the Settings tab (doc drift). +- app/build.gradle.kts:146-147 versionCode history comment stops at 1.2.2 (no 1.3.0 line). nit. +- AGENTS.md §2 "Minimal permission set" still lists BLUETOOTH_SCAN although removed (0044 APP-13). doc nit. + +--- +https://github.com/tedsluis/opencontrolpixelbudspro2/blob/main/ai-sessions/0093_AUDIT_RESULT_2026_10_15.md - https://tedsluis.github.io/opencontrolpixelbudspro2/ai-sessions/0093_AUDIT_RESULT_2026_10_15 diff --git a/ai-sessions/INDEX.md b/ai-sessions/INDEX.md index 4642595..d5f5793 100644 --- a/ai-sessions/INDEX.md +++ b/ai-sessions/INDEX.md @@ -99,6 +99,7 @@ a new session** — the same "check before assigning" discipline `id_registry.cs | 0090 | CROSSCHECK | 2026-10-14 | Point-by-point check of the pbpctrl-derived findings list (E-01 … E-23: `qzed/pbpctrl`, MagicPodsCore's Pixel Buds branch, omarchy-pixelbuds at pinned commits) against this project's captures, APK notes and documents — led by the question whether the app can learn a press-and-hold's noise-control mode on DLCI 0x02 (Maestro setting 13, `SubscribeToSettingsChanges`) instead of the contested Message Stream; proposals only where the rules require the maintainer; no app code, no wire bytes | complete — the leading question answered yes: setting 13 = the mode on DLCI 0x02, pushed on `SubscribeToSettingsChanges` for 73/73 Buds-side and 39/39 app-driven changes (also with no Message Stream open), `ReadSetting 4:13` 359/382; OpenControl never subscribed (0/33 logs); the APK feeds its ANC state only from 13 (`fxb.smali:1237`). Approved in chat 2026-10-14 and applied: `PROTOCOL.md` §4.1 🟢 Update (+ §6, §2.2, §4.5.7, §0.1 notes), **ADR-064** (subscribe + read 13, read only; build next) with pointers on ADR-025/034/036, ADR-060 item (vii), the `qhr` register Update, a desk entry, `CAP-051-FINDINGS.md` §3, `AGENTS.md` §5 note, `ARCHITECTURE.md` §5a rule, `TODO.md`, `scripts/anc_setting13_crosscheck.py`; no app code; commits `7bfb078` … `0b1681a` on `research/pbpctrl-crosscheck`, pushed, PR #55. **Continuation in chat 2026-10-14:** the existing captures checked for the new findings — one-bud/two-bud stored mode 28 of 28 in the official app's captures (🟡 strong, `PROTOCOL.md` §6, `scripts/wear_mode_crosscheck.py`); settings 8/9/10 and `SubscribeToOobeActions` never on any wire; runtime-info 7.x = the charging flags in 96.9 % | | 0091 | FEATURE | 2026-10-14 | Build ADR-064: one `SubscribeToSettingsChanges` and one `ReadSetting 4:13` per MAESTRO session (Connect and every ADR-044 re-open), the stream feeding every setting the app reads; ADR-061/062/063 (the hold, the re-claim, the "Noise control with Google Play services" card, the Play-services line) replaced by one new ADR and removed from the app; the cause line rewritten (wear-driven changes stay 🟡); version 1.3.0; real fixtures (`CAP-051`, `CAP-056`, `CAP-038`, `CAP-045`, `CAP-021`, `CAP-027`), mutation checks, `CAP-076` rewritten as the 1.3.0 run in both users; `WriteSetting 4:{13:n}` out of scope | done 2026-10-14 — built on branch `feature/settings-stream` (ADR-065 accepted in chat; the gate green, M1–M5 caught; `CAP-076` skeleton rewritten; pushed 2026-10-14, commits `bb7a843` … `391b0c5`, PR #56 open); RESULT `0091_FEATURE_RESULT_2026_10_14.md`; hardware run analysed by 0092 (`CAP-076`, 2026-10-15: verdict release as built + Known issues) | | 0092 | CAPTURE | 2026-10-15 | Full analysis of `CAP-076` (Group BL: the 1.3.0 build `ff27ee1`, the settings stream on the app's own channel — ADR-064/065 — **two parts**: the Owner user with Google Play (06:35–06:52, the update 1.2.2 → 1.3.0 on film: one screen recording with audio, two HCI logs, one export, the app logcat, the system log, a full `logcat -b all`, the adb shell log) and the secondary user without Google Play (07:01–07:08: one screen recording with audio, two HCI logs, one export, the app logcat)): the real events per part, the two new requests once per MAESTRO session, every press-and-hold against the screen, the own taps' stream packets, the re-homes and re-opens, the comparison with `CAP-075`, the counts for the verdict, the release verdict proposal (`RELEASING.md` C3/C4); no app change | complete 2026-10-15 — `CAP-076` analysed (every hold on the noise-control tab shown: 29/29 with Play, 12/12 without; three minor defects); verdict release as built + Known issues; commits `c32b297` … `b06181e`; 1.3.0 released 2026-10-15 (tag `v1.3.0` on `ff27ee1`, PR #56) | +| 0093 | AUDIT | 2026-10-15 | Exhaustive end-to-end audit of the whole project after 1.3.0 — documentation, protocol, captures, architecture, decisions, `TODO.md`, release process and app code; deep re-review of `0069` … `0092` with `0090` (the pbpctrl cross-check behind ADR-064), `0091` (the 1.3.0 build) and `0092` (the `CAP-076` run) in depth; a structured reverse-engineering pass over the official APK `v1.0.955078536-10253511` (settings 23/24/26/30/31/32/39, the setting-13 stream schema, DLCI 0x08/0x0a, the MAESTRO service descriptors, Message Stream groups, gestures/wear/dosimeter, BLE) to clarify, correct and extend the protocol findings; improvement proposals for 19 documents; decisions for the maintainer with advice; review only — no file other than the RESULT changed | complete — review only (no file changed but its RESULT): 69 findings (0 critical, 7 major, 50 minor, 12 nit), 5 protocol leads, proposals for 19 documents, 10 decisions with advice (§7), a 12-row action list (§8); six read-only sub-reviews (Appendix B); the session moved from Fable 5.1 to Opus 5.5 after a usage limit; committed on `audit/0093-full-audit` | --- https://github.com/tedsluis/opencontrolpixelbudspro2/blob/main/ai-sessions/INDEX.md - https://tedsluis.github.io/opencontrolpixelbudspro2/ai-sessions/INDEX