Skip to content

1.3.0: the settings stream on the app's own channel (ADR-064/065) - #56

Merged
tedsluis merged 11 commits into
mainfrom
feature/settings-stream
Oct 15, 2026
Merged

tedsluis merged 11 commits into
mainfrom
feature/settings-stream

Conversation

@tedsluis

@tedsluis tedsluis commented Oct 14, 2026 •

Copy link
Copy Markdown
Owner

What

1.3.0 (ai-sessions/0091, 2026-10-14): the app learns the noise-control mode — and every other setting it shows — from the Buds' settings stream on its own channel (DLCI 0x02), so a press-and-hold on a bud shows on any tab, with or without Google Play services. Builds DECISIONS.md ADR-064 and the new ADR-065 (both maintainer-approved in chat 2026-10-14).

  • Once per MAESTRO session (Connect and every ADR-044 re-open), after SubscribeRuntimeInfo and before GetHardwareInfo: one SubscribeToSettingsChanges and one ReadSetting 4:13, byte-identical to the official app's CAP-051 1177/1178 (channel 21) and CAP-056 1451/1450 (channel 19). No retry, no polling; nothing written in reaction to a stream packet. The only new bytes on the wire are these two requests.
  • Every SERVER_STREAM packet for a field the app reads (2, 4, 7, 11, 12, 13, 15, 16, 17, 19, 22, 27, 28, 29) goes through the same decoders as a read answer and replaces the value with its receive time; setting 13 = the mode (0 = no mode settable, never shown as a mode; it feeds the availability). A packet for a field the app does not read is logged payload-free, not an UnidentifiedFrame.
  • Cause rules: the app's own Set within 2 s → the app's change; a differing push → "Changed by the Buds at HH:MM:SS: the Buds reported a new mode — a press-and-hold on a bud, or a change they made themselves."; a differing read answer (the Get's Notify, the 4:13 RESPONSE) → "Changed since the app last saw it (read at HH:MM:SS): …", also across a Connect. The ANC (i) ends with the maintainer's text (the wear-driven switch stays a hypothesis).

Removed (ADR-065 supersedes ADR-061/062/063): the hold of the shared channel while the noise-control tab is shown, the re-claim chain, the setting "Noise control with Google Play services" with its card, "Take it back again", the "Google Play services: found / not found" line, PlayServicesProbe and the manifest's <queries> entry. A stored hold_reclaim_mode is ignored (never migrated). The Message Stream is claimed only for a tap, the tile, Refresh, a pull and the Connect's snapshot, as before 1.2.0.

Evidence (real bytes, in the fixtures): CAP-051 1177/1178/1225/1717/1755/1802 and the own tap 1527 → 1529 → 1530/1533 → 1534 (269 ms); CAP-056 1451/1450/1468/1696/2175/2259/2324/4025/4028; CAP-038 2170/2205 (no Message Stream open); CAP-045 2033/2277 (value 0); CAP-021 1089/1842; CAP-027 923; CAP-069 2502; CAP-015 2116; CAP-036 1517/1519.

Checks

  • Gate cd android && ./gradlew assembleDebug testDebugUnitTest test lint --max-workers=2: BUILD SUCCESSFUL, 0 failing tests (:data 1672, :hardware 64, :domain 42, :ui 92 per variant), 7 lint warnings, no error; python3 scripts/lint_docs.py exit 0.
  • Mutation checks, one at a time: M1 the subscription once per process (2 tests fail), M2 value 0 as Off (4), M3 the own Set's stream packet as the Buds' change (1), M4 the stream to UnidentifiedFrame (2), M5 an EQ stream packet ignored (6) — all caught; the sources restored and verified.
  • Compliance: no new permission, no <queries>, no INTERNET; no build-script or dependency change (:app:dependencies --configuration releaseRuntimeClasspath checked for network/telemetry SDKs: none); the AGPL header on every new and changed Kotlin file; no hidden API; every new log line payload-free, no MAC or serial.
  • Commits per concern: bb7a843 feat, 7756e55 refactor, 152c5b3 test, 8a72572 + 391b0c5 docs. The gate ran on the final tree.

Known issues of 1.3.0 (CHANGELOG.md)

  • A tap claims the shared channel for about 1.5 s; with Google Play services, Play's Fast Pair may be on it at that moment — the tap fails with a message; tap again.
  • When the Buds close the session themselves (one bud out while both are worn — 12 of 77 such changes in the captures), the app re-opens it and reads the mode again; in between the mode shown can be a few seconds old.

Release checklist (RELEASING.md)

A. Prepare

  • A1. Branch feature/settings-stream from an up-to-date main (c1d898b).
  • A2. versionName = "1.3.0", versionCode = 10300.
  • A3. CHANGELOG.md [1.3.0] (no date yet), README.md, scripts/release_notes.template.
  • A4. Gate and lint_docs.py exit 0 (above).
  • A5. Commits per concern, pushed, this pull request.
  • A6. Both CI workflows green (build 5m52s, lint-docs 7s, 2026-10-15). Do not merge yet.

B. Build and verify — from the pushed branch tip

  • B1. you scripts/release.sh 1.3.0 on the branch tip (2026-10-15) — build commit: ff27ee1; APK SHA-256 03272c6f954316d76c3ef6d1d020abd8bdb97755d7f8e165c84d3f16a9c6a96c; 136 third-party libraries.
  • B2. you Certificate SHA-256 a7530f5c…d8dcb = README.md line 19 / SECURITY.md line 40 (checked 2026-10-15).
  • B3. you cp -a dist/1.3.0 ~/opencontrol-1.3.0-tested.

C. Test this exact APK

  • C1. you Install it in both GrapheneOS users; Info shows "App: 1.3.0, build " without "-dirty".
  • C2. you CAP-076 (Group BL, the rewritten skeleton under captures/): holds with the noise-control tab closed and open in both users, the own tap and the tile, the wear-change re-home, Bluetooth off/on, taps while Play holds the channel; the counts for the verdict.
  • C3. A CAPTURE session analyses it (FINDINGS, notes, index and registry rows on this branch, after the build commit). — ai-sessions/0092, commits c32b297 … b06181e (CAP-076-FINDINGS.md).
  • C4. Verdict. — minor issue: release as built + Known issues (the maintainer, chat 2026-10-15; CAP-076-FINDINGS.md §12): every hold on the noise-control tab shown (29/29 with Play, 12/12 without); Known-issues lines in CHANGELOG.md [1.3.0] and the kept release-notes.md.

D. Merge and publish — D1 merge commit, D2 signed tag on the B1 commit, D3 the draft release from the kept copy, D4 the phone. E. After publishing — E1–E4 by a small pull request of its own.

Session record: ai-sessions/0091_FEATURE_RESULT_2026_10_14.md.

🤖 Generated with Claude Code

tedsluis and others added 11 commits October 14, 2026 22:39
…3 at every session (ADR-064/065)

Once per MAESTRO session — at Connect and at every ADR-044 re-open — the Connect
sequence sends one SubscribeToSettingsChanges and one ReadSetting 4:13 after
SubscribeRuntimeInfo and before GetHardwareInfo, byte-identical to the official
app's CAP-051 1177/1178 (channel 21) and CAP-056 1451/1450 (channel 19). Every
SERVER_STREAM packet for a field the app reads goes through the same decoders as
a read answer and replaces the value with the packet's receive time; setting 13
is the noise-control mode (0 = no mode settable, never shown as a mode; it also
feeds the availability). A stream packet for a field the app does not read is
logged payload-free (RoutedFrame.UnreadSetting), not an UnidentifiedFrame.

Why: a press-and-hold on a bud changes the mode on the Buds, and the Message
Stream's Notify reaches only the client holding DLCI 0x04 — in the user with
Google Play services about half of the holds at best. The Buds push the same
change on the app's own channel to a subscribed client (CAP-051 1717/1755/1802,
CAP-038 2170/2205 with no Message Stream open), so the hold and the re-claim of
ADR-061/062 are no longer needed; this file also drops them (ADR-065).

