From eb6d70fb62a0c726bc5fd7c0f7e022391f8dd6bb Mon Sep 17 00:00:00 2001 From: fy59 Date: Sun, 20 Sep 2026 17:54:47 +0200 Subject: [PATCH] docs(sync): document Android conversation ownership --- CHANGELOG.md | 11 +++++++++++ docs/android.md | 23 ++++++++++++++++++----- docs/current_state.md | 28 ++++++++++++++++++++-------- docs/tests.md | 26 ++++++++++++++++++-------- 4 files changed, 67 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f21ede..cf7d168 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,17 @@ Development opened after the stable v0.1.2 release. +- Made explicit Android synchronization intent authoritative over automatic + background resumption. A process-wide conversation arbiter now yields an + older background wait before the foreground publishes its fresh request + UUID, and `NEW_EXPLICIT` ignores the request/run that existed before the + click. Background USB and Drive workers classify new requests, new + correlated remote evidence, legitimate handoff, and stale resumable state; + terminal runs are no longer retried every five seconds without new proof. + Durable generations are retained across yield and handoff, and desktop + request-ID deduplication remains strict. No schema, protocol, causal rule or + exchange format changed. + - Hardened direct-MTP full-generation conversations after a private Android run was frozen before generation capture. Android now emits bounded run/phase metadata, refreshes the user-enabled background listener before a diff --git a/docs/android.md b/docs/android.md index 374da15..4c310f6 100644 --- a/docs/android.md +++ b/docs/android.md @@ -1077,11 +1077,24 @@ Manifest/reference remain the commit markers; scanning does not change their bytes or protocol meaning. Production logging records only `run_id`, phase, artifact name, generation ID and result. -When background synchronization is enabled, an explicit UI request refreshes -the foreground listener before starting the UI-owned conversation. The shared -process lock prevents a second run while the UI coordinator is alive; if the -activity is frozen or destroyed, the listener can validate and resume the -durable request without inventing another generation. +When background synchronization is enabled, an explicit UI request announces +foreground ownership before refreshing the foreground listener. Service +liveness and conversation ownership are separate: the service may keep the +process alive, but it cannot acquire automatic ownership while explicit intent +is pending. The process-wide arbiter gives `NEW_EXPLICIT` priority over +`RESUME_BACKGROUND`; a background waiter observes cooperative yield, returns a +typed superseded result, and releases ownership before the foreground emits a +fresh request UUID. The explicit coordinator baselines the existing desktop +run and accepts only a different run after that trigger, so it cannot attach +the click to a stale `request-v1.json`. + +An interrupted explicit conversation leaves captured/published generations +durable. The listener may resume that same run without recapture or a second +generation. Automatic actionability separately classifies a new request, new +correlated desktop ACK/reference evidence, a legitimate active handoff, and a +stale resumable generation. A locally terminal run is suppressed until its +run ID changes or its correlated remote evidence advances; mere presence of a +resumable generation does not schedule the same run every five seconds. The Mensurations grid uses two 140 dp compact fields plus a 10 dp gutter at normal phone widths. The deployed 1080 px/480 dpi phone supplies approximately diff --git a/docs/current_state.md b/docs/current_state.md index f143027..7b0a916 100644 --- a/docs/current_state.md +++ b/docs/current_state.md @@ -7,7 +7,14 @@ basis without rolling back USB. Both transports move the same generation/ACK bytes; neither transports SQLite. Android has an explicitly enabled foreground USB listener with mandatory notification and a private-folder Drive grant with bounded WorkManager checks. Both use the same concurrency-guarded generation -coordinator as SyncScreen. +coordinator as SyncScreen. That coordinator has one process-wide ownership +arbiter: explicit foreground intent has priority over automatic background +resumption, while the foreground service may remain alive without owning the +conversation. An automatic run yields cooperatively at safe boundaries; its +durable generation is retained for a correlated handoff rather than deleted. +Background actionability distinguishes a new request or new correlated remote +evidence from a merely stale resumable generation, preventing five-second +terminal retry loops. Direct-MTP publication now has an explicit Android visibility boundary. Durable immutable generation files are submitted to MediaProvider and all scan @@ -20,13 +27,18 @@ individual adapter budget is distinct from the overall conversation deadline, so an absent correlated generation reports a protocol wait timeout while an actually blocked adapter reports `transport_timeout`. -The failed private run `sy_9639f06e-58ba-4bc0-ab1b-1488aefe011c` received its -desktop request and archive-ACK files on the Android filesystem, but created no -Android generation row/reference/object. System evidence shows the activity -was moved to Android's file-access settings and frozen before capture. The -retained Android generation reference and MTP bytes both belonged to an older -run; no hidden new generation was found. No causal or business mutation -occurred. +The resumed private run `sy_9639f06e-58ba-4bc0-ab1b-1488aefe011c` received its +desktop request and archive-ACK files, captured generation +`gen_532da95e-713f-4163-acc1-d838673c33bf`, and published its immutable objects +and reference with manifest SHA-256 +`170b55ad5bf3e44501fb506107cd26ab807d0179c95a5f1bf73f8251ea1a14e8`. +Filesystem and MTP exposed the same bytes. It then waited for a desktop ACK +that could not arrive because the daemon correctly refused to create another +conversation for the already-consumed request UUID. No causal or business +mutation occurred. The remaining defect was Android ownership: an automatic +resume could hold the old run while the explicit foreground attempt failed +before publishing a fresh request UUID. The arbiter and explicit run baseline +now prevent that state. Existing preparation rows expose separate editing and Android-delivery states and an accessible send action. Draft/local advances the same preparation to a diff --git a/docs/tests.md b/docs/tests.md index fbbcfb3..f507b0a 100644 --- a/docs/tests.md +++ b/docs/tests.md @@ -1555,14 +1555,24 @@ the measured 312 dp phone content width as two columns, a true 280 dp narrow layout as one column, 1.5 font-scale fallback, long French labels and `kg`/`cm` suffixes. -Read-only forensics for failed run -`sy_9639f06e-58ba-4bc0-ab1b-1488aefe011c` found the correlated desktop request -and archive acknowledgements on Android, but no correlated generation -reference, immutable directory or error record. The direct filesystem and MTP -views contained identical bytes for the retained older Android reference. -Logcat showed Android moving Trainlog behind the file-access settings surface -and freezing it before capture. The post-v19 backup predates this run and is -not presented as SQLite evidence for it. +Android conversation-ownership regressions reproduce the production race: an +automatic owner waiting on an old resumable run yields to explicit foreground +intent, the click publishes a fresh request UUID, the explicit coordinator +ignores the pre-click desktop run, and exactly one generation is captured for +the new run. Additional fixtures cover typed cancellation and durable handoff, +terminal-run suppression across five scheduling cycles, reactivation by new +correlated desktop evidence, and exclusive ownership. Desktop routing tests +retain the complementary invariant that an already-consumed Android request +UUID cannot create a second daemon run. + +The resumed run `sy_9639f06e-58ba-4bc0-ab1b-1488aefe011c` subsequently proved +the MTP publication path: Android captured +`gen_532da95e-713f-4163-acc1-d838673c33bf`, published the immutable objects and +reference, and exposed bytes identical to the filesystem through MTP. It +stopped after `generation_reference_published` because no ACK could be created +for the daemon-deduplicated old request UUID. This evidence supersedes the +earlier pre-resume forensic snapshot without rewriting or deleting the +incomplete generation. The authorized 2026-09-17 rollout added regression coverage for production failures found only after installation: feedback import under `sqlite3.Row`,