docs(sync): document Android conversation ownership

This commit is contained in:
fy59 2026-09-20 17:54:47 +02:00
parent 38d304d014
commit eb6d70fb62
4 changed files with 67 additions and 21 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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`,