Cause rules (ADR-065 item 4, the maintainer's choices in chat 2026-10-14): a
stream value equal to the app's own Set within ANC_SET_WINDOW_MS (2 s) is the
app's change; a differing push is "Changed by the Buds"; a differing read answer
(the Get's Notify or the 4:13 RESPONSE) is "Changed since the app last saw it",
also across a Connect — the cause is no longer reset at Connect. Nothing is
written in reaction to a stream packet; no retry, no polling.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ices setting and the package query (ADR-065)

Gone: the hold of the DLCI 0x04 claim while the noise-control tab is shown
(ADR-061), the re-claim chain with the "Noise control with Google Play services"
setting, its card, "Take it back again" and the hint line (ADR-062), the
"Google Play services: found / not found" line, PlayServicesProbe and the
manifest's <queries> entry (ADR-063). HoldReclaimMode, ChannelTaken,
PlayServicesPresence and HoldReclaimSettingsStore are deleted; the stored key
hold_reclaim_mode is no longer read — ignored, never migrated, never removed
(DarkModeSettingsStore documents it and gains a DataStore constructor for the
JVM test). The ANC (i) ends with the settings-stream sentence (the maintainer's
text, hedged: the wear-driven switch stays a hypothesis) and carries the two
rewritten cause lines; the Message Stream claim is ADR-032's again: a tap, the
tile, Refresh, a pull and the Connect's snapshot, released after the linger.

Why: with the settings stream (previous commit) the mode reaches the app on its
own channel, so nothing needs the shared channel beyond a tap; the Play-user
tug of war of 1.2.1/1.2.2 and its setting lose their reason (DECISIONS.md
ADR-065, maintainer-approved in chat 2026-10-14, ai-sessions/0091).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…re-claim tests removed

Repository (FakeBudsTransport, runCurrent/settle): the Connect sequence's bytes
and order (fifteen requests); the ADR-044 re-open subscribing again, end to end
through connect() with a stub android.bluetooth.BluetoothDevice (the SDK stub's
package-private constructor); a session lost at the subscription not retried; a
push shown as changed by the Buds with its time, nothing sent, no claim; value 0
never a mode; a differing read of 13 ("changed since"), the CAP-051 2247 case;
the own Set in CAP-051's order, before the ACK and after a cut-off answer; the
CAP-056 wear sequence; the other fields from the stream with the own-write
mirror keeping its mark; an unread field logged and ignored; nothing held or
re-claimed. The 0089 cause tests rewritten for one rule across a Connect; the
ADR-061/062 blocks deleted. UI: the (i) text, the two cause lines, no card, no
hint, no button, no Play line. A 1.2.2 settings file with hold_reclaim_mode is
read and written without touching that key.

Mutation checks, one at a time: M1 the subscription once per process, M2 0 as
Off, M3 the own Set's stream packet as the Buds' change, M4 the stream to
UnidentifiedFrame, M5 an EQ stream packet ignored — each caught (2/4/1/2/6
failing tests).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…essions/0091)

DECISIONS.md: ADR-065 (maintainer-approved in chat 2026-10-14) supersedes
ADR-061/062/063 and amends ADR-064 items 2 and 6; their Status lines and pointer
Updates on ADR-032 and ADR-064; id_registry.csv accordingly. AGENTS.md §1: the
approved pointer (the package query is gone). ARCHITECTURE.md §2, §2.4, §3.1,
§3.2 (ANC_SET_WINDOW_MS; HOLD_RECLAIM_* gone), §5a, §6.0b, §7, §9. The CAP-076
skeleton, Group BL, its index and registry rows and APP_TESTPLAN.md §Y rewritten
as the 1.3.0 run in both users (holds with the tab closed and open, the own tap,
the wear-change re-home, Bluetooth off/on, taps while Play holds the channel, the
counts for the verdict). CHANGELOG [1.3.0], README, PROJECT, the release-notes
template, versionName 1.3.0 / versionCode 10300, TODO.md, INDEX, the session's
RESULT.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…(the 1.3.0 run in both users)

The maintainer ran scripts/release.sh 1.3.0 (build commit ff27ee1), installed the
APK over 1.2.2 in both GrapheneOS users and recorded CAP-076 in part (only the
changed functionality). This prompt lets a fresh session analyse the two
recordings, four HCI logs, two exports, two app logcats, the system log, the
full logcat and the adb shell log per the project's rules (reading first,
resumable phases, no sampling, evidence traceability, approvals in chat), and
propose the release verdict (RELEASING.md C3/C4). Why a separate session: the
analysis is a CAPTURE task of its own, and this session's context is spent.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ysed (ai-sessions/0092)

