| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
Restrict calendar gap checks to locally known uncaptured meetings | 24 天前 | |
C1 step 6: cut the production capture path over to the session owner (#14583) * harden(dev-harness): define V1 live-session and V8 spine contracts Add strict Python/Dart pending contracts and introducing-commit protection. Reserve the live-session CLI, wire, ownership/attachment seams and failing builder acceptance tests. Add live attribution before freezing evidence v1. Verification: - bash scripts/dev-harness/run-tests.sh: 287 passed, 7 existing skips, 40 strict xfailed (24 pending V1 markers), Python 3.11.15. - make preflight: 16 checks passed, using supported PYTHON selection. - make mobile-verify fast --paths harness/session contract inputs with an absolute evidence directory: 20 executed, 20 passed, no failures/skips. - bash app/test.sh: 2014 passed, 5 skipped, one unchanged UTC+7 search_rank_grouping_test failure; affected baseline inputs match main. - Real module CLI refusals exit 2 without traceback; regression covered. - Dart SDK probe proves ext. prefix required; marker-removal formatter probe proves no incidental Dart diff. Runtime is intentionally unimplemented. App UI must repair current invalid VM extension names before live acceptance; unsupported journeys block. Simulator/emulator latency and detached-process acceptance remain V1 work. make setup installed hooks but full backend wheel sync stalled; isolated Python 3.11 harness dependencies ran the owner checks without any skip flag. Failure-Class: none * harden(dev-harness): scope the spine-contracts check to spine paths The check protects files under the spine roots and their registry, so it only needs to run when those change. A repo-wide trigger would run it, and its full-history requirement, on every unrelated contributor PR. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(dev-harness): clarify the live journey adapter boundary V1 must refuse live journey execution until a shared executable adapter exists; B1 addressability alone cannot attach Dart integration tests. Pure admission contracts remain valid with injected supported IDs. Verification: harness 287 passed, 7 skipped, 40 strict xfailed; make preflight passed 20 checks. * harden(app): define capture ownership and seam contracts C1 pending contracts specify recovery, foreground service, socket and session ownership; no existing runtime path changed. Verified UTC app suite (2041 passed), harness (287 passed, 7 skipped, 40 strict xfailed), 20 hermetic journeys, and 21 preflight checks. * harden(app): close capture adoption and late-completion contract gaps Specify inventory revision coalescing without losing explicit retries, late location-upload/finalization fences, and zero legacy actor adoption checks. Existing protected tests are unchanged. UTC app: 2048 passed; harness: 287 passed, 7 skipped, 40 strict xfailed; preflight: 21 passed. Correction to earlier verification record: C1 selected 3 journeys, all passed, not 20. One intermediate unchanged baseline replay test returned zero restored WALs; the full final run passed and its exact failure is retained in .local/c1-app-final.log. * harden(dev-harness): pin reviewed spine revisions and align B0 capabilities Coordinator turn-4 review authorizes the V1 fixture correction. Exact before/after digests pin the revised oracle; immutable revision records do not permit builder assertion edits. Active tests reject missing/tampered records and prove the fake machine response. Verification: full harness 289 passed, 7 skipped, 40 strict xfailed; UTC app 2020 passed; spine 24 pending/7 protected; make preflight 16 passed. No live device acceptance claimed. * harden(dev-harness): preserve marker retirement across spine revisions Track retained marker occurrences rather than counts so retiring a second test cannot restore the first test's retired marker. The active regression covers this swap and legitimate continued retirement. Verification: full harness 290 passed, 7 skipped, 40 strict xfailed; spine 24 pending/7 protected; make preflight 16 passed. Dart sources unchanged after the earlier UTC app 2020-pass run. * harden(dev-harness): retain corrected spine oracles through squash merges A squash can introduce a corrected contract and its review records in the same commit. Absorb only the revision prefix ending at that introducing file's exact digest, preserving immutable records and later marker-only edits. Active temp-repo regression covers multiple corrections in one squash and rejects restoring old or altered assertions. Verification: harness 291 passed, 7 skipped, 40 strict xfailed; B1 make preflight 20 passed; final B1 journeys 20/20. Dart unchanged after UTC app 41 opt-in and 2051 ordinary passed. * test(dev-harness): revise live session acceptance after builder review * test(app): scope capture ownership adoption and exercise real effects * test(dev-harness): bind machine fixture to actual child identity * test(app): keep capture adoption scoped to ownership extractions * docs: align capture contributor guidance with staged adoption * harden(app): implement CaptureLifetime resource bag Timers, streams, and owned cancels drain on close even when one cancel fails. Close is idempotent and invalidates first, so a late acquisition after close dies immediately. Co-authored-by: Cursor <cursoragent@cursor.com> * harden(app): untrack CaptureLifetime resources on fire and cancel A day-long capture session would pin one dead cancel closure per once()/listen() that completed on its own. Release on fire, caller cancel, and stream done so the bag only holds live work. Co-authored-by: Cursor <cursoragent@cursor.com> * harden(app): forward capture composition seams into CaptureProvider composeCaptureProvider now passes WAL, mic, auth, clock, scheduling, connectivity, preferences, BLE, and socket through CaptureProvider into CaptureController. Production no-argument defaults stay; composeProductionCaptureProvider refuses FLUTTER_TEST before any singleton. Co-authored-by: Cursor <cursoragent@cursor.com> * test(app): prove capture composition routes seams and refuses FLUTTER_TEST Builder tests lock the cut-1 lazy passes: openSocket/WAL/mic must actually be forwarded, and the production composer cannot construct ConnectivityService with the health URL under test. Retires only the two composition spine markers this cut satisfies. Co-authored-by: Cursor <cursoragent@cursor.com> * style(app): apply dart format to capture_controller.dart Formatting only; required by the pre-push dart format gate. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * harden(app): join in-flight CaptureLifetime close and keep terminal bookkeeping A second close() while the first is still draining must wait for the same completion, asFuture must untrack even after it replaces inner callbacks, and replacement data/error/done handlers must not run after close begins. Co-authored-by: Cursor <cursoragent@cursor.com> * harden(app): keep injected capture preferences after construction Store the supplied SharedPreferencesUtil and route later reads and writes through it so batchMuted and the rest of the seam are not the global singleton, while ordinary construction still defaults. Co-authored-by: Cursor <cursoragent@cursor.com> * harden(app): own capture connect/reconnect and session generation Keep-alive during an in-flight connect now joins one socket attempt, and work started under generation N cannot publish after N+1. The explicit conversation-open path forwards session geolocation the way production already does, without changing default CaptureProvider() wiring. Co-authored-by: Cursor <cursoragent@cursor.com> * harden(app): join explicit CaptureLifetime cancellation until it finishes Untracking a callback is not the same as teardown completing; close now waits for in-flight own/subscription cancels without running them a second time. Co-authored-by: Cursor <cursoragent@cursor.com> * docs(app): record that CaptureLifetime close joins in-flight cancellation Co-authored-by: Cursor <cursoragent@cursor.com> * harden(app): fence capture publication to the admitted generation Admission already captured generation; publication after every await now keeps that original operation generation. Stop invalidates in-flight work before WAL teardown, and a skipped persist cannot occupy the fingerprint. Co-authored-by: Cursor <cursoragent@cursor.com> * test(app): register the CaptureLifetime drain spine file Co-authored-by: Cursor <cursoragent@cursor.com> * test(app): register capture publication-fence spine files Co-authored-by: Cursor <cursoragent@cursor.com> * harden(app): fence FGS intent and recovery wakes at the session owner A stop racing an in-flight start settles once; concurrent recovery requests join one drain. Stale finalize cannot wake a later session. Co-authored-by: Cursor <cursoragent@cursor.com> * chore(app): restore memories/page.dart to origin/main after merge format The merge-forward pre-commit hook reindented main's file and tripped INV-MEM-1 on a formatter-only diff. Lifetime does not own that page. Co-authored-by: Cursor <cursoragent@cursor.com> * chore(app): restore dart format on merge-forward trailing-comma splits Conflict resolution dropped trailing commas while keeping a split layout the formatter will not emit. Let dart format rewrite those 14 files; no semantic change. Co-authored-by: Cursor <cursoragent@cursor.com> * harden(app): cut production capture over to the session owner Both main.dart construction arms now go through composeProductionCaptureProvider, so C1 fences sit on the path the shipped app actually takes. Co-authored-by: Cursor <cursoragent@cursor.com> * docs(app): drop the inflated CaptureProvider() site count from OWNERSHIP.md The remaining test/fixture constructor is the obstacle, not a 122-site count from substring matches against composeCaptureProvider. Co-authored-by: Cursor <cursoragent@cursor.com> * harden(app): retire the five satisfied C1 lifetime pending markers #14327 landed c1_lifetime_review_test.dart and c1_lifetime_terminal_test.dart on main with pendingContract('C1') still set. The behaviour they pin was already implemented on this branch by f04ec71709 and acdc197180, so after the merge-forward all five executed green and the contract harness failed them as XPASS ("remove the pending marker") rather than as unmet contracts. No implementation change is required, and none is made here; only the five stale markers are removed, which is the one edit the spine protection allows. What each contract pins, and the capture_lifetime.dart behaviour that satisfies it: - concurrent close joins outstanding cancellation: a second close() used to hand back a fresh completed future and report teardown finished while the first drain was still awaiting an own() cancel. close() now short-circuits only on _closeFinished and otherwise returns the memoised _closing future, so every caller joins the same drain. - asFuture preserves completion untracking: _inner.asFuture replaces the subscription's done handler, so the listen-time drop() never ran and the registration outlived its future. _LifetimeSubscription.asFuture now chains whenComplete(_drop). - C1 asFuture error completion releases its registration: the same path on the error completion - whenComplete(_drop) untracks on throw as well as on done. - replaced callbacks and errors stay closed while cancellation drains: a handler installed through onData/onError after listen bypassed the closed guard and could still fire while close() was blocked on an earlier release. Those wrappers now consult _isClosed() before forwarding. - C1 original and replacement done callbacks respect closing: both the listen-time onDone wrapper and an onDone replacement now drop() the registration but return without calling the owner once _closed is set. Verified in this worktree at app/: flutter test 2161 passed, 0 failed. scripts/check_spine_contracts.py exit 0, 69 pending markers (was 74), 27 protected files. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(app): restore spine original wrapping after merge format The merge commit's dart format rewrapped c1_lifetime_review_test.dart. Spine originals may only drop pending markers, so restore the lifetime bytes. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(app): keep c1_lifetime_review_test.dart at the spine original pre-commit dart format rewrapped this protected original. Restore the lifetime bytes; spine allows pending-marker removal only. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(app): drop unused CaptureProvider import after composition merge composeCaptureProvider is the seam; the provider import is leftover. Co-authored-by: Cursor <cursoragent@cursor.com> * style(app): format the two analytics files the socket merge left non-canonical These entered at this commit's parent merge. The merge resolution needed OMI_SKIP_DART_FORMAT=1 so the pre-commit hook could not rewrite a digest-pinned spine oracle (c1_lifetime_review_test.dart) — but that skip is all-or-nothing, so it also left analytics_manager.dart and omi_double_tap_goldens_test.dart unformatted, and they rode up the rest of the C1 chain. Formatter only, no semantic change. origin/main formats both paths with 0 changed, so this is the merge-collateral class, not new content. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com> | 1 个月前 | |
feat(app): capture regression gates — continuity suite, invariant monitor, rollout-hop capture health Builds 1.0.560-563 (#20837) skipped pendant capture after a reconnect during an upload pass, and ~640 users lost audio for a week before anyone noticed. Three layers so the next one is caught at dev time, on TestFlight, or before a store rollout widens: 1. Dev time: capture_continuity_test.dart streams a simulated pendant (unique frame ids) through the real controller, socket service, WAL and transfer coordinator: the exact 10-20 reconnect during a stuck upload pass, background/foreground, and a ten-minute soak with every fault (network change, server close, reconnects mid-pass, background). It complements #22873's count-based single-fault guards. PendantFrameLedger asserts every notified frame is streamed or persisted (incl. uploaded), and live capture resumes within 5 s. On the pre-#22873 tree it loses 125 s (RCA path) and 320 s (soak). 2. Debug/TestFlight: CaptureContinuityMonitor declares Capture Wedge Detected trigger=connected_not_capturing when a connected pendant that should be captured has no capture subscription and no frame reaches custody for 60 s (the state connected_no_bytes cannot see: no capture session). Event in all builds; Home banner only in debug/TestFlight. Telemetry only. 3. Store hops: mobile_capture_health.py mirrors the operator gate's judge (wedge users reweighted by omi_sample_pct / pendant users per os+build; BAD at >2x the previous build or >50 wedge users/day) and holds the iOS phased jump and the Android ladder hop. Fails closed; one-shot capture_health_override input on the train and rollout-watch workflows. Stacked on #22873 (fix/app-capture-recovery). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> | 11 天前 | |
feat(app): capture regression gates — continuity suite, invariant monitor, rollout-hop capture health Builds 1.0.560-563 (#20837) skipped pendant capture after a reconnect during an upload pass, and ~640 users lost audio for a week before anyone noticed. Three layers so the next one is caught at dev time, on TestFlight, or before a store rollout widens: 1. Dev time: capture_continuity_test.dart streams a simulated pendant (unique frame ids) through the real controller, socket service, WAL and transfer coordinator: the exact 10-20 reconnect during a stuck upload pass, background/foreground, and a ten-minute soak with every fault (network change, server close, reconnects mid-pass, background). It complements #22873's count-based single-fault guards. PendantFrameLedger asserts every notified frame is streamed or persisted (incl. uploaded), and live capture resumes within 5 s. On the pre-#22873 tree it loses 125 s (RCA path) and 320 s (soak). 2. Debug/TestFlight: CaptureContinuityMonitor declares Capture Wedge Detected trigger=connected_not_capturing when a connected pendant that should be captured has no capture subscription and no frame reaches custody for 60 s (the state connected_no_bytes cannot see: no capture session). Event in all builds; Home banner only in debug/TestFlight. Telemetry only. 3. Store hops: mobile_capture_health.py mirrors the operator gate's judge (wedge users reweighted by omi_sample_pct / pendant users per os+build; BAD at >2x the previous build or >50 wedge users/day) and holds the iOS phased jump and the Android ladder hop. Fails closed; one-shot capture_health_override input on the train and rollout-watch workflows. Stacked on #22873 (fix/app-capture-recovery). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> | 11 天前 | |
feat(finops): PostHog cost reduction — client telemetry transport + collector + admin reader (#21449) * feat(finops): PostHog cost reduction — client telemetry transport + collector + admin reader Cuts the projected $7.5k/period PostHog bill at the source while preserving audio custody, capture recovery, product signals and reliability denominators (sol-reviewed plan; every disposition frozen in docs/finops/telemetry-transport-contract.md). Backend (lane 1): POST /v1/telemetry/client/batches + admin GET aggregates with durable owner+event dedup, exact counters/histograms, physical-vs-floating voice populations; day aggregates hash-sharded (16/day) with additive merge (2,008-actor capacity regression test); registered allowlists bound every fallback dimension; explicit latency sample counts; ops endpoints (seal-day / purge-expired / enroll-channel); finops gross+credits+net reconciliation fixtures. Flutter (lane 2): rearming outbox drain, attempt-claim persisted before send, monotonic latency anchors, 401-only token refresh, ack schema+count validation, owner-fenced processing anchors, +Inf histogram wire format. Custody/recovery/ 10s pendant drain untouched. Swift (lane 3): NSNumber/CFBoolean reload identity, durable Retry-After parking across restart, suggestion dwell/cooldown suppression without pacing changes, screen-task terminal aggregation with exact denominators, health events retained. Admin (lane 4): replacement reliability reader (default OFF) — unavailable stays unavailable (never zero), true legacy-vs-replacement rendered parity, 50,000-row boundary matched to contract. Flags default OFF; live canary/consumer-switch/PostHog console changes are explicit parent gates (see runbook). * test(finops): register telemetry ops helpers in the firestore query-driver registry recent_owner_ids and purge_expired_observations stream collections by id with no query filters — reasoned SkipEntry per the guard's contract. * test(finops): digest-pin the telemetry ops skip entries The guard requires body-digest literals for every skip; both ops helpers are unchanged since review. * feat(finops): register client telemetry flags in the feature-flag registry client_telemetry_transport_shadow (rollout, default off) and client_telemetry_posthog_retirement (ops kill switch, structurally exempt health/connection events); rendered doc regenerated as-of 2026-10-13. * docs(finops): add FC-client-telemetry-cost-blindness failure class Captures the October incident class: unmonitored telemetry volume doubling from routine PRs, a warehouse backfill billing across a period boundary, and no scheduled reader on PostHog's billing meter. The prevention artifacts are this lane's transport contract, default-off flags, source-side rate shaping, and the capacity regression test. * docs(finops): defer failure-class registration to a registry-only lifecycle PR The failure-class protocol requires instance-fix branches to leave registry edits to a dedicated lifecycle PR; the definition returns there. * chore(finops): internal-only mobile changelog fragment for the telemetry lane * test(finops): client-telemetry-shadow e2e flow + automation bridge probes Flow covers the seven new Telemetry/* sources: flag snapshot proves migration and retirement stay off; the shadow probe enqueues an observation, reloads the store from disk (restart semantics), and asserts the wire body never carries the owner id. * feat(finops): register telemetry ops routes in the route policy manifest Admin-key-gated seal-day / purge-expired / enroll-channel — same policy shape as the aggregates route (admin surface, metrics domain, no rate limit). * fix(finops): swiftlint force-unwrap cleanup + e2e probe compile fix UUID literals use nil-coalescing fallbacks, the fixture helper avoids XCTUnwrap in a non-throwing closure, and the e2e probe drops a stale observationID reference. * fix(finops): clear pyright strict errors on the telemetry modules - _fail annotated NoReturn so pyright narrows after guard calls - UUID/timestamp parses use explicit Optional narrowing - typed dict locals for stored/row/shard kill index-type errors - dead _optional/_label_or_unknown helpers removed - owner strip check drops redundant isinstance (typed str param) - store ref helper made public for cross-module ops use - HTTP-path test stamps its fixture occurred_at fresh (24h age guard is real-clock) * test(finops): allowlist the sharding capacity regression's CPU time 2,200-actor volume run costs ~0.86s CPU — the property under test is volume, so it is allowlisted like the other hermetic-but-heavy baselines. * fix(finops): clear dart analyzer ratchet regressions in telemetry files - dead_null_aware / prefer_if_null / prefer_initializing_formals fixed in client_telemetry.dart, sync_upload_gate.dart, client_telemetry_outbox.dart - unnecessary_import count returned to baseline (12 == 11+1 moved file); baseline updated via --update-baseline * chore(finops): drop leftover conflict marker from allowlist rebase * docs(finops): regenerate feature-flag registry doc after main merge * fix(finops): correct rate_limit indentation in telemetry route policies * fix(finops): drop duplicated telemetry ops route blocks from rebase * style(finops): dart format captureGap signature (rebase merge artifact) * fix(finops): repair rebase-merged subscriptionFailed and wal telemetry imports - subscriptionFailed keeps its parameterless signature (the audio-subscription error listener path) and drops the undefined reason/phase refs - local_wal_sync gains the AnalyticsManager import its Stuck-telemetry fallback references - captureGap formatting normalized * chore(finops): update dart analyzer baseline to CI-stable counts The CI analyzer (flutter stable on linux) reports 6 curly_braces and 4 depend_on_referenced_packages where the local mise dart run reports 3/3; neither set is in files this PR touches — this locks the baseline to the CI toolchain's counts so the ratchet stops flagging pre-existing debt. | 18 天前 | |
test(app): pin the capture pause timer semantics (#22867) * test(app): pin the capture pause timer semantics The deferred ask on #22732: a batch-mode pause must freeze the card timer, and resume must not fold the paused interval into the elapsed time. Boots the composition harness with batch mode on so the pause runs the pendant batch path rather than the live one. Amp-Thread-ID: https://ampcode.com/threads/T-01a154a6-835d-760f-b1e8-7c1f11b08015 Co-authored-by: Amp <amp@ampcode.com> * test(app): add the test-only changelog fragment and isolate the replay world Hygiene requires a fragment for any app change outside the exempt list. Codex flagged that booting a second CaptureReplayWorld while the setUp world is live retargets process-global prefs/path-provider/WAL state; dispose it first. Amp-Thread-ID: https://ampcode.com/threads/T-01a154a6-835d-760f-b1e8-7c1f11b08015 Co-authored-by: Amp <amp@ampcode.com> --------- Co-authored-by: Amp <amp@ampcode.com> | 10 天前 | |
harden(app): route capture timers, streams and listeners through CaptureLifetime (#14549) * harden(dev-harness): define V1 live-session and V8 spine contracts Add strict Python/Dart pending contracts and introducing-commit protection. Reserve the live-session CLI, wire, ownership/attachment seams and failing builder acceptance tests. Add live attribution before freezing evidence v1. Verification: - bash scripts/dev-harness/run-tests.sh: 287 passed, 7 existing skips, 40 strict xfailed (24 pending V1 markers), Python 3.11.15. - make preflight: 16 checks passed, using supported PYTHON selection. - make mobile-verify fast --paths harness/session contract inputs with an absolute evidence directory: 20 executed, 20 passed, no failures/skips. - bash app/test.sh: 2014 passed, 5 skipped, one unchanged UTC+7 search_rank_grouping_test failure; affected baseline inputs match main. - Real module CLI refusals exit 2 without traceback; regression covered. - Dart SDK probe proves ext. prefix required; marker-removal formatter probe proves no incidental Dart diff. Runtime is intentionally unimplemented. App UI must repair current invalid VM extension names before live acceptance; unsupported journeys block. Simulator/emulator latency and detached-process acceptance remain V1 work. make setup installed hooks but full backend wheel sync stalled; isolated Python 3.11 harness dependencies ran the owner checks without any skip flag. Failure-Class: none * harden(dev-harness): scope the spine-contracts check to spine paths The check protects files under the spine roots and their registry, so it only needs to run when those change. A repo-wide trigger would run it, and its full-history requirement, on every unrelated contributor PR. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(dev-harness): clarify the live journey adapter boundary V1 must refuse live journey execution until a shared executable adapter exists; B1 addressability alone cannot attach Dart integration tests. Pure admission contracts remain valid with injected supported IDs. Verification: harness 287 passed, 7 skipped, 40 strict xfailed; make preflight passed 20 checks. * harden(app): define capture ownership and seam contracts C1 pending contracts specify recovery, foreground service, socket and session ownership; no existing runtime path changed. Verified UTC app suite (2041 passed), harness (287 passed, 7 skipped, 40 strict xfailed), 20 hermetic journeys, and 21 preflight checks. * harden(app): close capture adoption and late-completion contract gaps Specify inventory revision coalescing without losing explicit retries, late location-upload/finalization fences, and zero legacy actor adoption checks. Existing protected tests are unchanged. UTC app: 2048 passed; harness: 287 passed, 7 skipped, 40 strict xfailed; preflight: 21 passed. Correction to earlier verification record: C1 selected 3 journeys, all passed, not 20. One intermediate unchanged baseline replay test returned zero restored WALs; the full final run passed and its exact failure is retained in .local/c1-app-final.log. * harden(dev-harness): pin reviewed spine revisions and align B0 capabilities Coordinator turn-4 review authorizes the V1 fixture correction. Exact before/after digests pin the revised oracle; immutable revision records do not permit builder assertion edits. Active tests reject missing/tampered records and prove the fake machine response. Verification: full harness 289 passed, 7 skipped, 40 strict xfailed; UTC app 2020 passed; spine 24 pending/7 protected; make preflight 16 passed. No live device acceptance claimed. * harden(dev-harness): preserve marker retirement across spine revisions Track retained marker occurrences rather than counts so retiring a second test cannot restore the first test's retired marker. The active regression covers this swap and legitimate continued retirement. Verification: full harness 290 passed, 7 skipped, 40 strict xfailed; spine 24 pending/7 protected; make preflight 16 passed. Dart sources unchanged after the earlier UTC app 2020-pass run. * harden(dev-harness): retain corrected spine oracles through squash merges A squash can introduce a corrected contract and its review records in the same commit. Absorb only the revision prefix ending at that introducing file's exact digest, preserving immutable records and later marker-only edits. Active temp-repo regression covers multiple corrections in one squash and rejects restoring old or altered assertions. Verification: harness 291 passed, 7 skipped, 40 strict xfailed; B1 make preflight 20 passed; final B1 journeys 20/20. Dart unchanged after UTC app 41 opt-in and 2051 ordinary passed. * test(dev-harness): revise live session acceptance after builder review * test(app): scope capture ownership adoption and exercise real effects * test(dev-harness): bind machine fixture to actual child identity * test(app): keep capture adoption scoped to ownership extractions * docs: align capture contributor guidance with staged adoption * harden(app): implement CaptureLifetime resource bag Timers, streams, and owned cancels drain on close even when one cancel fails. Close is idempotent and invalidates first, so a late acquisition after close dies immediately. Co-authored-by: Cursor <cursoragent@cursor.com> * harden(app): untrack CaptureLifetime resources on fire and cancel A day-long capture session would pin one dead cancel closure per once()/listen() that completed on its own. Release on fire, caller cancel, and stream done so the bag only holds live work. Co-authored-by: Cursor <cursoragent@cursor.com> * harden(app): forward capture composition seams into CaptureProvider composeCaptureProvider now passes WAL, mic, auth, clock, scheduling, connectivity, preferences, BLE, and socket through CaptureProvider into CaptureController. Production no-argument defaults stay; composeProductionCaptureProvider refuses FLUTTER_TEST before any singleton. Co-authored-by: Cursor <cursoragent@cursor.com> * test(app): prove capture composition routes seams and refuses FLUTTER_TEST Builder tests lock the cut-1 lazy passes: openSocket/WAL/mic must actually be forwarded, and the production composer cannot construct ConnectivityService with the health URL under test. Retires only the two composition spine markers this cut satisfies. Co-authored-by: Cursor <cursoragent@cursor.com> * style(app): apply dart format to capture_controller.dart Formatting only; required by the pre-push dart format gate. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * harden(app): restore capture mute and custom-STT buffering comments Those comments record why-knowledge this file breaks without: mute must survive process death so streamDeviceRecording can re-apply it, and custom STT buffering is surfaced so the UI shows offline instead of Listening. Pay for the lines by extracting BleBridgeCaptureListeners. Co-authored-by: Cursor <cursoragent@cursor.com> * harden(app): route capture timers, streams and listeners through CaptureLifetime Wrap the injected scheduler in CaptureLifetime so once/periodic, connectivity listen, BLE finalized/stream subscriptions, and the live-segment listener cancel on dispose. close() now drains every sync cancel before returning so void dispose() stays synchronous. Extract image-chunk emission so the controller shrinks. Co-authored-by: Cursor <cursoragent@cursor.com> * test(app): prove dispose cancels the leaked voice-command timeout The only CaptureController timer that dispose did not cancel was the 15s voice-command timeout (the class of 05c43c014d). After dispose, advancing fake time leaves no pending timers. Also lock close() to cancel every owned timer before the caller awaits. Co-authored-by: Cursor <cursoragent@cursor.com> * style(app): apply dart format to capture_controller.dart Formatting only; required by the pre-push dart format gate. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * harden(app): join in-flight CaptureLifetime close and keep terminal bookkeeping A second close() while the first is still draining must wait for the same completion, asFuture must untrack even after it replaces inner callbacks, and replacement data/error/done handlers must not run after close begins. Co-authored-by: Cursor <cursoragent@cursor.com> * harden(app): keep injected capture preferences after construction Store the supplied SharedPreferencesUtil and route later reads and writes through it so batchMuted and the rest of the seam are not the global singleton, while ordinary construction still defaults. Co-authored-by: Cursor <cursoragent@cursor.com> * harden(app): join explicit CaptureLifetime cancellation until it finishes Untracking a callback is not the same as teardown completing; close now waits for in-flight own/subscription cancels without running them a second time. Co-authored-by: Cursor <cursoragent@cursor.com> * docs(app): record that CaptureLifetime close joins in-flight cancellation Co-authored-by: Cursor <cursoragent@cursor.com> * test(app): register the CaptureLifetime drain spine file Co-authored-by: Cursor <cursoragent@cursor.com> * chore(app): restore memories/page.dart to origin/main after merge format The merge-forward pre-commit hook reindented main's file and tripped INV-MEM-1 on a formatter-only diff. Lifetime does not own that page. Co-authored-by: Cursor <cursoragent@cursor.com> * chore(app): restore dart format on merge-forward trailing-comma splits Conflict resolution dropped trailing commas while keeping a split layout the formatter will not emit. Let dart format rewrite those 14 files; no semantic change. Co-authored-by: Cursor <cursoragent@cursor.com> * harden(app): retire the five satisfied C1 lifetime pending markers #14327 landed c1_lifetime_review_test.dart and c1_lifetime_terminal_test.dart on main with pendingContract('C1') still set. The behaviour they pin was already implemented on this branch by f04ec71709 and acdc197180, so after the merge-forward all five executed green and the contract harness failed them as XPASS ("remove the pending marker") rather than as unmet contracts. No implementation change is required, and none is made here; only the five stale markers are removed, which is the one edit the spine protection allows. What each contract pins, and the capture_lifetime.dart behaviour that satisfies it: - concurrent close joins outstanding cancellation: a second close() used to hand back a fresh completed future and report teardown finished while the first drain was still awaiting an own() cancel. close() now short-circuits only on _closeFinished and otherwise returns the memoised _closing future, so every caller joins the same drain. - asFuture preserves completion untracking: _inner.asFuture replaces the subscription's done handler, so the listen-time drop() never ran and the registration outlived its future. _LifetimeSubscription.asFuture now chains whenComplete(_drop). - C1 asFuture error completion releases its registration: the same path on the error completion - whenComplete(_drop) untracks on throw as well as on done. - replaced callbacks and errors stay closed while cancellation drains: a handler installed through onData/onError after listen bypassed the closed guard and could still fire while close() was blocked on an earlier release. Those wrappers now consult _isClosed() before forwarding. - C1 original and replacement done callbacks respect closing: both the listen-time onDone wrapper and an onDone replacement now drop() the registration but return without calling the owner once _closed is set. Verified in this worktree at app/: flutter test 2161 passed, 0 failed. scripts/check_spine_contracts.py exit 0, 69 pending markers (was 74), 27 protected files. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(app): cancel the keep-alive periodic synchronously on dispose dispose() previously only cancelled the keep-alive periodic through the lifetime release, which runs behind the async drain. Tests that assert the no-pending-timers invariant immediately after dispose() saw the 15s periodic still pending. Cancel it synchronously in dispose() before unawaited(lifetime.close()); the drain's release stays idempotent (Timer.cancel on an already-cancelled timer is a no-op). Captured in the chain-1 merge-forward: CI on 97a028257c failed capture_provider_test 'shows reconnecting warning when socket closes during phone mic recording' with the pending-timer assertion. Full capture + provider suites green locally (118 passed). --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com> | 1 个月前 | |
Flag rulings b18: sync/capture graduations and with_legacy dates (#22846) * refactor: graduate sync capture flags and date legacy rulings * test: align sync fixtures with graduated capture contracts * chore: refresh Codemagic contract after capture define graduation * chore: record internal mobile capture graduation * refactor: extract graduated capture evidence identity state * test: preserve explicit claimless lineage attempts in shared fixture * test: separate sync fixture setup from measured coordinator calls * test: collect prior coordinator mock graphs during fixture setup * test: complete capture fixtures for graduated span protocol * test: initialize replay and persistence capture fixture fields * test: initialize live transcript merge capture fixture state * chore: record graduated sync flag tombstones * test: exercise named-set boolean validation after span graduation | 11 天前 | |
fix(app): verify quiet only with this generation's audio, name the failure, guard Reconnect Review fixes for #23703 round 1: - Honesty rule (#20378): a confirmed-subscription quiet verifies only when its generation already delivered audio. Native's snapshot has carried last_audio_at_ms all along (reset on every generation change and on a failed subscription), so verifiedAt now requires it for quiet: a fresh_subscription_quiet or budget_spent_quiet with no audio this generation stays unverified - the island grace, then the unheard notice with Reconnect - instead of Listening with a running clock forever. The same predicate gates the native_ingress capture-started telemetry, so a zero-audio link marks no recording started. - The Home live row's failure state never says Listening: the title is "No audio from pendant" with the plain-words line under it. - A reader's Reconnect coalesces concurrent taps into one managed reconnect (pendantReconnectInFlight; Home and Device Settings disable their action while it runs), is a no-op when the reader paused capture or disconnected the pendant themselves (the manual-disconnect choice is never cleared), and a tap with no pendant reports to the surface instead of an uncaught StateError. - The replayed zero-notification incident trace now asserts nothing ever verifies; the regenerated evidence shots carry the corrected copy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> | 5 天前 | |
fix(app): stop the card's clock while pendant audio is unverified, and keep a rejected setting from reaching the card - While pendant audio is unverified the card's clock stops, so waiting for audio is not counted as capture time. - If the Show on Lock Screen write is rejected, the cached preference is restored before the error, so the next card update cannot send the rejected value. - The lifetime listener guard is tested while a close is still in flight. - The changelog note names the iOS versions the buttons and the moving wave need. | 27 天前 | |
Episodes v1: live episodes over conversations (backend + mobile) (#22877) * Episodes v1: live episodes over conversations, backend and mobile Backend: an episode overlay (users/{uid}/episodes) that groups the day's conversations into occasions without rewriting them. Moments are cut from the realtime fan-out (pause, word cap, conversation change); each moment gets one cheap incremental update (episode_incremental, gpt-5-nano) from the previous state plus the new text. Close detection: Jev boundary question, 3h hard cap, idle gap, and POST /v1/episodes/{id}/wrap-up. Provisional tasks never enter Tasks; they are reconciled against saved action items at each conversation's finalization (kept/dropped shadow metric). Kill switch EPISODES_ENABLED (on), EPISODES_PERCENT ramp, capability flag on every read. Metrics for per-moment calls, tokens, cost, close reasons, provisional precision and wrap-up latency. App: EpisodeProvider (capability-gated; 404 keeps today's UI), the live episode as Today's first row, filled rows for offline sync, the quiet sync ring, the device page's stats/right now/recently/controls, LiveEpisodePage replacing ConversationCapturingPage (summary + transcript with moment dividers and captured items with Undo, Wrap Up Now), and a collapsed Moments section on finished conversations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * failure-classes: point the capture recovery hint at the file that exists Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Episodes v1: changelog fragments and synthetic-data UI renders Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * agent-docs: point the telemetry inventory at the live episode page Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * proactivity: cite the mentor follow-up issue Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * episodes: narrow optional reads for pyright Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * episodes: defer the overlay import in the finalizer; baseline the moment background task The finalizer imported utils.episodes.service at module load, which pulled database._client.run_transactional into harnesses that stub that module. The background moment update is a reviewed baseline entry: a moment lost at SIGKILL is re-applied by the conversation's finalization tail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * episodes: expire the finalizer hook budget deterministically in its test Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Episodes v1: render polish (sync ring, speaker names, alignment) - The filled row's sync indicator is the shared activity ring drawn inline: a faint full track with a turning arc, centred on the text line, standing in for the separator. The header's quiet sync ring uses the same painter, so every frame reads as a ring instead of a stray tick. - The live episode header names participants from the transcript's own speaker resolution (owner plus speakers resolved to the user's people), so it never names someone the transcript calls "Speaker N". The model's mention list no longer feeds the header. - Moments: the section title sits flush with the summary text, and every moment row keeps the chevron's slot so lengths line up. - Renders: fixed clock, precached asset images (device glyph and photo), a resolved and an unresolved speaker, and page heights that show whole screens. All ten evidence images re-rendered. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: make flaky Dart, backend, and desktop Swift tests deterministic - Desktop: the brief-card test counted ink on an 8 px grid of 40 px inset. Thin glyph strokes hit the grid by alignment, so the count (19 vs 25) was stable on every run and independent of scale; the render itself was correct. Count dark pixels across the whole inset card region and assert a fraction of it, so the check does not depend on grid alignment or scale. - Dart: capture_live_recovery_test polled a fixed 200 x 5 ms for durable frames, which ended before WAL file I/O finished on a loaded runner. Wait on the condition with a 15 s bound and still assert the exact count. The hung-pass test raced a real 50 ms pass timeout against its own connect and packet burst; RecordingTransferCoordinator gains an optional scheduleTimeout seam (default behaviour unchanged) so the test fires the limit explicitly. - Backend: gate_values() in the Soniox ack test walked the whole Prometheus registry per call (~1 s CPU on loaded runners); read the labeled counter child instead. The batch policy test parsed a 240 KB YAML with the pure Python loader; use the C loader. The batch live-preview kill-switch sweeps use a 100-owner prefix; the 1000-owner bucket sweep is kept. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> | 10 天前 | |
Choose liveness recovery from current socket readiness | 24 天前 | |
fix(app): reduce and diagnose iOS background energy (#11892) * fix(app): reduce and diagnose iOS background energy Stop the iOS-only foreground-task engine and unused app-refresh request, gate reconnects and UI samplers on active work, limit diagnostic RSSI polling, and throttle native battery-history writes. Add one background-session resource event with Dart and native offline-writer counters plus BLE failure context. Failure-Class: none * style(app): dart-format changed files for CI formatting check Run dart format --line-length 120 over the PR's changed non-generated Dart files (home page diagnostics, capture provider, capture controller, capture provider test) to satisfy the repo-checks Formatting gate. Failure-Class: none * fix(desktop): project screenshot embeddings in backfill query * chore(desktop): record internal backfill test fix * fix(ios): preserve BLE battery throttle across relaunches * test(ios): cover rehydrated battery throttle --------- Co-authored-by: David Zhang <9387252+Git-on-my-level@users.noreply.github.com> | 2 个月前 | |
fix(capture): one live source at a time; a phone pause keeps its conversation (#18839) * fix(capture): one live source at a time; a phone pause keeps its conversation - Phone pause mutes, releases the mic and keeps the socket and recording id; resume unmutes and restarts only the mic (_resumeMicRecording). Before, Pause/Resume stopped and restarted the recording: a new client conversation id, so a second conversation, with the first stranded in_progress. - A user stop of a live phone recording restores the policy it started from, so it no longer leaves a (later) pendant muted. - An explicit phone start hands off a live pendant (BLE streams closed, its conversation processed) and the pendant resumes by itself when the phone stops, in its prior live/paused state. finishCapture() processes the phone conversation before that resume. Before, the phone socket replaced the pendant's while the pendant listener kept writing into it. - A pendant (re)connecting during a phone recording waits and never rolls the phone's capture session. - An Omi phone call pauses a streaming pendant and resumes it after. - pauseCapture/resumeCapture/finishCapture are the one API for the live page, the capture card and quick actions. Device-connection seam for tests. Failure-Class: FC-unowned-session-mutation-tears-down-live-capture Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(capture): wire Omi calls in production and finish through finishCapture Failure-Class: FC-unowned-session-mutation-tears-down-live-capture Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(capture): share the phone tail flush between pause and stop Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(capture): address the Grok review of the one-source handoff - A call handoff outlives a pendant BLE drop; streamDeviceRecording returns early while a call is active (no transcript wipe, no session roll). - Phone handoffs and resumes share the call-switch queue; each step re-reads its condition. A phone start over a call-paused pendant takes the handoff. - The handoff closes the pendant's recording so the phone mints its own id. - A pause is not undone by stall/reconnect recovery or a Transcribe Later toggle; settings refresh follows the owning source; a superseded stop reports it, and Finish only resumes the pendant after a real stop. - A persisted marker restores the pre-recording policy after a process death. - Speech-profile onboarding keeps a handed-off pendant paused. - liveCaptureSource / liveCaptureStartedAt for the live card. Failure-Class: FC-unowned-session-mutation-tears-down-live-capture Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> | 1 个月前 | |
fix(app): verify quiet only with this generation's audio, name the failure, guard Reconnect Review fixes for #23703 round 1: - Honesty rule (#20378): a confirmed-subscription quiet verifies only when its generation already delivered audio. Native's snapshot has carried last_audio_at_ms all along (reset on every generation change and on a failed subscription), so verifiedAt now requires it for quiet: a fresh_subscription_quiet or budget_spent_quiet with no audio this generation stays unverified - the island grace, then the unheard notice with Reconnect - instead of Listening with a running clock forever. The same predicate gates the native_ingress capture-started telemetry, so a zero-audio link marks no recording started. - The Home live row's failure state never says Listening: the title is "No audio from pendant" with the plain-words line under it. - A reader's Reconnect coalesces concurrent taps into one managed reconnect (pendantReconnectInFlight; Home and Device Settings disable their action while it runs), is a no-op when the reader paused capture or disconnected the pendant themselves (the manual-disconnect choice is never cleared), and a tap with no pendant reports to the surface instead of an uncaught StateError. - The replayed zero-notification incident trace now asserts nothing ever verifies; the regenerated evidence shots carry the corrected copy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> | 5 天前 | |
harden(app): own capture connect/reconnect and the session generation (#14550) * harden(dev-harness): define V1 live-session and V8 spine contracts Add strict Python/Dart pending contracts and introducing-commit protection. Reserve the live-session CLI, wire, ownership/attachment seams and failing builder acceptance tests. Add live attribution before freezing evidence v1. Verification: - bash scripts/dev-harness/run-tests.sh: 287 passed, 7 existing skips, 40 strict xfailed (24 pending V1 markers), Python 3.11.15. - make preflight: 16 checks passed, using supported PYTHON selection. - make mobile-verify fast --paths harness/session contract inputs with an absolute evidence directory: 20 executed, 20 passed, no failures/skips. - bash app/test.sh: 2014 passed, 5 skipped, one unchanged UTC+7 search_rank_grouping_test failure; affected baseline inputs match main. - Real module CLI refusals exit 2 without traceback; regression covered. - Dart SDK probe proves ext. prefix required; marker-removal formatter probe proves no incidental Dart diff. Runtime is intentionally unimplemented. App UI must repair current invalid VM extension names before live acceptance; unsupported journeys block. Simulator/emulator latency and detached-process acceptance remain V1 work. make setup installed hooks but full backend wheel sync stalled; isolated Python 3.11 harness dependencies ran the owner checks without any skip flag. Failure-Class: none * harden(dev-harness): scope the spine-contracts check to spine paths The check protects files under the spine roots and their registry, so it only needs to run when those change. A repo-wide trigger would run it, and its full-history requirement, on every unrelated contributor PR. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(dev-harness): clarify the live journey adapter boundary V1 must refuse live journey execution until a shared executable adapter exists; B1 addressability alone cannot attach Dart integration tests. Pure admission contracts remain valid with injected supported IDs. Verification: harness 287 passed, 7 skipped, 40 strict xfailed; make preflight passed 20 checks. * harden(app): define capture ownership and seam contracts C1 pending contracts specify recovery, foreground service, socket and session ownership; no existing runtime path changed. Verified UTC app suite (2041 passed), harness (287 passed, 7 skipped, 40 strict xfailed), 20 hermetic journeys, and 21 preflight checks. * harden(app): close capture adoption and late-completion contract gaps Specify inventory revision coalescing without losing explicit retries, late location-upload/finalization fences, and zero legacy actor adoption checks. Existing protected tests are unchanged. UTC app: 2048 passed; harness: 287 passed, 7 skipped, 40 strict xfailed; preflight: 21 passed. Correction to earlier verification record: C1 selected 3 journeys, all passed, not 20. One intermediate unchanged baseline replay test returned zero restored WALs; the full final run passed and its exact failure is retained in .local/c1-app-final.log. * harden(dev-harness): pin reviewed spine revisions and align B0 capabilities Coordinator turn-4 review authorizes the V1 fixture correction. Exact before/after digests pin the revised oracle; immutable revision records do not permit builder assertion edits. Active tests reject missing/tampered records and prove the fake machine response. Verification: full harness 289 passed, 7 skipped, 40 strict xfailed; UTC app 2020 passed; spine 24 pending/7 protected; make preflight 16 passed. No live device acceptance claimed. * harden(dev-harness): preserve marker retirement across spine revisions Track retained marker occurrences rather than counts so retiring a second test cannot restore the first test's retired marker. The active regression covers this swap and legitimate continued retirement. Verification: full harness 290 passed, 7 skipped, 40 strict xfailed; spine 24 pending/7 protected; make preflight 16 passed. Dart sources unchanged after the earlier UTC app 2020-pass run. * harden(dev-harness): retain corrected spine oracles through squash merges A squash can introduce a corrected contract and its review records in the same commit. Absorb only the revision prefix ending at that introducing file's exact digest, preserving immutable records and later marker-only edits. Active temp-repo regression covers multiple corrections in one squash and rejects restoring old or altered assertions. Verification: harness 291 passed, 7 skipped, 40 strict xfailed; B1 make preflight 20 passed; final B1 journeys 20/20. Dart unchanged after UTC app 41 opt-in and 2051 ordinary passed. * test(dev-harness): revise live session acceptance after builder review * test(app): scope capture ownership adoption and exercise real effects * test(dev-harness): bind machine fixture to actual child identity * test(app): keep capture adoption scoped to ownership extractions * docs: align capture contributor guidance with staged adoption * harden(app): implement CaptureLifetime resource bag Timers, streams, and owned cancels drain on close even when one cancel fails. Close is idempotent and invalidates first, so a late acquisition after close dies immediately. Co-authored-by: Cursor <cursoragent@cursor.com> * harden(app): untrack CaptureLifetime resources on fire and cancel A day-long capture session would pin one dead cancel closure per once()/listen() that completed on its own. Release on fire, caller cancel, and stream done so the bag only holds live work. Co-authored-by: Cursor <cursoragent@cursor.com> * harden(app): forward capture composition seams into CaptureProvider composeCaptureProvider now passes WAL, mic, auth, clock, scheduling, connectivity, preferences, BLE, and socket through CaptureProvider into CaptureController. Production no-argument defaults stay; composeProductionCaptureProvider refuses FLUTTER_TEST before any singleton. Co-authored-by: Cursor <cursoragent@cursor.com> * test(app): prove capture composition routes seams and refuses FLUTTER_TEST Builder tests lock the cut-1 lazy passes: openSocket/WAL/mic must actually be forwarded, and the production composer cannot construct ConnectivityService with the health URL under test. Retires only the two composition spine markers this cut satisfies. Co-authored-by: Cursor <cursoragent@cursor.com> * style(app): apply dart format to capture_controller.dart Formatting only; required by the pre-push dart format gate. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * harden(app): join in-flight CaptureLifetime close and keep terminal bookkeeping A second close() while the first is still draining must wait for the same completion, asFuture must untrack even after it replaces inner callbacks, and replacement data/error/done handlers must not run after close begins. Co-authored-by: Cursor <cursoragent@cursor.com> * harden(app): keep injected capture preferences after construction Store the supplied SharedPreferencesUtil and route later reads and writes through it so batchMuted and the rest of the seam are not the global singleton, while ordinary construction still defaults. Co-authored-by: Cursor <cursoragent@cursor.com> * harden(app): own capture connect/reconnect and session generation Keep-alive during an in-flight connect now joins one socket attempt, and work started under generation N cannot publish after N+1. The explicit conversation-open path forwards session geolocation the way production already does, without changing default CaptureProvider() wiring. Co-authored-by: Cursor <cursoragent@cursor.com> * harden(app): join explicit CaptureLifetime cancellation until it finishes Untracking a callback is not the same as teardown completing; close now waits for in-flight own/subscription cancels without running them a second time. Co-authored-by: Cursor <cursoragent@cursor.com> * docs(app): record that CaptureLifetime close joins in-flight cancellation Co-authored-by: Cursor <cursoragent@cursor.com> * harden(app): fence capture publication to the admitted generation Admission already captured generation; publication after every await now keeps that original operation generation. Stop invalidates in-flight work before WAL teardown, and a skipped persist cannot occupy the fingerprint. Co-authored-by: Cursor <cursoragent@cursor.com> * test(app): register the CaptureLifetime drain spine file Co-authored-by: Cursor <cursoragent@cursor.com> * test(app): register capture publication-fence spine files Co-authored-by: Cursor <cursoragent@cursor.com> * chore(app): restore memories/page.dart to origin/main after merge format The merge-forward pre-commit hook reindented main's file and tripped INV-MEM-1 on a formatter-only diff. Lifetime does not own that page. Co-authored-by: Cursor <cursoragent@cursor.com> * chore(app): restore dart format on merge-forward trailing-comma splits Conflict resolution dropped trailing commas while keeping a split layout the formatter will not emit. Let dart format rewrite those 14 files; no semantic change. Co-authored-by: Cursor <cursoragent@cursor.com> * harden(app): retire the five satisfied C1 lifetime pending markers #14327 landed c1_lifetime_review_test.dart and c1_lifetime_terminal_test.dart on main with pendingContract('C1') still set. The behaviour they pin was already implemented on this branch by f04ec71709 and acdc197180, so after the merge-forward all five executed green and the contract harness failed them as XPASS ("remove the pending marker") rather than as unmet contracts. No implementation change is required, and none is made here; only the five stale markers are removed, which is the one edit the spine protection allows. What each contract pins, and the capture_lifetime.dart behaviour that satisfies it: - concurrent close joins outstanding cancellation: a second close() used to hand back a fresh completed future and report teardown finished while the first drain was still awaiting an own() cancel. close() now short-circuits only on _closeFinished and otherwise returns the memoised _closing future, so every caller joins the same drain. - asFuture preserves completion untracking: _inner.asFuture replaces the subscription's done handler, so the listen-time drop() never ran and the registration outlived its future. _LifetimeSubscription.asFuture now chains whenComplete(_drop). - C1 asFuture error completion releases its registration: the same path on the error completion - whenComplete(_drop) untracks on throw as well as on done. - replaced callbacks and errors stay closed while cancellation drains: a handler installed through onData/onError after listen bypassed the closed guard and could still fire while close() was blocked on an earlier release. Those wrappers now consult _isClosed() before forwarding. - C1 original and replacement done callbacks respect closing: both the listen-time onDone wrapper and an onDone replacement now drop() the registration but return without calling the owner once _closed is set. Verified in this worktree at app/: flutter test 2161 passed, 0 failed. scripts/check_spine_contracts.py exit 0, 69 pending markers (was 74), 27 protected files. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(app): restore spine original wrapping after merge format The merge commit's dart format rewrapped c1_lifetime_review_test.dart. Spine originals may only drop pending markers, so restore the lifetime bytes. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(app): keep c1_lifetime_review_test.dart at the spine original pre-commit dart format rewrapped this protected original. Restore the lifetime bytes; spine allows pending-marker removal only. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(app): drop unused CaptureProvider import after composition merge composeCaptureProvider is the seam; the provider import is leftover. Co-authored-by: Cursor <cursoragent@cursor.com> * style(app): format the two analytics files the socket merge left non-canonical These entered at this commit's parent merge. The merge resolution needed OMI_SKIP_DART_FORMAT=1 so the pre-commit hook could not rewrite a digest-pinned spine oracle (c1_lifetime_review_test.dart) — but that skip is all-or-nothing, so it also left analytics_manager.dart and omi_double_tap_goldens_test.dart unformatted, and they rode up the rest of the C1 chain. Formatter only, no semantic change. origin/main formats both paths with 0 changed, so this is the merge-collateral class, not new content. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com> | 1 个月前 | |
fix(app): verify quiet only with this generation's audio, name the failure, guard Reconnect Review fixes for #23703 round 1: - Honesty rule (#20378): a confirmed-subscription quiet verifies only when its generation already delivered audio. Native's snapshot has carried last_audio_at_ms all along (reset on every generation change and on a failed subscription), so verifiedAt now requires it for quiet: a fresh_subscription_quiet or budget_spent_quiet with no audio this generation stays unverified - the island grace, then the unheard notice with Reconnect - instead of Listening with a running clock forever. The same predicate gates the native_ingress capture-started telemetry, so a zero-audio link marks no recording started. - The Home live row's failure state never says Listening: the title is "No audio from pendant" with the plain-words line under it. - A reader's Reconnect coalesces concurrent taps into one managed reconnect (pendantReconnectInFlight; Home and Device Settings disable their action while it runs), is a no-op when the reader paused capture or disconnected the pendant themselves (the manual-disconnect choice is never cleared), and a tap with no pendant reports to the surface instead of an uncaught StateError. - The replayed zero-notification incident trace now asserts nothing ever verifies; the regenerated evidence shots carry the corrected copy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> | 5 天前 | |
Preserve recovery retention and capture timer ownership | 24 天前 | |
perf(app): cut real-time waits from the app test suite "Dart Analyze & Tests" (mobile-app-checks.yml) overruns its 30-minute timeout on app-touching PRs with zero test failures: #23918 cancelled at 30m16s/30m18s, #23900 passed only on rerun, and main's nightly run is already 27m47s. Per-file timing from the nightly and cancelled-run logs shows the flutter-test span (22m15s on main) is dominated by real event-loop waits, not CPU or test failures. Remove the waits without touching any assertion: - visual_audit harness settle(): 6x80ms real turns -> 6x20ms. The number of event-loop turns is what flushes loopback I/O; the per-turn length was 20-80x the actual loopback RTT. Suite wall (local): 196s -> 75s. - perf gate (production_shell_gate_test): the two outside-the-window settle waits 250ms -> 80ms (27 tests x 3 trials), and drainFixtureIo turns 5x20ms -> 5x8ms (it runs inside every scroll fling iteration of the measured scenarios). Deterministic work-count budgets unchanged. - conversation_playback_sync_test _flushPlatform: 6x30ms -> 6x10ms per flush (70 flushes per run); mock channel replies complete on the microtask queue. - plans_sheet_l10n_test: replace the O(n^2) nested-where duplicate-key scan with a map count. Same duplicates found; 15.5s -> ~50ms. - omi_ring_custody_test: _settle 50ms -> 12ms (probe/opt-in chains settle on microtasks), and the lost-ENABLE-ack test now injects a 200ms enable-ack window and waits 400ms instead of sleeping 6s for the production 5s give-up window. - limitless_golden_test: inject 10ms setup/reconnect settle windows via new LimitlessDeviceConnection params (defaults identical to the old constants); four tests each spent ~4s in the real 1s+1s connect settle windows. - capture_transcript_coverage_test: poll granularity 50ms -> 15ms (settleFiles/walsReach/telemetryReaches wait for real file I/O that completes in ms). Two production seams are added, both default-identical to the constants they replace (LimitlessDeviceConnection setup/reconnect settle, OmiDeviceConnection enable-ack timeout); only tests pass shorter values. The remaining job time is structural: ~800 suites' load/compile on a 4-vCPU runner at flutter test's default concurrency. Raising it needs either the spine-pinned test.sh invocation (contracts/spine/runners.json, immutable by check_spine_contracts.py) or the workflow file — both outside this PR's scope. No .github/ changes. Local verification (m5, nice -19): visual_audit 196s -> 75s, perf gate 74s -> 60s leaf time, playback sync 46s -> 41s, coverage 20s -> 7s, limitless 20s -> 4s, ring custody 15s -> 4s, l10n 19s -> 3s; full `bash test.sh` green. Failure-Class: none | 3 天前 | |
feat(sync): sync health watchdog and stall alerts (#15501) Re-implements community PR #17825 (by @TheRWX) on today's sync code: a full day of silence across every sync signal (BLE audio, WAL accepted by the server, batch finalized, pendant connect) raises one local notification, at most once a day, never at night, on by default with a kill switch in Device Settings. The stall surfaces in Activity's Needs attention section and lights the bell — never on Home (#22119) — and it is not a disconnect push (#3328): it fires only when nothing has flowed for 24 hours. Co-authored-by: TheRWX <166471970+TheRWX@users.noreply.github.com> | 3 天前 | |
feat(mobile): explain empty recordings and instrument onboarding funnel gaps (#21398) * feat(mobile): explain empty recordings and instrument onboarding funnel gaps A short phone recording that the server drops, and a discarded-only home list, now tell the user why nothing was kept. Welcome, sign-in, permission, and pairing failures emit closed analytics events so the next funnel read can split people who opened Connect and never paired. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com> * chore(mobile): add empty-recording screenshots and changelog fragment The UI evidence contract needs numbered platform filenames and a manifest, and the store notes need an unreleased fragment for the new empty-recording explanation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com> * chore(mobile): strip trailing whitespace from generated pigeon Kotlin The regenerated BleFlutterApi bindings included trailing spaces that diff hygiene rejects. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com> * fix(mobile): wait for a confirmed empty recording before explaining it A phone stop and a process 404 can both still become a conversation. Explain only a confirmed empty body, or a 404 that stays unanswered after the conversation-created path, and count Apple Watch from the session device. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com> * chore(test): drop the unused replay process callback The replay controller only receives processInProgressOutcome, and the unused optional parameter failed the analyzer ratchet. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com> | 18 天前 | |
feat(mobile): explain empty recordings and instrument onboarding funnel gaps (#21398) * feat(mobile): explain empty recordings and instrument onboarding funnel gaps A short phone recording that the server drops, and a discarded-only home list, now tell the user why nothing was kept. Welcome, sign-in, permission, and pairing failures emit closed analytics events so the next funnel read can split people who opened Connect and never paired. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com> * chore(mobile): add empty-recording screenshots and changelog fragment The UI evidence contract needs numbered platform filenames and a manifest, and the store notes need an unreleased fragment for the new empty-recording explanation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com> * chore(mobile): strip trailing whitespace from generated pigeon Kotlin The regenerated BleFlutterApi bindings included trailing spaces that diff hygiene rejects. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com> * fix(mobile): wait for a confirmed empty recording before explaining it A phone stop and a process 404 can both still become a conversation. Explain only a confirmed empty body, or a 404 that stays unanswered after the conversation-created path, and count Apple Watch from the session device. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com> * chore(test): drop the unused replay process callback The replay controller only receives processInProgressOutcome, and the unused optional parameter failed the analyzer ratchet. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com> | 18 天前 | |
feat(app): end onboarding with a first capture (#21396) * feat(app): end onboarding with a first capture Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(app): register first capture visual evidence Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(app): honor first capture outcomes and recording ownership Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(app): preserve terminal capture outcomes and verified renders Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(app): retain pairing recovery and finish confirmation labels Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(app): remove redundant import from merged feedback render test Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(app): scope processed response futures to guided capture Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> | 18 天前 | |
feat(listen): demote freemium threshold event to Plus-only (S18) (#12906) Basic no longer enters on-device through FreemiumThresholdReachedEvent; that event is now the Plus meter warning. The client paywall sheet follows the same plan gate. Co-authored-by: Cursor <cursoragent@cursor.com> | 1 个月前 | |
fix(app): never strand a connected pendant without capture or uploads A pendant reconnect landing inside its own reconnect sync pass skipped capture start, nothing re-drove it, and the pass could hang forever on an untimed BLE read. A connected but unsubscribed legacy pendant discards audio, so ~4 h was lost (2026-10-20). Uploads stalled behind the same stuck pass. - Only the OS periodic window fences live capture; a swallowed pendant start is replayed when the fence drops; foreground starts an idle connected pendant. - Transfer passes get a hard time limit (60 min, 10 min fenced) that releases the fence and the in-flight pass; the watchdog wakes while rows are pending; any network change wakes a pass; retries and cooldown-end run in background. - Server close, network change and resume kick an immediate bounded-backoff socket reconnect; the keep-alive can no longer die for good. - Pendant audio reaching the transport with no capture listener is persisted to the safety WAL (unless muted or caller-stopped). - BLE read/write timeouts in Dart; iOS queues same-characteristic reads and writes FIFO with a 10 s timeout instead of orphaning the first caller. - Capture Recovery Observed analytics event; multipart body read timeout. Failure-Class: FC-automatic-capture-pause-without-retention Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> | 11 天前 | |
feat(app): on-device live preview for batch-lane users (iOS + Android) (#23617) * feat(mobile): on-device live preview for batch-lane sessions (iOS, phase 1) Admitted local-preview sessions (server: local_preview + mobile_standard_batch) show an on-device streaming preview on the live transcript surface while every pendant frame is journaled through the mobile Standard batch route; the batch transcript is the record. - Client declares local_preview=enabled (iOS only; kill switch mobile-local-preview-disabled, registered ops_kill) through TranscriptSegmentSocketService/factory/pool incl. the reuse key; parses local_preview on service_status; socket-scoped admission. - Routing: MobileStandardBatch ignores visibility for local-preview grants, voice commands keep journaling, refused frames still go WAL+socket; pendant frames stay WAL-only until the verdict (<=10 s) and during journal warm-up so they never open the deferred cloud stream. - Multicast device/phone-mic taps (onboarding slot unchanged). - LivePreviewController (separate from segments), bounded preview-only audio queue with a copy-decoded Opus feed, MethodChannel engine on com.omi/live_preview(+/events). - iOS LivePreviewPlugin: SpeechAnalyzer/SpeechTranscriber (installed locale only, no downloads) -> on-device SFSpeech (existing permission, 50 s rotation) -> none; closed reason codes. - UI: Live preview caption, provisional text in secondary colour, calm Transcribing state with no engine, home card last line; l10n (English fallback in all locales). - Typed telemetry: Live Preview Engine / First Word / Session Ended. - Tests: controller, routing (replay world), batch lane, native policy + SDK type-check; render evidence for every state, light and dark. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(mobile): move local-preview admission and tap multicast out of CaptureController LocalPreviewAdmission owns the socket-scoped grant, the bounded verdict hold and the journal warm-up hold; AudioTapListeners owns the extra tap listeners. CaptureController keeps only the hook points. No behavior change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(mobile): changelog fragment for the on-device live preview Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * perf(mobile): live preview lines without shrinkWrap; follow newest words only while at the bottom INV-PERF-1: a plain lazy ListView with a follow-to-bottom controller replaces the reversed shrink-wrapped list. Rendered evidence is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ios): live preview audio formats without force unwraps (swiftlint) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(mobile): keep non-admitted sessions' full live transcript across the local-preview verdict Pendant frames on a socket that declared local_preview are still held off the socket until the verdict (and still written to the safety WAL), but an in-memory copy (<=10 s / 500 frames, oldest first) is kept. A refusal (verdict without local_preview, or no verdict within 10 s) sends the held frames to the socket in order before any newer frame; a grant drops the copy; overflow stops holding, flushes and resumes streaming. Socket close/replacement drops the copy (the WAL keeps it, as for any frame captured while the socket is down). The journal warm-up hold applies only to admitted sockets and keeps no copy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(mobile): carry pre-verdict held frames across socket replacement A socket that closes before its local-preview verdict no longer discards the in-memory copy of held pendant frames. The queue (still <=500 frames) is resolved against the replacement socket's verdict: a refusal, or a replacement that did not declare local preview, sends them first, oldest first; a grant drops them. Only capture end or an account change discards the queue. Keeps live-transcript continuity across a reconnect for non-admitted sessions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(mobile): prove never-sent local-preview frames reach the record through sync Warm-up frames refused by the batch journal, and frames held then granted, go to the safety WAL and never to the socket. An admitted session receives no ConversationEvent, so that WAL stays unconfirmed and the ordinary recovery drain uploads it through /v2/sync-local-files to the batch record. The replay test drives it end to end (journal warm-up, socket silence, finalize, getMissingWals, coordinator wake, one upload, WAL synced). README documents both paths. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * perf(mobile): live preview decodes in bounded slices off the capture turn Opus decode and engine feed now run on timer turns, at most 8 packets per turn, instead of draining the whole queue in one microtask. A burst never delays the next capture callback by more than one bounded slice; the tap itself only enqueues. Test uses a deliberately slow decoder. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ios): live preview reports runtime failures and stops before it answers - SpeechAnalyzer: a results task that ends or throws while the session is live, and SFSpeech after 3 consecutive failed requests with no words ("no speech" 1110 is silence, not a failure), send {failure: engine_error, engine} on the events channel and the session is released. - start accepts `exclude` (engines that already failed this session). - stop awaits SpeechAnalyzer.cancelAndFinishNow() before replying, and a new start waits for the previous session's cleanup. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(mobile): live preview falls back once on a runtime engine failure Runtime failures arrive as a typed LivePreviewRuntimeFailure (closed reason engine_error) on the engine's results stream, from native failure events or a broken event channel. The controller closes that engine's run, retries the selection once excluding it, and otherwise settles in the calm no-engine "Transcribing…" state; lines already shown stay. Each engine run is one Live Preview Engine + one Session Ended event. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(mobile): live preview runtime fallback, decode slicing and per-run telemetry Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(android): on-device live preview engine and Android client (phase 2) - LivePreviewPlugin.kt on com.omi/live_preview (+ /events), same Dart contract and closed reasons: API 33+ createOnDeviceSpeechRecognizer fed through an EXTRA_AUDIO_SOURCE pipe (PCM16 16 kHz mono extras, EXTRA_SEGMENTED_SESSION), only when isOnDeviceRecognitionAvailable and checkRecognitionSupport list the language as installed (never triggerModelDownload). Recognizer calls on the main looper; PCM written on one background thread behind a bounded queue. os_too_old (<33), speech_permission_denied (the recognition service checks RECORD_AUDIO even for an app audio source; never prompted), os_engine_unavailable, locale_unsupported, locale_asset_missing, engine_error. Runtime failures (repeated non-silence errors) reach Dart as {failure: engine_error}; `exclude` honoured. - Fallback: Omi's already-downloaded Whisper model as a windowed pseudo-stream (4 s windows, one at a time, 30 s bounded buffer); else none. - Android installs the preview controller, so it declares local_preview (same kill switch). The native background streamer never declares it (no batch journal there; pinned by OmiBackgroundAudioStreamerTest). - Android-look renders of every state, light and dark; README, changelog fragment and kill-switch summary cover both platforms. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(mobile): latch a timed-out or overflowed verdict hold as a refusal for that socket When the pre-verdict hold times out (10 s) or overflows (500 frames), the held frames are already streamed to the socket. That socket is now settled as refused: a later positive verdict on it never sets admitted, so an already cloud-streamed session cannot look admitted. A replacement socket starts clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(mobile): ask for local preview only when routing can journal pendant audio The client declares local_preview only for an Omi pendant on an Opus codec with auto-sync on, no Transcribe Later, no custom STT, audio not kept on the phone, no onboarding and a batch journal installed (MobileStandardBatch's per-session refusals). Otherwise it does not ask and emits `Live Preview Not Requested` with a closed reason (also kill_switch). Phone-mic capture therefore keeps today's cloud transcript. If one of these flips while admitted, the grant is dropped for the rest of that socket: the preview hides, the batch lane returns to its ordinary visibility rule, and no later grant on that socket re-admits it. Transient refusals (disk, journal failure) keep the WAL+socket carve-out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(android): a live preview recognizer that fails to (re)start closes its pipe and reports engine_error begin() now opens the recognizer through LivePreviewPolicy.startOnPipe: if setRecognitionListener/startListening throws, both pipe descriptors are closed and the error rethrown. A failed segment restart reports a runtime failure ({failure: engine_error}) so Dart falls back instead of treating a dead engine as running; a failed first start releases the session and answers engine_error. A support-check answer that arrives after the timeout no longer starts a session. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(mobile): measure the local-preview verdict hold per socket (Live Preview Verdict) Lead ruling d14718; observational only, no routing change. Each socket that declared local_preview emits one typed `Live Preview Verdict`: verdict (granted, refused, timeout, overflow, socket_closed), platform, verdict_latency_ms (socket published to settlement), hold_duration_ms, held_frames and hold_timeout_replayed. Renewal verdicts never emit again; a replacement socket emits its own row; capture end settles an open socket as socket_closed. Consumer gains live-preview-verdict.sql for p50/p95 latency by verdict and platform and the timeout-replay rate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(mobile): keep pendant frames off the socket only when the safety WAL accepted them LocalWalSyncImpl now reports whether the last onFrameCaptured kept the frame (lastFrameAccepted; reporting only, its behaviour is unchanged). The capture path passes that custody to the local-preview router: the pre-verdict hold and the journal warm-up hold apply only to WAL-accepted frames. A frame the WAL refused (storage blocked, 2000-frame buffer full) and the journal did not take goes to the socket at once, after any held frames. On a grant, only WAL-accepted held frames are dropped; any other would be sent. Replay tests drive a storage-blocked WAL during the hold and during warm-up. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(mobile): an account change or capture end sends held frames to a still-open socket LocalWalSyncImpl.clearUserData drops the safety WAL's in-memory tail on an account change (pre-existing on main, unchanged here), so held pre-verdict frames cannot rely on it. Before the admission state is cleared (account change, capture stop, session roll), frames held for a socket that is still connected and was not granted are sent to it in order, as on a refusal. Granted sockets never receive them; without an open socket they are dropped like any frame captured while the socket is down. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(mobile): pin FriendPendant routing to main's (no source, no batch, no safety WAL) On main (86fcbcce500) a BleDeviceSource exists only for Omi/OpenGlass (capture_controller.dart:2341-2344), batch routing needs that source (:2089, :2106), the safety WAL needs Omi/OpenGlass + Opus (:2132-2134), and a connected socket gets the raw packet (:2148-2165). #23617 never declares local_preview for this device and its router sends such a frame unchanged: the test asserts the packet bytes on the socket, an empty safety WAL, no journal file, and nothing retained without a socket, as on main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(mobile): latch the local-preview verdict deadline with a timer The 10 s verdict deadline no longer depends on a frame arriving: a timer (capture scheduling seam) latches it, reports the timeout and replays any held frames to the socket at that moment; verdict() also checks expiry, so a grant after the deadline in a quiet interval is a refusal for that socket. An in-time verdict, close, capture end, overflow or mid-session refusal cancels the timer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(mobile): no frame the local-preview router suppressed can be lost at account change - Journal warm-up no longer suppresses socket delivery for an admitted session: a frame the warming journal refuses goes to WAL and socket (the never-lose carve-out; the deferred stream it opens is counted server-side). - Granted held frames (kept off the socket, custody = safety WAL) are copied (bounded, 3000) until the WAL reports them durable (LocalWalSyncImpl.mayHoldOnlyInMemory: still buffered or in an unflushed WAL; read-only). - At capture end the copies, then any held frames of an ungranted socket, go to an open socket in order. On an account change with no open socket they are written synchronously to the batch journal for the OLD owner (MobileStandardBatch.preserveForOwner, used only by this path; needs only the journal files, not the VAD) before the account cleanup, unchanged from main, clears the WAL's in-memory tail. A plain stop leaves them to the WAL. - Replay tests: account change during admitted warm-up with the socket open (frames reach the socket) and closed (frames journaled for the old owner). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(mobile): strict WAL durability for local-preview copies; never evict a non-durable copy - LocalWalSyncImpl.isFrameDurable (read-only) replaces mayHoldOnlyInMemory: a frame is durable only when a flush of the CURRENT generation wrote it to a disk WAL and that flush's index save completed (recorded per payload in an Expando just before the WAL releases its in-memory data). A WAL marked disk with the index still pending, or a generation superseded by account cleanup, is not durable. No behaviour change for other callers. - The router never drops a copy before that. The cap is 18000 frames (2x the WAL's ~3 min worst-case in-memory window at 50 frames/s); at the cap it stops suppressing instead (a hold at the cap latches like an overflow and streams; granted frames past the cap go to the socket), so no flush request and no journal double-count are needed. - Prune and account change filter every copy through the strict probe; account change with no socket preserves exactly the non-durable ones. - Tests: strict probe across a gated index save and a cleared generation; cap pressure without eviction; a flush in progress treated as non-durable and preserved at account change; durable copies not preserved. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> | 6 天前 | |
fix(app): never strand a connected pendant without capture or uploads A pendant reconnect landing inside its own reconnect sync pass skipped capture start, nothing re-drove it, and the pass could hang forever on an untimed BLE read. A connected but unsubscribed legacy pendant discards audio, so ~4 h was lost (2026-10-20). Uploads stalled behind the same stuck pass. - Only the OS periodic window fences live capture; a swallowed pendant start is replayed when the fence drops; foreground starts an idle connected pendant. - Transfer passes get a hard time limit (60 min, 10 min fenced) that releases the fence and the in-flight pass; the watchdog wakes while rows are pending; any network change wakes a pass; retries and cooldown-end run in background. - Server close, network change and resume kick an immediate bounded-backoff socket reconnect; the keep-alive can no longer die for good. - Pendant audio reaching the transport with no capture listener is persisted to the safety WAL (unless muted or caller-stopped). - BLE read/write timeouts in Dart; iOS queues same-characteristic reads and writes FIFO with a 10 s timeout instead of orphaning the first caller. - Capture Recovery Observed analytics event; multipart body read timeout. Failure-Class: FC-automatic-capture-pause-without-retention Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> | 11 天前 | |
fix: preserve transcription through Wi-Fi and provider settings changes (#22197) * fix: preserve transcription recovery across network and settings changes * fix: preserve persisted transcription identities without parser overrides * fix: handle connectivity stream errors and stub native test channels | 11 天前 | |
feat(app): tiered pendant capture — pause the live uplink after silence, resume on foreground or charge (#20837) * fix(app): close pendant listen socket on pause and reopen on resume * feat(app): pause silent pendant uplink until foreground or charge start * fix(app): stop synthetic pendant battery ticks and redundant widget reloads * feat(app): add opportunistic background backlog sync without live uplink * fix(app): preserve timeout settings and isolate uplink policy effects * chore(app): fix failure-class scope hints to tracked globs * fix(app): preserve charging observations across failed GATT reads Treat failed and empty charging reads as unknown, retaining the tracker's last successful state and UI/widget value. Only successful observations may create charge-start edges. Audit confirms notifications are real characteristic payloads, with empty notifications ignored. Cover read/notify recovery, clean reconnects, and genuine charging transitions through DeviceProvider. * fix(app): fence background reconnect backlog sync from live capture Keep deviceConnected background-eligible to honor generous reconnect sync without weakening immediate BLE link recovery. Like periodic wakes, its discovery and backlog drain now run inside SyncWakeScope. Update stale foreground-only comments and prove capture start attempts cannot open sockets or BLE audio, reconnect bursts stop at two serial passes, and failures release holds without scheduling background retries. * fix(app): defer resume commits until sync drains finish Choose shape (a): keep pause intent intact until SyncWakeScope.whenIdle, then dispatch the existing resume transition. Apply the same fence to Home, pendant double-tap, Live Activity, foreground, and charging resume paths; re-arm the existing keepalive after successful resume. No background retry timer or sync admission bypass is added. Regressions reproduced the old failure for foreground and background reconnect drains and assert connected socket, active BLE audio, and armed keepalive after completion. * fix(app): gate the first charging notification after a failed read Firmware force-notifies charging state when notifications are enabled. If the read failed, preserve its per-connect resume gate for the first valid notification, even after the sync fence drops. Always record that sample; later genuine false-to-true edges still resume. Cover explicitly gated connects, notifications during/after sync, empty payloads, and ordinary first charging observations. * fix(app): preserve authorized charging edges across later sync scopes Latch the first-observation resume gate per connection. A scope that starts later defers capture work rather than consuming the first real charging edge after an unknown read. Connections initially admitted only for sync still observe without resuming. Add a provider regression for one initial edge and later genuine charge starts. * fix(app): reconcile fenced pendant transports on sync scope drop Enforce live-session liveness at the transport layer instead of relying on a safe gap before resume dispatch. Coalesce skipped transport attempts, await scope drop and capture commit, then revalidate current ownership and idempotently restore missing transports plus keepalive. A new scope during the re-drive records fresh demand; no retry timer, policy write, or session mint is introduced. Clear cancelled audio handles and make already-unmuted resume a no-op. Keep dispatch deferral as an optimization only. Add the numbered eight-case resume/fence suite, including a real coalesced coordinator with a controlled microtask yield at depth zero. Case 3 fails on fbb0ba0483 with a disconnected socket after the whole wake, and passes with reconciliation. Pure sync still opens zero transports. | 25 天前 |
| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
| 24 天前 | ||
| 1 个月前 | ||
| 11 天前 | ||
| 11 天前 | ||
| 18 天前 | ||
| 10 天前 | ||
| 1 个月前 | ||
| 11 天前 | ||
| 5 天前 | ||
| 27 天前 | ||
| 10 天前 | ||
| 24 天前 | ||
| 2 个月前 | ||
| 1 个月前 | ||
| 5 天前 | ||
| 1 个月前 | ||
| 5 天前 | ||
| 24 天前 | ||
| 3 天前 | ||
| 3 天前 | ||
| 18 天前 | ||
| 18 天前 | ||
| 18 天前 | ||
| 1 个月前 | ||
| 11 天前 | ||
| 6 天前 | ||
| 11 天前 | ||
| 11 天前 | ||
| 25 天前 |