| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
fix(app): distinguish cutover outages from migration Keep account cutover access fail-closed while presenting pending, transport, and unavailable control states separately from server-confirmed migration. Reject wrong-typed control enum fields as unavailable and serialize manual and timer retries. Tests: flutter test test/unit/account_cutover_gate_test.dart test/widgets/account_cutover_fence_test.dart; bash app/test.sh; app/scripts/analyze_ratchet.sh Failure-Class: FC-unestablished-capability-default | 18 天前 | |
fix(app+backend): first-party summaries labeled "Unknown App"; explicit template reprocess failed silently (#12240) * fix(backend): fail closed when an explicitly selected summary app produces nothing Reprocess with ?app_id= ran first-party get_conversation_notes and then the selected app, but _trigger_apps swallowed the app's execution exception and appended empty-content results silently. Under notes v2 the write-back guard is tautologically true, so the failed run also persisted apps_results: [], wiping the previously stored app summary, and the route still returned 200: the client showed first-party notes plus a mislabeled footer while the user's template selection quietly did nothing (SCA-359). Now _trigger_apps raises ExplicitAppSelectionFailedError when an explicit selection ends with no non-empty result (execution failure or empty model output). process_conversation captures it around the derived-effect bundle so the persist-as-today write-back, action items, and goals still run, then re-raises through conversation_processing_http_exception - the same generic-processing-error contract the structured-summary boundary already enforces. Automatic selection (preferred app, suggestions) stays fail-open. Verified: backend/.venv pytest tests/unit/test_process_conversation_usage_context.py tests/unit/test_reprocess_app_selection.py tests/routers/test_conversations_processing_rollback.py - 62 passed (7 new: explicit-selection fail-closed x2, automatic stays fail-open, opt-in preferred app still auto-runs, success persists the app's result, failure surfaces 500 + persists-as-today). Failure-Class: FC-denial-rendered-as-empty-success Co-authored-by: multica-agent <github@multica.ai> * fix(app): label first-party summaries Summary and resolve apps from the catalog Every conversation whose visible summary was the notes-v2 structured overview (appId == null) rendered "Unknown App" in the attribution footer: getSummarizedApp() fabricates AppResponse(overview, appId: null), findAppById(null) is null by design, and both AppResultDetailWidget title sites fell into the unknownApp branch. Unknown App is now reserved for a non-null app id whose catalog lookup failed; a first-party summary is labeled "Summary" - the existing l10n key the sheet's capability title, the bottom pill, and desktop's summary header already use, so no third name is invented (SCA-359). findAppById also fell back to nothing when the two sheet caches were empty, so even a real app_id showed Unknown App until the summary sheet was opened. It now falls back to the durable appProvider.apps catalog loaded at startup. Verified: flutter test test/widgets/app_result_summary_source_label_test.dart test/providers/conversation_detail_find_app_test.dart test/providers/conversation_detail_summary_selection_test.dart test/widgets/conversation_summary_sections_test.dart - 15 passed (7 new). Failure-Class: none Co-authored-by: multica-agent <github@multica.ai> * fix(app): localize the summary pill fallback label The bottom-bar pill's no-app fallback was a hardcoded English 'Summary' while the attribution footer now uses context.l10n.summary for the same first-party concept; unlocalized, non-English locales would show two different names for one thing. Reuse the same key so the pill and footer agree in every locale (SCA-359 cleanup). Verified: analyzer clean on the file; pill renders through the same l10n delegate used by the footer tests. Failure-Class: none Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: David Zhang <9387252+Git-on-my-level@users.noreply.github.com> Co-authored-by: multica-agent <github@multica.ai> | 21 天前 | |
fix(app): let long app titles expand Failure-Class: none | 13 天前 | |
test(app): align AudioWavePainter tests with the 0.005 repaint threshold shouldRepaint shipped with a > 0.005 threshold since 468758b1a; the tests still asserted the old > 0.01 threshold (false-cases used a 0.005 diff that floats just above 0.005). Updated false-cases to clearly-below-0.005 diffs and refreshed names/comments. Code unchanged. | 2 个月前 | |
fix(app): harden Bluetooth recovery Failure-Class: FC-denial-rendered-as-empty-success | 1 个月前 | |
fix(app): keep the bottom tab bar above the Android system navigation bar (#12858) * fix(app): keep the bottom tab bar above the Android system navigation bar BottomNavBar reserved no bottom safe-area inset, so on Android 15+ (targetSdk 36 enforces edge-to-edge) its tap targets were drawn under the system navigation bar. In 3-button mode that bar is ~48dp and opaque, covering most of the tab row. Reserve MediaQuery.viewPaddingOf(context).bottom in the nav container and carry the same inset on the home chat bar positioned against it. A device reporting a zero bottom inset lays out exactly as before. Fixes #12352 * test(app): harden the nav bar inset regression test Drop unnecessary interpolation braces (flutter_lints), tolerate sub-pixel layout rounding, and key the bar per pump so the cached _navigation subtree is rebuilt for each inset rather than reusing the element. * fix(app): address review on the bottom nav inset fix - Make the regression test discriminate viewPadding from padding. The old helper set both to the same value, so it passed even if the bar read paddingOf. A second case now pins the keyboard-open state: padding zero, viewPadding at the system bar height. - Derive the home chat bar's offset from the nav row's own geometry (kBottomNavBarHeight / kBottomNavChatBarGap / bottomNavBarReservedInset) so the two cannot drift apart. - Soften the edge-to-edge comment: the reserved inset is whatever the window reports, so a window that is not drawn under the system bar reports zero and the row is never lifted twice. * style(app): format the bottom navigation chat offset | 8 天前 | |
| 15 天前 | ||
fix(app): clear new analyzer-ratchet warnings in chat citation work - drop unused temp.dart import from the citation taps test - make the evidence envelope a const declaration (prefer_const_constructors) - drop a dead non-null assertion promoted by the earlier null check These rules are absent from analysis_baseline.json, so any new occurrence fails the analyze ratchet that gates Dart Analyze & Tests. | 16 天前 | |
Mobile chat: preserve mixed structured fallback content (#12844) * Fix mobile chat block fallback and hydration states * Avoid duplicate fallback prose in mobile chat * Preserve mixed chat fallback lines on mobile * fix(mobile): preserve malformed chat block fallbacks | 9 天前 | |
ci(desktop): pin ship Xcode and fail closed on compiler-gated APIs (#13564) * ci(desktop): pin ship/CI Xcode to 26.6 (17F113) from one source of truth PR #12867 looked right on a local Xcode 26 named bundle and shipped the icon-less system Picker fallback: Liquid Glass sat behind #if compiler(>=6.2) while desktop CI (GHA desktop-swift-ci.yml and Codemagic omi-desktop-swift-release/-preview) compiled with Xcode 16.4, so the ship toolchain never typechecked the glass APIs (#13548 reverted the tab bar). Bump the desktop ship/CI toolchain off 16.4 and make drift impossible: - desktop/macos/ci/xcode-pin.json is now the only place the version (26.6), build (17F113), and expected app path (/Applications/Xcode_26.6.app) live. 26.6/17F113 exists on both vendors: GHA macos-26 image 20260907.0351.1 (default Xcode, /Applications/Xcode_26.6.app) and Codemagic mac_mini_m4 image 'Xcode 26.6.x (default)' (/Applications/Xcode-26.6.app). - run-swift-ci.sh reads the pin file (fails closed when it is missing) and keeps asserting the exact version+build after selection. - desktop-swift-ci.yml macOS jobs move macos-15 -> macos-26, cache key prefixes move xcode164 -> xcode266 so 16.4 caches cannot poison 26.x builds, and the select steps name the pinned version. - codemagic.yaml omi-desktop-swift-release/-preview move xcode: 16.4 -> 26.6 (Android 16.4 and iOS 26.0.1 untouched); the reviewed codemagic_workflow_contract digests are refreshed for that edit. - test_desktop_swift_ci_contract.py loads the pin file instead of literals and adds codemagic pin-agreement + macos-26 runner assertions. Verified locally: bash desktop/macos/tests/test-run-swift-ci.sh (pin 26.6/17F113 sandbox incl. wrong-version/wrong-build/missing-pin sabotage); python3 .github/scripts/test_desktop_swift_ci_contract.py (38 tests OK); python3 .github/scripts/check-release-process-guards.py (exit 0); xcrun swift build -c debug --package-path Desktop on Xcode 26.6 (17F113) completes (1855s). * ci(desktop): fail closed on compiler-gated Apple SDK APIs New desktop-compiler-gates manifest check: desktop/macos/scripts/ check-desktop-compiler-gates.py fails when Desktop/Sources or Desktop/Tests contain #if compiler(...) / #elseif compiler(...) — the shape that let #12867 compile Liquid Glass out of the Xcode 16.4 ship toolchain while CI stayed green (#13548 reverted the tab bar). Runtime availability gating (if #available(macOS 26, *) with a working fallback) is the sanctioned pattern and passes. The allowlist ships empty and is asserted by exact contents in test_check_desktop_compiler_gates.py, so it cannot silently grow; entries require a documented reason and a matching path+line. checks-manifest.yaml also extends desktop-swift-ci-contract triggers with the pin file, runner script, launcher test, and codemagic.yaml so every pin consumer re-runs the contract test on drift. Verified locally: python3 .github/scripts/test_check_desktop_compiler_gates.py (9 tests OK — planted #if compiler(>=6.2) glassEffect sabotage fails the checker and removing it passes); python3 desktop/macos/scripts/ check-desktop-compiler-gates.py on the real tree reports none found; make preflight manifest lane: all selected checks pass except the pre-existing loaded-host flake in test_pr_preflight.py SingleFlightTests.test_identical_processes_join_and_execute_once, which fails 3/3 on pristine origin/main at the same host load (passes in isolation) and is unrelated to this diff. * changelog(desktop): mark ci xcode pin as internal-only (kind: none) The desktop changelog check requires an in-repo fragment for production desktop paths even with the no-changelog-needed label: the label is invisible after merge and would redden main's Release Eligibility run. This PR is CI/infra only (toolchain pin + fail-closed gate), so the marker is kind: none. * ci(desktop): raise verify ceiling to 90m for the first cold macos-26 full lane Measured on run 34687313733 (PR #13564): with the pin moved to Xcode 26.6, every cache cold under the new xcode266 keys, the full Swift suite ran past 55 minutes and the 60-minute job ceiling cancelled the job mid serial-cluster. Every completed suite was green (one wedged batch cost 1650s before its 1500s watchdog bisected it cleanly; zero test failures) — purely a timing ceiling, so: - desktop-swift-verify timeout-minutes 60 -> 90 - full-lane OMI_SWIFT_TEST_STEP_BUDGET_SECONDS 2700 -> 4200 Both changes cite the run; contract test constants updated to match. * test(ci): close compiler-gate checker holes from review Match compiler() anywhere in #if/#elseif, ignore block-commented directives, and fail closed when the scan root has no Swift files. * test(app): accept ActionItemsFetcher due-window params #13570 added dueStartDate/dueEndDate to ActionItemsFetcher on main. GitHub analyzes the merge of this PR, so the existing test stubs must accept those named args or Dart Analyze fails. * style(app): dart format test stubs at line-length 120 CI Check Dart formatting uses --line-length 120 after pub get. * chore: restore dart test stubs from origin/main after merge format noise * test(app): match ActionItemsFetcher due-window params | 2 天前 | |
feat: add JIT knowledge ledger foundation and guarded adoption (#12084) * feat: add JIT knowledge ledger foundation * chore: refresh integration OpenAPI contract * fix: make trigger evaluation release-safe Failure-Class: none * fix: preserve lifecycle semantics in ledger apply Failure-Class: none * feat: adopt guarded JIT knowledge surfaces Route the agent preference writer through the intent-backed ledger, register a privacy-filtered entity timeline tool, render optional evidence on Windows, and add a base-ref-protected Gate F legacy-surface ratchet. Failure-Class: none * feat: add progressive JIT knowledge reads Register owner-scoped current-ledger search and explicit playbook hydration, with pre-limit semantic filtering and bounded outputs. Add a content-free planner/resume migration fixture without claiming canonical transaction completion.\n\nValidation: 141 focused backend tests passed; backend typecheck reported 0 errors; repository preflight passed 120 checks. * feat: render chat evidence on web Render bounded, fail-soft conversation evidence after authoritative answers in both web chat entry points. Unsupported, future, duplicate, and raw failure details remain inert.\n\nValidation: 337 web tests passed; web typecheck, oxlint, and Prettier passed; repository preflight passed 120 checks. * feat: require intent-backed ledger search results Apply the intent-backed requirement at the final merged canonical/history filter, with a passive historical-row regression case.\n\nValidation: 54 focused backend tests passed. * test: keep agent tool isolation stubs current * feat: gate JIT conversation retrieval * fix: make entity timeline scans deterministic Failure-Class: none * fix: honor rejected ledger projections Failure-Class: none * feat: render inert screen evidence on web * fix: reuse canonical review projection Failure-Class: none * fix(web): await recap context effect Failure-Class: none * test: amortize preference tool isolation load Failure-Class: none * feat(app): add knowledge ledger review surface Failure-Class: none * feat(macos): use canonical ledger prompt projection Failure-Class: none * test(memory): classify legacy surface inventory roles Failure-Class: none * fix(app): preserve ledger history completeness state Failure-Class: none * feat(macos): preserve canonical ledger mirror metadata Failure-Class: none * fix(app): match canonical ledger ordering Failure-Class: none * test(api): prove ledger client schema parity Failure-Class: none * feat(memory): expose bounded ledger history Failure-Class: none * chore(api): generate ledger history clients Failure-Class: none * feat(retrieval): add bounded card participants Failure-Class: none * feat(macos): project ledger trigger watchlist Failure-Class: none * fix(clients): fail closed on ledger authority Failure-Class: none * feat(macos): expose bounded trigger snapshot Failure-Class: none * fix(memory): keep closed history read only Failure-Class: none * feat(app): disclose partial ledger history Failure-Class: none * test(macos): cover ledger trigger bridge Failure-Class: none * fix(app): use neutral ledger accents Failure-Class: none * chore(api): declare ledger history route policy Failure-Class: none * test(macos): remove unsafe JSON fixture unwraps Failure-Class: none * fix(memory): satisfy typed history boundary Failure-Class: none * fix(macos): require prompt snapshot authority Failure-Class: none * test(memory): prove ledger migration on emulator Failure-Class: none * feat(retrieval): emit bounded screen evidence Failure-Class: none * test(memory): classify maintenance retirement readiness Failure-Class: none * feat(macos): adapt Rewind metadata for triggers Failure-Class: none * test(retrieval): align screen timestamp contract Failure-Class: none * feat(memory): correct ledger facts by amendment Failure-Class: none * test(memory): prove ledger correction on emulator Failure-Class: none * feat(macos): harden local trigger observations Failure-Class: none * feat(agent): search bounded historical facts Failure-Class: none * test(macos): cover trigger observation adapter * fix(memory): gate historical fact retrieval * feat(memory): add gated JIT retrieval strategy * test(memory): prove mixed-version JIT runtime parity * chore(memory): keep JIT gate exports type-safe * refactor(memory): isolate JIT prompt contract * fix(conversations): round-trip owner-scoped references Accept the conversation:<id> references emitted by JIT result cards while retaining strict UUID-only bare IDs and share links. Restrict machine IDs to a bounded safe alphabet so evidence suffixes and path-like values fail closed. Failure-Class: none * test(memory): join JIT citations to evidence envelope * fix(retrieval): enforce JIT conversation search budget Cap JIT summary searches per request and bound database hydration to the projection limit before reads. Preserve the legacy path when JIT is disabled. Failure-Class: FC-unbounded-user-collection-in-prompt * fix(memory): keep JIT retrieval request scoped * test(macos): prove future JIT evidence stays inert * fix(memory): keep JIT card citations request-global Failure-Class: new * fix(retrieval): separate JIT hydration from search Treat gated owner-scoped references as exact hydration without searching transcript text for the reference. Charge every JIT candidate search to the shared four-search request budget, including snippet-bearing requests, while keeping exact hydration free and preserving released JIT-off UUID/share-link behavior.\n\nVerified:\n- cd backend && ./.venv/bin/python -m pytest tests/unit/test_conversation_jit_processing.py tests/unit/test_conversation_exact_reference_search.py -q (58 passed)\n- cd backend && uvx --from pyright==1.1.403 pyright -p pyrightconfig.json --pythonpath .venv/bin/python (0 errors)\n- git diff --check\n\nFailure-Class: FC-unbounded-user-collection-in-prompt * fix(memory): keep repeated JIT cards index-safe * fix(retrieval): satisfy JIT card type contract * fix(retrieval): hydrate collected JIT cards * test(app): preserve answers during delayed evidence requests * test(app): exercise production evidence composition * feat(memories): restore superseded ledger facts * fix(memories): reconcile reverted ledger facts * feat(memories): append reverted ledger facts * feat(memories): synchronize revert client contract * fix(memory): name ledger revert identity * fix(memories): type and enlarge revert controls * fix(memories): fence revert retries and refreshes * fix(memories): fence ledger revert authority * test(memory): count ledger revert rate limit * feat: expose agent-controlled historical facts * feat: reopen standalone ledger facts * feat: add fail-closed JIT QA bundle routing * feat: add safe local JIT QA backend stack * fix: harden isolated JIT QA stack * feat: add explicit multi-source entity timeline * feat(backend): add JIT rollout authority * feat(backend): fence every proactive paid boundary * fix(backend): release proactive quota on cancellation Release the reserved proactive quota exactly once when cancellation interrupts paid-boundary refresh or a provider retry, then re-raise cancellation without emitting retry telemetry. Add deterministic regression coverage for both cancellation points. Failure-Class: FC-proactive-quota-cancellation | new * fix(backend): make proactive quota cancellation safe Detach in-flight Redis reservations on request cancellation and release only admitted slots once they settle. Move direct-provider fallback telemetry behind the fresh paid-boundary rollout check so late kill or unknown decisions cannot report false recovery.\n\nFailure-Class: FC-proactive-quota-cancellation | new * fix(backend): preserve quota compensation during shutdown Keep late Redis reservation compensators outside the ordinary cancellable background-task drain. Desktop and main application shutdown paths now wait for these critical compensators before cancelling ordinary work, with deterministic blocked-thread and lifecycle-order regressions.\n\nFailure-Class: FC-proactive-quota-cancellation | new * fix(backend): use expiring proactive quota leases * fix(backend): make quota finalization clock-safe * fix(backend): isolate jit rollout control plane * fix(backend): close jit control plane safely * fix(backend): emit retry recovery after quota commit * test(backend): keep rollout app contract fast * feat(jit): add guarded proactivity and first-open policies * chore(desktop): mark jit policy as internal * test(desktop): cover jit proactivity policy flow * feat(backend): wire durable JIT first-open processing * feat(desktop): fence JIT proactivity runtime admission * feat: activate authoritative JIT proactivity runtime * fix: harden JIT proactivity authority * fix: close proactive runtime authority gaps * fix(jit): make first-open effects resumable * fix(jit): fence outstanding first-open work * fix(jit): resume app usage receipts * fix(jit): make app usage retries no-op Failure-Class: none * fix(jit): allow completed usage after app deletion Failure-Class: none * fix(jit): register first-open folder query Failure-Class: none * Fix first-open import isolation * feat(memory): govern ledger slots and prompt winners * feat(macos): stage guarded ledger prompt adoption * feat(jit): adopt authoritative ledger prompts on macOS * fix(jit): close ledger adoption authority leaks * fix(jit): reauthorize every ledger migration write * fix(jit): fence ledger cutover publication * fix: keep ledger prompt rollback reversible * feat(jit): add guarded frame request retention contracts * fix(jit): close frame retention authority and evidence lifecycle * fix(jit): make frame retention retries and cleanup durable * fix(jit): make frame evidence recovery and retention complete * fix(jit): close frame retention recovery gaps * Harden temporary frame retention and deployment * fix: harden JIT frame retention and consumption * fix: close JIT frame lifecycle recovery gaps * fix: unify JIT frame authority and retention Failure-Class: FC-split-mutation-authority * docs: keep frame retention guidance lean * fix: retire duplicate frame flag bindings Failure-Class: FC-split-mutation-authority * fix: register frame keyframe queries Failure-Class: FC-split-mutation-authority * fix: serialize frame retention deploys Failure-Class: FC-split-mutation-authority * test: cover frame pixel deletion ordering * style: format cumulative Dart changes * fix(app): retain permanent conversation photo fetches * fix: bound frame vision retention and authority * fix: drain terminal frame request metadata * chore: record internal ledger adoption change * feat(memory): add dark daily sweep authority * feat(memory): harden daily sweep fences and runtime seam * feat(memory): reconcile existing standing triggers in sweep adapter * fix(memory): harden daily sweep recovery and source fences * fix(memory): close daily sweep source producers * fix(memory): close daily sweep review findings * Add dark daily memory sweep authority and recovery * fix(memory): harden daily sweep rejection repairs * test(listen): stub onboarding admission in bootstrap regression The daily sweep PR fences onboarding mode behind the server-owned backend admission (get_backend_onboarding_admission), so the bootstrap regression test now simulates an admitted session instead of failing closed on a real Firestore read. Verification: focused test passes in 1.64s (previously failed after a 4m27s Firestore timeout); full test_listen_runtime_regressions.py + test_onboarding_question_start.py: 26 passed; black --check clean. * fix(memory): close daily sweep rollout and retry cursors * fix(memory): isolate daily sweep lifecycle and retry fairness * Harden daily sweep admission and completed-day staging * fix daily memory sweep reliability boundaries * preserve daily sweep invocation tombstones * close daily sweep invocation lifecycle fences * fix: keep daily sweep lifecycle cleanup active * fix: acquire ledger snapshot client off event loop * fix(memory): preserve migration tier fence without legacy growth * test(memory): prove legacy adjudication race fences * fix(dev): allow bounded ADC readiness refresh * test: keep ledger prepush deterministic * test(memory): register prompt receipt control path * fix(memory): fence ledger writer transitions * feat(backend): preserve closed ledger history in export * feat(memory): define ledger query semantics * fix(backend): fence trigger snapshots on final authority * fix(backend): bypass stale coalesced JIT refreshes * feat(macos): mirror bounded memory evidence Decode generated v3 evidence into a domain mirror, persist canonical bounded JSON through the memory cache, and preserve it across compatibility sync and older-local conflicts. Invalid, future-shaped, oversized, and over-count payloads fail closed without hiding memory text or granting prompt authority. Tests: xcrun swift test --package-path Desktop --filter ServerMemoryV17DecodingTests Tests: xcrun swift test --package-path Desktop --filter MemoryLedgerMirrorTests Tests: python3 scripts/check_desktop_test_quality.py Failure-Class: none * fix(macos): fence and classify memory evidence Keep generated memory fields independent from malformed evidence, distinguish absent valid and invalid evidence states, preserve prior evidence on invalid payloads, and gate replacements on a monotonic server timestamp so stale active evidence cannot resurrect redacted rows. Cover populated-table migration upgrades. Tests: xcrun swift test --package-path Desktop --filter ServerMemoryV17DecodingTests Tests: xcrun swift test --package-path Desktop --filter MemoryLedgerMirrorTests Tests: python3 scripts/check_desktop_test_quality.py Failure-Class: none * fix(macos): preserve evidence fences and scrub redactions Advance evidence revisions for identical valid payloads, fence stale active responses after a local edit, and remove artifact/device pointers from redacted evidence before canonical persistence. Tests: xcrun swift test --package-path Desktop --filter ServerMemoryV17DecodingTests Tests: xcrun swift test --package-path Desktop --filter MemoryLedgerMirrorTests Tests: python3 scripts/check_desktop_test_quality.py Failure-Class: none * chore(macos): record ledger evidence mirror * feat(macos): deep-link local evidence cards to Rewind * fix(macos): fence Rewind frame evidence version * fix(macos): validate Rewind evidence card availability * fix(macos): bind task detail Rewind navigation to local leases * fix(macos): fence Rewind citation owner handoff * chore(macos): register Rewind evidence deep links * test(macos): cover Rewind evidence navigation * feat(desktop): evaluate JIT trigger watchlists locally * feat(desktop): wire authoritative JIT trigger runtime * feat(desktop): bind JIT claims to snapshot authority * fix(desktop): revalidate trigger authority at execution * fix(desktop): keep JIT execution leases live * test(memory): bind standalone reopen to direct-user writer * fix: make JIT QA sign-in self-contained Failure-Class: new Verification: bash desktop/macos/tests/test-jit-qa-target.sh; bash desktop/macos/tests/test-yolo-dev-backend.sh; repaired named-bundle Google sign-in reached authenticated onboarding. * feat(memory): complete JIT policy and native Windows parity * docs(backend): keep service map within context budget * test(macos): cover JIT client and staging flows * chore(backend): declare JIT mirror route policy * fix(backend): use strict Firestore boundary for JIT admission Failure-Class: FC-malformed-doc-read * chore(quality): register malformed-document guard surface * fix(backend): fail closed on malformed JIT authority Failure-Class: FC-malformed-doc-read * refactor(backend): name JIT workflow boundary results * test: repair JIT CI contracts * fix(backend): preserve ledger query exports Retain the explicit same-name re-exports consumed by tests and downstream callers while satisfying the enforced Pyright unused-import boundary after the main rebase. Failure-Class: none * test(backend): isolate gateway setup timing Failure-Class: none * style(memory): format direct-user evidence path Failure-Class: none * test(agent): isolate ACP process-group fallback Failure-Class: none * fix(dev-harness): preserve ownership markers in narrow CI * test(jit): refresh emulator fixtures for current contracts * test(jit): orchestrate local rollout dogfood * test(jit): harden local dogfood authority * fix(dev-harness): install PostHog for CI tests * fix(chat): project server JIT rollout into retrieval Resolve the backend-owned PostHog decision inside the bounded agent setup path and pass only its boolean result to prompt/tool configuration. Unknown or failed authority remains on the released legacy path, while callers cannot self-enroll through configurable input.\n\nVerification: backend/.venv/bin/python -m pytest -q backend/tests/unit/test_chat_async_offload.py backend/tests/unit/test_atomicity_lifecycle_regressions.py (41 passed)\n\nFailure-Class: new * fix(memory): preserve preference writer compatibility Select the agent preference write path from the canonical per-user writer control. Default compatibility mode retains the released MemoryService payload and receipt behavior; ledger mode keeps the retry-stable ledger write, and transition states fail closed.\n\nVerification: backend/.venv/bin/python -m pytest -q backend/tests/unit/test_chat_async_offload.py backend/tests/unit/test_atomicity_lifecycle_regressions.py (41 passed)\n\nFailure-Class: FC-split-mutation-authority * fix(jit): separate migration rollout authority Keep staged JIT chat and proactive exposure independent from legacy-row migration and writer cutover. Migration now requires its own default-off PostHog flag and still rechecks the shared kill switch at every mutation and publication boundary. Repair the isolated conversation-JIT fixture for main's chat-scope import. Verification: 217 focused JIT, chat-scope, migration, and lifecycle tests passed; 28 conversation-JIT fixture tests passed; independent Sol review accepted the split for QA-only dev rollout. Failure-Class: FC-split-mutation-authority * fix(photos): preserve retained image retrieval Treat an empty legacy inline marker as absent when permanent storage is authoritative, while malformed non-empty inline payloads still fail closed. Route live and retained thumbnails through the storage-aware image loader and preserve the conversation identity through the full-screen viewer.\n\nVerification: backend data-export tests 32 passed; Flutter photo-viewer tests 5 passed; focused Dart analysis clean; independent Sol review found and verified the viewer identity repair.\n\nFailure-Class: none * fix(memory): keep disabled daily sweep dark Resolve the backend-owned authority before inventory and require its literal true decision before any UID discovery, registry, cleanup, scheduler, model, or commit work. Missing, malformed, throwing, disabled, and kill-switched authority now exits without touching user data; enabled behavior is preserved.\n\nVerification: 60 focused daily-sweep job, scheduler, and inventory tests passed; independent Sol review accepted the fail-closed gate.\n\nFailure-Class: FC-split-mutation-authority * fix(jit): satisfy fail-closed type contracts * test(backend): admit full runtime contract checks * style(backend): format conversation bound test * test(backend): keep conversation router isolation current * test(backend): admit export boundary duration * fix(macos): persist failed chat turn notice Failure-Class: none * fix(macos): repair JIT rollout admission contracts Failure-Class: none * fix(windows): treat JIT screen evidence as untrusted Failure-Class: none * fix(backend): preserve explicit app failure contract Failure-Class: none * fix(app): finish photo viewer consolidation * fix(backend): make provider writes lock-free against the deletion gate The account-wide legal-hold deletion gate wrapped every GCS upload and Pinecone/Typesense upsert in an exclusive per-uid Firestore mutex with no lease: concurrent same-account writes hard-failed (dropped audio, lost vectors) and a crash between acquire and finish blocked the account's gated operations forever, with no janitor. Provider writes now use a lock-free fence that refuses only during account deletion or a live destructive operation; destructive kinds keep exclusive ownership, an abandoned gate self-expires after six hours, and releasing a gate on the failure path can no longer mask the original error. Failure-Class: none Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(backend): issue onboarding admission at socket connect The completed-onboarding early exit returned False from an Optional[str] function; the listen runtime derives admission via 'is not None', so users who had already completed onboarding were admitted with a fabricated session id — the exact provenance forgery the admission exists to prevent. Separately, the 20-minute admission TTL was anchored to the app-launch state read, so a user reaching the speech-profile step late (or any client that never calls the state endpoint) silently lost onboarding questions and is_user tagging. The bootstrap now issues or refreshes the admission from the durable account state at connect time; completed accounts still can never re-enter, and issuing stays best-effort with the read failing closed. Failure-Class: none Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(backend): keep the released proactivity lane open for legacy clients Gating /v1/desktop/proactivity/completions on the JIT cohort returned 403 to every non-admitted user — which is the entire deployed desktop fleet on deploy day, since shipped clients poll this route continuously and treat 403 as a plain error. Context-bucket extraction and the director would have died fleet-wide, dark cohort or not, and any environment without a PostHog key (local, self-host) would have lost the lane entirely. The route returns to merge-base admission semantics (tier quotas only); JIT admission remains enforced on the JIT reservation routes, and retiring this lane stays a later explicit operation after clients migrate. Failure-Class: none Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(backend): withhold JIT tools and history reads outside the rollout Five new tools (search_knowledge, search_historical_facts, read_playbook, get_entity_timeline, look_at_frame) sat unconditionally in CORE_TOOLS, so every legacy chat request carried their schemas and the model burned tool budget on 'no entries found' answers. They are now filtered per request off the same resolved rollout boolean that gates the JIT prompt appendix. The memories-tab ledger-history endpoint likewise answered every user with a bounded 501-row provider scan that can only ever be empty outside the rollout; it now returns empty without the scan for non-admitted (and unknown/error) states. Failure-Class: none Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(backend): bound rollout control-plane cost and confine sync resolution Synchronous callers resolved rollout flags via per-call asyncio.run against the shared provider singleton, crossing event loops: awaiting a Task attached to another loop raises, a timed-out asyncio.run strands a coalescer entry that then serves stale UNKNOWN forever, and the LRU cache was mutated from multiple threads. Sync resolution now runs on one long-lived control-loop thread with its own authority instance. Unknown snapshots gain a 5-second negative cache — UNKNOWN can never authorize work, and without it a fleet whose flags are simply absent pays one uncached PostHog call per conversation finalization. The screen-sync loop drops its force_refresh (one uncached decide per device per minute fleet-wide) and moves to its own rate bucket so two Macs' background sync can no longer starve conversation photo reads out of the shared 120/hour frame-requests bucket. The first-open policy's kill-switch telemetry label also reported str(Enum) instead of the value and could never match. Failure-Class: none Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(backend): skip eager extraction under a non-compatibility writer mode A ledger-cutover user still ran the full L1 extraction model call at finalization, after which writer admission refused the compatibility write — the conflict retried, exhausted, and failed the entire finalization for every conversation, with the model spend already paid. Extraction now checks the canonical writer mode first and skips when the daily sweep owns memory formation; only a positively-read non-compatibility mode skips, so any control-state read failure preserves the legacy eager path. Failure-Class: none Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(backend): export tolerates byte-less legacy photo rows A conversation photo row carrying the legacy empty inline marker and no storage reference failed the whole portability export forever, though it holds no durable image anywhere — there is nothing to omit. Such rows now export as metadata with a content-free gap reason. Frame requests in a retained state keep the fail-closed contract via an explicit require_bytes parameter. Failure-Class: none Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(windows): harden JIT delivery, admission, and bootstrap boundaries Five verified defects: (1) the exclusive notification delivery slot leaked on any throw between reservation and commit — one SQLite hiccup during a JIT turn permanently silenced every proactive lane; the span is now try/finally-guarded and stale slots expire after ten minutes. (2) The ambient lane interpolated the raw window title into a tool-capable agent prompt; the turn now carries only the opaque context handle plus a sanitized executable name, framed as untrusted data like the nano-triage lane. (3) Google Calendar was fetched every ~60s before admission, so non-cohort users with Google connected paid ~1,440 reads a day for a refused feature; observation now gates calendar evidence on the cached authority. (4) Rollout-authority errors reset the cache and retried every frame (~1 req/s offline, forever); failures now back off from 30s to 10 minutes. (5) An unguarded JIT schema exec inside the shared database open could abort local storage for all features; the mirror bootstrap is now isolated, keeps the host-facing tables alive, and JIT stays inert when unavailable. Also re-checks the control-plane owner before committing the toast so an account switch mid-turn cannot show the previous owner's advice. Failure-Class: none Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(macos): restore screen provenance, guard migrations, fence chat turns Four verified defects: (1) every pre-existing screen-derived task lost its 'Screen context / Open Rewind' source row because the new evidence policy dropped any provenance that is not rewind_frame.v1; the merge-base fallback row is restored for capture.v2/legacy refs (a test flipped to match the regression is restored to its merge-base assertions). (2) RewindDatabase published its pool before migrating, latching a failed migration into a permanent false-initialized state, and three unguarded ALTER TABLE memories migrations died with duplicate-column on machines that ran earlier builds of this branch; migration now precedes publication and the ALTERs/CREATEs are existence-guarded. (3) EventKit was queried on every context visit before the flags check; non-admitted owners now build no observation inputs. (4) A failed chat turn's reconstructed notice could be appended into a different conversation's transcript when the user switched sessions or cleared chat mid-flight; both transcript resets now revoke the active turn like selectApp already did. The pre-terminalized discard class (user Stop/watchdog) still drops the durable notice on relaunch — pinned by a characterization test in agent/tests/conversation-journal.test.ts with the least-invasive fix described there. Failure-Class: none Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(testing): resolve firebase-tools from the checked-in dependency npx --prefix resolves the package bin against the current directory on some npm versions, and the admission runner deliberately launches from an isolated temp dir (firebase writes debug logs to cwd) — surfacing as 'sh: firebase: command not found' on hosts without brew node@22. Prefer the vendored node_modules binary when it matches the pin; npx remains the fallback. Failure-Class: none Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(backend): keep one eager-extraction call site for the surface ratchet Failure-Class: none Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(backend): gate eager extraction at the public boundary The writer-mode skip moves from _extract_memories_inner to extract_memories: the replace-policy contract test pins the inner helper to exactly the canonical replacement path, and the public boundary is the better seam anyway — a sweep-owned user now skips parity capture and usage tracking along with the model call. Failure-Class: none Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(listen): stub onboarding admission issuance in bootstrap regression The connect-time ensure call landed in a harness that only stubbed the read, so the bootstrap test paid an extra real-module exception path and grazed the 0.30s fast-unit CPU budget under fanout load. Stub the issuance like the read. Failure-Class: none Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(listen): allowlist the bootstrap regression's CPU budget The full listen-runtime bootstrap test measures exactly at the 0.30s fast-unit CPU budget under a saturated pre-push fanout (CPU inflates ~2x there per the guard's own notes) while passing comfortably alone. It exercises deliberately heavyweight machinery; record it as an intentional exception rather than trimming the coverage. Failure-Class: none Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(backend): keep list(CORE_TOOLS) literal through JIT tool gating The JIT-only tool filter replaced the list(CORE_TOOLS) assignment with an inline comprehension, which broke the prompt-cache structural invariant (test_prompt_cache_optimization.py::test_core_tools_used_in_both_functions). Restore the list(CORE_TOOLS) copy and apply the JIT-only filter as a conditional pass, preserving rollout semantics and tool order. * feat(jit): drop automatic goal updates from the JIT featureset Product decision (David, 2026-08-26): goals change only through explicit user action for JIT-admitted conversations. Goal progress is no longer a first-open obligation — the effect is removed from FIRST_OPEN_EFFECTS and the worker, and the policy plan can no longer express deferring it. Legacy obligations carrying a pending goal_progress row are normalized away and complete on the remaining two effects. Non-JIT (legacy eager) conversations keep today's automatic goal updates unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(sweep): one summary-spine agent pass per day, with folder backstop Replaces the per-conversation transcript extractor in the completed-day producer with a single two-phase agent run: the whole day's conversation summaries go in as one bounded spine (200 conversations / 120k chars — effectively unreachable, so heavy days no longer stall the cursor), and the agent may request up to 8 raw transcript excerpts (8k chars each) to verify specifics before finalizing. At most two provider calls per user per day, both inside the existing at-most-once invocation fence; the staged page carries the memory candidates AND folder assignments for the day's unopened, unfiled conversations, applied idempotently (first-open or user assignment always wins). Memories must cite their source conversations; uncited output is dropped. The cost gate becomes a worst-case ceiling checked before any call. The onboarding cold-start channel keeps per-conversation transcript extraction unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(sweep): harden the daily agent prompts from a real-data lab pass Iterated on one real heavy day (26 conversations) with strong- and weak-model stand-ins, an adversarial judge, and hand-verified transcript ground truths. Rules added, each pinned to an observed failure: actor binding in active voice with a personal-attribute gate (a discussed or recommended topic is never someone's attribute; judgments about named people are stored as assessments); decision-state basis labels binding the verb (decided/proposed/observed, discussed-no-outcome dropped); salience ordering (money, metrics, named-party intent, identity, and durable decisions before any operational fact; one fact per memory); never guessing the direction of an invitation/offer/commitment (verify or drop); and no deferring the whole answer to verification. The agent output schema gains a 'basis' field. The memories QoS call-site inventories now count the daily-sweep agent's call site (3 -> 4). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(sweep): tune the daily agent prompts against the real memories model Ran the assembled prompts against gpt-5.6-luna (the real 'memories' route model) on the same real day. Three refinements from observed behavior: the basis label no longer leaks into memory text (metrics read as metrics, not 'David observed that…'); the never-guess-direction trigger is mechanical (passive/verbless summary phrasing or 'Speaker' as the actor forces a transcript_request — luna confidently inverted 'Tim: Invited to New York' until this; with it, phase B verifies and corrects to the true direction), hedging is itself a request signal, and nothing high-salience may be silently dropped; and a rich-day yield anchor (8-16 memories for 15+ conversations) counters the model's over-pruning without inviting padding. Final real-model run: 11 true memories + 2 legitimate verification requests, zero fabrications, ~22k tokens (~2 calls) for a 26-conversation day. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(sweep): profile-maintaining slots, ledger lookups, cache-ready prompts The daily agent now sees the user's current profile (the same get_prompt_memories seam chat uses — the ledger render for migrated users), may run up to 4 owner-scoped prior-memory keyword lookups (provider fail-soft; hits re-read through the canonical store before disclosure) to dedup and supersede, and may name a slot for standing attributes — an occupied slot becomes an amend through the existing canonical occupancy check, so the daily run maintains the rendered profile with no second write path. Both phase prompts share a byte-identical prefix (pinned by a test) and pass a per-user prompt_cache_key through get_llm; measured against gpt-5.6-luna the provider cache is exact-match rather than prefix-based today, so this is future-proofing rather than present savings. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(sweep): type the memory-searcher seam for the pyright contract CI's authoritative typecheck rejected the untyped lookup seam (memories.py: list(Any or [])). The searcher is now Optional[Callable[[str], Sequence[str]]] and results are built through a typed comprehension; behavior unchanged (absent or failing searcher still degrades to an empty result block). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: repair four main-inherited CI breakages after sync origin/main is currently red on its own tip; syncing it into this PR inherits the breakage, so the fixes ride here: - subscription.py: drop the unused get_byok_keys import (pyright reportUnusedImport fails the Backend unit suite). - AppState+Transcription.swift: explicit self for alertPresenter inside the escaping showAlert completion (strict-concurrency compile error in all three Desktop Swift lanes, shipped red on main by d49f978512). - AppState+Permissions.swift: pinned swift-format drift from the same main commit (desktop-swift-format-lint). - web/app/bun.lock: add the prettier + prettier-plugin-tailwindcss entries 64db30c791 pinned in package.json without updating the lockfile (frozen install fails web-app-checks). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(sweep): close the second review round's findings Three parallel adversarial reviews over the post-takeover additions: - Clamp every model-controlled phase-B input (draft memories, request reasons, lookup queries/results) and add the clamped worst case to the pre-call cost ceiling, which previously under-estimated phase B. - Attest an empty consumed day when the staged page carries an older stage schema version instead of stalling the cursor forever on every deploy-boundary schema bump. - Make the folder backstop's unfiled check and write share one transaction so a concurrent first-open/user assignment always wins. - Let equal-rank sweep candidates amend sweep-authored slot occupants: the profile-maintenance path froze after a slot's first write. User statements still always win; slotless subject matches still dedup. - Neutralize ``` fences in summaries/excerpts/lookup results, and mark raw-transcript fallback rows '(unstructured transcript excerpt)' with a prompt rule refusing slots/personal attributes from them without transcript verification (test pins the marker to the rule). - Remove the dead first-open goal-authority threading left by the goals removal, and update the stale jit-first-open-runtime doc. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: repair three more main-inherited breakages All shipped red on main and only surfaced once earlier failures were cleared: - AppState.swift: move the alertPresenter default out of the stored property initializer — Xcode 16.4's SILGen segfaults (signal 11) emitting it, which failed all three Desktop Swift lanes even after the explicit-self fix. - test_byok_security.py: main's BYOK rewrite (d0e3a4eb3a, 1da8880175) changed request_has_llm_byok_key to per-provider enrollment checks and made partial headers fail closed, but left the tests targeting the old get_byok_keys()-based lenient contract (masked on main because pyright failed before pytest ran). The tests now assert the shipped strict contract their own docstrings already describe. - subscription.py: pinned-black formatting for the BYOK fallback expression (the Formatting lane rejects the file as main wrote it). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(tests): stub the chat-agent gateway route pin in the chat router harness Main's a6988be309 made routers.chat import CHAT_AGENT_ROUTE_DIRECT / get_chat_agent_route from utils.llm.gateway_client, but the chat-router test harness (and test_chat_file_upload_unsupported's local override) stub utils.llm.gateway_client without those symbols, so every suite that loads the real router failed at import — masked on main because pyright fails its Backend unit suite before pytest runs. Ninth main-inherited repair in this sync. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(tests): teach test_chat_quota's utils.byok stub the rewritten import surface utils/subscription.py now imports get_byok_uid and get_cached_byok_state (main's BYOK rewrite); the module-scoped utils.byok fake predates them, so reloading subscription under the fake raised ImportError at setup — and the polluted process took test_chat_openapi_operation_ids and test_desktop_screen_crisp down with it in CI's batched run (all three pass standalone). Tenth main-inherited repair, same pyright-masked pattern. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(tests): update three more suites for main's BYOK/gateway import surface Same pyright-masked pattern as the harness and test_chat_quota repairs: - test_desktop_transcribe stubbed utils.llm as a non-package, so routers.chat's new utils.llm.gateway_client import could not resolve (50 failures); the submodule is now in its stub list. - test_paywall_reconnect_gate's BYOK escape-hatch tests never set the request uid context that the enrollment-verifying rewrite requires (middleware sets it in production); they now do, and teardown clears it. - test_chat_session_app_identity's enforce_chat_quota stub rejected the new required_llm_provider keyword. All three suites pass locally (69 + 35 + 6). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(tests): enroll fingerprints in the desktop BYOK tests PR #11454 moved macOS BYOK activation to enrollment-verified fingerprints (isByokActive and usableBYOKEnvironment gate on persistEnrolledFingerprints), and its own test lanes shipped red: the tests store raw keys but never enroll them, so every key reads as inactive. Their teardowns already clear enrollment — the setups now enroll what they store, matching the production activation path. All 8 previously-failing cases (BYOKPaywallTests + the two AgentRuntimeProcessTests BYOK-environment cases) pass locally. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(deploy): enable the daily memory sweep on development The sweep's five deployment inputs were pinned off in every environment, so cohort enrolment alone could never start it -- turning it on for a dogfood account required a second PR. Development now carries the live values: - ENABLED/MODEL_ENABLED on, so the job stops exiting at its first authority gate and the model authority can budget a route. - MODEL_NAME pinned to gpt-5.6-luna, which is the declaration interlock the runner checks against get_model('memories') before any provider call. - MAX_MODEL_COST_USD 0.80, the worst-case pre-call ceiling for a maximal day including phase B's clamped draft/reason/lookup overhead. - COHORT_ENABLED on with COHORT_FLAG daily-memory-sweep-v1, so enrolment is a per-uid PostHog boolean and an unnamed cohort stays a closed rollout. Production is deliberately untouched and stays fully pinned off. The job still cannot form a memory for anyone until that flag exists and resolves true for a uid, which remains a control-plane action rather than a deployment one. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(firestore): terminate the daily-sweep occupant indexes with __name__ The six daily-sweep occupant lookups were the only declarations in the manifest without a trailing __name__ field -- 63 of 69 entries carry one, and main had none missing it. Firestore appends the terminator itself and reports the index back that way, so these six could never match the live inventory. The failure mode is not a missing index; the indexes build fine. It is that reconciliation never converges: every run reports the same six as missing, tries to create them, and fails on ALREADY_EXISTS. That takes down the Firestore schema workflow on both environments permanently, and with it the development backend deploy's readiness gate -- the same class of outage the workflow's own header records from the hourly_usage index in PR #11979. The derived specs previously appended their extra predicates to the base spec's index_fields, which would have placed them after the terminator, so the shared prefixes are now named explicitly and each spec ends with __name__. Verified against real Firestore: reconciliation reports zero missing indexes in both based-hardware and based-hardware-dev. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: close final JIT rollout and CI gaps Fence direct JIT tools and frame pixels, keep Windows account wipes safe after optional schema failures, and repair inherited CI regressions. Failure-Class: none --------- Co-authored-by: David Zhang <9387252+Git-on-my-level@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> | 19 天前 | |
fix(app): stop chat transcript scrunching on scroll Failure-Class: new Rebuild the chat list only when follow-mode changes and keep ListView padding stable. Per-frame setState plus a 10→72 padding flip was caching tiny sliver extents for markdown and citation titles. Verification: bash test.sh test/widgets/chat_scroll_layout_test.dart (5 passed). Sabotaging nextMode to always return freeScrolling on drag fails the rebuild-once and continued-drag tests. | 15 天前 | |
Repair PostHog telemetry ownership and coverage (#10660) * Repair mobile device lifecycle telemetry * Restore Omi device purchase intent telemetry * Track each permissions interstitial presentation * Report backend account deletion outcomes * Add macOS device pairing telemetry * Restore Windows PostHog delivery * Guard analytics emitter reachability * Define PostHog regression alert contracts * Document load-bearing analytics events * Add macOS pairing telemetry changelog * Cover device vendor mapping in desktop flow * Use typed pairing defaults in device tests * Stub the new telemetry module in account-deletion isolation tests Both files close the utils namespace (__path__ = []), so the added utils.integration_telemetry import in account_deletion.py resolved only against sys.modules and raised ModuleNotFoundError in CI. * Stop a Swift property modifier leaking onto the next analytics method The modifier scan walked back from func to the previous brace, so a 'private var x' declared directly above 'func setX' donated its private and the method was audited as an unreachable helper. Main's integrationConnect test seam hit exactly that shape on merge. Baseline records main's integrationConnect call-site counts and its test-only seam. * fix(telemetry): avoid deleted-UID PostHog identity and pin Windows host Account-deletion completion/failure telemetry now uses a service distinct_id with $process_person_profile=false so wiped Firebase UIDs are never re-identified in PostHog. Windows PostHog host is fixed to the CSP-allowed us.i.posthog.com origin. * fix(ci): black-format deletion telemetry and ratchet line counts Format account_deletion.py for black 26.5.1 and raise product-file line-count baselines for users.py and storage.py with justifications; ratchet OmiApp.swift down to the post-repair line count. * fix(ci): classify analytics test seams and re-baseline against main The reachability tripwire audited `set*TelemetryCaptureForTests` as if it were a production emitter, so every new scoped test seam had to be hand-added to `public_orphans` — main's `setSuggestionAssistantTelemetryCaptureForTests` failed the check for exactly that reason. Installing or forwarding to a test capture is production-unreachable by construction, so `emitters()` now drops `*ForTests`/`*ForTesting` methods before the audit and the five seam entries leave the baseline. The baseline also predated main. Regenerating it records main's own drift: 25ea86a1d2 consolidated the onboarding Google-connect flow through ConnectorImportRunner, dropping the four SBOnboardingModel+Steps emit sites (integrationConnectAttempted 3->1, Succeeded/Failed 2->1 — each still requires its remaining call site), and main's live-suggestion work grew suggestionAssistantGateOutcome to 3 and suggestionAssistantDeliveryOutcome to 2. Only those ten entries moved; nothing else was absorbed. Verified: - python3 .github/scripts/check_analytics_reachability.py -> "analytics reachability static tripwire passed" - python3 .github/scripts/test_check_analytics_reachability.py -> Ran 7 tests, OK (new test_test_only_seams_are_not_audited_as_emitters) * fix(desktop): assert the pinned PostHog host at runtime, not in source text The host-pin regression scraped analytics.ts and asserted the file never contains "VITE_POSTHOG_HOST" — which the comment explaining why the override was dropped also matches, so the check failed on its own explanation. It was a static tripwire either way; it never proved the override was inert. It now stubs VITE_POSTHOG_HOST to an origin the renderer CSP does not allow, re-imports the module, and asserts fetch still goes to us.i.posthog.com. Verified in desktop/windows: - npx vitest run src/renderer/src/lib/analytics.test.ts -> 6 passed - restoring the `import.meta.env.VITE_POSTHOG_HOST ||` fallback in analytics.ts fails it with Received "https://not-in-csp.example.com/i/v0/e/", so the test fails for the reason it claims - npx prettier --check on the file -> clean --------- Co-authored-by: Max Carter 祁明思 <136312656+undivisible@users.noreply.github.com> | 1 个月前 | |
fix(app): remove no-op non-null assertions Removes all 20 unnecessary_non_null_assertion warning-severity diagnostics. Each removed '!' sat on an expression the analyzer proves non-nullable (AppLocalizations.of(context) which returns non-null in this codebase's generated l10n, conv.structured, res.text), so removal is a strict no-op — the assertion could never have fired. Edits were applied at the exact (line, column) positions reported by dart analyze --format=machine, with a guard verifying each removed character was a postfix '!' (not a prefix negation). Verification: - dart analyze: UNNECESSARY_NON_NULL_ASSERTION -> 0 (was 20), no ERROR-severity diagnostics, no other rule changed. - app/scripts/analyze_ratchet.sh: only unnecessary_non_null_assertion improved (20->0), no rule regressed. - --update-baseline: git diff shows only that key dropped. - bash app/test.sh: 763 passed, 0 failed. | 2 个月前 | |
Merge remote-tracking branch 'origin/main' into codex/universal-memory-task-convergence # Conflicts: # backend/docs/llm/model_endpoint_inventory.yaml # backend/tests/unit/test_llm_gateway_coverage_guardrails.py | 1 个月前 | |
fix(app): breathing room above conversation markdown headers (#12933) * fix(app): add breathing room above conversation markdown headers flutter_markdown defaults h*Padding to zero, so ##/###/#### sections in conversation summaries sat flush against the previous block (#5622). Failure-Class: none * chore: retrigger CI after flaky local_segment_store test Dart Analyze & Tests failed once on CaptureProvider listener persists when a live session is active (empty digest). Unrelated to markdown header spacing; passes locally and on main. Empty commit to re-run. Failure-Class: none * fix(app): apply markdown header padding to h5 and h6 Match the existing breathing-room const across every header level flutter_markdown defaults to zero padding. Failure-Class: none * chore: drop accidental Flutter ephemeral Package.swift change Failure-Class: none | 7 天前 | |
feat(desktop): Activity's row is a View, and every page it opens can get back Follow-up to #12042, which shipped the Activity spine, its navigating chip row and the summary sections. Three things that PR left wrong, plus the five defects an independent review found in it. **The row's header called itself `Filter`.** Its chips open pages; nothing about them narrows a list. The word now follows the behaviour rather than the surface — Home's search results still say `Filter`, because there the chips genuinely do filter in place, and `QueryPanelChipBehavior` is what decides. It was hoisted out of the generic panel so a test can assert the two cases without naming three view types. **`Tasks` and `Rewind` were in the row.** Both already have their own pill in the bar two inches above; a second control to the same place is not a shortcut. The row is now exactly the hub's four pages, and a test pins that — it is the reachability mechanism, so a page dropped from it must fail a test rather than quietly become unreachable. **The row is a one-way door.** It lives on Activity's panel and does not travel, so opening Conversations, Memories or Brain Map left no way back except knowing the top-bar pill was the answer. Each of those pages, and the chat-first Conversations route that mounts its own host, now carries a `‹ Activity` control that names where it goes. Review findings fixed: - **The standalone Memories page lost its glass.** #12042's `PageGlassLane` rule keyed panel-ownership on `.conversations` *and* `.memories`; only the first is the hub. In the modern shell `SidebarNavItem.memories` renders a standalone `MemoriesPage` that paints no ground, so pressing `Activity` and then reaching Memories by a route that does not reset the persisted destination drew its rows onto the wallpaper. The test added with it asserted the wrong claim for that index and locked it in; it now asserts the opposite. - **`SummarySection.id` was the heading.** Model-written blocks repeat headings and may have none, and duplicate `ForEach` ids make SwiftUI drop or duplicate rows. - **Desktop and mobile disagreed on provenance.** Flutter suppresses `sections` when a summarization app produced the summary; desktop rendered them anyway, stacking a second unattributed Omi summary under the app's. Desktop now matches. - **Flutter: an empty-content app result hid the sections and reported "no summary."** `getSummarizedApp()` returned `appResults[0]` without checking it carried anything. - **Flutter: one malformed section killed the whole conversation decode.** The sections loop threw where the `actionItems` and `events` loops beside it tolerate bad entries. Also: `ActivityDestinationChip.hubDestination` is non-optional, so "every chip opens a hub page" is a compiler guarantee; the orphaned `MemoryHubDestination.switcherOrder` is gone with four tests that had become assertions about nothing; and four e2e flows plus several doc comments stop describing the deleted switcher and the retired `Library` pill. Verified: `scripts/swift-test-suites.sh` — 665 suites in isolation, 0 failures. `app/test.sh` — 1421 passed, 5 skipped, 0 failures, each Flutter fix confirmed to fail without it. `check-e2e-flow-coverage.py --strict` — exit 0. Clicked through a real signed build for the `View` header, the four-chip row, and `‹ Activity` on Conversations and Brain Map. INV-NAV-1 INV-UI-1 Failure-Class: FC-mirrored-model-omits-new-member Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> | 24 天前 | |
Maps: keyless static previews + native-map handoff; fix Unknown location labels (#12841) * feat(backend): authed static-map proxy route with Redis-cached renders GET /v1/static-map renders dark-styled Google Static Maps images server-side (the only Maps key stays server-restricted) and caches the bytes in Redis keyed by the quantized pin set + size, so repeat renders of the same place across users/sessions cost one upstream call per distinct pin set per week. utils/static_map.py is the single provider seam for app map previews; swapping providers touches that module only. - pins parsed/bounded/de-duplicated/sorted, capped at 50; ~11m quantization makes users at the same place share one cached image - one pin centers at street zoom; several pins use provider auto-fit - failures return 502 (never cached) - the app renders its offline pin-dot canvas; auth prevents an open proxy on the project key - rate policy static_map:get (240/h per uid) stops hot loops; cached hits are one Redis read Tests: tests/routers/test_static_map.py (auth 401, 400 malformed pins, 502 upstream failure, 200 + private cache headers, cache hit/miss/order- insensitivity/no-cache-on-failure/missing key tolerances). Verified: .venv/bin/python -m pytest tests/routers/test_static_map.py -q -> 14 passed; scan_async_blockers clean. * fix(backend): enrich sync-path geolocation at the pipeline coordinator Failure-Class: FC-sync-geolocation-missing-address | new | none Offline-synced conversations were created with raw coordinates and no address: REST create, developer API, integration ingest, and live finalization all run resolve_geolocation, but the sync path shipped raw coords straight through. Recaps built from synced conversations showed 'Unknown' timeline rows. _run_full_pipeline_background_async - the one coordinator both the inline and Cloud Tasks dispatch branches call - now enriches the job's geolocation once, before any segment is processed and before the concurrency gate (no slot held during the geocode call). The resolver keeps the caller's exact coordinates and returns its input unchanged on any geocode miss/error, so a failure never drops the location. Tests: tests/unit/test_sync_geolocation_enrichment.py drives the real coordinator with a fake geocoder - one geocode per job (not per segment), enriched value reaches every segment, raw geolocation survives a geocode failure, None passthrough. Fixture stub lists in test_sync_v2 / test_sync_transcription_prefs gained the utils.conversations.location stub with an identity passthrough (the real resolver's miss behavior). Verified: pytest tests/unit/test_sync_geolocation_enrichment.py test_sync_v2.py test_sync_transcription_prefs.py -> all passed. * fix(backend): fill empty daily-summary pin addresses at read time Failure-Class: FC-sync-geolocation-missing-address | new | none generate_comprehensive_daily_summary copied c.geolocation.address verbatim, so pins from conversations created before write-time enrichment (the sync path) rendered as 'Unknown' recap timeline rows. The pins loop now fills an empty address through the shared ~100m- rounded geocode cache - the same entries write-time enrichment writes, so an already-enriched day costs no extra upstream call. All three callers are sync contexts, so the sync geocoder is used. A geocode miss or error leaves the address empty and keeps the pin (the app labels it 'Unknown'); regenerating a summary retroactively fixes history with no migration. Tests: tests/unit/test_daily_summary_location_address_fill.py (filled from geocoder with exact coords preserved, present address skips the geocoder, miss and exception both keep the pin address-less). Verified: pytest tests/unit/test_daily_summary_location_address_fill.py test_daily_summary_zero_coordinate_locations.py -> all passed. * feat(app): OmiMapPreview - one static-map widget with an offline pin-dot canvas Every map preview now funnels through a single widget backed by the authed backend static-map proxy (GET /v1/static-map): URL built by buildOmiStaticMapUrl (pins quantized to 4 decimals to match the server's cache quantization, deduped, capped at 50), image fetched with the session's Authorization header, and a deterministic dark canvas with one white dot per pin while loading, offline, or on any failure - never an error state. Product direction: previews in-app, tapping opens the native map app via MapsUtil.launchMap. The conversation detail geolocation card migrates from the client-keyed getMapImageUrl URL to this widget; getMapImageUrl is removed (maps_util keeps launchMap + place URL), taking the last direct client-side Google Static Maps call with it. Verified: flutter test test/widgets/omi_map_preview_test.dart test/widgets/daily_summary_card_test.dart (widget+URL builder tests); flutter analyze clean on changed files; analyze_ratchet.sh passed. * feat(app): replace CARTO tile maps with static previews; drop flutter_map CARTO began enforcing API keys on basemaps.cartocdn.com (keyless requests now return watermarked tiles), and Omi has no CARTO key. All three flutter_map surfaces now render OmiMapPreview instead - zero remaining tile traffic, no keyed tile provider in the app: - conversation map page: static preview of every ~100m cluster anchor (tap opens the native map app) above a grouped 'conversations at this place' list; single-conversation places open the conversation directly, multi-conversation places keep the cluster bottom sheet; grouping logic and cluster-row keys unchanged - daily summary card: 96px preview strip of the day's pins in the recap carousel (the highest-frequency map surface) - now one cached server-side render per distinct pin set instead of dozens of tiles per card - daily summary detail 'Your Day's Journey': 200px preview; image tap still opens the day's first stop (Apple Maps cannot take waypoints via map_launcher), per-stop timeline rows unchanged flutter_map is removed from pubspec (latlong2 stays - journey grouping uses it). The client-embedded Env.googleMapsApiKey goes with it: all static maps come from the server-restricted key behind the proxy now. envied outputs regenerated; test EnvFields stubs updated to match. Verified: bash test.sh / flutter test -> 1758 passed, 5 skipped, 0 failed; scripts/analyze_ratchet.sh passed (9 lint counts improved). * test(app): update map-surface tests for static previews - daily summary card: assert the preview strip, proxy URL pins/quantize/ dedupe, offline canvas fallback, and no-map-for-invalid-coordinates (was: tile provider request counting) - daily summary detail page: drop the TileProvider seam (widget no longer takes one) - conversation map groups: group-card key + cluster sheet rows replace marker keys; journey/env stubs gain the EnvFields change Verified: flutter test on all four files -> passed. * test(backend): stub utils.conversations.location in the cloud-tasks loader pipeline.py now imports async_resolve_geolocation at module scope; the cloud-tasks stub loader replaces its parent packages with MagicMocks, so the submodule import fails in file isolation (the earlier green run leaned on a module cached by another test file). Adds the explicit stub with an identity passthrough - the real resolver's miss behavior. Verified: BACKEND_UNIT_TEST_FILE_LIST=<this file> bash test.sh -> 91 passed. * chore(app): drop GOOGLE_MAPS_API_KEY from the env template The client no longer embeds a Maps key - all static-map renders go through the server-restricted key behind GET /v1/static-map. * test(backend): provide redis r on the usage-tracking stub set external_integrations now imports utils.conversations.location (daily- summary address fill), which imports the redis client symbol; the usage-tracking stub environment replaces database.redis_db with an empty module, so the import fails in file isolation. Give the stub an r. Verified: BACKEND_UNIT_TEST_FILE_LIST=<this file> bash test.sh -> 21 passed. * docs(backend): note the static-map provider URL budget with current limits * chore(backend): declare the static-map route policy in the manifest * fix(backend): drop the redundant isinstance on the typed pins parameter * fix(app): hold the preview canvas until the auth header resolves The first build fired the authed proxy request before getAuthHeader() resolved; CachedNetworkImage keys its cache by URL, so the later header-arriving setState never re-fetched - a permanent 401 fallback canvas. The widget now renders the pin-dot canvas (which already doubles as the error/offline path) until the header resolves, and only then mounts the network image with the Authorization header attached. The header resolver is injectable (authHeaderProvider) so tests control when it completes; an explicit imageUrl (test seam) skips the gate. Tests: new regression case asserts no CachedNetworkImage while auth is unresolved and the authed image after the completer fires; the card tests repoint URL assertions at the preview widget's pins (URL building has its own unit tests). Verified: flutter analyze clean; flutter test (full) -> 1759 passed. * fix(app): restore the conversation_map_marker automation key on group rows The per-place tappable key predates the static preview; keep it stable for automation (the PR brief promises preserved keys). Verified: flutter test test/unit/conversation_map_groups_test.dart passed. * fix(backend): normalize static-map dimensions, dedup render stampedes, count canvas fallbacks Review findings on the static-map proxy: - Dimension handling: one proportional scale factor (min(1, 640/w, 640/h)) computed once in fetch_static_map replaces the independent per-axis clamps - aspect is preserved and every request that differs only by scale normalizes onto the SAME cache entry (cache key and provider URL both use the effective size). - Stampede dedup: after a miss, a per-key render lock (r.set nx, 30s TTL) elects one renderer; concurrent misses poll the cache (0.25s interval, 15s budget) for the holder's result. Lock-held-timeout and lock-unavailable (Redis broken) both fail OPEN to an unlocked render - a lost lock never becomes a 502, and a broken Redis never pays the 15s wait budget. - record_fallback (component=static_map, provider_static_map -> client_pin_canvas, outcome=degraded) fires before the 502 so the degrade is counted in the shared telemetry. Tests: proportional normalization + shared cache entry, oversized fetch normalizes (size=640x150), concurrent misses render once, waiter polls a foreign lock holder to its hit, wait-timeout renders unlocked, 422s for out-of-bounds width/height via TestClient (Query contract), fallback telemetry kwargs. Verified: pytest tests/routers/test_static_map.py -> 21 passed; pyright (scripts/typecheck.sh) 0 errors, no findings in changed files. * fix(backend): cap daily-summary geocode attempts; boost-exempt static_map:get - The read-time address fill now bounds geocode ATTEMPTS at 10 per summary generation: cache hits are cheap but attempts are the deterministic wall-clock bound (10 x the geocoder's 10s worst case stays inside the job budget). Pins past the cap keep an empty address and the app's 'Unknown' fallback. Test: 11 empty-address pins -> 10 filled, 11th untouched, 10 geocoder calls. - static_map:get joins the boost-exempt rate policies: under prod's RATE_LIMIT_BOOST the 240/h hot-loop cap would otherwise resolve to 24k/h and stop protecting the billable provider calls. Verified: pytest test_daily_summary_location_address_fill.py -> 5 passed; utils.rate_limit_config import shows static_map:get in BOOST_EXEMPT_POLICIES. * test(backend): self-contained enrichment fakes; model the resolver short-circuit - test_sync_geolocation_enrichment no longer imports the transcription-prefs fixture module: sharing it loaded real heavyweight modules (GCS/Firestore protos, anthropic/jiter) inside the stub window, and stub_modules' teardown evicted them so later files broke on re-import (duplicate proto registration, jiter NameError) when run in one process. The fakes are now minimal and self-contained - every heavy leaf is stubbed, light enum modules (stt.outcomes, sync.lanes, sync.telemetry) stay real. - The None-geolocation test now models the real resolver's short-circuit (falsy input returns immediately, attempts recorded only for truthy input) and asserts: resolver called exactly once with None AND zero geocode attempts. Docstring states what is actually proven. - One-line caveat at the cloud-tasks loader injection site: new submodule imports must be added to heavy_deps explicitly (MagicMock parents do not resolve submodules). Migrating that hand-rolled loader to stub_modules would cascade across its 37 call sites - deliberately left as-is. Verified: pytest on the combined focused group (static_map, summary fill, sync enrichment, cloud tasks, sync v2, transcription prefs, geocode resolve) -> 372 passed in one process. | 9 天前 | |
Maps: keyless static previews + native-map handoff; fix Unknown location labels (#12841) * feat(backend): authed static-map proxy route with Redis-cached renders GET /v1/static-map renders dark-styled Google Static Maps images server-side (the only Maps key stays server-restricted) and caches the bytes in Redis keyed by the quantized pin set + size, so repeat renders of the same place across users/sessions cost one upstream call per distinct pin set per week. utils/static_map.py is the single provider seam for app map previews; swapping providers touches that module only. - pins parsed/bounded/de-duplicated/sorted, capped at 50; ~11m quantization makes users at the same place share one cached image - one pin centers at street zoom; several pins use provider auto-fit - failures return 502 (never cached) - the app renders its offline pin-dot canvas; auth prevents an open proxy on the project key - rate policy static_map:get (240/h per uid) stops hot loops; cached hits are one Redis read Tests: tests/routers/test_static_map.py (auth 401, 400 malformed pins, 502 upstream failure, 200 + private cache headers, cache hit/miss/order- insensitivity/no-cache-on-failure/missing key tolerances). Verified: .venv/bin/python -m pytest tests/routers/test_static_map.py -q -> 14 passed; scan_async_blockers clean. * fix(backend): enrich sync-path geolocation at the pipeline coordinator Failure-Class: FC-sync-geolocation-missing-address | new | none Offline-synced conversations were created with raw coordinates and no address: REST create, developer API, integration ingest, and live finalization all run resolve_geolocation, but the sync path shipped raw coords straight through. Recaps built from synced conversations showed 'Unknown' timeline rows. _run_full_pipeline_background_async - the one coordinator both the inline and Cloud Tasks dispatch branches call - now enriches the job's geolocation once, before any segment is processed and before the concurrency gate (no slot held during the geocode call). The resolver keeps the caller's exact coordinates and returns its input unchanged on any geocode miss/error, so a failure never drops the location. Tests: tests/unit/test_sync_geolocation_enrichment.py drives the real coordinator with a fake geocoder - one geocode per job (not per segment), enriched value reaches every segment, raw geolocation survives a geocode failure, None passthrough. Fixture stub lists in test_sync_v2 / test_sync_transcription_prefs gained the utils.conversations.location stub with an identity passthrough (the real resolver's miss behavior). Verified: pytest tests/unit/test_sync_geolocation_enrichment.py test_sync_v2.py test_sync_transcription_prefs.py -> all passed. * fix(backend): fill empty daily-summary pin addresses at read time Failure-Class: FC-sync-geolocation-missing-address | new | none generate_comprehensive_daily_summary copied c.geolocation.address verbatim, so pins from conversations created before write-time enrichment (the sync path) rendered as 'Unknown' recap timeline rows. The pins loop now fills an empty address through the shared ~100m- rounded geocode cache - the same entries write-time enrichment writes, so an already-enriched day costs no extra upstream call. All three callers are sync contexts, so the sync geocoder is used. A geocode miss or error leaves the address empty and keeps the pin (the app labels it 'Unknown'); regenerating a summary retroactively fixes history with no migration. Tests: tests/unit/test_daily_summary_location_address_fill.py (filled from geocoder with exact coords preserved, present address skips the geocoder, miss and exception both keep the pin address-less). Verified: pytest tests/unit/test_daily_summary_location_address_fill.py test_daily_summary_zero_coordinate_locations.py -> all passed. * feat(app): OmiMapPreview - one static-map widget with an offline pin-dot canvas Every map preview now funnels through a single widget backed by the authed backend static-map proxy (GET /v1/static-map): URL built by buildOmiStaticMapUrl (pins quantized to 4 decimals to match the server's cache quantization, deduped, capped at 50), image fetched with the session's Authorization header, and a deterministic dark canvas with one white dot per pin while loading, offline, or on any failure - never an error state. Product direction: previews in-app, tapping opens the native map app via MapsUtil.launchMap. The conversation detail geolocation card migrates from the client-keyed getMapImageUrl URL to this widget; getMapImageUrl is removed (maps_util keeps launchMap + place URL), taking the last direct client-side Google Static Maps call with it. Verified: flutter test test/widgets/omi_map_preview_test.dart test/widgets/daily_summary_card_test.dart (widget+URL builder tests); flutter analyze clean on changed files; analyze_ratchet.sh passed. * feat(app): replace CARTO tile maps with static previews; drop flutter_map CARTO began enforcing API keys on basemaps.cartocdn.com (keyless requests now return watermarked tiles), and Omi has no CARTO key. All three flutter_map surfaces now render OmiMapPreview instead - zero remaining tile traffic, no keyed tile provider in the app: - conversation map page: static preview of every ~100m cluster anchor (tap opens the native map app) above a grouped 'conversations at this place' list; single-conversation places open the conversation directly, multi-conversation places keep the cluster bottom sheet; grouping logic and cluster-row keys unchanged - daily summary card: 96px preview strip of the day's pins in the recap carousel (the highest-frequency map surface) - now one cached server-side render per distinct pin set instead of dozens of tiles per card - daily summary detail 'Your Day's Journey': 200px preview; image tap still opens the day's first stop (Apple Maps cannot take waypoints via map_launcher), per-stop timeline rows unchanged flutter_map is removed from pubspec (latlong2 stays - journey grouping uses it). The client-embedded Env.googleMapsApiKey goes with it: all static maps come from the server-restricted key behind the proxy now. envied outputs regenerated; test EnvFields stubs updated to match. Verified: bash test.sh / flutter test -> 1758 passed, 5 skipped, 0 failed; scripts/analyze_ratchet.sh passed (9 lint counts improved). * test(app): update map-surface tests for static previews - daily summary card: assert the preview strip, proxy URL pins/quantize/ dedupe, offline canvas fallback, and no-map-for-invalid-coordinates (was: tile provider request counting) - daily summary detail page: drop the TileProvider seam (widget no longer takes one) - conversation map groups: group-card key + cluster sheet rows replace marker keys; journey/env stubs gain the EnvFields change Verified: flutter test on all four files -> passed. * test(backend): stub utils.conversations.location in the cloud-tasks loader pipeline.py now imports async_resolve_geolocation at module scope; the cloud-tasks stub loader replaces its parent packages with MagicMocks, so the submodule import fails in file isolation (the earlier green run leaned on a module cached by another test file). Adds the explicit stub with an identity passthrough - the real resolver's miss behavior. Verified: BACKEND_UNIT_TEST_FILE_LIST=<this file> bash test.sh -> 91 passed. * chore(app): drop GOOGLE_MAPS_API_KEY from the env template The client no longer embeds a Maps key - all static-map renders go through the server-restricted key behind GET /v1/static-map. * test(backend): provide redis r on the usage-tracking stub set external_integrations now imports utils.conversations.location (daily- summary address fill), which imports the redis client symbol; the usage-tracking stub environment replaces database.redis_db with an empty module, so the import fails in file isolation. Give the stub an r. Verified: BACKEND_UNIT_TEST_FILE_LIST=<this file> bash test.sh -> 21 passed. * docs(backend): note the static-map provider URL budget with current limits * chore(backend): declare the static-map route policy in the manifest * fix(backend): drop the redundant isinstance on the typed pins parameter * fix(app): hold the preview canvas until the auth header resolves The first build fired the authed proxy request before getAuthHeader() resolved; CachedNetworkImage keys its cache by URL, so the later header-arriving setState never re-fetched - a permanent 401 fallback canvas. The widget now renders the pin-dot canvas (which already doubles as the error/offline path) until the header resolves, and only then mounts the network image with the Authorization header attached. The header resolver is injectable (authHeaderProvider) so tests control when it completes; an explicit imageUrl (test seam) skips the gate. Tests: new regression case asserts no CachedNetworkImage while auth is unresolved and the authed image after the completer fires; the card tests repoint URL assertions at the preview widget's pins (URL building has its own unit tests). Verified: flutter analyze clean; flutter test (full) -> 1759 passed. * fix(app): restore the conversation_map_marker automation key on group rows The per-place tappable key predates the static preview; keep it stable for automation (the PR brief promises preserved keys). Verified: flutter test test/unit/conversation_map_groups_test.dart passed. * fix(backend): normalize static-map dimensions, dedup render stampedes, count canvas fallbacks Review findings on the static-map proxy: - Dimension handling: one proportional scale factor (min(1, 640/w, 640/h)) computed once in fetch_static_map replaces the independent per-axis clamps - aspect is preserved and every request that differs only by scale normalizes onto the SAME cache entry (cache key and provider URL both use the effective size). - Stampede dedup: after a miss, a per-key render lock (r.set nx, 30s TTL) elects one renderer; concurrent misses poll the cache (0.25s interval, 15s budget) for the holder's result. Lock-held-timeout and lock-unavailable (Redis broken) both fail OPEN to an unlocked render - a lost lock never becomes a 502, and a broken Redis never pays the 15s wait budget. - record_fallback (component=static_map, provider_static_map -> client_pin_canvas, outcome=degraded) fires before the 502 so the degrade is counted in the shared telemetry. Tests: proportional normalization + shared cache entry, oversized fetch normalizes (size=640x150), concurrent misses render once, waiter polls a foreign lock holder to its hit, wait-timeout renders unlocked, 422s for out-of-bounds width/height via TestClient (Query contract), fallback telemetry kwargs. Verified: pytest tests/routers/test_static_map.py -> 21 passed; pyright (scripts/typecheck.sh) 0 errors, no findings in changed files. * fix(backend): cap daily-summary geocode attempts; boost-exempt static_map:get - The read-time address fill now bounds geocode ATTEMPTS at 10 per summary generation: cache hits are cheap but attempts are the deterministic wall-clock bound (10 x the geocoder's 10s worst case stays inside the job budget). Pins past the cap keep an empty address and the app's 'Unknown' fallback. Test: 11 empty-address pins -> 10 filled, 11th untouched, 10 geocoder calls. - static_map:get joins the boost-exempt rate policies: under prod's RATE_LIMIT_BOOST the 240/h hot-loop cap would otherwise resolve to 24k/h and stop protecting the billable provider calls. Verified: pytest test_daily_summary_location_address_fill.py -> 5 passed; utils.rate_limit_config import shows static_map:get in BOOST_EXEMPT_POLICIES. * test(backend): self-contained enrichment fakes; model the resolver short-circuit - test_sync_geolocation_enrichment no longer imports the transcription-prefs fixture module: sharing it loaded real heavyweight modules (GCS/Firestore protos, anthropic/jiter) inside the stub window, and stub_modules' teardown evicted them so later files broke on re-import (duplicate proto registration, jiter NameError) when run in one process. The fakes are now minimal and self-contained - every heavy leaf is stubbed, light enum modules (stt.outcomes, sync.lanes, sync.telemetry) stay real. - The None-geolocation test now models the real resolver's short-circuit (falsy input returns immediately, attempts recorded only for truthy input) and asserts: resolver called exactly once with None AND zero geocode attempts. Docstring states what is actually proven. - One-line caveat at the cloud-tasks loader injection site: new submodule imports must be added to heavy_deps explicitly (MagicMock parents do not resolve submodules). Migrating that hand-rolled loader to stub_modules would cascade across its 37 call sites - deliberately left as-is. Verified: pytest on the combined focused group (static_map, summary fill, sync enrichment, cloud tasks, sync v2, transcription prefs, geocode resolve) -> 372 passed in one process. | 9 天前 | |
merge: integrate current main into web parity Resolve the manifest and brand-check conflicts while retaining both branches’ active CI coverage. Tighten the hex token boundary so the merged brand test rejects embedded literals. Failure-Class: none | 27 天前 | |
app: use neutral white (not purple) for storage bar — brand INV-UI-1 | 2 个月前 | |
Speaker identification: measured threshold + margin, live clip pooling, SpeechBrain retirement; carries #12531 without the onboarding-step removal (#12935) * fix: unblock speech-profile redo and STT pre-flight for already-onboarded accounts Rebased onto origin/main as a single commit. Keep both main's open_provider_selection_circuit and this PR's is_stt_available helpers, then regenerate OpenAPI clients from the rebased backend. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(app): fade transcript words in as they arrive on the speech-profile screens Add FadeInWordsText: a centered word Wrap where only the words appended since the previous render animate from transparent to opaque with a short stagger, existing words stay put, and a rewritten transcript re-reveals from the start. Both the onboarding speech-profile step and the Settings redo page adopt it in the next commit so the live transcript reads the same whether the words come from the server or the on-device fallback. Verification: flutter test test/widgets/fade_in_words_text_test.dart (4 passed); observed on an iPhone 16 Pro via hot reload while dictating. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F1zmRTRh3DV8NsM3QUrrAB * feat(app): fall back to on-device speech recognition when server STT is unavailable The speech-profile question flow (onboarding step and Settings redo) needs a transcript only to drive the questions and progress; the voice print itself is computed server-side from the WAV uploaded at finalize(). So when the backend's streaming STT is down, transcribe on the phone instead of dead-ending: - SpeechProfileProvider gains a local-STT mode. It is entered up front when the stt-availability pre-flight fails, or mid-session after the existing three 1011 closes with no captured speech (previously STT_UNAVAILABLE). The socket becomes the existing CompositeTranscriptionSocket: an on-device polling primary (Apple speech on iOS, downloaded Whisper on Android) forwarding suggested_transcript frames to the backend listen socket in custom_stt mode, which the OnboardingHandler already consumes like server STT output. No backend change; a receiver regression test pins that seam. - iOS on-device recognition hardening (AppDelegate.swift): resolve the app's bare language code to an installed on-device locale (a recognizer built from "en" failed every request with kAFAssistantErrorDomain 1101); reply exactly once per clip on final result, error, or a 20 s timeout, keeping partial results; and expose onDeviceAvailable, which probes a silent clip so a phone with Siri and Dictation disabled (kLSRErrorDomain 201) is reported as "no local STT" instead of entering the fallback blind. - PurePollingSocket bounds each transcribe() with a 30 s timeout. A provider that never answered left the processing flag set forever and silently stopped transcription for the rest of the session; now the audio is requeued and the next tick retries. This also protects the main app's on-device mode. - When neither server nor on-device STT is available, the pre-flight dialog now says to check the connection or turn on Dictation. - Speech-profile UI: subtler mic-level glow, and the live transcript uses the new fade-in words widget. Verification: - flutter test (full suite): 1731 passed, 5 skipped; scripts/analyze_ratchet.sh passed - new tests: speech_profile_provider_test (5 fallback cases), pure_polling_test (hung-provider timeout), fade_in_words_text_test (4), backend test_onboarding_question_start (suggested_transcript reaches the transcript queue only in custom-STT mode) - live on iPhone 16 Pro against the local dev harness with the STT primary forced unavailable: session connects with custom_stt+onboarding flags, Apple on-device recognition returns the spoken answer (~180 ms per clip) and it is forwarded to the backend; with Dictation disabled the probe reports unavailable and the dialog appears. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F1zmRTRh3DV8NsM3QUrrAB * feat(app): use SpeechAnalyzer for on-device speech on iOS 26 SFSpeechRecognizer's on-device mode fails with kLSRErrorDomain 201 whenever Siri and Dictation are turned off in Settings, which is what produced the "turn on Dictation" pre-flight dialog in the speech-profile fallback. iOS 26's SpeechAnalyzer/SpeechTranscriber has no such dependency: the language model is an asset the app installs itself through AssetInventory. - transcribe: on iOS 26 run the clip through SpeechAnalyzer (preset .transcription, analyzeSequence(from:) + finalizeAndFinish), falling back to the SFSpeechRecognizer path only if the analyzer throws. - onDeviceAvailable: report true when a supported locale's model is installed or installs within 8 s; a longer download keeps going in the background and the first transcribe() waits for it. Concurrent callers share one download. - SFSpeechRecognizer remains the path for iOS 15-18. Verified on an iPhone (iOS 26.6.1) with Dictation off: the speech-profile redo enters local-STT mode and transcribes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017iFNr6rZDcD1FkCS3dJ3Cm * feat(app): speech profile talks through three topics and completes on a word target Speech-profile recording (onboarding step and Settings redo) no longer walks one question at a time with a percentage bar. Instead: - A compact white-outlined card headed "Answer with your voice:" lists three topics (where you live, what you do for work, your long-term goal), and a thin bar under it fills as the user speaks. Reaching SpeechProfileProvider.targetWordCount (60 spoken words) finalizes the recording; the backend's onboarding_complete event no longer does, so "bar full" and "done" are the same moment. Omi's own question segments are excluded from the count. - The live transcript is bottom-anchored in a box exactly three lines tall above the card, so whole lines scroll off the top and nothing overlaps. - The Play button on the Settings page plays the saved profile audio in place (just_audio) and turns into Stop, instead of opening the samples page. Redo stops playback first. - Backend ONBOARDING_QUESTIONS is the same three topics, and OnboardingHandler keeps the transcript across questions so one stretch of speech can satisfy several of them. - Removed the unused percentage progress-bar widget and the "Skip this question" button; onboarding keeps "Skip for now". Tests: speech_profile_provider_test (word target fills, finalizes once, ignores Omi segments and the backend completion event); backend test_onboarding_talk_about_flow (one transcript answers every topic; the transcript is kept when it stops answering). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017iFNr6rZDcD1FkCS3dJ3Cm * feat(dev-harness): advertise local-storage links on OMI_DEV_HOST A phone built against OMI_DEV_HOST could reach the backend but not the files it links to: OMI_LOCAL_STORAGE_BASE_URL was always http://127.0.0.1:<port>/_local/storage, so playing the saved speech profile from a device failed. The harness now derives a dev_advertise_host from OMI_DEV_HOST (loopback stays the default) and uses it only for that base URL; every other service address still binds and talks over loopback. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017iFNr6rZDcD1FkCS3dJ3Cm * fix(app): keep the last three whole transcript lines instead of a clipped scroll The speech-profile transcript was a bottom-scrolled ListView clipped to a three-line box, so a sliver of the line above always showed at the top edge and read as cut-off text. FadeInWordsText now takes visibleLines: it replays the Wrap line breaking with measured word widths and builds only the words on the last N lines, so earlier lines drop off whole, nothing is clipped or scrolled, and words keep their reveal state while on screen. Both screens use visibleLines: 3 inside a fixed three-line, bottom-anchored area, moved a little further above the topics card. Test: fade_in_words_text_test covers short text showing everything, earlier lines dropping once the text exceeds three lines, and the shown words matching the line-break replay. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017iFNr6rZDcD1FkCS3dJ3Cm * fix(app): measure transcript lines with the effective text style; 40-word target FadeInWordsText replayed the Wrap line breaking with the caller's raw style, but each word's Text inherits the ambient DefaultTextStyle (font family, weight) under that style, so the replay undercounted lines and the real layout could reach four lines and draw over the topics card. Measure with the same merged style, and clip the fixed three-line area on both screens as a safety net so a stray line can never overlap the card. Also lower SpeechProfileProvider.targetWordCount from 60 to 40 so the recording finishes sooner. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017iFNr6rZDcD1FkCS3dJ3Cm * fix(app): keep the "<Name>'s Speech Profile" title on one line The title wrapped onto two lines for longer names; it now scales down to fit a single line instead. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017iFNr6rZDcD1FkCS3dJ3Cm * fix(app): never clip the speech-profile transcript; raise it above the card The three-line transcript area was a fixed-height clipped box, so whenever the rendered lines ran taller than the fontSize*height estimate (text scaling, font metrics) the top line was cut off. FadeInWordsText already guarantees at most three lines, so the area now only has a three-line minimum height (scaled with the text scaler) and grows to its content instead of clipping. Both screens also keep more space between the transcript and the topics card. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017iFNr6rZDcD1FkCS3dJ3Cm * feat(app): finish the speech profile after three sentences; no page-load spinner The recording now completes once the user has spoken three sentences (SpeechProfileProvider.targetSentenceCount, counted on ./!/? boundaries followed by a space or the end of the text, so "3.5" is not one) instead of a word count, and the bar under the topics card fills per sentence. The progress-bar widget is renamed SpeechProgressBar to match. The Settings speech-profile page no longer swaps its Play/Redo or Get Started buttons for a spinner while the page initialises or the STT pre-flight runs; the buttons stay put and startRecording() ignores taps until the check finishes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017iFNr6rZDcD1FkCS3dJ3Cm * fix(app): play the saved speech profile on the loudspeaker The app's audio session is normally configured for recording, so tapping Play on the Settings speech-profile page routed the WAV to the quiet earpiece. Before playing, configure a playback-category session (default mode, media usage on Android) and play at full volume, so the profile comes out of the main speaker like any other media. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017iFNr6rZDcD1FkCS3dJ3Cm * fix(app): start every speech-profile recording with an empty transcript Tapping Redo showed the previous recording's words (and counted them toward the sentence target) because nothing cleared the provider's transcript before a new session; only close() did, on leaving the page. initialise() now calls a new resetTranscript() first, which forgets the segments, text, progress, completion and upload flags without touching the audio storage it recreates right after. resetSegments() reuses it. Test: a completed session's transcript is gone after resetTranscript and the fresh session counts sentences from zero and can finalize again. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017iFNr6rZDcD1FkCS3dJ3Cm * fix(app): finish the speech profile after a pause, keeping the last sentence on screen Reaching the third sentence finalized immediately, which stopped the mic mid-utterance (the recognizers punctuate each clip, so a pause can read as a sentence end) and swapped the transcript for a spinner at once. Now: - After the target is reached the provider waits completionGrace (2 s) without new speech before finalizing, restarting the wait on every new segment, and finalizes at completionCap (8 s past the target) at the latest. Once fired it does not re-arm; resetTranscript() clears it. - Both screens keep the last three transcript lines visible through the upload and the All done state, so the final sentence lingers instead of vanishing. Tests: grace/cap timing under fakeAsync, no double finalize, and the reset test now elapses the grace. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017iFNr6rZDcD1FkCS3dJ3Cm * fix(backend): give every speech-profile recording its own conversation Tapping Redo within two minutes of the previous attempt showed last time's words as soon as the user spoke again. The new listen socket attached to the still-open in-progress conversation from the previous attempt (same source, inside conversation_creation_timeout), so combine_segments() merged the first new segment into that conversation's last segment and the merged segment, old text included, was what the client received. LiveConversationController.prepare() now always creates a fresh in-progress conversation for onboarding_mode sessions (the onboarding step and the Settings redo both set it) instead of consulting the in-progress pointer. Ordinary listen sessions are unchanged. Test: test_listen_speech_profile_fresh_conversation.py. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017iFNr6rZDcD1FkCS3dJ3Cm * fix(app): cross-fade the speech-profile recording UI into a plain All done button Keeping the transcript on screen through the upload made it pop back in on its own above the spinner and the All done button. The Settings page now cross-fades (450 ms) from the recording UI (transcript, topics card, bar) to nothing while uploading and then to the All done button, which is the same black capsule with a plain white border as the other buttons instead of the gradient box. The onboarding step likewise no longer shows the transcript in its upload and All done states. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017iFNr6rZDcD1FkCS3dJ3Cm * chore(app): remove the speech-samples page and dead progress-state code The Play button now plays the saved profile in place, so the samples page and its provider became unreachable (CI dead-code ratchet). Also drop the scroll controllers and SCROLL_DOWN signal the old clipped transcript used, and the word-count progress-message state (SpeechProfileProgressState, percentageCompleted, questionProgress) nothing reads any more. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017iFNr6rZDcD1FkCS3dJ3Cm * fix(app): hold the finished speech-profile recording on screen before All done After the third sentence the final words disappeared as soon as the upload began. Both screens now keep the finished recording (last words, topics card, full bar) on screen through the upload and for a further 1.5 s (allDoneHold) after the profile is saved, then cross-fade into the All done button. Onboarding's upload spinner row and its now-unused loading-text helper are gone; Skip for now hides once recording ends. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017iFNr6rZDcD1FkCS3dJ3Cm * fix(app): fade the finished speech-profile recording out as one block Parts of the finished recording could change on their own before the cross-fade (the transcript and the mic disclaimer are built from live provider state that finalize() and its callbacks touch), so they did not disappear together. Both screens now snapshot the recording view (last words, no-device flag) the moment recording ends and build from that until a new recording starts, and the onboarding step's All done switch is now the same AnimatedSwitcher cross-fade as the Settings page, so the words, the topics card, the bar and the disclaimer fade out at the same time. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017iFNr6rZDcD1FkCS3dJ3Cm * fix(app): keep the speech-profile bar full until the finished recording fades finalize() clears the provider's text once the profile is saved, and the bar derived its value from that text, so it dropped back to zero before the cross-fade. The frozen recording view now pins the bar at full from the moment recording ends. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017iFNr6rZDcD1FkCS3dJ3Cm * feat(app): drop the speech-profile and memory-graph steps from first-run onboarding Onboarding now goes from Permissions straight to the completion screen. The speech profile is recorded from Settings instead, and the memory-graph preview (with its background graph prebuild) is gone. The two step widgets are deleted; their page indices stay as placeholders like the other retired steps so the existing page constants keep working. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017iFNr6rZDcD1FkCS3dJ3Cm * fix(app): ease the mic glow shut as the finished speech profile fades The white glow behind the device graphic vanished the instant the upload began. It now stays through the upload and hold and eases down to nothing over the same 450 ms in which the recording view fades into All done. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017iFNr6rZDcD1FkCS3dJ3Cm * fix(app): hold the mic glow at its last size until it eases out The glow followed the live mic level, which drops to zero the instant the microphone stops after the recording ends, so it snapped down to its resting size before the ease-out. The frozen recording view now also captures the last mic level, so the glow holds still and then eases shut with the fade. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017iFNr6rZDcD1FkCS3dJ3Cm * Revert "feat(app): drop the speech-profile and memory-graph steps from first-run onboarding" This reverts commit 4ebcb3d2c773747051dfeb2d519fe88aeb1faea0. * feat(backend): tune speaker verification from measured enrollments and retire the SpeechBrain matcher Speaker identification rejected most of the owner's own audio. The verification threshold (0.45 cosine distance) was copied from a clean-studio VoxCeleb figure; an offline bench over real enrollments in the speech-profiles bucket (229 users with a current profile plus an older one, 16 with extra recordings, 60 taught persons, 400 impostors; wespeaker-voxceleb-resnet34-LM, the diarizer's /v2/embedding model) puts same-user cross-session distance at a median of 0.40-0.53 and other users at 0.93. At 0.45 the owner was rejected 37-71% of the time at a 0.0% false-accept rate; the equal-error threshold is ~0.78. Same-session audio matched at either value, which is why the old constant looked fine in demos. - New utils/stt/speaker_match.py owns the policy (numpy only, shared by the live socket and the sync pipeline): threshold 0.65, plus a 0.10 margin over the runner-up so the owner is not guessed as a taught household member. - Live sessions pool up to three recent clips per diarized speaker and decide on the centroid once 5 s of clip audio has accumulated, instead of letting the first 2 s clip that lands under the threshold stick for the session. - Both surfaces log one structured speaker_id_decision line (best, runner-up, evidence, accepted) so the prod distribution can be checked against the bench from a day of logs. - The bench scripts live in backend/scripts/speaker_id_bench for reruns; user audio never leaves the machine running them. - Retire the dead SpeechBrain speaker-identification path: modal/speech_profile_modal, utils/stt/speech_profile (zero production callers), the /v1/speaker-identification route, HOSTED_SPEECH_PROFILE_API_URL in every chart/env, the speechbrain dependency, the shared-package COPY lines in the modal image, the dev-harness and e2e fakes, and the unused ListenLimits.speaker_id_target_audio field. Drop the now-unused is_same_speaker/find_best_match/bytes helpers. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(backend): keep speaker_match real in sync test isolation, allowlist its import cost utils/sync/pipeline.py now imports utils.stt.speaker_match, but the hand-maintained heavy_deps mock list in test_sync_cloud_tasks.py and test_sync_v2.py didn't know about it, so `from utils.stt.speaker_match import select_speaker_match` raised ModuleNotFoundError: 'utils.stt' is not a package once utils.stt was replaced with a MagicMock. Real-import speaker_match (pure, dependency-free, like utils.stt.outcomes) instead of stubbing it, since a MagicMock decision object would also break the %.3f log formatting on decision.best_distance/runner_up_distance. Also allowlist test_speaker_match.py::test_short_clips_are_pooled_before_a_live_decision in the fast-unit duration guard: it's the first test in the file to import routers.listen.speakers, so it amortizes that module's FastAPI router-graph import cost, same structural pattern already documented for other files in the allowlist. * fix(speaker-id): preserve distinct evidence and household ambiguity Serialize live matches per speaker, subtract previously embedded audio, invalidate late session results, and keep all enrolled candidates in sync margin comparisons before enforcing unique assignment. Validation: 396 selected backend tests passed; Python typecheck has zero errors. Five live regression cases and two sync cases failed before the fixes. Changed sync expectations follow PR #12935's measured household-confusion margin. Failure-Class: new * fix(speaker-id): include owners in household benchmark cohorts Include available owner profiles even outside legacy, additional, and impostor cohorts. Distinguish offline benchmark evidence from deployed accuracy. Validation: synthetic manifest regression passes for owners outside other cohorts and people without an owner profile. No private audio or threshold retuning. Failure-Class: new * fix(speech-profile): bound native recognition and discard stale work Use one native completion owner so availability deadlines do not wait for shared model downloads and recognition cleanup precedes timeout completion. Serialize legacy recognition callbacks on the main queue. Propagate native failures to retain audio for retry; remove the polling Future timeout that allowed overlapping work. Scope fallback availability and polling results to their recording session. Validation: full Flutter suite 1836 passed, 5 skipped; analyzer ratchet passed. Native deadline behavioral tests pass and are registered in the existing manifest. Native speech code typechecks for iOS 15 deployment with Flutter boundary stubs; no full iPhone build or live enrollment claim. Preflight passed 53 selected checks. Failure-Class: new * fix(l10n): translate speech-profile flow in every supported locale Translate the eight speech-profile keys across all 48 non-English ARBs and fill two inherited missing keys exposed by generation. Use device-neutral speech recognition guidance and regenerate localization output from source catalogs. Validation: flutter gen-l10n reports zero untranslated messages; owner-name placeholders and complete catalog coverage verified. Full Flutter suite passed. Failure-Class: new * fix(speaker-id): require persisted speech profile before the redo admission bypass cubic P1: the client-supplied speech_profile_redo flag alone proved nothing; any authenticated client could send it to skip the completed-account onboarding-provenance admission gate. The runtime now confirms the redo from durable state (an actually stored speech_profile.wav) before taking the bypass, and an unprovable claim falls through to the provenance admission, failing closed when the check errors. Adds a regression test asserting a redo claim without a persisted profile is judged by the gate. * fix(listen): gate the onboarding fresh-conversation path on server admission cubic P2: onboarding=enabled is a client hint, yet prepare() took the fresh-conversation shortcut on the raw flag even when _bootstrap refused to admit the session — a client could dodge the existing-conversation lookup with a query parameter. The path now requires the runtime's onboarding_admitted (also true for the authorized Settings redo); an unadmitted claim keeps an ordinary session's behavior. Adds a regression test for the unadmitted path. * fix(onboarding): queue segments that arrive during AI answer checks cubic P2: is_checking_answer stayed set across up to three awaited LLM calls in _check_answer, and on_segments_received dropped everything spoken in that window, so answers covering later topics could be lost. Segments received while a check is in flight are now queued and replayed when it finishes, re-entering the normal accumulate-and-timer flow. Adds a regression test. * fix(speaker-id-bench): report the production threshold and true impostor rates cubic P2 x2: score.py evaluated the retired 0.45 operating point while the README and shipped policy (SPEAKER_MATCH_THRESHOLD) sit at 0.65, making its false-reject/false-accept and live-decision numbers misleading; and cohort-C impostor distances included the current user's own owner profile when that user was also sampled as an impostor, folding owner-vs-own-person confusion into the random-impostor sweep. score.py now pins T to the production 0.65 and formats every label from it; sweep.py excludes each cohort-C user's own profile from their impostor pool (the confusion keeps its dedicated diagnostic). Owner profiles for cohort C were already added to the cohort inputs by 8126713612. * fix(speech-profile): close startup, playback, and socket adoption races cubic review follow-ups still present after 1d7a2fd917: - page.dart: _isCheckingAvailability is now held until the entire startup path exits (dialogs, codec lookup, stopDeviceRecording, initialise), not just the availability round-trip, so a second tap cannot race socket and microphone init; context/mounted are rechecked after the language dialog and before initialise. - page.dart: profile playback deactivates the activated audio session on every teardown path (stop, natural completion, failure after activation, disposal) instead of leaving media routing active. - speech_profile_provider: a socket created while the session was closed or reset is discarded instead of adopted, which previously leaked a live backend session stop() never saw. - transcription_service: the speech-profile on-device fallback forwards raw audio per config.sendRawAudioToOmi, matching the conversation composite, instead of hardcoding every frame onto the Omi socket; suggested transcripts still flow and keep the backend session clock alive. Pinned by a factory test. * docs(app): keep AGENTS.md within its lean-budget ratchet after the main merge The merge combined this PR's on-device speech pointer with main's profile-build-mode and batch-contract lines, pushing app/AGENTS.md past its agents-md-lean budget (11747 > 11500 bytes). Tightens wording without dropping any fact: the batch-writer guarantee detail lives in the manifest reason and the ruby test itself; the other compressions are same-fact rewording. --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Nathan Cheng <nathanjcx@gmail.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: Nathan <nathan@Nathans-MacBook-Air.local> | 7 天前 | |
Merge remote-tracking branch 'origin/main' into pr-9499 # Conflicts: # .github/scripts/product_file_line_count_ratchet_baseline.json # app/lib/services/services.dart # desktop/macos/Desktop/Sources/MainWindow/DesktopHomeView.swift # desktop/macos/Desktop/Sources/MainWindow/Pages/MemoriesPage.swift # desktop/macos/Desktop/Sources/Rewind/Core/VideoChunkEncoder.swift # desktop/macos/e2e/flows/memories.yaml | 1 个月前 | |
Fix mobile conversation rendering stalls | 1 个月前 | |
Revert "feat(app): make home a day-at-a-time timeline (#12474)" This reverts commit 469f7e56e10c9996ab6280e492b65d2444cd36fe, reversing changes made to 8c9e38c853c96f4a5db1f6aecc635d345c1b14b8. | 14 天前 | |
Enable memory currency, evidence weighting, and history across Beta (#13953) * feat(desktop-windows): add beta memory temporal reads and use feedback * fix(hooks): verify web formatters against pinned Bun lockfiles * feat(memory): add beta desktop and web history controls * feat(memory): enable beta belief evidence and temporal reads * feat(memory): guard Windows history use controls * feat(memory): stabilize beta client pagination and feedback * feat(memory): bound beta empty-query retrieval * feat(memory): add Flutter beta currency history and use controls * feat(memory): classify the belief automation pause setting * feat(memory): register owner use feedback route policy * feat(memory): require belief settings on both ingestion hosts * feat(memory): preserve filtered ledger metadata without rebuilding keys * fix(checks): distinguish canonical metadata from legacy memory mechanisms * test(memory): make chat fixtures exercise bounded cursor reads * Align desktop Beta deployment and generated schema contracts * Update isolated memory router and evidence fixtures * Recognize archive exclusion structurally in default-read guard * Keep Windows memory capability state compatible with React compiler * test(memory): pin belief automation pause literal in dev pusher contract The dev pusher runtime contract already registers MEMORY_BELIEF_AUTOMATION_PAUSED='false' (and the prod counterpart), and the rendered deployment binds it consistently; the pinned literal snapshot in test_verify_pusher_config_references.py was not updated. Also grandfather the render+deepcopy-heavy literal-policy test in the duration allowlist next to its siblings from the same file. * fix(memory): route owner use feedback through the customer data plane POST /v3/memories/{id}/use is registered on desktop-backend, whose memory items live in the configured customer data plane (OMI_FIRESTORE_DATA_PLANE_ PROJECT is mandatory there). The route handed the compute Firestore client to the canonical mutation adapter, so valid feedback could report missing memories or mutate the wrong project. Follow the jit_ledger_snapshot / jit_rollout precedent and resolve get_data_plane_firestore_client() at request time; pin the seam with a route contract test. * fix(memory): sign ledger-history continuation before the 501-row sentinel read_ledger_history_page set next_start_after to the sentinel row itself: the next keyset page started strictly after it, so the row at the sentinel position was permanently skipped whenever it was an eligible history row. Stop the scan at the sentinel and keep the continuation key on the last row considered for emission, so the next page rescans the sentinel row. Covers search_ledger_history_page, which pages over the same provider. * fix(memory): harden belief backfill resume and evidence eligibility Four correctness fixes from PR review triage: - _default_item_reader passed a bare string id as the Firestore __name__ cursor; every real resume failed at query build. Build the DocumentReference like every sibling scanner (review_queue et al). - Checkpoint completed[] entries were terminal without comparing the stored revision: an owner-edited unknown/skipped row was never reclassified. Invalidate stale entries so reruns reclassify them. - A dry-run --checkpoint preview cached classifications but advanced the cursor past the page, so the later --apply run stranded the cache forever. Record each cached entry's page-start read cursor and have an apply run re-read the earliest cached-but-unapplied page; dry-run cursor advance (past unreadable rows) is unchanged. - eligible_record admitted pending/blocked processing states into belief corroboration; require processing_state == processed, mirroring the canonical read fence. * fix(memory): close review-verified Beta correctness gaps in backend Six fixes from PR review triage, each with a focused test: - Offset fallback for temporal views post-filtered one already-paged released window, dropping history rows and underfilling pages; pass view/as_of into MemoryService.read so admission precedes slicing. - The use-feedback route leaked 500s for MemoryFirestoreApplyError and CanonicalMemoryIntakePausedError (not RuntimeError subclasses); map intake pause to 503 and apply-store fences to 409 like jit_rollout. - The retrieval tool's temporal loop dropped page.truncated when a budget-truncated page had no continuation cursor and reported a partial scan as complete; propagate truncation and stop. - A non-string arguments.decision from the model raised TypeError in set membership and discarded the whole observation batch; drop it like any invalid decision instead. - The daily-sweep add path omitted candidate arguments from its LedgerWrite, losing proposed decisions and scoped qualifiers; pass them through like the amend path. - Canonical conversation capture dropped candidate predicate/arguments; carry them onto the persisted Memory. * fix(web): coalesce use-feedback refresh and stop dropped view changes Four review-verified fixes in the memories Beta surfaces: - The canonical refresh is now single-flight: a use-feedback commit fires the cache-invalidation listener synchronously (before its caller resumes), which used to steal the fetch lock and make refresh(true) report failure after a successful server commit. Concurrent callers now join the in-flight refresh, and the invalidation effect reuses refresh() instead of a duplicate loader. - A view/category change that arrived while any fetch was in flight was consumed by prevQueryRef and then dropped by the fetchingRef guard, permanently keeping the previous view's rows. The query is consumed only when the fetch starts, re-armed via fetchIdleTick when a fetch goes idle, and responses are fenced by query as well as scope so a stale page can never land on a new view. - The memories prefetcher bails out after each await when the owner scope changed mid-flight, so one owner's response can no longer be cached under the other's IndexedDB scope. - The prefetch effect depends on the stable scopeKey primitive instead of the per-render scope object, so a rerender inside the two-second delay no longer cancels the owner's prefetch permanently. * fix(app): deleted-row guard, single-flight history load-more, banner semantics, l10n - A deleted legacy/cached row without an assessment timestamp returned isUsefulNow=true and could surface in the useful-now collection; gate the unassessed branch on !deleted. - Two rapid taps on Show-more both fetched the same offset page and each advanced _ledgerHistoryOffset, skipping the next history page; add the single-flight guard mirroring _inFlightLoad. - The history banner's unconditional excludeSemantics hid the new load-more button from screen readers; exclude only when there is no action so the button stays reachable (label merges into the banner). - Translate memoryHistory in all 48 non-English ARBs (Arabic السجل matching memoryHistoryPartial terminology) and the generated localizations. * fix(windows): honest truncation, view-switch state sync, sheet and memo fixes - The bulk pager treated a budget-truncated cursor page (no continuation) as normal completion, silently exporting/purging a partial set; detect X-Omi-List-Truncated on the break paths and fail loudly. Offset-mode recovery by rows-received still resolves. - hydrateFromDisk skipped the loaded reset when the cache list was already null, so the revalidation effect never fetched the new view; reset whenever the view differs and sync hook state on view change so stale rows drop immediately (and stay cleared on fetch failure). - The open detail sheet kept the stale suppression label after a successful allow/suppress; patch detailMemory like onEdit. - The inline onUseAction adapter broke MemoryCard memo on every parent render; pass a useCallback-stable latest-ref adapter. - Enter/Space on the feedback buttons bubbled to the card's keydown handler and opened the sheet on top of the action; stop keydown propagation on the feedback group. * fix(macos): view-consistent auto-refresh cursor; drop dead history API - refreshMemoriesIfNeeded fetched the released projection and committed its cursor, which loadMore then paginated as the selected temporal view (pages skip/mix). Route the refresh through the device-scope-aware funnel so it fetches the selected view and commits its own cursor, restoring device-scope handling and the 400 retry it bypassed. Test seam via memoriesPageFetch; regression tests assert the refresh request carries view=history and commits that response's cursor. - Remove the never-called getMemoryHistoryPage: the History surface is served by getMemoriesPage(view:), already covered by URL contract tests. * chore(app): restore Flutter ephemeral Package.swift from main The app follow-up accidentally committed flutter pub get output, which stripped plugin packages and left trailing whitespace that fails diff-hygiene. * fix(web): avoid useMemories inFlight TDZ for typecheck Assign the refresh promise through a holder so the finally clause can drop the single-flight ref without using the binding before initialization. * fix(macos): keep memory sync UserDefaults key off the inline-literal lint The auto-refresh test latched the default-scope sync flag with an inline forKey string; route it through a local binding like production. * fix(macos): wait for the memory auto-refresh seam without sleeping The new view-cursor test polled with Task.sleep, which tripped the desktop test-quality wall-clock ratchet. Yield until the fetch seam fires instead. * fix(app): restore memoryHistory English ARB template The main merge dropped the History filter key from app_en.arb, so flutter gen-l10n deleted the getter across every locale at push time. * fix(macos): make memoriesPageFetch optional for test injection The auto-refresh seam was a required closure, so `if let` would not compile. Keep production on the shared client and let tests replace it. * fix(ci): close memory-use, STT, l10n, and client lint holes on Beta Mock the data-plane Firestore client in memory-use HTTP tests. Give pusher capability fixtures a dummy SONIOX_API_KEY. Add Allow use and Dont use localization keys. Sync Windows memory view state during render; unwrap the macOS test user id. * fix(windows): sync memory view from state, not a render-time ref The previous setState-in-effect replacement still failed eslint (Cannot access refs during render). Adjust local snapshot from state during render when the requested view changes. * fix(windows): do not leak useful-now rows into the history view mock The pager issues a follow-up offset request. mockImplementationOnce let the previous useful-now fixture merge into the history result. | 17 小时前 | |
Merge current main into durable queue substrate | 12 天前 | |
Merge origin/main into codex/issue-9446-architecture-maps Resolve conflicts by keeping main's checks-manifest preflight model, rewiring architecture-guardrails with --base, retaining both AGENTS guidance bullets, and exempting the isinstance ratchet from the manifest contract. Co-authored-by: Cursor <cursoragent@cursor.com> | 2 个月前 | |
fix(app): make daily-summary Right/Wrong/Fix actually tappable (#12772) * fix(app): make daily-summary Right/Wrong/Fix act without a loaded memories row The recap card gated its controls on MemoriesProvider list membership even though every mutation it triggers is id-addressed. On a cold provider (nothing at app start initialises it), a truncated bulk list, or an id the client never paged in, the rows rendered dead chrome: labels painted, taps ignored, and reviewMemory() refused to persist before the request because the id was not in _memories. - Card: a row with a known memoryId is actionable from the item's identity. Accept/reject/edit go through the same provider calls by id (single mutation owner unchanged); a failed write still shows the inline error and reverts. A verdict or correction persisted while unresolved settles the row for the session (_settledReviews, mirroring _settledEdits); the live memory keeps winning once it answers for the id. The dead _RowState.loading chrome is gone, and without a MemoriesProvider in the tree no controls render at all. - Provider: reviewMemory() persists by id when the row is not in the loaded list (nothing to roll back locally); new hasLoaded flag distinguishes "never fetched" from "fetch in flight" for _loading, which starts true. - Card hydrate retry: the static _requestedIds one-shot that permanently skipped hydrate after one failed attempt is replaced by capped per-id attempts that retry only while loads keep failing. Recap text behavior is untouched: prompts, knowledge_nuggets, Learnings section, section order, and memories_learned selection are all unchanged. Verification (worktree, base f5f424a8e2): - bash app/test.sh — 1723 passed, 5 skipped, 0 failed - bash app/scripts/analyze_ratchet.sh — passed (all metrics at or below baseline) - flutter test test/widgets/memory_review_card_test.dart test/widgets/daily_summary_detail_page_test.dart test/widgets/chat_content_block_test.dart test/providers/ — 355 passed Failure-Class: new * fix(app): harden review-card verdict and hydration session state Address review feedback on the id-addressed review path: - Restore the locked-memory guard for cache misses: a locked row is refused before any request, whether or not the id resolves in the loaded list. - Own settled verdicts for unresolved ids on the provider (settledReviewFor), so a card State rebuilt by scrolling reads the same settled row instead of re-offering an already-recorded vote. clearUserData() clears them with the session. - Move the per-id hydration attempt budget into the provider session (consumeHydrationAsk): a user-data reset restores eligibility for a new account instead of leaving ids permanently ineligible. - Retry hydration from the build that observes a settled failed load, so the capped retry path actually runs after a first fetch failure. - Coalesce concurrent same-parameter loadMemories calls into the in-flight load; mounting cards join it instead of racing loads the sequence guard would discard. --------- Co-authored-by: David Zhang <9387252+Git-on-my-level@users.noreply.github.com> | 10 天前 | |
Test notifications loading shimmer | 2 个月前 | |
Maps: keyless static previews + native-map handoff; fix Unknown location labels (#12841) * feat(backend): authed static-map proxy route with Redis-cached renders GET /v1/static-map renders dark-styled Google Static Maps images server-side (the only Maps key stays server-restricted) and caches the bytes in Redis keyed by the quantized pin set + size, so repeat renders of the same place across users/sessions cost one upstream call per distinct pin set per week. utils/static_map.py is the single provider seam for app map previews; swapping providers touches that module only. - pins parsed/bounded/de-duplicated/sorted, capped at 50; ~11m quantization makes users at the same place share one cached image - one pin centers at street zoom; several pins use provider auto-fit - failures return 502 (never cached) - the app renders its offline pin-dot canvas; auth prevents an open proxy on the project key - rate policy static_map:get (240/h per uid) stops hot loops; cached hits are one Redis read Tests: tests/routers/test_static_map.py (auth 401, 400 malformed pins, 502 upstream failure, 200 + private cache headers, cache hit/miss/order- insensitivity/no-cache-on-failure/missing key tolerances). Verified: .venv/bin/python -m pytest tests/routers/test_static_map.py -q -> 14 passed; scan_async_blockers clean. * fix(backend): enrich sync-path geolocation at the pipeline coordinator Failure-Class: FC-sync-geolocation-missing-address | new | none Offline-synced conversations were created with raw coordinates and no address: REST create, developer API, integration ingest, and live finalization all run resolve_geolocation, but the sync path shipped raw coords straight through. Recaps built from synced conversations showed 'Unknown' timeline rows. _run_full_pipeline_background_async - the one coordinator both the inline and Cloud Tasks dispatch branches call - now enriches the job's geolocation once, before any segment is processed and before the concurrency gate (no slot held during the geocode call). The resolver keeps the caller's exact coordinates and returns its input unchanged on any geocode miss/error, so a failure never drops the location. Tests: tests/unit/test_sync_geolocation_enrichment.py drives the real coordinator with a fake geocoder - one geocode per job (not per segment), enriched value reaches every segment, raw geolocation survives a geocode failure, None passthrough. Fixture stub lists in test_sync_v2 / test_sync_transcription_prefs gained the utils.conversations.location stub with an identity passthrough (the real resolver's miss behavior). Verified: pytest tests/unit/test_sync_geolocation_enrichment.py test_sync_v2.py test_sync_transcription_prefs.py -> all passed. * fix(backend): fill empty daily-summary pin addresses at read time Failure-Class: FC-sync-geolocation-missing-address | new | none generate_comprehensive_daily_summary copied c.geolocation.address verbatim, so pins from conversations created before write-time enrichment (the sync path) rendered as 'Unknown' recap timeline rows. The pins loop now fills an empty address through the shared ~100m- rounded geocode cache - the same entries write-time enrichment writes, so an already-enriched day costs no extra upstream call. All three callers are sync contexts, so the sync geocoder is used. A geocode miss or error leaves the address empty and keeps the pin (the app labels it 'Unknown'); regenerating a summary retroactively fixes history with no migration. Tests: tests/unit/test_daily_summary_location_address_fill.py (filled from geocoder with exact coords preserved, present address skips the geocoder, miss and exception both keep the pin address-less). Verified: pytest tests/unit/test_daily_summary_location_address_fill.py test_daily_summary_zero_coordinate_locations.py -> all passed. * feat(app): OmiMapPreview - one static-map widget with an offline pin-dot canvas Every map preview now funnels through a single widget backed by the authed backend static-map proxy (GET /v1/static-map): URL built by buildOmiStaticMapUrl (pins quantized to 4 decimals to match the server's cache quantization, deduped, capped at 50), image fetched with the session's Authorization header, and a deterministic dark canvas with one white dot per pin while loading, offline, or on any failure - never an error state. Product direction: previews in-app, tapping opens the native map app via MapsUtil.launchMap. The conversation detail geolocation card migrates from the client-keyed getMapImageUrl URL to this widget; getMapImageUrl is removed (maps_util keeps launchMap + place URL), taking the last direct client-side Google Static Maps call with it. Verified: flutter test test/widgets/omi_map_preview_test.dart test/widgets/daily_summary_card_test.dart (widget+URL builder tests); flutter analyze clean on changed files; analyze_ratchet.sh passed. * feat(app): replace CARTO tile maps with static previews; drop flutter_map CARTO began enforcing API keys on basemaps.cartocdn.com (keyless requests now return watermarked tiles), and Omi has no CARTO key. All three flutter_map surfaces now render OmiMapPreview instead - zero remaining tile traffic, no keyed tile provider in the app: - conversation map page: static preview of every ~100m cluster anchor (tap opens the native map app) above a grouped 'conversations at this place' list; single-conversation places open the conversation directly, multi-conversation places keep the cluster bottom sheet; grouping logic and cluster-row keys unchanged - daily summary card: 96px preview strip of the day's pins in the recap carousel (the highest-frequency map surface) - now one cached server-side render per distinct pin set instead of dozens of tiles per card - daily summary detail 'Your Day's Journey': 200px preview; image tap still opens the day's first stop (Apple Maps cannot take waypoints via map_launcher), per-stop timeline rows unchanged flutter_map is removed from pubspec (latlong2 stays - journey grouping uses it). The client-embedded Env.googleMapsApiKey goes with it: all static maps come from the server-restricted key behind the proxy now. envied outputs regenerated; test EnvFields stubs updated to match. Verified: bash test.sh / flutter test -> 1758 passed, 5 skipped, 0 failed; scripts/analyze_ratchet.sh passed (9 lint counts improved). * test(app): update map-surface tests for static previews - daily summary card: assert the preview strip, proxy URL pins/quantize/ dedupe, offline canvas fallback, and no-map-for-invalid-coordinates (was: tile provider request counting) - daily summary detail page: drop the TileProvider seam (widget no longer takes one) - conversation map groups: group-card key + cluster sheet rows replace marker keys; journey/env stubs gain the EnvFields change Verified: flutter test on all four files -> passed. * test(backend): stub utils.conversations.location in the cloud-tasks loader pipeline.py now imports async_resolve_geolocation at module scope; the cloud-tasks stub loader replaces its parent packages with MagicMocks, so the submodule import fails in file isolation (the earlier green run leaned on a module cached by another test file). Adds the explicit stub with an identity passthrough - the real resolver's miss behavior. Verified: BACKEND_UNIT_TEST_FILE_LIST=<this file> bash test.sh -> 91 passed. * chore(app): drop GOOGLE_MAPS_API_KEY from the env template The client no longer embeds a Maps key - all static-map renders go through the server-restricted key behind GET /v1/static-map. * test(backend): provide redis r on the usage-tracking stub set external_integrations now imports utils.conversations.location (daily- summary address fill), which imports the redis client symbol; the usage-tracking stub environment replaces database.redis_db with an empty module, so the import fails in file isolation. Give the stub an r. Verified: BACKEND_UNIT_TEST_FILE_LIST=<this file> bash test.sh -> 21 passed. * docs(backend): note the static-map provider URL budget with current limits * chore(backend): declare the static-map route policy in the manifest * fix(backend): drop the redundant isinstance on the typed pins parameter * fix(app): hold the preview canvas until the auth header resolves The first build fired the authed proxy request before getAuthHeader() resolved; CachedNetworkImage keys its cache by URL, so the later header-arriving setState never re-fetched - a permanent 401 fallback canvas. The widget now renders the pin-dot canvas (which already doubles as the error/offline path) until the header resolves, and only then mounts the network image with the Authorization header attached. The header resolver is injectable (authHeaderProvider) so tests control when it completes; an explicit imageUrl (test seam) skips the gate. Tests: new regression case asserts no CachedNetworkImage while auth is unresolved and the authed image after the completer fires; the card tests repoint URL assertions at the preview widget's pins (URL building has its own unit tests). Verified: flutter analyze clean; flutter test (full) -> 1759 passed. * fix(app): restore the conversation_map_marker automation key on group rows The per-place tappable key predates the static preview; keep it stable for automation (the PR brief promises preserved keys). Verified: flutter test test/unit/conversation_map_groups_test.dart passed. * fix(backend): normalize static-map dimensions, dedup render stampedes, count canvas fallbacks Review findings on the static-map proxy: - Dimension handling: one proportional scale factor (min(1, 640/w, 640/h)) computed once in fetch_static_map replaces the independent per-axis clamps - aspect is preserved and every request that differs only by scale normalizes onto the SAME cache entry (cache key and provider URL both use the effective size). - Stampede dedup: after a miss, a per-key render lock (r.set nx, 30s TTL) elects one renderer; concurrent misses poll the cache (0.25s interval, 15s budget) for the holder's result. Lock-held-timeout and lock-unavailable (Redis broken) both fail OPEN to an unlocked render - a lost lock never becomes a 502, and a broken Redis never pays the 15s wait budget. - record_fallback (component=static_map, provider_static_map -> client_pin_canvas, outcome=degraded) fires before the 502 so the degrade is counted in the shared telemetry. Tests: proportional normalization + shared cache entry, oversized fetch normalizes (size=640x150), concurrent misses render once, waiter polls a foreign lock holder to its hit, wait-timeout renders unlocked, 422s for out-of-bounds width/height via TestClient (Query contract), fallback telemetry kwargs. Verified: pytest tests/routers/test_static_map.py -> 21 passed; pyright (scripts/typecheck.sh) 0 errors, no findings in changed files. * fix(backend): cap daily-summary geocode attempts; boost-exempt static_map:get - The read-time address fill now bounds geocode ATTEMPTS at 10 per summary generation: cache hits are cheap but attempts are the deterministic wall-clock bound (10 x the geocoder's 10s worst case stays inside the job budget). Pins past the cap keep an empty address and the app's 'Unknown' fallback. Test: 11 empty-address pins -> 10 filled, 11th untouched, 10 geocoder calls. - static_map:get joins the boost-exempt rate policies: under prod's RATE_LIMIT_BOOST the 240/h hot-loop cap would otherwise resolve to 24k/h and stop protecting the billable provider calls. Verified: pytest test_daily_summary_location_address_fill.py -> 5 passed; utils.rate_limit_config import shows static_map:get in BOOST_EXEMPT_POLICIES. * test(backend): self-contained enrichment fakes; model the resolver short-circuit - test_sync_geolocation_enrichment no longer imports the transcription-prefs fixture module: sharing it loaded real heavyweight modules (GCS/Firestore protos, anthropic/jiter) inside the stub window, and stub_modules' teardown evicted them so later files broke on re-import (duplicate proto registration, jiter NameError) when run in one process. The fakes are now minimal and self-contained - every heavy leaf is stubbed, light enum modules (stt.outcomes, sync.lanes, sync.telemetry) stay real. - The None-geolocation test now models the real resolver's short-circuit (falsy input returns immediately, attempts recorded only for truthy input) and asserts: resolver called exactly once with None AND zero geocode attempts. Docstring states what is actually proven. - One-line caveat at the cloud-tasks loader injection site: new submodule imports must be added to heavy_deps explicitly (MagicMock parents do not resolve submodules). Migrating that hand-rolled loader to stub_modules would cascade across its 37 call sites - deliberately left as-is. Verified: pytest on the combined focused group (static_map, summary fill, sync enrichment, cloud tasks, sync v2, transcription prefs, geocode resolve) -> 372 passed in one process. | 9 天前 | |
app(test): cover intro skip button and back-arrow skip | 2 个月前 | |
feat(telemetry): close mobile lifecycle and device gaps (#11539) * feat(telemetry): standardize mobile app context * feat(telemetry): record mobile account creation * feat(telemetry): add mobile app sessions * feat(telemetry): normalize mobile search events * feat(telemetry): add mobile hardware family * feat(telemetry): distinguish hardware identity * feat(telemetry): instrument firmware updates * feat(telemetry): propagate recording firmware context * feat(telemetry): record subscription plan changes * feat(telemetry): add mobile recording lifecycle identity * feat(telemetry): instrument recording upload lifecycle * test(telemetry): align mobile context assertions * fix(app): bound analytics package metadata lookup Package metadata enrichment now shares the analytics initialization timeout so a missing or unresponsive platform plugin cannot block event delivery. Adds a regression test for the hanging method-channel path and initializes package metadata in the permissions analytics widget test.\n\nVerification:\n- flutter test test/unit/analytics_manager_package_info_timeout_test.dart test/widgets/permissions_interstitial_analytics_test.dart test/unit/analytics_manager_fail_open_test.dart --reporter expanded\n\nFailure-Class: none * fix(telemetry): close capture lifecycle gaps Focused Flutter tests pass for capture telemetry, device analytics, device families, and DAT DFU guards. Failure-Class: none * fix(capture): reject stale transcription socket results * fix(telemetry): classify upload HTTP failures * fix(telemetry): normalize unavailable firmware versions Failure-Class: none * docs(telemetry): keep event catalog off the public docs site Main removed docs/analytics/events.md from docs.omi.me. Preserve 11539's mobile telemetry dictionary in web/admin/docs/posthog-events.md, and update test overrides for main's geolocation and clientConversationId signatures. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: axAilotl <231548431+axAilotl@users.noreply.github.com> Co-authored-by: Max Carter 祁明思 <undivisible@users.noreply.github.com> Co-authored-by: David Zhang <david@scalingforever.com> Co-authored-by: Cursor <cursoragent@cursor.com> | 15 天前 | |
feat: add JIT knowledge ledger foundation and guarded adoption (#12084) * feat: add JIT knowledge ledger foundation * chore: refresh integration OpenAPI contract * fix: make trigger evaluation release-safe Failure-Class: none * fix: preserve lifecycle semantics in ledger apply Failure-Class: none * feat: adopt guarded JIT knowledge surfaces Route the agent preference writer through the intent-backed ledger, register a privacy-filtered entity timeline tool, render optional evidence on Windows, and add a base-ref-protected Gate F legacy-surface ratchet. Failure-Class: none * feat: add progressive JIT knowledge reads Register owner-scoped current-ledger search and explicit playbook hydration, with pre-limit semantic filtering and bounded outputs. Add a content-free planner/resume migration fixture without claiming canonical transaction completion.\n\nValidation: 141 focused backend tests passed; backend typecheck reported 0 errors; repository preflight passed 120 checks. * feat: render chat evidence on web Render bounded, fail-soft conversation evidence after authoritative answers in both web chat entry points. Unsupported, future, duplicate, and raw failure details remain inert.\n\nValidation: 337 web tests passed; web typecheck, oxlint, and Prettier passed; repository preflight passed 120 checks. * feat: require intent-backed ledger search results Apply the intent-backed requirement at the final merged canonical/history filter, with a passive historical-row regression case.\n\nValidation: 54 focused backend tests passed. * test: keep agent tool isolation stubs current * feat: gate JIT conversation retrieval * fix: make entity timeline scans deterministic Failure-Class: none * fix: honor rejected ledger projections Failure-Class: none * feat: render inert screen evidence on web * fix: reuse canonical review projection Failure-Class: none * fix(web): await recap context effect Failure-Class: none * test: amortize preference tool isolation load Failure-Class: none * feat(app): add knowledge ledger review surface Failure-Class: none * feat(macos): use canonical ledger prompt projection Failure-Class: none * test(memory): classify legacy surface inventory roles Failure-Class: none * fix(app): preserve ledger history completeness state Failure-Class: none * feat(macos): preserve canonical ledger mirror metadata Failure-Class: none * fix(app): match canonical ledger ordering Failure-Class: none * test(api): prove ledger client schema parity Failure-Class: none * feat(memory): expose bounded ledger history Failure-Class: none * chore(api): generate ledger history clients Failure-Class: none * feat(retrieval): add bounded card participants Failure-Class: none * feat(macos): project ledger trigger watchlist Failure-Class: none * fix(clients): fail closed on ledger authority Failure-Class: none * feat(macos): expose bounded trigger snapshot Failure-Class: none * fix(memory): keep closed history read only Failure-Class: none * feat(app): disclose partial ledger history Failure-Class: none * test(macos): cover ledger trigger bridge Failure-Class: none * fix(app): use neutral ledger accents Failure-Class: none * chore(api): declare ledger history route policy Failure-Class: none * test(macos): remove unsafe JSON fixture unwraps Failure-Class: none * fix(memory): satisfy typed history boundary Failure-Class: none * fix(macos): require prompt snapshot authority Failure-Class: none * test(memory): prove ledger migration on emulator Failure-Class: none * feat(retrieval): emit bounded screen evidence Failure-Class: none * test(memory): classify maintenance retirement readiness Failure-Class: none * feat(macos): adapt Rewind metadata for triggers Failure-Class: none * test(retrieval): align screen timestamp contract Failure-Class: none * feat(memory): correct ledger facts by amendment Failure-Class: none * test(memory): prove ledger correction on emulator Failure-Class: none * feat(macos): harden local trigger observations Failure-Class: none * feat(agent): search bounded historical facts Failure-Class: none * test(macos): cover trigger observation adapter * fix(memory): gate historical fact retrieval * feat(memory): add gated JIT retrieval strategy * test(memory): prove mixed-version JIT runtime parity * chore(memory): keep JIT gate exports type-safe * refactor(memory): isolate JIT prompt contract * fix(conversations): round-trip owner-scoped references Accept the conversation:<id> references emitted by JIT result cards while retaining strict UUID-only bare IDs and share links. Restrict machine IDs to a bounded safe alphabet so evidence suffixes and path-like values fail closed. Failure-Class: none * test(memory): join JIT citations to evidence envelope * fix(retrieval): enforce JIT conversation search budget Cap JIT summary searches per request and bound database hydration to the projection limit before reads. Preserve the legacy path when JIT is disabled. Failure-Class: FC-unbounded-user-collection-in-prompt * fix(memory): keep JIT retrieval request scoped * test(macos): prove future JIT evidence stays inert * fix(memory): keep JIT card citations request-global Failure-Class: new * fix(retrieval): separate JIT hydration from search Treat gated owner-scoped references as exact hydration without searching transcript text for the reference. Charge every JIT candidate search to the shared four-search request budget, including snippet-bearing requests, while keeping exact hydration free and preserving released JIT-off UUID/share-link behavior.\n\nVerified:\n- cd backend && ./.venv/bin/python -m pytest tests/unit/test_conversation_jit_processing.py tests/unit/test_conversation_exact_reference_search.py -q (58 passed)\n- cd backend && uvx --from pyright==1.1.403 pyright -p pyrightconfig.json --pythonpath .venv/bin/python (0 errors)\n- git diff --check\n\nFailure-Class: FC-unbounded-user-collection-in-prompt * fix(memory): keep repeated JIT cards index-safe * fix(retrieval): satisfy JIT card type contract * fix(retrieval): hydrate collected JIT cards * test(app): preserve answers during delayed evidence requests * test(app): exercise production evidence composition * feat(memories): restore superseded ledger facts * fix(memories): reconcile reverted ledger facts * feat(memories): append reverted ledger facts * feat(memories): synchronize revert client contract * fix(memory): name ledger revert identity * fix(memories): type and enlarge revert controls * fix(memories): fence revert retries and refreshes * fix(memories): fence ledger revert authority * test(memory): count ledger revert rate limit * feat: expose agent-controlled historical facts * feat: reopen standalone ledger facts * feat: add fail-closed JIT QA bundle routing * feat: add safe local JIT QA backend stack * fix: harden isolated JIT QA stack * feat: add explicit multi-source entity timeline * feat(backend): add JIT rollout authority * feat(backend): fence every proactive paid boundary * fix(backend): release proactive quota on cancellation Release the reserved proactive quota exactly once when cancellation interrupts paid-boundary refresh or a provider retry, then re-raise cancellation without emitting retry telemetry. Add deterministic regression coverage for both cancellation points. Failure-Class: FC-proactive-quota-cancellation | new * fix(backend): make proactive quota cancellation safe Detach in-flight Redis reservations on request cancellation and release only admitted slots once they settle. Move direct-provider fallback telemetry behind the fresh paid-boundary rollout check so late kill or unknown decisions cannot report false recovery.\n\nFailure-Class: FC-proactive-quota-cancellation | new * fix(backend): preserve quota compensation during shutdown Keep late Redis reservation compensators outside the ordinary cancellable background-task drain. Desktop and main application shutdown paths now wait for these critical compensators before cancelling ordinary work, with deterministic blocked-thread and lifecycle-order regressions.\n\nFailure-Class: FC-proactive-quota-cancellation | new * fix(backend): use expiring proactive quota leases * fix(backend): make quota finalization clock-safe * fix(backend): isolate jit rollout control plane * fix(backend): close jit control plane safely * fix(backend): emit retry recovery after quota commit * test(backend): keep rollout app contract fast * feat(jit): add guarded proactivity and first-open policies * chore(desktop): mark jit policy as internal * test(desktop): cover jit proactivity policy flow * feat(backend): wire durable JIT first-open processing * feat(desktop): fence JIT proactivity runtime admission * feat: activate authoritative JIT proactivity runtime * fix: harden JIT proactivity authority * fix: close proactive runtime authority gaps * fix(jit): make first-open effects resumable * fix(jit): fence outstanding first-open work * fix(jit): resume app usage receipts * fix(jit): make app usage retries no-op Failure-Class: none * fix(jit): allow completed usage after app deletion Failure-Class: none * fix(jit): register first-open folder query Failure-Class: none * Fix first-open import isolation * feat(memory): govern ledger slots and prompt winners * feat(macos): stage guarded ledger prompt adoption * feat(jit): adopt authoritative ledger prompts on macOS * fix(jit): close ledger adoption authority leaks * fix(jit): reauthorize every ledger migration write * fix(jit): fence ledger cutover publication * fix: keep ledger prompt rollback reversible * feat(jit): add guarded frame request retention contracts * fix(jit): close frame retention authority and evidence lifecycle * fix(jit): make frame retention retries and cleanup durable * fix(jit): make frame evidence recovery and retention complete * fix(jit): close frame retention recovery gaps * Harden temporary frame retention and deployment * fix: harden JIT frame retention and consumption * fix: close JIT frame lifecycle recovery gaps * fix: unify JIT frame authority and retention Failure-Class: FC-split-mutation-authority * docs: keep frame retention guidance lean * fix: retire duplicate frame flag bindings Failure-Class: FC-split-mutation-authority * fix: register frame keyframe queries Failure-Class: FC-split-mutation-authority * fix: serialize frame retention deploys Failure-Class: FC-split-mutation-authority * test: cover frame pixel deletion ordering * style: format cumulative Dart changes * fix(app): retain permanent conversation photo fetches * fix: bound frame vision retention and authority * fix: drain terminal frame request metadata * chore: record internal ledger adoption change * feat(memory): add dark daily sweep authority * feat(memory): harden daily sweep fences and runtime seam * feat(memory): reconcile existing standing triggers in sweep adapter * fix(memory): harden daily sweep recovery and source fences * fix(memory): close daily sweep source producers * fix(memory): close daily sweep review findings * Add dark daily memory sweep authority and recovery * fix(memory): harden daily sweep rejection repairs * test(listen): stub onboarding admission in bootstrap regression The daily sweep PR fences onboarding mode behind the server-owned backend admission (get_backend_onboarding_admission), so the bootstrap regression test now simulates an admitted session instead of failing closed on a real Firestore read. Verification: focused test passes in 1.64s (previously failed after a 4m27s Firestore timeout); full test_listen_runtime_regressions.py + test_onboarding_question_start.py: 26 passed; black --check clean. * fix(memory): close daily sweep rollout and retry cursors * fix(memory): isolate daily sweep lifecycle and retry fairness * Harden daily sweep admission and completed-day staging * fix daily memory sweep reliability boundaries * preserve daily sweep invocation tombstones * close daily sweep invocation lifecycle fences * fix: keep daily sweep lifecycle cleanup active * fix: acquire ledger snapshot client off event loop * fix(memory): preserve migration tier fence without legacy growth * test(memory): prove legacy adjudication race fences * fix(dev): allow bounded ADC readiness refresh * test: keep ledger prepush deterministic * test(memory): register prompt receipt control path * fix(memory): fence ledger writer transitions * feat(backend): preserve closed ledger history in export * feat(memory): define ledger query semantics * fix(backend): fence trigger snapshots on final authority * fix(backend): bypass stale coalesced JIT refreshes * feat(macos): mirror bounded memory evidence Decode generated v3 evidence into a domain mirror, persist canonical bounded JSON through the memory cache, and preserve it across compatibility sync and older-local conflicts. Invalid, future-shaped, oversized, and over-count payloads fail closed without hiding memory text or granting prompt authority. Tests: xcrun swift test --package-path Desktop --filter ServerMemoryV17DecodingTests Tests: xcrun swift test --package-path Desktop --filter MemoryLedgerMirrorTests Tests: python3 scripts/check_desktop_test_quality.py Failure-Class: none * fix(macos): fence and classify memory evidence Keep generated memory fields independent from malformed evidence, distinguish absent valid and invalid evidence states, preserve prior evidence on invalid payloads, and gate replacements on a monotonic server timestamp so stale active evidence cannot resurrect redacted rows. Cover populated-table migration upgrades. Tests: xcrun swift test --package-path Desktop --filter ServerMemoryV17DecodingTests Tests: xcrun swift test --package-path Desktop --filter MemoryLedgerMirrorTests Tests: python3 scripts/check_desktop_test_quality.py Failure-Class: none * fix(macos): preserve evidence fences and scrub redactions Advance evidence revisions for identical valid payloads, fence stale active responses after a local edit, and remove artifact/device pointers from redacted evidence before canonical persistence. Tests: xcrun swift test --package-path Desktop --filter ServerMemoryV17DecodingTests Tests: xcrun swift test --package-path Desktop --filter MemoryLedgerMirrorTests Tests: python3 scripts/check_desktop_test_quality.py Failure-Class: none * chore(macos): record ledger evidence mirror * feat(macos): deep-link local evidence cards to Rewind * fix(macos): fence Rewind frame evidence version * fix(macos): validate Rewind evidence card availability * fix(macos): bind task detail Rewind navigation to local leases * fix(macos): fence Rewind citation owner handoff * chore(macos): register Rewind evidence deep links * test(macos): cover Rewind evidence navigation * feat(desktop): evaluate JIT trigger watchlists locally * feat(desktop): wire authoritative JIT trigger runtime * feat(desktop): bind JIT claims to snapshot authority * fix(desktop): revalidate trigger authority at execution * fix(desktop): keep JIT execution leases live * test(memory): bind standalone reopen to direct-user writer * fix: make JIT QA sign-in self-contained Failure-Class: new Verification: bash desktop/macos/tests/test-jit-qa-target.sh; bash desktop/macos/tests/test-yolo-dev-backend.sh; repaired named-bundle Google sign-in reached authenticated onboarding. * feat(memory): complete JIT policy and native Windows parity * docs(backend): keep service map within context budget * test(macos): cover JIT client and staging flows * chore(backend): declare JIT mirror route policy * fix(backend): use strict Firestore boundary for JIT admission Failure-Class: FC-malformed-doc-read * chore(quality): register malformed-document guard surface * fix(backend): fail closed on malformed JIT authority Failure-Class: FC-malformed-doc-read * refactor(backend): name JIT workflow boundary results * test: repair JIT CI contracts * fix(backend): preserve ledger query exports Retain the explicit same-name re-exports consumed by tests and downstream callers while satisfying the enforced Pyright unused-import boundary after the main rebase. Failure-Class: none * test(backend): isolate gateway setup timing Failure-Class: none * style(memory): format direct-user evidence path Failure-Class: none * test(agent): isolate ACP process-group fallback Failure-Class: none * fix(dev-harness): preserve ownership markers in narrow CI * test(jit): refresh emulator fixtures for current contracts * test(jit): orchestrate local rollout dogfood * test(jit): harden local dogfood authority * fix(dev-harness): install PostHog for CI tests * fix(chat): project server JIT rollout into retrieval Resolve the backend-owned PostHog decision inside the bounded agent setup path and pass only its boolean result to prompt/tool configuration. Unknown or failed authority remains on the released legacy path, while callers cannot self-enroll through configurable input.\n\nVerification: backend/.venv/bin/python -m pytest -q backend/tests/unit/test_chat_async_offload.py backend/tests/unit/test_atomicity_lifecycle_regressions.py (41 passed)\n\nFailure-Class: new * fix(memory): preserve preference writer compatibility Select the agent preference write path from the canonical per-user writer control. Default compatibility mode retains the released MemoryService payload and receipt behavior; ledger mode keeps the retry-stable ledger write, and transition states fail closed.\n\nVerification: backend/.venv/bin/python -m pytest -q backend/tests/unit/test_chat_async_offload.py backend/tests/unit/test_atomicity_lifecycle_regressions.py (41 passed)\n\nFailure-Class: FC-split-mutation-authority * fix(jit): separate migration rollout authority Keep staged JIT chat and proactive exposure independent from legacy-row migration and writer cutover. Migration now requires its own default-off PostHog flag and still rechecks the shared kill switch at every mutation and publication boundary. Repair the isolated conversation-JIT fixture for main's chat-scope import. Verification: 217 focused JIT, chat-scope, migration, and lifecycle tests passed; 28 conversation-JIT fixture tests passed; independent Sol review accepted the split for QA-only dev rollout. Failure-Class: FC-split-mutation-authority * fix(photos): preserve retained image retrieval Treat an empty legacy inline marker as absent when permanent storage is authoritative, while malformed non-empty inline payloads still fail closed. Route live and retained thumbnails through the storage-aware image loader and preserve the conversation identity through the full-screen viewer.\n\nVerification: backend data-export tests 32 passed; Flutter photo-viewer tests 5 passed; focused Dart analysis clean; independent Sol review found and verified the viewer identity repair.\n\nFailure-Class: none * fix(memory): keep disabled daily sweep dark Resolve the backend-owned authority before inventory and require its literal true decision before any UID discovery, registry, cleanup, scheduler, model, or commit work. Missing, malformed, throwing, disabled, and kill-switched authority now exits without touching user data; enabled behavior is preserved.\n\nVerification: 60 focused daily-sweep job, scheduler, and inventory tests passed; independent Sol review accepted the fail-closed gate.\n\nFailure-Class: FC-split-mutation-authority * fix(jit): satisfy fail-closed type contracts * test(backend): admit full runtime contract checks * style(backend): format conversation bound test * test(backend): keep conversation router isolation current * test(backend): admit export boundary duration * fix(macos): persist failed chat turn notice Failure-Class: none * fix(macos): repair JIT rollout admission contracts Failure-Class: none * fix(windows): treat JIT screen evidence as untrusted Failure-Class: none * fix(backend): preserve explicit app failure contract Failure-Class: none * fix(app): finish photo viewer consolidation * fix(backend): make provider writes lock-free against the deletion gate The account-wide legal-hold deletion gate wrapped every GCS upload and Pinecone/Typesense upsert in an exclusive per-uid Firestore mutex with no lease: concurrent same-account writes hard-failed (dropped audio, lost vectors) and a crash between acquire and finish blocked the account's gated operations forever, with no janitor. Provider writes now use a lock-free fence that refuses only during account deletion or a live destructive operation; destructive kinds keep exclusive ownership, an abandoned gate self-expires after six hours, and releasing a gate on the failure path can no longer mask the original error. Failure-Class: none Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(backend): issue onboarding admission at socket connect The completed-onboarding early exit returned False from an Optional[str] function; the listen runtime derives admission via 'is not None', so users who had already completed onboarding were admitted with a fabricated session id — the exact provenance forgery the admission exists to prevent. Separately, the 20-minute admission TTL was anchored to the app-launch state read, so a user reaching the speech-profile step late (or any client that never calls the state endpoint) silently lost onboarding questions and is_user tagging. The bootstrap now issues or refreshes the admission from the durable account state at connect time; completed accounts still can never re-enter, and issuing stays best-effort with the read failing closed. Failure-Class: none Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(backend): keep the released proactivity lane open for legacy clients Gating /v1/desktop/proactivity/completions on the JIT cohort returned 403 to every non-admitted user — which is the entire deployed desktop fleet on deploy day, since shipped clients poll this route continuously and treat 403 as a plain error. Context-bucket extraction and the director would have died fleet-wide, dark cohort or not, and any environment without a PostHog key (local, self-host) would have lost the lane entirely. The route returns to merge-base admission semantics (tier quotas only); JIT admission remains enforced on the JIT reservation routes, and retiring this lane stays a later explicit operation after clients migrate. Failure-Class: none Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(backend): withhold JIT tools and history reads outside the rollout Five new tools (search_knowledge, search_historical_facts, read_playbook, get_entity_timeline, look_at_frame) sat unconditionally in CORE_TOOLS, so every legacy chat request carried their schemas and the model burned tool budget on 'no entries found' answers. They are now filtered per request off the same resolved rollout boolean that gates the JIT prompt appendix. The memories-tab ledger-history endpoint likewise answered every user with a bounded 501-row provider scan that can only ever be empty outside the rollout; it now returns empty without the scan for non-admitted (and unknown/error) states. Failure-Class: none Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(backend): bound rollout control-plane cost and confine sync resolution Synchronous callers resolved rollout flags via per-call asyncio.run against the shared provider singleton, crossing event loops: awaiting a Task attached to another loop raises, a timed-out asyncio.run strands a coalescer entry that then serves stale UNKNOWN forever, and the LRU cache was mutated from multiple threads. Sync resolution now runs on one long-lived control-loop thread with its own authority instance. Unknown snapshots gain a 5-second negative cache — UNKNOWN can never authorize work, and without it a fleet whose flags are simply absent pays one uncached PostHog call per conversation finalization. The screen-sync loop drops its force_refresh (one uncached decide per device per minute fleet-wide) and moves to its own rate bucket so two Macs' background sync can no longer starve conversation photo reads out of the shared 120/hour frame-requests bucket. The first-open policy's kill-switch telemetry label also reported str(Enum) instead of the value and could never match. Failure-Class: none Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(backend): skip eager extraction under a non-compatibility writer mode A ledger-cutover user still ran the full L1 extraction model call at finalization, after which writer admission refused the compatibility write — the conflict retried, exhausted, and failed the entire finalization for every conversation, with the model spend already paid. Extraction now checks the canonical writer mode first and skips when the daily sweep owns memory formation; only a positively-read non-compatibility mode skips, so any control-state read failure preserves the legacy eager path. Failure-Class: none Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(backend): export tolerates byte-less legacy photo rows A conversation photo row carrying the legacy empty inline marker and no storage reference failed the whole portability export forever, though it holds no durable image anywhere — there is nothing to omit. Such rows now export as metadata with a content-free gap reason. Frame requests in a retained state keep the fail-closed contract via an explicit require_bytes parameter. Failure-Class: none Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(windows): harden JIT delivery, admission, and bootstrap boundaries Five verified defects: (1) the exclusive notification delivery slot leaked on any throw between reservation and commit — one SQLite hiccup during a JIT turn permanently silenced every proactive lane; the span is now try/finally-guarded and stale slots expire after ten minutes. (2) The ambient lane interpolated the raw window title into a tool-capable agent prompt; the turn now carries only the opaque context handle plus a sanitized executable name, framed as untrusted data like the nano-triage lane. (3) Google Calendar was fetched every ~60s before admission, so non-cohort users with Google connected paid ~1,440 reads a day for a refused feature; observation now gates calendar evidence on the cached authority. (4) Rollout-authority errors reset the cache and retried every frame (~1 req/s offline, forever); failures now back off from 30s to 10 minutes. (5) An unguarded JIT schema exec inside the shared database open could abort local storage for all features; the mirror bootstrap is now isolated, keeps the host-facing tables alive, and JIT stays inert when unavailable. Also re-checks the control-plane owner before committing the toast so an account switch mid-turn cannot show the previous owner's advice. Failure-Class: none Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(macos): restore screen provenance, guard migrations, fence chat turns Four verified defects: (1) every pre-existing screen-derived task lost its 'Screen context / Open Rewind' source row because the new evidence policy dropped any provenance that is not rewind_frame.v1; the merge-base fallback row is restored for capture.v2/legacy refs (a test flipped to match the regression is restored to its merge-base assertions). (2) RewindDatabase published its pool before migrating, latching a failed migration into a permanent false-initialized state, and three unguarded ALTER TABLE memories migrations died with duplicate-column on machines that ran earlier builds of this branch; migration now precedes publication and the ALTERs/CREATEs are existence-guarded. (3) EventKit was queried on every context visit before the flags check; non-admitted owners now build no observation inputs. (4) A failed chat turn's reconstructed notice could be appended into a different conversation's transcript when the user switched sessions or cleared chat mid-flight; both transcript resets now revoke the active turn like selectApp already did. The pre-terminalized discard class (user Stop/watchdog) still drops the durable notice on relaunch — pinned by a characterization test in agent/tests/conversation-journal.test.ts with the least-invasive fix described there. Failure-Class: none Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(testing): resolve firebase-tools from the checked-in dependency npx --prefix resolves the package bin against the current directory on some npm versions, and the admission runner deliberately launches from an isolated temp dir (firebase writes debug logs to cwd) — surfacing as 'sh: firebase: command not found' on hosts without brew node@22. Prefer the vendored node_modules binary when it matches the pin; npx remains the fallback. Failure-Class: none Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(backend): keep one eager-extraction call site for the surface ratchet Failure-Class: none Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(backend): gate eager extraction at the public boundary The writer-mode skip moves from _extract_memories_inner to extract_memories: the replace-policy contract test pins the inner helper to exactly the canonical replacement path, and the public boundary is the better seam anyway — a sweep-owned user now skips parity capture and usage tracking along with the model call. Failure-Class: none Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(listen): stub onboarding admission issuance in bootstrap regression The connect-time ensure call landed in a harness that only stubbed the read, so the bootstrap test paid an extra real-module exception path and grazed the 0.30s fast-unit CPU budget under fanout load. Stub the issuance like the read. Failure-Class: none Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(listen): allowlist the bootstrap regression's CPU budget The full listen-runtime bootstrap test measures exactly at the 0.30s fast-unit CPU budget under a saturated pre-push fanout (CPU inflates ~2x there per the guard's own notes) while passing comfortably alone. It exercises deliberately heavyweight machinery; record it as an intentional exception rather than trimming the coverage. Failure-Class: none Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(backend): keep list(CORE_TOOLS) literal through JIT tool gating The JIT-only tool filter replaced the list(CORE_TOOLS) assignment with an inline comprehension, which broke the prompt-cache structural invariant (test_prompt_cache_optimization.py::test_core_tools_used_in_both_functions). Restore the list(CORE_TOOLS) copy and apply the JIT-only filter as a conditional pass, preserving rollout semantics and tool order. * feat(jit): drop automatic goal updates from the JIT featureset Product decision (David, 2026-08-26): goals change only through explicit user action for JIT-admitted conversations. Goal progress is no longer a first-open obligation — the effect is removed from FIRST_OPEN_EFFECTS and the worker, and the policy plan can no longer express deferring it. Legacy obligations carrying a pending goal_progress row are normalized away and complete on the remaining two effects. Non-JIT (legacy eager) conversations keep today's automatic goal updates unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(sweep): one summary-spine agent pass per day, with folder backstop Replaces the per-conversation transcript extractor in the completed-day producer with a single two-phase agent run: the whole day's conversation summaries go in as one bounded spine (200 conversations / 120k chars — effectively unreachable, so heavy days no longer stall the cursor), and the agent may request up to 8 raw transcript excerpts (8k chars each) to verify specifics before finalizing. At most two provider calls per user per day, both inside the existing at-most-once invocation fence; the staged page carries the memory candidates AND folder assignments for the day's unopened, unfiled conversations, applied idempotently (first-open or user assignment always wins). Memories must cite their source conversations; uncited output is dropped. The cost gate becomes a worst-case ceiling checked before any call. The onboarding cold-start channel keeps per-conversation transcript extraction unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(sweep): harden the daily agent prompts from a real-data lab pass Iterated on one real heavy day (26 conversations) with strong- and weak-model stand-ins, an adversarial judge, and hand-verified transcript ground truths. Rules added, each pinned to an observed failure: actor binding in active voice with a personal-attribute gate (a discussed or recommended topic is never someone's attribute; judgments about named people are stored as assessments); decision-state basis labels binding the verb (decided/proposed/observed, discussed-no-outcome dropped); salience ordering (money, metrics, named-party intent, identity, and durable decisions before any operational fact; one fact per memory); never guessing the direction of an invitation/offer/commitment (verify or drop); and no deferring the whole answer to verification. The agent output schema gains a 'basis' field. The memories QoS call-site inventories now count the daily-sweep agent's call site (3 -> 4). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(sweep): tune the daily agent prompts against the real memories model Ran the assembled prompts against gpt-5.6-luna (the real 'memories' route model) on the same real day. Three refinements from observed behavior: the basis label no longer leaks into memory text (metrics read as metrics, not 'David observed that…'); the never-guess-direction trigger is mechanical (passive/verbless summary phrasing or 'Speaker' as the actor forces a transcript_request — luna confidently inverted 'Tim: Invited to New York' until this; with it, phase B verifies and corrects to the true direction), hedging is itself a request signal, and nothing high-salience may be silently dropped; and a rich-day yield anchor (8-16 memories for 15+ conversations) counters the model's over-pruning without inviting padding. Final real-model run: 11 true memories + 2 legitimate verification requests, zero fabrications, ~22k tokens (~2 calls) for a 26-conversation day. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(sweep): profile-maintaining slots, ledger lookups, cache-ready prompts The daily agent now sees the user's current profile (the same get_prompt_memories seam chat uses — the ledger render for migrated users), may run up to 4 owner-scoped prior-memory keyword lookups (provider fail-soft; hits re-read through the canonical store before disclosure) to dedup and supersede, and may name a slot for standing attributes — an occupied slot becomes an amend through the existing canonical occupancy check, so the daily run maintains the rendered profile with no second write path. Both phase prompts share a byte-identical prefix (pinned by a test) and pass a per-user prompt_cache_key through get_llm; measured against gpt-5.6-luna the provider cache is exact-match rather than prefix-based today, so this is future-proofing rather than present savings. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(sweep): type the memory-searcher seam for the pyright contract CI's authoritative typecheck rejected the untyped lookup seam (memories.py: list(Any or [])). The searcher is now Optional[Callable[[str], Sequence[str]]] and results are built through a typed comprehension; behavior unchanged (absent or failing searcher still degrades to an empty result block). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: repair four main-inherited CI breakages after sync origin/main is currently red on its own tip; syncing it into this PR inherits the breakage, so the fixes ride here: - subscription.py: drop the unused get_byok_keys import (pyright reportUnusedImport fails the Backend unit suite). - AppState+Transcription.swift: explicit self for alertPresenter inside the escaping showAlert completion (strict-concurrency compile error in all three Desktop Swift lanes, shipped red on main by d49f978512). - AppState+Permissions.swift: pinned swift-format drift from the same main commit (desktop-swift-format-lint). - web/app/bun.lock: add the prettier + prettier-plugin-tailwindcss entries 64db30c791 pinned in package.json without updating the lockfile (frozen install fails web-app-checks). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(sweep): close the second review round's findings Three parallel adversarial reviews over the post-takeover additions: - Clamp every model-controlled phase-B input (draft memories, request reasons, lookup queries/results) and add the clamped worst case to the pre-call cost ceiling, which previously under-estimated phase B. - Attest an empty consumed day when the staged page carries an older stage schema version instead of stalling the cursor forever on every deploy-boundary schema bump. - Make the folder backstop's unfiled check and write share one transaction so a concurrent first-open/user assignment always wins. - Let equal-rank sweep candidates amend sweep-authored slot occupants: the profile-maintenance path froze after a slot's first write. User statements still always win; slotless subject matches still dedup. - Neutralize ``` fences in summaries/excerpts/lookup results, and mark raw-transcript fallback rows '(unstructured transcript excerpt)' with a prompt rule refusing slots/personal attributes from them without transcript verification (test pins the marker to the rule). - Remove the dead first-open goal-authority threading left by the goals removal, and update the stale jit-first-open-runtime doc. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: repair three more main-inherited breakages All shipped red on main and only surfaced once earlier failures were cleared: - AppState.swift: move the alertPresenter default out of the stored property initializer — Xcode 16.4's SILGen segfaults (signal 11) emitting it, which failed all three Desktop Swift lanes even after the explicit-self fix. - test_byok_security.py: main's BYOK rewrite (d0e3a4eb3a, 1da8880175) changed request_has_llm_byok_key to per-provider enrollment checks and made partial headers fail closed, but left the tests targeting the old get_byok_keys()-based lenient contract (masked on main because pyright failed before pytest ran). The tests now assert the shipped strict contract their own docstrings already describe. - subscription.py: pinned-black formatting for the BYOK fallback expression (the Formatting lane rejects the file as main wrote it). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(tests): stub the chat-agent gateway route pin in the chat router harness Main's a6988be309 made routers.chat import CHAT_AGENT_ROUTE_DIRECT / get_chat_agent_route from utils.llm.gateway_client, but the chat-router test harness (and test_chat_file_upload_unsupported's local override) stub utils.llm.gateway_client without those symbols, so every suite that loads the real router failed at import — masked on main because pyright fails its Backend unit suite before pytest runs. Ninth main-inherited repair in this sync. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(tests): teach test_chat_quota's utils.byok stub the rewritten import surface utils/subscription.py now imports get_byok_uid and get_cached_byok_state (main's BYOK rewrite); the module-scoped utils.byok fake predates them, so reloading subscription under the fake raised ImportError at setup — and the polluted process took test_chat_openapi_operation_ids and test_desktop_screen_crisp down with it in CI's batched run (all three pass standalone). Tenth main-inherited repair, same pyright-masked pattern. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(tests): update three more suites for main's BYOK/gateway import surface Same pyright-masked pattern as the harness and test_chat_quota repairs: - test_desktop_transcribe stubbed utils.llm as a non-package, so routers.chat's new utils.llm.gateway_client import could not resolve (50 failures); the submodule is now in its stub list. - test_paywall_reconnect_gate's BYOK escape-hatch tests never set the request uid context that the enrollment-verifying rewrite requires (middleware sets it in production); they now do, and teardown clears it. - test_chat_session_app_identity's enforce_chat_quota stub rejected the new required_llm_provider keyword. All three suites pass locally (69 + 35 + 6). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(tests): enroll fingerprints in the desktop BYOK tests PR #11454 moved macOS BYOK activation to enrollment-verified fingerprints (isByokActive and usableBYOKEnvironment gate on persistEnrolledFingerprints), and its own test lanes shipped red: the tests store raw keys but never enroll them, so every key reads as inactive. Their teardowns already clear enrollment — the setups now enroll what they store, matching the production activation path. All 8 previously-failing cases (BYOKPaywallTests + the two AgentRuntimeProcessTests BYOK-environment cases) pass locally. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(deploy): enable the daily memory sweep on development The sweep's five deployment inputs were pinned off in every environment, so cohort enrolment alone could never start it -- turning it on for a dogfood account required a second PR. Development now carries the live values: - ENABLED/MODEL_ENABLED on, so the job stops exiting at its first authority gate and the model authority can budget a route. - MODEL_NAME pinned to gpt-5.6-luna, which is the declaration interlock the runner checks against get_model('memories') before any provider call. - MAX_MODEL_COST_USD 0.80, the worst-case pre-call ceiling for a maximal day including phase B's clamped draft/reason/lookup overhead. - COHORT_ENABLED on with COHORT_FLAG daily-memory-sweep-v1, so enrolment is a per-uid PostHog boolean and an unnamed cohort stays a closed rollout. Production is deliberately untouched and stays fully pinned off. The job still cannot form a memory for anyone until that flag exists and resolves true for a uid, which remains a control-plane action rather than a deployment one. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(firestore): terminate the daily-sweep occupant indexes with __name__ The six daily-sweep occupant lookups were the only declarations in the manifest without a trailing __name__ field -- 63 of 69 entries carry one, and main had none missing it. Firestore appends the terminator itself and reports the index back that way, so these six could never match the live inventory. The failure mode is not a missing index; the indexes build fine. It is that reconciliation never converges: every run reports the same six as missing, tries to create them, and fails on ALREADY_EXISTS. That takes down the Firestore schema workflow on both environments permanently, and with it the development backend deploy's readiness gate -- the same class of outage the workflow's own header records from the hourly_usage index in PR #11979. The derived specs previously appended their extra predicates to the base spec's index_fields, which would have placed them after the terminator, so the shared prefixes are now named explicitly and each spec ends with __name__. Verified against real Firestore: reconciliation reports zero missing indexes in both based-hardware and based-hardware-dev. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: close final JIT rollout and CI gaps Fence direct JIT tools and frame pixels, keep Windows account wipes safe after optional schema failures, and repair inherited CI regressions. Failure-Class: none --------- Co-authored-by: David Zhang <9387252+Git-on-my-level@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> | 19 天前 | |
chore(app): dart format --line-length 120 on all Dart files Run dart format across all 456 Dart files to fix formatting drift. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> | 5 个月前 | |
fix(app): warn and retry when conversation processing stalls (#12936) * fix(app): warn and offer retry when conversation processing stalls After two minutes the homepage processing card shows a timeout message and Retry, which re-triggers server reprocess (#5481). Stage labels and completion push are left for follow-up. Failure-Class: none * fix(app): anchor processing timeout to capture end and harden retry Use finishedAt (not createdAt) so long recordings are not instantly flagged, reset the local clock after a successful retry, catch malformed reprocess bodies with the snackbar, and assert via l10n in the widget tests. Failure-Class: none * fix(app): clear analyzer ratchet hits in processing timeout tests Drop unnecessary non-null assertions and const the FormatException. Failure-Class: none | 6 天前 | |
fix(app): stop Profile settings rows overflowing with enlarged text (#13019) The Profile page's "Transcribe Later" row painted RIGHT OVERFLOWED stripes and "Voice response" broke mid-word next to its "Headphones only" chip once system text was enlarged (#12898). The row builder put the title and BETA tag in a rigid Row, so the title could not give up width, and the value chip took whatever it wanted from the title. - New ProfileSettingsTile widget (extracted from the two row builders in profile.dart): title and tag sit in a Wrap so the title wraps by word and the tag flows beside or below it; the chip is capped to 60% of the width shared with the title and ellipsizes past that. - settings_drawer.dart's row gets the same Wrap for its title and tags. - Widget test renders the tile narrow at 1.6x text and asserts no overflow, that a long chip is capped and the title keeps its share, and the subtitle/chevron options. Failure-Class: none Claude-Session: https://claude.ai/code/session_01YU5BMEAfcFxNSWmLLDNfZ3 Co-authored-by: Nathan <nathan@Nathans-MacBook-Air.local> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> | 7 天前 | |
fix(app): add iOS rage-click context | 2 个月前 | |
Merge remote-tracking branch 'origin/main' into pr-9499 # Conflicts: # .github/scripts/product_file_line_count_ratchet_baseline.json # app/lib/services/services.dart # desktop/macos/Desktop/Sources/MainWindow/DesktopHomeView.swift # desktop/macos/Desktop/Sources/MainWindow/Pages/MemoriesPage.swift # desktop/macos/Desktop/Sources/Rewind/Core/VideoChunkEncoder.swift # desktop/macos/e2e/flows/memories.yaml | 1 个月前 | |
fix record options sheet icons rendering outside their circles (#9721) FaIcon (font_awesome_flutter 11) intentionally omits the SizedBox+Center wrapper that material Icon has and paints with TextOverflow.visible, so inside the tightly-constrained 44x44 circle Container (no alignment) the glyph rendered at the top-left, bleeding outside the circle. Fix: set alignment: Alignment.center on the circle container, which loosens the child constraints and centers the icon. RecordOptionsSheet is made public so the regression test can pump it directly. Verification: - flutter test test/widgets/record_options_sheet_icon_test.dart — passes with the fix; fails (icon box 44.0, top-left) with the alignment line removed. - dart analyze on both changed files: no new issues. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> | 2 个月前 | |
app: test ReviewAvatar contrast on light/dark backgrounds | 2 个月前 | |
refactor(app): isolate app review presentation | 13 天前 | |
fix: link meeting notes from always-on capture (#11468) * fix: link meeting notes from always-on capture Use machine-observed call edges to rotate always-on transcription sessions, persist meeting provenance through cloud and local processing, and materialize an exact conversation link in Chat after durable completion. Coalesce completion wakes and keep retries idempotent. Failure-Class: none Verification: backend focused suite (122 passed); Swift focused suite (20 passed); agent journal (63 passed) and TypeScript build; xcrun swift build; make preflight. * test: restore weak-reference compile compatibility Declare the weak reference before assigning it so Swift 6 warning-as-error builds retain the deallocation assertion without diagnosing an immutable weak variable. Verification: focused Swift suite compiled and passed 20 tests. * refactor: extract meeting completion boundaries Keep meeting edge handling and repair-safe receipt emission in focused owners, and document the remaining exhaustive-switch/API-boundary line-count deltas.\n\nVerification: 123 focused backend tests; 37 focused Swift tests. * docs: add meeting notes changelog * test: cover meeting boundary ownership * fix: satisfy meeting receipt type contracts Failure-Class: none * docs: refresh app client OpenAPI contract * fix: version conversation-link materialization Keep the released v1 response union stable while updated desktop clients opt into v2 conversation-link receipts.\n\nFailure-Class: none * security: register v2 materialization policy * fix: model legacy materialization independently Failure-Class: none * chore: refresh generated API clients * chore: refresh generated Swift API client * fix: harden meeting completion delivery Failure-Class: none * chore: record reviewed meeting wiring growth * fix: initialize proactivity failure telemetry Failure-Class: none * fix: address meeting insight review gaps Failure-Class: none * chore: record reviewed delivery guards Failure-Class: none * chore: align Dart formatting with CI * fix: close always-on meeting lifecycle review gaps * fix: keep deferred router imports hermetic | 1 个月前 | |
fix(app): recover terminal auth sessions Prevent authenticated requests from degrading to anonymous traffic, bind token refreshes to the originating Firebase UID, and terminate confirmed dead sessions exactly once. Clear user-scoped provider/cache state, cancel stale async work and conversation retries, route to localized reauthentication UI, and add a signed iOS refresh canary. Verification: cd app && flutter test --no-pub (704 passed); flutter gen-l10n (zero untranslated warnings); targeted dart analyze (no compile errors); git diff --check; independent agent review approved. UI limitation: no physical iOS device or simulator was available for the signed canary; the reauthentication path was exercised through its Flutter widget regression test. | 2 个月前 | |
chore(app): dart format --line-length 120 on all Dart files Run dart format across all 456 Dart files to fix formatting drift. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> | 5 个月前 | |
Merge main and address overlay PR feedback | 2 个月前 | |
feat(app): make first-run voice enrollment a short free-speech sample (#13952) * fix(app): restore speech enrollment escape after WAV creation fails Catch local WAV creation failures through the existing upload-failure state so uploading clears, Skip is reachable, and All Done stays hidden. Regression executes finalize with a failing storage boundary. Validation: flutter test test/providers/speech_profile_provider_test.dart (25 passed). Combined full app suite: 1948 passed, 5 skipped; touched Dart analysis and analyzer ratchet passed. Live mobile mic/upload could not be exercised: no mobile device or simulator is available. Failure-Class: none * feat(app): make first-run voice enrollment a short free-speech sample Explain whose voice Omi needs and the approximate five-second effort, accept any topic, and demote Skip while retaining the escape. Keep progress and automatic completion on the same onboarding-only transcript-duration policy. Settings redo retains its sentence target. Translate the prompt in all 49 locales and document the long-term approach. TDD: new duration and widget expectations failed before implementation. Verification: focused provider/widget tests 30 passed; bash app/test.sh 1948 passed, 5 skipped; touched Dart analysis no issues; analyzer ratchet passed; flutter gen-l10n no untranslated warnings. Production widget exercised with hardware boundaries faked. Live mobile mic/upload and agent-flutter device checks unavailable: only macOS detected and no iOS simulator. * fix(app): address cubic review on voice enrollment copy and tests Naturalize the Croatian first-run prompt, add the missing ARB description, and assert Skip instead of an internal close() count. Validation: flutter test test/providers/speech_profile_provider_test.dart test/widgets/speech_profile_onboarding_test.dart (30 passed); dart analyze on touched files (no issues); flutter gen-l10n. Failure-Class: none | 1 天前 | |
app: widget test pinning the keyless-Positioned diff regression Two-case widget test that exercises the same body-Stack shape used by conversation_detail/page.dart: a GestureDetector plus two conditional Positioned children, the second of which wraps a stateful subtree. A _DisposeCounter counts initState / dispose calls on the overlay subtree. The first test omits keys, drops the bar's `if` to false, and asserts the overlay was disposed and remounted (disposeCount == 1, initCount == 2) — the structural failure that breaks the search TextField's TextInputConnection in production. The second test adds ValueKeys and asserts no remount. Locks the fix in place: if anyone removes the keys later (or adds a new keyless conditional sibling that destabilises the slot), the first test will start failing in the wrong direction and the second in the keyed direction will catch the regression directly. | 3 个月前 | |
fix(app): stop the summary-templates sheet spinning forever when there is nothing to show (#12129) The sheet decided it was loading by looking at the data alone: final isLoading = enabledApps.isEmpty && suggestedApps.isEmpty; so "still loading" and "loaded, and there is nothing" were the same state. Both fetches also swallow their errors into empty lists, which makes the two cases indistinguishable from the data side by construction. A user with no installed summary app -- every new account -- got a shimmer skeleton that never resolved (ShimmerWithTimeout only freezes the animation after 5s, leaving the same grey placeholder on screen). Track completion explicitly instead, so the sheet can render its empty state: the "Get Creative" section, which is how a user with no templates gets a first one. Regression test in app/test/widgets/summarized_apps_sheet_empty_state_test.dart holds both fetches open, asserts the skeleton while they are in flight, then resolves them empty and asserts the skeleton is gone and the empty state is on screen. Without this change the second case still finds four ShimmerWithTimeout widgets. Failure-Class: none | 22 天前 | |
| 19 天前 | ||
fix(app): keep capture teardown safe before service init Converge the published Dart diff on the pinned formatter and refresh backend isolation fixtures for the current imported contracts. Failure-Class: none | 15 天前 | |
fix(app): stop the live transcript follow from stealing reader drags The live Listening transcript re-pins to the live edge while the reader is dragging away from it (#12767). While the follow's driven animateTo owns the Scrollable, a pending reader drag can be orphaned outright (the scrollable ignores pointers and swaps drag recognizers while a driven activity runs), so no user-scroll signal ever fires; the follow then finishes, resets _userHasScrolled, and the next live tick pins again. The reader now always wins: - Any genuine user scroll signal (pointer-driven ScrollStart, drag-detail ScrollUpdate, non-idle UserScroll) immediately records unfollow intent, cancels the in-flight follow (_followAgain / _activeFollow), and stops the running animateTo at the current offset instead of maxScrollExtent. - Follow no longer starts or continues while the reader is dragging or has unfollowed: _scrollToBottomGently and the ScrollMetrics re-entry both gate on _isUserScrolling / _userHasScrolled / _userInterruptedAutoScroll, and follow completion only re-pins when it truly ended at the live edge. - Anchor restore defers to an active reader gesture instead of fighting it mid-drag. - An ancestor Listener recovers the orphaned case: a pointer drag against a running programmatic scroll interrupts it and drives the offset directly until the native gesture path takes over or the pointer lifts. - Returning to the live edge clears a stale interrupt, so follow-on-latest is unchanged for readers who never scroll away. Regression tests overlap an in-flight 500ms follow with a reader drag plus a mid-gesture live tick (fails on origin/main: the scroll state re-pins isAtBottom=true and the drag is lost), and pin same-ID growth of the live segment while unfollowed. Verification (base f5f424a8e282 = origin/main): - cd app && flutter test --no-pub test/widgets/transcript_test.dart (17/17) - cd app && bash test.sh (1721 passed, 5 skipped) - cd app && bash scripts/analyze_ratchet.sh (passed) Failure-Class: new Co-authored-by: multica-agent <github@multica.ai> | 10 天前 | |
fix(app): resume phone mic after other-app audio on iPadOS (#4706) (#11146) * fix(app): resume phone mic after other-app audio on iPadOS (#4706) Heal silent mic death when Stage Manager / YouTube stops capture without an AVAudioSession interruption: rebuild a dead engine on foreground, always probe resume when an interruption ends, and trip the Dart stall path after suspended timers so the UI no longer stays stuck on Listening. Co-authored-by: Cursor <cursoragent@cursor.com> * chore(ci): retrigger checks after hermetic scope timeout (#4706) Detect Hermetic Backend Scope hit the 2m job timeout (cancelled); Merge Gate fail-closed on SCOPE_RESULT=cancelled. App-only PR — no backend scope change. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(app): soft-rearm phone-mic stall on foreground (#4706) Address cubic review: foreground must not race native rebuild or false- restart healthy sessions. Soft-rearm the stall clock instead of escalating immediately; add widget coverage for non-call interrupted → Paused; reformat Dart for CI. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(app): stub probeStallAfterForeground in mic test doubles (#4706) Dart Analyze CI failed: FakeMic / _ContendedMic missing the new IMicRecorderService method. Also tighten #4706 widget coverage to assert Paused text plus orange status-dot and play affordance. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> | 1 个月前 | |
merge: integrate current main into web parity Resolve the manifest and brand-check conflicts while retaining both branches’ active CI coverage. Tighten the hex token boundary so the merged brand test rejects embedded literals. Failure-Class: none | 27 天前 | |
chore(app): dart format --line-length 120 on all Dart files Run dart format across all 456 Dart files to fix formatting drift. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> | 5 个月前 | |
Update test harness to match safe cast fix Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> | 5 个月前 | |
fix(test): cancel WaveformSection periodic timer in test teardown (#7048) The waveform test creates a WaveformSection with isPlaying=true, which starts a 250ms Timer.periodic. Without unmounting the widget tree, the timer remains active and triggers the test framework's !timersPending assertion. Adding pumpWidget(SizedBox.shrink()) triggers dispose() which cancels the timer. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> | 4 个月前 |
| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
| 18 天前 | ||
| 21 天前 | ||
| 13 天前 | ||
| 2 个月前 | ||
| 1 个月前 | ||
| 8 天前 | ||
| 15 天前 | ||
| 16 天前 | ||
| 9 天前 | ||
| 2 天前 | ||
| 19 天前 | ||
| 15 天前 | ||
| 1 个月前 | ||
| 2 个月前 | ||
| 1 个月前 | ||
| 7 天前 | ||
| 24 天前 | ||
| 9 天前 | ||
| 9 天前 | ||
| 27 天前 | ||
| 2 个月前 | ||
| 7 天前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 14 天前 | ||
| 17 小时前 | ||
| 12 天前 | ||
| 2 个月前 | ||
| 10 天前 | ||
| 2 个月前 | ||
| 9 天前 | ||
| 2 个月前 | ||
| 15 天前 | ||
| 19 天前 | ||
| 5 个月前 | ||
| 6 天前 | ||
| 7 天前 | ||
| 2 个月前 | ||
| 1 个月前 | ||
| 2 个月前 | ||
| 2 个月前 | ||
| 13 天前 | ||
| 1 个月前 | ||
| 2 个月前 | ||
| 5 个月前 | ||
| 2 个月前 | ||
| 1 天前 | ||
| 3 个月前 | ||
| 22 天前 | ||
| 19 天前 | ||
| 15 天前 | ||
| 10 天前 | ||
| 1 个月前 | ||
| 27 天前 | ||
| 5 个月前 | ||
| 5 个月前 | ||
| 4 个月前 |