Why: RELEASING.md C3 — the hardware run of the release build ff27ee1 must be analysed and
committed on the release branch after the build commit before the verdict (C4). The run shows
the settings stream of ADR-064/065 working in both users: every press-and-hold on the
noise-control tab shown within 0.1 s (29/29 with Google Play services holding the shared
channel, 12/12 without), the two new requests once per MAESTRO session, byte-identical.

The folder renamed to the films' first and last phone times; the films, HCI logs, exports and
app logcats as delivered (Git LFS); the two phone-wide logs kept local and committed as the
redacted extract (serials removed); the read-only adb checks of 07:52 (the installed APK's
SHA-256 = the tested build's) and a sha256sum of the data files. EVENT-NOTES gets the timeline
of both parts (the skeleton kept as the appendix); FINDINGS is new.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Why: PROJECT_RULES.md rule 4a — a script behind a count is committed. pwhdlc_crc_table.py
(every pw_hdlc frame of DLCI 0x02/0x03 CRC-32 checked, as a table — pwrpc_decode.py does not
check the CRC), message_stream_claims.py (the DLCI 0x04 claim inventory of both clients),
stream_events.py (the settings stream's field-13 pushes merged into change events — the Buds
push each change twice with both buds out) and film_mode_buttons.py (the selected mode button
read at 10 fps from a screen recording). Each reproduces the analysis's results byte for byte.
No app file changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, Known issues

Why: the maintainer's decisions on CAP-076 (chat 2026-10-15, ai-sessions/0092): the verdict
"release as built + Known issues" (C4 minor issue) and the approved texts. DECISIONS.md ADR-065
Update (hardware-verified, with the numbers and the three minor defects); PROTOCOL.md §4.1 note
and §8 row (no status change); ARCHITECTURE.md §5a/§6.0b; CHANGELOG.md [1.3.0] Known issues (the
"changed since" line after a reconnect; the tap/collision line reworded); TODO.md (CAP-076 done,
the Buds' state, P1–P6 recorded, no FEATURE prompt now); APP_TESTPLAN.md §Y results;
TESTPLAN Group BL; the CAPTURE index row and Group BL status; id_registry.csv; PROJECT.md
status; the sidebar's new path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Why: AI_SESSION_LOG_PROCEDURE.md — the session's record (the phases, the checkpoints verbatim,
the files read, the deferred items); INDEX row 0092 and the 0091 back-fill "hardware run
analysed by 0092".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tedsluis
tedsluis merged commit aec17a5 into main Oct 15, 2026
2 checks passed
@tedsluis
tedsluis deleted the feature/settings-stream branch October 15, 2026 18:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant