Skip to content

feat(mobile): add flagged contextual paywall sheet and upsell moments - #21363

Merged
Git-on-my-level merged 117 commits into
lane/sub-w6-paywallfrom
lane/sub-w6-mobile-storefront
Oct 13, 2026
Merged

Git-on-my-level merged 117 commits into
lane/sub-w6-paywallfrom
lane/sub-w6-mobile-storefront

Conversation

@Git-on-my-level

@Git-on-my-level Git-on-my-level commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator

What changed and why

The mobile plans sheet still reads GET v2/payments/plans?source=&trigger=&locale= and accepts only schema_version 2. Backend strings render verbatim. Chrome stays in l10n.

The thirteen fixtures in app/test/fixtures/storefront_v2/ are byte copies of backend/tests/fixtures/storefront_v2/ at lane/sub-w5-presentation-v2 445c5f7f494 (#21397). legacy_neo.json is new. The byte-compare test still checks those copies when the backend directory is present.

Contract the sheet now honors:

  • Every is_current card publishes bullets: [], and the sheet renders none.
  • Switch-to-yearly: the current tier has action: "checkout", tier cta_label: null, and only the yearly price. The price-level cta_label is the button text (Switch to yearly · save $39.98).
  • Cancel scheduled: the current card publishes only the current price, and yearly_toggle_label: null hides the Monthly/Yearly toggle.
  • legacy_neo renders like legacy_unlimited: current line, legacy note, and Manage billing. No ladder and no checkout.

Flags mobile-plan-storefront and mobile-contextual-upsells stay off. No payment-rail change.

Reconciliation with #21397

#21397 also touches three files under app/:

  • app/lib/backend/schema/gen/storefront_wire.g.dart
  • app/test/backend/storefront_wire_test.dart
  • app/changelog/unreleased/20261013-storefront-presentation-v2.json

Those stay on #21397. The generated DTO is still schema 1 here. Copying the schema 2 generator output without that PR's OpenAPI spec would fail generate_dart_models.py --all --check and export_openapi.py --check. Schema 2 decoding on this branch is PlanStorefront.fromJson, covered by plan_storefront_test.dart.

storefront_wire_test.dart is restored to the schema 1 generated-client test at the merge base, so #21397's rewrite of that file applies cleanly whichever PR merges second. The generated Dart file and the presentation changelog are untouched here for the same reason.

Product invariants affected

none

How it was verified

  • Focused Flutter tests passed (57): test/models/plan_storefront_test.dart, test/widgets/storefront_plans_content_test.dart, test/widgets/storefront_checkout_handoff_test.dart, test/backend/storefront_wire_test.dart, test/unit/plan_storefront_api_test.dart. The handoff test taps Switch to yearly · save $39.98 and reaches upgrade with the yearly price. Cancel-scheduled asserts the toggle is absent.
  • scripts/l10n.py check: 49 locales, 3876 keys. dart analyze on the touched Dart files reported no issues. scripts/pr-preflight --lane local passed 28 checks on this commit; the pre-push gate passed before the push.
  • Hermetic screenshots were regenerated, one per golden (including legacy_neo), plus banner, meter, small-phone light, empty, and error. No production API calls, Stripe writes, deploys, or flag flips.

Checkout predicate

storefrontCheckoutDecision decides both the card button and every tap.

  • The only same-tier purchase is monthly → annual, and only when the live price interval is month. Annual → monthly and every other same-tier price are refused, including when the current card publishes only the yearly price.
  • action: "checkout" may omit the tier cta_label when the price cta_label carries the words. action: current proceeds only when that price has a cta_label. A null price id refuses.
  • Cancel scheduled resubscribes only the current price. Every other price is refused in the predicate, omitted from the sheet, and dropped by the tap guard.
  • Price id, interval, and amount must match the existing catalog or checkout is refused.
  • Upward moves stay allowed when cancellation is not scheduled. A lower ladder tier, legacy, live unlimited, usage-legacy transcription, and an unrecognized current id are refused.

UI evidence

001-mobile-ios-architect-settings

Path: .agent-artifacts/ui-evidence/w6-mobile-storefront/001-mobile-ios-architect-settings.png

002-mobile-ios-cancel-scheduled-plus

Path: .agent-artifacts/ui-evidence/w6-mobile-storefront/002-mobile-ios-cancel-scheduled-plus.png

003-mobile-ios-free-budget-80

Path: .agent-artifacts/ui-evidence/w6-mobile-storefront/003-mobile-ios-free-budget-80.png

004-mobile-ios-free-mcp-cap

Path: .agent-artifacts/ui-evidence/w6-mobile-storefront/004-mobile-ios-free-mcp-cap.png

005-mobile-ios-free-pro-gate-proactive

Path: .agent-artifacts/ui-evidence/w6-mobile-storefront/005-mobile-ios-free-pro-gate-proactive.png

006-mobile-ios-free-pro-gate-screen

Path: .agent-artifacts/ui-evidence/w6-mobile-storefront/006-mobile-ios-free-pro-gate-screen.png

007-mobile-ios-free-settings

Path: .agent-artifacts/ui-evidence/w6-mobile-storefront/007-mobile-ios-free-settings.png

008-mobile-ios-legacy-unlimited

Path: .agent-artifacts/ui-evidence/w6-mobile-storefront/008-mobile-ios-legacy-unlimited.png

009-mobile-ios-plus-annual-current

Path: .agent-artifacts/ui-evidence/w6-mobile-storefront/009-mobile-ios-plus-annual-current.png

010-mobile-ios-plus-budget-80

Path: .agent-artifacts/ui-evidence/w6-mobile-storefront/010-mobile-ios-plus-budget-80.png

011-mobile-ios-plus-monthly-yearly-toggle

Path: .agent-artifacts/ui-evidence/w6-mobile-storefront/011-mobile-ios-plus-monthly-yearly-toggle.png

012-mobile-ios-pro-settings

Path: .agent-artifacts/ui-evidence/w6-mobile-storefront/012-mobile-ios-pro-settings.png

013-mobile-ios-banner

Path: .agent-artifacts/ui-evidence/w6-mobile-storefront/013-mobile-ios-banner.png

014-mobile-ios-meter

Path: .agent-artifacts/ui-evidence/w6-mobile-storefront/014-mobile-ios-meter.png

015-mobile-ios-small-phone-light

Path: .agent-artifacts/ui-evidence/w6-mobile-storefront/015-mobile-ios-small-phone-light.png

016-mobile-ios-empty

Path: .agent-artifacts/ui-evidence/w6-mobile-storefront/016-mobile-ios-empty.png

017-mobile-ios-error

Path: .agent-artifacts/ui-evidence/w6-mobile-storefront/017-mobile-ios-error.png

018-mobile-ios-legacy-neo

Path: .agent-artifacts/ui-evidence/w6-mobile-storefront/018-mobile-ios-legacy-neo.png

Failure class (fixes)

Failure-Class: none

Reviewer risks

  1. The generated storefront DTO on this branch is still schema 1. Schema 2 request decoding is PlanStorefront. feat(billing): plans sheet schema_version 2 #21397 owns the generator output.
  2. Non-English locales show the English deck chrome for the new keys. plansAndBilling keeps its previous translations for the old sheet.
  3. A server offer still cannot check out unless the existing price catalog matches id, interval, and amount.
  4. Flags stay off. Do not enable them before the backend contract is on the serving API.
  5. legacy_unlimited and legacy_neo are not drawn as ladder cards. The legacy note is the sheet for those goldens.

Git-on-my-level and others added 30 commits October 10, 2026 18:15
The notes worker-loop clone only accepted ChatOpenAI, so a provider-switched Anthropic key raised TypeError and the conversation returned HTTP 500.

Co-authored-by: Cursor <cursoragent@cursor.com>
JsonOutputParser drops the whole summary when a notes completion quotes a phrase inside a JSON string.

Co-authored-by: Cursor <cursoragent@cursor.com>
A provider 4xx on a chat attachment is a typed client outcome. Logging it with a traceback made Cloud Error Reporting group chat_file.py:150 as an outage.

Co-authored-by: Cursor <cursoragent@cursor.com>
The sanitization loader stubs chat_file, so the new typed catch needs a real exception class there or the import fails.

Co-authored-by: Cursor <cursoragent@cursor.com>
#21357)

A Monitoring IAM gap (HTTP 403) in the dead-man step blocked the Grafana WAL
rollout in prod run 38344425582. Run it after Helm so it still fails closed
without holding the monitoring stack deploy hostage.

Failure-Class: FC-batch-fault-domain-head-of-line-poison

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
#21358)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…21365)

* feat(monitoring): make Telegram pages actionable and batch warnings

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(monitoring): keep real silence links and point GPU/egress pages at their dashboards

Grafana silence URLs for our rules are 256-341 characters, so the 200-character
cap dropped every one. Parakeet GPU and Cloud Run egress rules linked to log
dashboards that do not show the firing series.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Fix a deterministic false short-window refusal in live speaker matching:
full received PCM spans lost their last sample when epoch-time
subtraction was truncated back to bytes. In a
receiver-capture/send-proof-to-FIFO-runner reproduction, exactly 2+2+1
seconds of audio produced one extraction and no owner comparison.
Preserving the stored span's integer end now delivers all five seconds,
three initial queries and an owner decision under the existing policy.

One shared byte-window primitive corrects exact full-span ends for both
extraction and physical byte proof. Interior endpoints retain the
existing PCM16 floor; actual gaps, eviction and unavailable/unplaced
capture windows remain refused. Transient restored-sample metadata stays
inside the existing bytes contract; provider timestamps, stored
transcript/audio fields, wire formats, thresholds/margins, manual veto,
contention, authority/stale-generation fences, FIFO/proven-rejector
behavior and recovery ledger are unchanged.

The new
omi_speaker_id_pcm_windows_restored_total{reason="full_span_rounding"}
counts selected candidate windows whose true received PCM reaches their
current extraction floor while the old floored byte count on those
selected windows would not. It is emitted before quota admission; it
counts neither GPU reservations nor unique voices, owner comparisons, or
recognition gains. The single preinitialized closed reason adds one
total series (the shared runtime metrics endpoint suppresses created
series). No IDs or content are metric labels or new log fields.
Recovery's newer pool uses the caller's same floor for this observation,
avoiding an insufficient-new-pool/fallback count. Existing
#21181/#21235/#21298/#21222 telemetry stays compatible.

Production read-only evidence: source main b8076ba, primary listen
6ef38cd; current one-hour aggregate counters show ~34.7k Soniox
outside-send refusals, ~20.4k explicitly wire-race-unplaceable
intervals, ~7.85k short-window matcher exits. The dominant placement
refusals are intentionally fail-closed under an uncertain reported
finalize clock. This PR repairs the independent physical-window defect;
it does not clamp untrusted provider timestamps, enlarge the ring, alter
flags or claim those Soniox intervals are newly placeable. All reads
used server-side Prometheus sums plus whitelisted deployment metadata,
no account transcripts/audio/embeddings/log rows, no api.omi.me, no
deployment or merge.

Request bounds remain twelve initial attempts, at most eighteen with
already-approved eligible recovery, 16 tasks and ten seconds of
PCM/query, all through the unchanged 16-token socket burst and 240/hour
refill. Maximum GPU calls cannot rise beyond those bounds; realized
calls can rise as false minimum refusals disappear (the five-second test
spends 3 versus 1), or fall if an owner accepts earlier and stops
retrying. Failures still spend. Production owner uplift is unmeasured;
this is not a forecast that recovers the unplaced cohort. After
deployment compare restored candidate windows, short exits, actual
reservations/comparisons and owner accepts per conversation;
owner_identified excluding single-speaker vs comparable-volume same-hour
prior-day baselines, plus failures/contention/GPU.

Validation: new test suite 19 cases (four additional cases after the
broader run) and broader 29-file matcher/capture/recovery/authority
suite 778 cases pass through credential-stripped backend/test.sh. Before
the fix all six complete-span/rate cases plus the real runner failed.
Canonical make setup provisioned this worktree's Python 3.11 environment
and refreshed the branch to then-current main. Committed head
2c6f61b passed 32/32 exact-head make
preflight in 491.18s and relevant run-unit-ci shard 2/6 passed 5,398
cases across 293 selected files, with zero typecheck errors. Normal push
hooks passed, including 32/32 preflight (546.30s), zero-error typecheck,
runtime closure, changed-file tests and OpenAPI compatibility.
Exact-head GitHub Actions are terminal green: all 12 workflow runs
succeeded; all six backend shards, guardrails, E2E/gauntlet and security
passed. The first guardrail attempt passed all 2,040 tests then exceeded
its 10-minute total job limit during wrap-up; rerunning only that job
and its dependent aggregator succeeded on unchanged head, without any
timeout/allowlist or source change. The external Cubic check is neutral
because its monthly quota is exhausted; no automated review completion
is claimed. No skip hatch or force-push.

Failure-Class: new

FC-audio-ring-full-span-rounding-loss records integer whole-span
delivery and physical-proof parity with real-runner and
gap/partial/eviction guards. The shared byte-window primitive removes
the repeated arithmetic cause; this is same-clock quantization, not the
existing independent-clock failure class. No existing registry
definition or validator changes.

## Product invariants affected

none

Reviewer risks: realistic epoch-end correction changes a tiny portion of
the PCM and can move a query across an existing evidence floor; actual
utilization/timing can change inside unchanged caps. The candidate
counter is a comparison against old flooring on currently selected
windows, not a historical counterfactual of all scheduler/evidence
states or causal recognition saved. Dominant unplaced refusals remain;
current diagnostics cannot identify what share of production short exits
are true short utterances vs physical truncation vs this rounding
defect. New direct regression cases use synthetic audio and mocked
embeddings; production identity accuracy is not established. No
release/deployment is authorized by this PR.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/BasedHardware/omi/pull/21353?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="View guided diff"
src="https://www.cubic.dev/buttons/review-in-cubic-light.svg"></picture></a>
<a
href="https://www.cubic.dev/action/auto-fix/pr/BasedHardware/omi/21353?returnTo=https%3A%2F%2Fgithub.com%2FBasedHardware%2Fomi%2Fpull%2F21353&source=description"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/turn-on-auto-fix-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/turn-on-auto-fix-light.svg"><img
alt="Turn on auto-fix"
src="https://www.cubic.dev/buttons/turn-on-auto-fix-light.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
…first queues (#21359)

* refactor(queues): retire unused projection and automatic chat queues

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(queues): align retired index inventory contract

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(queues): update remaining field mode count

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(queues): align cleanup imports and service guide

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(firestore): count the retired projection_repairs field override

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
)

* feat(monitoring): gate alert admission with historical backtests

Add read-only condition-chain replay, offline inventory lint and synthetic state tests. Remove the retired proactive rule, repair endpoint readiness telemetry and zero-initialize invalid-request counters.

Failure-Class: FC-alert-never-provably-fired

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(monitoring): record absent-rule proof and pause admission holds

Record the full 28-rule production backtest: 19 deploy candidates, eight paused fixes and one retired-rule deletion. Bound replay requests by day, preserve explicit admission holds and block unpausing without review.

Failure-Class: FC-alert-never-provably-fired

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(monitoring): key alert-lint debt on firing semantics, not routing

The legacy-debt baseline fingerprinted the whole rule, so the fleet-wide
notification timing change (#21365) voided every baseline and failed the
offline admission gate. Fingerprint only condition/data/for/noData/execErr.

Failure-Class: FC-batch-fault-domain-head-of-line-poison

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…21368)

* test(billing): characterize Stripe entitlement ingestion before extraction

* test(billing): characterize the one-pass stale guard across a clock rollback
* feat: add dark bounded locked-content recovery at quota reset

* refactor: share bounded unlock batches without growing domain modules

* test: cover bounded unlock query shapes and atomic failure resume

* feat: invalidate enforcement cache after reset recovery

* docs: keep reset recovery routing within backend guide budget

* docs: shorten reset recovery guide route

* refactor: expose token-fenced lease release and narrow optional request
…the rules (#21374)

Grafana rejects __dashboardUid__ without __panelId__ (HTTP 400), which blocked
the live rollout of #21365 for 38 rules. Dashboard-only links now use a
dashboard_url annotation that the Telegram template falls back to.

Failure-Class: FC-alert-never-provably-fired

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Retain mobile storefront gates and the independently added locked-content recovery gate.

Failure-Class: none
* feat(billing): add fixture-only ladder price dry-run tooling

* fix(billing): validate dry-run ladder registrations and fixtures
* feat(subscription): add dark period-scoped at-cap choice contract

* feat(subscription): declare at-cap choice route policies

* feat(subscription): keep choice metadata independent of BYOK validation

* feat(subscription): export at-cap choice app-client schema

* feat(subscription): regenerate at-cap choice client contracts

* fix: type budget-choice contracts and address review findings

* refactor: split Dart wire schema catalog from generator

* docs: record internal-only budget choice wire generation
#21370)

* test(billing): characterize Stripe entitlement ingestion before extraction

* test(billing): characterize the one-pass stale guard across a clock rollback

* refactor(billing): extract pure Stripe entitlement state machine

* refactor(billing): narrow resolved projection types

* refactor(billing): freeze stale preservation before the Stripe lookup

* billing: address state-machine review guards and effects
* test(billing): characterize Stripe entitlement ingestion before extraction

* test(billing): characterize the one-pass stale guard across a clock rollback

* refactor(billing): extract pure Stripe entitlement state machine

* refactor(billing): narrow resolved projection types

* refactor(billing): freeze stale preservation before the Stripe lookup

* feat(billing): add read-only subscription drift reconciler

* test(billing): distinguish organization payment grants from subscription drift

* billing: address state-machine review guards and effects

* billing: correct reconciliation uncertainty and legacy grants
#21351)

* feat(billing): add shadow price-keyed entitlement versions

* refactor(billing): expose versions through the catalog facade

* fix(billing): type the generated entitlement projection

* fix(billing): retain trialing and unknown-price legacy entitlements

* fix(billing): retain inactive paid shadow entitlements through period end
Git-on-my-level and others added 22 commits October 13, 2026 07:57
…llation (#21339)

## Problem

A user on Unlimited cancels their plan, but the cancellation takes
effect immediately instead of at period end — the customer paid through
`current_period_end` (e.g. Nov 3), yet the app immediately shows
"Upgrade to Unlimited" and paid features lock.

## Root cause

When a subscription is cancelled via the Stripe customer portal in
"cancel immediately" mode, Stripe fires `customer.subscription.deleted`
/ `updated` with `status='canceled'` while `current_period_end` is still
in the future. `_build_subscription_from_stripe_object`
(`backend/routers/payment.py`) downgrades **any**
non-`active`/`trialing` status to Basic on the spot, discarding the
remaining paid period. The stored row becomes `basic`, so
`get_user_valid_subscription`'s paid-period grace in `database/users.py`
never applies — the plan is already Basic.

The in-app cancel path is unaffected: it sets
`cancel_at_period_end=true` (status stays `active`).

## Fix

In `_build_subscription_from_stripe_object`: when a subscription event
carries status `canceled` but `current_period_end` is still in the
future and the price resolves to a known paid plan, keep that plan
(status `active`, `cancel_at_period_end=True`, unchanged period end) —
the exact same representation the normal cancel-at-period-end path
produces. Everything downstream (`get_user_valid_subscription`, app plan
gating, reactivation) then behaves identically to a scheduled
cancellation.

The grace candidate is downgrade-shaped for main's stale-downgrade
protection: if the stored row is a newer valid paid subscription, or
Stripe exposes a different active paid subscription, the event keeps
main's semantics (shield short-circuit or replacement adoption, Basic
fallback otherwise). The grace also only fires when the canceled event's
subscription id is the user's current subscription — a superseded id
keeps the plain downgrade. Schedule-completed/canceled and checkout
paths are untouched (they build without the grace grant).

Unchanged behavior: `unpaid`/`incomplete_expired`, cancellations after
period end, and unresolvable price IDs still downgrade to Basic
immediately.

New failure class — an early terminal event (portal cancel-immediately)
must not strip a period the customer already paid for. The definition
file `.github/failure-classes/FC-early-terminal-strips-paid-period.json`
is added in this commit range alongside the fix; `evidence_prs` is empty
because this PR is the first instance.

Failure-Class: new

Line-Count-Exception: backend/routers/payment.py | 1639 -> 1715 |
payment.py concentrates the Stripe webhook projection; the grace branch
plus its stale-shield routing need the price-resolution and projection
helpers already in this file, and splitting one entitlement decision
across modules would scatter the cancel flow

## Review answers

**Q3 — does the trigger actually fire (top-level
`current_period_end`)?** Yes. The backend pins `stripe==11.3.0`
(`backend/requirements.txt`), whose requests default to Stripe API
version `2024-11-20.acacia` (verified from the installed library:
`stripe.api_version`). In the acacia line, `current_period_end` and
`current_period_start` remain top-level subscription fields; the move to
per-item/current-item period fields only affects API versions after
2025-03-31.basil. So the `customer.subscription.deleted` payload for an
immediately-cancelled, paid-through subscription carries a future
top-level `current_period_end`, and the branch fires.

**Q4 — refunds (billing policy).** Preserving access depends only on
Stripe's own entitlement signal, exactly as it already does for
scheduled cancellations: a scheduled cancel keeps access until
`current_period_end` even when the portal issues a prorated credit, so
an immediate cancel now behaves identically to the existing
scheduled-cancel path rather than being treated more harshly. If policy
later wants refund-aware entitlement, that is a follow-up change to both
cancel paths together, not a reason this parity fix should keep a worse
inconsistency in place.

**Merge conflict with main.** Resolved by rebasing the grace branch onto
main's state-machine refactor: the inactive path keeps the new grace
branch, the Basic fallback goes through main's
`project_subscription(stripe_sub, None, get_basic_plan_limits())`, and
grace candidates flow through `transition()` with a `grace_paid` marker
so the stale-downgrade shield and replacement adoption still apply. The
behavioral test harness namespace carries both the `time` and
`project_subscription` entries. The lifecycle characterization matrix
pins the new canceled-with-future-period-end expectation; every other
characterization row (stale shield, adoption, schedule matrices) is
unchanged and green.

## Testing

`backend/tests/unit/test_stripe_webhook_behavioral.py` (builder is
extracted and executed directly):

- immediately-canceled + future period end → keeps Unlimited, active,
`cancel_at_period_end=True`
- immediately-canceled + unresolvable price → Basic (grace cannot invent
a plan)
- `unpaid` + future period end → Basic (grace limited to `canceled`)
- canceled paid-through without the grace grant (schedule/checkout
paths) → Basic
- existing canceled-after-period-end cases remain Basic

```
337 passed  # test_stripe_webhook_behavioral, test_stripe_webhook_none_guard, test_payment_cancel_subscription_http_exception, test_subscription_ingestion_characterization, test_subscription_state_machine, test_stripe_subscription_terminal_status
```

One known trade-off: the `customer.subscription.deleted` webhook for an
immediately-canceled-but-paid-through sub now counts
`resulting_paid_subscription_id` as non-null in billing-product
telemetry, so the Stripe-side churn event is still recorded raw but the
projection may under-count immediate churn. Entitlement correctness for
the paying customer took priority.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/BasedHardware/omi/pull/21339?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="View guided diff"
src="https://cubic.dev/buttons/review-in-cubic-light.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
…, keep unproven speech (#21403)

* fix(sync): suppress safety WAL dual writes across the fleet

Failure-Class: FC-offline-merge-clock-offset-dup

* fix(sync): preserve unproven WAL speech during dedupe

Require corroborated clock and speaker identity for text drops, consume live matches once, and verify every audio interval. Restore legacy playback and gate provider stamps with the kill switch.

Failure-Class: FC-offline-merge-clock-offset-dup

* fix(sync): idempotent safety-WAL counters; mirror WAL flag in runtime-env fixtures

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The sheet now uses the presentation-v2 fixtures, including blocked_line, compact, and per-price cta_label, so savings text is no longer computed on the client.

Co-authored-by: Cursor <cursoragent@cursor.com>
Auto-generated: consolidates unreleased changelog fragments into
v0.12.481 and regenerates CHANGELOG.json.
…assified (#21408)

## Summary

Follow-up to #21316 (merged as 80788c0): two review findings on the
gateway-normalized 400 classifier landed after the merge and are
addressed here.

1. **Nested attachment rejections collapsed to `invalid_messages`**
(thread [PRRT_kwDOLkKqys6r4_Vf]): the gateway's `_provider_rejection()`
roots `messages[0].content[1].file.file_id` to `messages`, so an
unreadable/stale user file took the error-level branch in
`search_files_tool` and re-entered Cloud Error Reporting as a server
fault — the exact incident #21316 fixed. The gateway now preserves a
bounded attachment-specific classification (`invalid_attachment` /
`unsupported_attachment`) for allowlisted attachment param suffixes, and
the soft path honors it.
2. **Gateway-injected option rejections downgraded** (thread
[PRRT_kwDOLkKqys6r4_Vs]): the gateway injects `reasoning_effort: medium`
on the file-chat lanes
(`llm_gateway/config/generated_route_overrides.yaml`), so
`invalid/unsupported_reasoning_effort` is a gateway-config fault, not a
bad file. Those members join the request-shape set and stay error-level.

Both changes stay inside the closed, allowlisted `ProviderRejection`
classifier — no upstream body strings cross the wire.

## Product invariants affected

none

## Test plan

- [x] `backend/.venv/bin/python -m pytest
tests/unit/test_chat_file_completions.py
tests/unit/test_llm_gateway_openai_provider.py -q` — `48 passed`
- [x] `backend/.venv/bin/python -m pytest
tests/unit/test_llm_gateway_openai_compatible.py -q` — `36 passed`
- [x] `backend/.venv/bin/python -m pytest
tests/unit/test_llm_gateway_metrics.py -q` — `25 passed`
- [x] `backend/.venv/bin/python -m pytest
tests/unit/test_file_tools_sanitization.py -q` — `4 passed`

Failure-Class: none


<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/BasedHardware/omi/pull/21408?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="View guided diff"
src="https://www.cubic.dev/buttons/review-in-cubic-light.svg"></picture></a>
<a
href="https://www.cubic.dev/action/auto-fix/pr/BasedHardware/omi/21408?returnTo=https%3A%2F%2Fgithub.com%2FBasedHardware%2Fomi%2Fpull%2F21408&source=description"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/turn-on-auto-fix-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/turn-on-auto-fix-light.svg"><img
alt="Turn on auto-fix"
src="https://www.cubic.dev/buttons/turn-on-auto-fix-light.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
…21139)

## What changed and why

Conversation notes failed closed when the structure model left prose
quotes unescaped inside an otherwise complete JSON object.
`JsonOutputParser` raised `OutputParserException` from
`langchain_core/output_parsers/json.py:91`, and `_get_structured` turned
that into a processing failure, so the conversation never got a summary.

Error group (source: gcp):
{"source": "gcp", "service": "backend", "group": "json.py:91 |
langchain_core.exceptions.OutputParserException: Invalid json output:
{\"title\": \"Fish identification\", \"overvi", "count": 1, "id":
"gcp:backend-json-py-91-langchain-core-exceptions-outputparserexception-invalid-json-output-title-fish-identificati"}

## Root cause

The traceback is `process_conversation.py` `_get_structured` →
`conversation_processing.py` `_get_shaped_conversation_notes` →
`extraction_parser.parse`. The completion is a `StructuredExtraction`
object (`title`, `overview`, `sections`, …) whose string values contain
raw `"` characters. Langchain's partial JSON repair cannot recover that
shape (`Expecting ',' delimiter` immediately after the interior quote),
so the strict parser raises and the notes call fails.

## Fix

`_parse_notes_model_output` still parses with `PydanticOutputParser`.
Only after that raises does it escape interior quotes whose next
non-space character is not a JSON closer (`, } ] : "`), then parse
again. Valid JSON, already-escaped quotes, and a missing comma between
fields are unchanged.

## Product invariants affected

none

## How it was verified

```
python3 -m py_compile backend/utils/llm/conversation_processing.py backend/tests/unit/test_conversation_notes_json_quotes.py
# COMPILE_OK

backend/.venv python -m pytest tests/unit/test_conversation_notes_json_quotes.py -q --tb=short
# ....  [100%]
# 4 passed in 3.72s
```

The logged Fish identification completion still raises on the strict
parser and now parses through `_parse_notes_model_output` as
`StructuredExtraction` with that title, a non-empty overview, and one
section.

## Tests

`backend/tests/unit/test_conversation_notes_json_quotes.py` covers
unescaped quotes in `overview` and in a section body, already-escaped
quotes, and a missing comma that must still raise.

## UI evidence

UI-Evidence: none -- backend notes parser only

## Failure class (fixes)

Failure-Class: none


<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/BasedHardware/omi/pull/21139?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="View guided diff"
src="https://www.cubic.dev/buttons/review-in-cubic-light.svg"></picture></a>
<a
href="https://www.cubic.dev/action/auto-fix/pr/BasedHardware/omi/21139?returnTo=https%3A%2F%2Fgithub.com%2FBasedHardware%2Fomi%2Fpull%2F21139&source=description"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/turn-on-auto-fix-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/turn-on-auto-fix-light.svg"><img
alt="Turn on auto-fix"
src="https://www.cubic.dev/buttons/turn-on-auto-fix-light.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
…21131)

## Summary

Error group: `{"source": "gcp", "service": "backend", "group":
"process_conversation.py:620 | fastapi.exceptions.HTTPException: 500:
Error processing conversation, please try again later", "count": 109,
"id":
"gcp:backend-process-conversation-py-620-fastapi-exceptions-httpexception-500-error-processing-conversation-please-"}`

Root cause: `_get_structured` wraps the notes failure as that generic
HTTP 500. The chained error is `TypeError: Shaped notes requires the
configured OpenAI-compatible notes lane` in `isolated_notes_model`.
Provider-switched BYOK notes use `ChatAnthropic`, and the worker-loop
transport clone only accepted `ChatOpenAI`, so those conversations never
reached the model.

Fix: clone the Anthropic async client onto a per-invocation HTTP
transport the same way the OpenAI lane already does, and leave the
cached client untouched.

## Verification

```
python3.11 -m py_compile backend/utils/llm/shaped_notes_transport.py backend/tests/unit/test_shaped_agent.py
python3.11 -m pytest tests/unit/test_shaped_agent.py::test_anthropic_only_byok_notes_preserve_transport_and_omit_cache_hints tests/unit/test_shaped_agent.py::test_notes_transport_is_owned_per_worker_loop -q --tb=short
2 passed, 8 warnings in 18.30s
```

Failure-Class: none


<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/BasedHardware/omi/pull/21131?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="View guided diff"
src="https://www.cubic.dev/buttons/review-in-cubic-light.svg"></picture></a>
<a
href="https://www.cubic.dev/action/auto-fix/pr/BasedHardware/omi/21131?returnTo=https%3A%2F%2Fgithub.com%2FBasedHardware%2Fomi%2Fpull%2F21131&source=description"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/turn-on-auto-fix-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/turn-on-auto-fix-light.svg"><img
alt="Turn on auto-fix"
src="https://www.cubic.dev/buttons/turn-on-auto-fix-light.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
…ain omi_ names (#21409)

backend-sync's omi_sync_phase_* histograms arrive as
stackdriver_generic_task_custom_googleapis_com_<name>, which the existing rewrite
does not match, so omi-cloud-run-metric-names-unnormalized has been pending at
critical and the inventory recorded the mangled names.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
….2) (#21410)

* feat(dream): require full-context sample consensus

* chore(deploy): declare full Dream in prod overlay source

* chore(dream): raise sync daily accounting cap to 250
* feat(app): end onboarding with a first capture

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(app): register first capture visual evidence

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(app): honor first capture outcomes and recording ownership

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(app): preserve terminal capture outcomes and verified renders

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(app): retain pairing recovery and finish confirmation labels

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(app): remove redundant import from merged feedback render test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(app): scope processed response futures to guided capture

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
The backend now offers only the price a monthly subscriber can switch to, or the current price when a cancellation is scheduled. Current cards publish no bullets, and a null yearly label hides the interval toggle.

Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(dream): settle reservations against per-user actual spend

* test(dream): seed complete settlement lease in query driver

* docs(dream): clarify daily spend cap scope
…ith a bounded sweeper (#21394)

* fix(task-intelligence): retry candidate integration exports and dead-letter the stale backlog

Accept still dispatches once inline. A bounded sweeper now reclaims failed and expired-lease rows, and dead-letters exports older than seven days without calling a provider.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(task-intelligence): declare the candidate integration sweep route policy

The scheduler endpoint is an internal OIDC route with the same reviewed policy shape as the other backend-sync sweeps.

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs(backend): keep AGENTS.md inside its byte budget

The sweeper is named by the scheduler manifest and the route. A service-map line pushed backend/AGENTS.md over its 39000-byte budget.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(task-intelligence): stop a second candidate export after the lease expires

The sweeper reclaims a processing row only after one hour, and both the inline job and the blocking dispatch re-stamp the lease before calling the provider. Dev has no scheduler entry because its data plane is production Firestore.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(task-integrations): do not retry an ambiguous provider create

A timeout or a lost export marker can mean the external task already exists, so those rows are dead-lettered instead of posted again. The sweep also skips deleting accounts, honors a redrive timestamp, and continues past unchanged outbox rows.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(task-integrations): treat a post-refresh resend failure and provider 5xx as in doubt

The Asana/Google Tasks resend after a 401 may reach the provider, so its failure
raises into the create's in_doubt path instead of reading as token_refresh_failed.
A 5xx can follow a committed create, so only <500 responses are definite errors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(task-integrations): keep the auto-sync result contract and carry the provider outcome internally

The public auto-sync dict is what conversation batch sync and HTTP callers already depend on, so the provider-attempt outcome now travels only on the candidate dispatch path.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(task-integrations): pin provider status classification beside the task_sync suite

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(task-integrations): satisfy the backend typecheck on the sweeper path

Narrow the sweep age anchor with isinstance, drop always-true str checks, and make
the outcome-carrying auto-sync entry point public for its one cross-module caller.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(task-intelligence): register the sweeper's exported write in the source contract

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(task-integrations): refuse a second provider create at claim time

Ambiguous outbox rows are dead-lettered or recovered inside the claim transaction, each sweep status is scanned once per pass, and blocking dispatch keeps one Firestore client from claim through completion.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(task-integrations): build the recovery patch before the blocking write

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(task-integrations): apply the retry-safe allowlist and attempt budget at claim time

A legacy failed row with no recorded outcome may already have created the task,
and direct dispatch paths could exceed max_attempts. Both now dead-letter in the
claim transaction every path shares; a pristine pending row still claims.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(task-integrations): mark in_doubt only at the provider boundary

The lease check before dispatch records not_sent; the begin_provider callback
marks in_doubt immediately before the create, so a crash before it stays retryable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…21422)

backend-sync serves POST /v2/candidate-integrations/sweep since 7741a1a.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(dream): sequence full samples and preserve segment scope

* test(dream): isolate retrieval fixtures from random cleanup

* docs(api): refresh fragment-target schema description

* Fix Dream deadline test clock and shared-process isolation

Failure-Class: FC-per-hop-timeout
…21343 follow-ups) (#21425)

* fix(app): restore green Flutter tests on main (#21302 #21340 #21341 #21343 follow-ups)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(app): measure category card text heights

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…find-us fits on screen (#21426)

* fix(onboarding): polish Watch, pairing recovery and acquisition survey

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(onboarding): record inspected iOS and Android polish renders

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(onboarding): follow screenshot evidence and release-note contracts

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(onboarding): preserve empty discovery and clear resolved reachability errors

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@Git-on-my-level
Git-on-my-level marked this pull request as ready for review October 13, 2026 21:34
@Git-on-my-level
Git-on-my-level merged commit a698d06 into lane/sub-w6-paywall Oct 13, 2026
34 of 38 checks passed
@Git-on-my-level
Git-on-my-level deleted the lane/sub-w6-mobile-storefront branch October 13, 2026 21:34

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 18e148c11a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

if (json['schema_version'] != 1) throw const FormatException('Unsupported storefront schema');
final tiers = (json['tiers'] as List).map((row) => StorefrontTier.fromJson(_object(row))).toList();
if (tiers.map((tier) => tier.id).toSet().length != tiers.length) {
if (json['schema_version'] != 2) throw const FormatException('Unsupported storefront schema');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Align the mobile parser with the backend storefront schema

When both storefront flags are enabled, every successful response from the newly added /v2/payments/plans endpoint is rejected here: backend/models/storefront.py still emits schema_version: 1 with fields such as display_name, tagline, and actions like upgrade, while this parser accepts only version 2 and expects a different DTO shape. Consequently the plans sheet enters its load-failure state and contextual gates silently remain unavailable; deploy a matching server schema or keep the client on the schema implemented by this commit.

AGENTS.md reference: AGENTS.md:L34-L34

Useful? React with 👍 / 👎.

request: ApiRequest(
url: '${baseUrl}v2/payments/plans?${Uri(queryParameters: {
'source': source,
'trigger': trigger,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Omit an unset trigger from the plans request

For ordinary plans-sheet entry points, storefrontTrigger retains its default empty string, but this request always serializes it as trigger=. The backend endpoint declares trigger as an optional Literal whose only valid values are proactive_agent, screen_context, usage_budget, and mcp_daily_cap; an explicitly empty value therefore returns 422 instead of behaving like an omitted optional parameter. This breaks non-contextual openings from settings, chat, and the freemium dialog once the sheet flag is enabled.

AGENTS.md reference: AGENTS.md:L34-L34

Useful? React with 👍 / 👎.

}
if (!mounted || generation != _generation || !provider.showSubscriptionUI) return;
final result =
await PlanStorefrontApi().load(source: 'budget_80', trigger: 'budget_80', locale: context.l10n.localeName);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Send the supported usage-budget trigger

When the budget upsell flags are enabled, this load always fails with 422 because budget_80 is an analytics source value, not one of the backend's accepted trigger values; the corresponding trigger is usage_budget. Since _data is populated only on ApiSuccess, the usage meter and budget upsell card can never render for this path.

AGENTS.md reference: AGENTS.md:L34-L34

Useful? React with 👍 / 👎.

// its predecessor has physically released the device. Stop/owner changes
// while waiting revoke this recovery; they must not resurrect capture.
await previous.waitForPhysicalStop()
guard isTranscribing, audioCaptureService === previous else { return false }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Release the capture gate when recovery loses ownership

If the session stops or audioCaptureService is replaced while waitForPhysicalStop() yields, this guard returns without resetting captureGateInFlight. The newly added owner-replacement tests exercise this exact exit, and afterward every reconcileCapture, preferred-microphone reapply, or further recovery sees the gate as permanently in flight and only queues work, potentially leaving the live session unable to react to later capture changes. Clear the gate on every post-acquisition exit, for example with a scoped defer.

Useful? React with 👍 / 👎.

Comment thread app/lib/l10n/app_es.arb
"reviewProposalConflict": "Esto cambió desde que Dream lo sugirió, así que no se aplicó nada.",
"itemLinkListTitle": "{count, plural, =0{Sin elementos} =1{1 elemento} other{{count} elementos}}"
"itemLinkListTitle": "{count, plural, =0{Sin elementos} =1{1 elemento} other{{count} elementos}}",
"paywallRecommended": "Recommended",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Supply translations for the new localized flows

For every non-English locale, the newly added paywall, first-capture, pairing, empty-recording, and guided-voice strings are copied verbatim from English; for example, all 49 ARB files contain the same English paywallRecommended, firstCaptureTitle, guidedVoiceExperience, and discardedOnlyRecording values. Users running Spanish and the other supported locales therefore encounter entire core onboarding and purchase surfaces in English, contrary to the repository requirement that callers supply translations when adding keys.

AGENTS.md reference: app/AGENTS.md:L118-L120

Useful? React with 👍 / 👎.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-13T21:45:47.034502Z 18e148c Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Git-on-my-level added a commit that referenced this pull request Oct 13, 2026
…ign (#21354)

* feat(mobile): define presentation-only storefront contract

* feat(mobile): bind storefront contract to exact checkout price

* chore(mobile): record internal storefront contract change

* feat(mobile): add flagged contextual paywall sheet and upsell moments (#21363)

* fix: keep Anthropic BYOK notes from failing conversation processing

The notes worker-loop clone only accepted ChatOpenAI, so a provider-switched Anthropic key raised TypeError and the conversation returned HTTP 500.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: keep conversation notes when the model leaves quotes unescaped

JsonOutputParser drops the whole summary when a notes completion quotes a phrase inside a JSON string.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: stop reporting rejected chat files as server errors

A provider 4xx on a chat attachment is a typed client outcome. Logging it with a traceback made Cloud Error Reporting group chat_file.py:150 as an outage.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: let isolated file-tool tests catch provider file rejections

The sanitization loader stubs chat_file, so the new typed catch needs a real exception class there or the import fails.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: preserve full PCM span ends for live owner matching

* fix(monitoring): reconcile the Grafana dead-man after the Helm rollout (#21357)

A Monitoring IAM gap (HTTP 403) in the dead-man step blocked the Grafana WAL
rollout in prod run 38344425582. Run it after Helm so it still fails closed
without holding the monitoring stack deploy hostage.

Failure-Class: FC-batch-fault-domain-head-of-line-poison

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* harden(mobile): validate storefront actions and legacy upsell eligibility

* feat(mobile): add flagged contextual storefront sheet and upsell moments

* test(mobile): capture storefront and contextual upsell UI states

* harden(mobile): declare storefront gates with registry conventions

* test(mobile): make storefront actions and evidence independent of test fonts

* test(mobile): scroll management action fully into the test viewport

* perf(monitoring): trim unused kubelet series and successful probe logs (#21358)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* feat(monitoring): make Telegram pages actionable and batch warnings (#21365)

* feat(monitoring): make Telegram pages actionable and batch warnings

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(monitoring): keep real silence links and point GPU/egress pages at their dashboards

Grafana silence URLs for our rules are 256-341 characters, so the 200-character
cap dropped every one. Parakeet GPU and Cloud Run egress rules linked to log
dashboards that do not show the firing series.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* feat(mobile): apply paywall design review and compact budget nudge

* feat(mobile): group legacy comparisons using backend presentation identity

* refactor(queues): retire unused projection-repair and automatic chat-first queues (#21359)

* refactor(queues): retire unused projection and automatic chat queues

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(queues): align retired index inventory contract

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(queues): update remaining field mode count

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(queues): align cleanup imports and service guide

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(firestore): count the retired projection_repairs field override

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* test(mobile): verify paywall fold in the real draggable sheet

* feat(monitoring): gate alert admission with historical backtests (#21366)

* feat(monitoring): gate alert admission with historical backtests

Add read-only condition-chain replay, offline inventory lint and synthetic state tests. Remove the retired proactive rule, repair endpoint readiness telemetry and zero-initialize invalid-request counters.

Failure-Class: FC-alert-never-provably-fired

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(monitoring): record absent-rule proof and pause admission holds

Record the full 28-rule production backtest: 19 deploy candidates, eight paused fixes and one retired-rule deletion. Bound replay requests by day, preserve explicit admission holds and block unpausing without review.

Failure-Class: FC-alert-never-provably-fired

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(monitoring): key alert-lint debt on firing semantics, not routing

The legacy-debt baseline fingerprinted the whole rule, so the fleet-wide
notification timing change (#21365) voided every baseline and failed the
offline admission gate. Fingerprint only condition/data/for/noData/execErr.

Failure-Class: FC-batch-fault-domain-head-of-line-poison

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* test(billing): audit and characterize Stripe entitlement ingestion (#21368)

* test(billing): characterize Stripe entitlement ingestion before extraction

* test(billing): characterize the one-pass stale guard across a clock rollback

* feat: dark reset recovery for already-locked content (#21375)

* feat: add dark bounded locked-content recovery at quota reset

* refactor: share bounded unlock batches without growing domain modules

* test: cover bounded unlock query shapes and atomic failure resume

* feat: invalidate enforcement cache after reset recovery

* docs: keep reset recovery routing within backend guide budget

* docs: shorten reset recovery guide route

* refactor: expose token-fenced lease release and narrow optional request

* fix(monitoring): pair dashboard links with panels so Grafana accepts the rules (#21374)

Grafana rejects __dashboardUid__ without __panelId__ (HTTP 400), which blocked
the live rollout of #21365 for 38 rules. Dashboard-only links now use a
dashboard_url annotation that the Telegram template falls back to.

Failure-Class: FC-alert-never-provably-fired

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* fix(mobile): protect legacy prices and desktop entitlements in storefront

Failure-Class: none

* feat(billing): fixture-only ladder Stripe price proposal (#21356)

* feat(billing): add fixture-only ladder price dry-run tooling

* fix(billing): validate dry-run ladder registrations and fixtures

* feat(subscription): add dark at-cap choice foundation (W4) (#21361)

* feat(subscription): add dark period-scoped at-cap choice contract

* feat(subscription): declare at-cap choice route policies

* feat(subscription): keep choice metadata independent of BYOK validation

* feat(subscription): export at-cap choice app-client schema

* feat(subscription): regenerate at-cap choice client contracts

* fix: type budget-choice contracts and address review findings

* refactor: split Dart wire schema catalog from generator

* docs: record internal-only budget choice wire generation

* refactor(billing): extract the legacy Stripe entitlement state machine (#21370)

* test(billing): characterize Stripe entitlement ingestion before extraction

* test(billing): characterize the one-pass stale guard across a clock rollback

* refactor(billing): extract pure Stripe entitlement state machine

* refactor(billing): narrow resolved projection types

* refactor(billing): freeze stale preservation before the Stripe lookup

* billing: address state-machine review guards and effects

* feat(billing): add read-only subscription drift reconciliation (#21371)

* test(billing): characterize Stripe entitlement ingestion before extraction

* test(billing): characterize the one-pass stale guard across a clock rollback

* refactor(billing): extract pure Stripe entitlement state machine

* refactor(billing): narrow resolved projection types

* refactor(billing): freeze stale preservation before the Stripe lookup

* feat(billing): add read-only subscription drift reconciler

* test(billing): distinguish organization payment grants from subscription drift

* billing: address state-machine review guards and effects

* billing: correct reconciliation uncertainty and legacy grants

* feat(billing): shadow entitlement versions with legacy golden contract (#21351)

* feat(billing): add shadow price-keyed entitlement versions

* refactor(billing): expose versions through the catalog facade

* fix(billing): type the generated entitlement projection

* fix(billing): retain trialing and unknown-price legacy entitlements

* fix(billing): retain inactive paid shadow entitlements through period end

* fix(mobile): keep storefront checkout on the upward ladder

Annual subscribers could buy their own monthly price, Architect could move onto Pro, and a scheduled cancellation had no resubscribe path.

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore: consolidate changelog for v0.12.478

* fix(mobile): don't open the required language picker over unfinished onboarding (#20798)

Every signed-in launch runs the primary-language setup, and when neither the
phone nor the server has a language it opened the required (undismissable)
language sheet. A relaunch mid-onboarding therefore covered whichever step
onboarding resumed on, consent included, on top of onboarding's own
language step.

Keep the server sync, but only show the required sheet once onboarding is
complete. setupUserPrimaryLanguage takes an optional fetch so the prompt can
be tested without the network.

Fixes #20788

* feat(billing): pin loaded input rate card v1 in shadow (#21352)

* feat(billing): add shadow price-keyed entitlement versions

* refactor(billing): expose versions through the catalog facade

* fix(billing): type the generated entitlement projection

* feat(billing): pin loaded input rate card v1 in shadow

* fix(billing): retain trialing and unknown-price legacy entitlements

* fix(billing): price routed Free speech and incomplete Gemini usage accurately

* docs(billing): align shadow usage guide with reviewed Free routing basis

* fix(billing): retain inactive paid shadow entitlements through period end

* fix(billing): preserve realtime reasoning and incomplete usage pricing

---------

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(mobile): pendant tutorial summary rows reopen a working step and return to the summary (#20805)

On "You're All Set", tapping "Ask Omi a Question" reopened the question
step on "Processing your question..." forever: questionSent was still true,
but the recreated step had no answer and only listened for new messages.
Finishing any reopened step also walked through every later step again,
power cycle included, instead of returning to the summary.

The reopened question step now shows the answer the provider already has
(with Continue), and a step reopened from the summary returns there when
finished.

Fixes #20787

* feat(mobile): render the plans sheet from storefront schema v2

The copy deck owns the strings, and the client rejects any payload that is not schema_version 2.

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(billing): dark unified storefront presentation contract (#21367)

* feat(billing): add dark versioned unified storefront contract

* refactor(codegen): extract storefront and goals schema-group metadata

* chore(mobile): record internal storefront wire contract

* test(billing): reuse payment router for byte-identity cases

* feat(billing): recommend storefront tier from opening context

* chore(codegen): refresh storefront context projections

* fix(billing): block lower storefront offers for legacy subscribers

* fix(mobile): a tutorial question sent by the 15 s timeout moves the tutorial on (#20806)

* fix(backend): declare Cloud Run Redis ports in runtime config

Keep the verified 13151 port on all four managed Cloud Run services in dev and prod. Reject omitted Redis port bindings before deploy and prove the port reaches the deploy renderer.

Failure-Class: FC-runtime-binding-contract

* chore: consolidate changelog for v0.12.463

* ci: accept commit-pinned image URLs as UI evidence so PR screenshots render (#21123)

The gate only accepted repo-relative paths, which GitHub shows as broken
images in PR descriptions, and rejected the raw.githubusercontent URL form
that renders. Accept URLs pinned to a commit SHA (not branch names); the
tracked-file and manifest requirements are unchanged.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* chore(app): add unreleased changelog fragment for batch speaker labeling

* chore(backend): enable keep-rescue shadow mode on prod

* fix(ci): stop peeling full-SHA revs; git 2.54 broke <sha>^{commit}

* feat: add messaging channel foundation and shared chat turns (#21115)

* feat: add channel-neutral messaging turn foundation and contract harness

* chore: regenerate messaging link client contracts

* feat: harden messaging admission and cross-surface continuity

* chore: record internal messaging wire contract change

* refactor: make messaging async storage boundaries explicit

* refactor: type the shared chat turn boundary

* refactor: retain outbound send classification type

* test: fix messaging foundation CI harnesses

* test: allow full-route contract timing

* feat: expose link targets and keep private memories off channel retrieval

Mint returns the configured deep link and address, sessions carry their channel fields, and channel turns drop private and restricted memories before they reach the model.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: read the private-memory filter from the surface runtime

The chat turn already installs that runtime for the whole turn, so the oversized agent file does not need another copy of the flag.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat: add Telegram and Linq iMessage adapters (#21116)

* feat: add channel-neutral messaging turn foundation and contract harness

* chore: regenerate messaging link client contracts

* feat: harden messaging admission and cross-surface continuity

* chore: record internal messaging wire contract change

* refactor: make messaging async storage boundaries explicit

* refactor: type the shared chat turn boundary

* refactor: retain outbound send classification type

* test: fix messaging foundation CI harnesses

* feat: add Telegram and Linq messaging adapters

* test: allow full-route contract timing

* test: register messaging retry helper in prompt harness

* test: isolate messaging imports in backend unit harnesses

* feat: expose link targets and keep private memories off channel retrieval

Mint returns the configured deep link and address, sessions carry their channel fields, and channel turns drop private and restricted memories before they reach the model.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: read the private-memory filter from the surface runtime

The chat turn already installs that runtime for the whole turn, so the oversized agent file does not need another copy of the flag.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore: consolidate changelog for v0.12.464

* feat(app): Chat apps screens for Telegram and iMessage (stacked on #21115) (#21118)

* feat: add channel-neutral messaging turn foundation and contract harness

* chore: regenerate messaging link client contracts

* feat: harden messaging admission and cross-surface continuity

* chore: record internal messaging wire contract change

* refactor: make messaging async storage boundaries explicit

* refactor: type the shared chat turn boundary

* refactor: retain outbound send classification type

* test: fix messaging foundation CI harnesses

* test: allow full-route contract timing

* feat(app): chat apps screens for connecting Telegram and iMessage

Adds the flag-gated Chat apps surface on Integrations: connect Telegram
through a one-time t.me deep link, connect iMessage by texting a one-time
code, manage a link (show its chats in the app, disconnect), the WhatsApp
waitlist, the free-plan upsell, and a read-only history of a chat app's
sessions. Uses only the Stage A /v1/messaging routes through a typed
executeApi client. The mobile-chat-apps PostHog flag fails closed; existing
links keep the entry reachable for disconnect.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat: expose link targets and keep private memories off channel retrieval

Mint returns the configured deep link and address, sessions carry their channel fields, and channel turns drop private and restricted memories before they reach the model.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: read the private-memory filter from the surface runtime

The chat turn already installs that runtime for the whole turn, so the oversized agent file does not need another copy of the flag.

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat: use minted link targets and real chat-app switches

Prefer the link-proof deep link and address, show a Telegram username when the link has one, and turn voice notes and private memories into per-link switches.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: keep the Messages brand-color exceptions on the color lines

The formatter had moved the allow comments off the hex literals, so the mobile UI contract counted them as new colors.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: keep Messages brand colors on one allowed line

The UI contract only honors an allow comment on the same line as Color(0x).

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: open minted chat-app links through the public launcher

The connect sheets were calling test-only helpers, which the analyzer ratchet rejects.

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(l10n): restore main's ARB key order after the restack merge

The three-way ARB merge re-serialized every locale in a different key order,
turning 102 new strings into a ~560k-line diff. Keep main's order and append
the new keys, as scripts/l10n.py does; regenerate gen-l10n output. Content is
unchanged: only the chat-apps keys are added.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(backend): fleet driver for the summary-vector backfill

* chore(backend): rename driver batch variable

* feat(backend): mirror fleet backfill state to Firestore for cross-execution resume

* feat(backend): index summaries on re-finalization of completed fanouts

* fix(backend): give Dream models readable evidence within token budgets (#21137)

* fix(backend): project readable dream evidence within token budgets

* fix(backend): preserve dream evidence depth during admission

* feat(backend): harden fleet driver resume state

* fix(cli): keep conversation Markdown export UTF-8 under legacy stdio (#21033)

* fix: preserve UTF-8 conversation exports through legacy stdio

Declare the exporter input and progress-output encoding independently of the host locale. Cover file and BOM stdin input through the real entry point under ASCII and CP1252 streams.

* fix: normalize Markdown export PR files to LF

* fix(onboarding): reset pendant tutorial question step on empty or no-speech press (#21069)

Pressing the pendant button twice without speaking, or asking something
Omi's server reports as no speech, left SinglePressStep stuck on
"Processing your question..." forever: DeviceOnboardingProvider set
questionSent=true on the second press and nothing ever reset it.

Wire CaptureController's existing emptyFrames drop and onNoSpeech
callback to DeviceOnboardingProvider.onVoiceQuestionFailed(), which
resets the step and shows the existing voiceQuestionNoSpeech retry
hint instead of spinning indefinitely.

fixes #20789

* Fix desktop goal type enum decoding (#21091)

* feat(backend): per-uid shard writes and bounded mirror state

* feat(messaging): enable David-only prod channels for all paid plans (#21138)

* chore: consolidate changelog for v0.12.465

* feat(backend): omit empty mirror maps and skip writes after failed reads

* chore: regenerate desktop Swift OpenAPI DTOs

* fix: preserve owner identity when repair lacks evidence

* fix: preserve unresolved identity across a mixed voice

* fix: improve dream triage recall with synthetic eval (#21145)

Failure-Class: none

* test(backend): completed-fanout summary indexing expectations

* chore: keep main's generated Swift DTOs (GoalType normalization)

* fix(desktop): generate the tolerant enum decoder that #21091 hand-wrote (#21149)

#21091 taught the GoalType decoder in OmiApi.generated.swift to accept a Python-qualified value ("GoalType.scale") by editing the generated file, but generate_swift_openapi_types.py still emitted the old decoder, so `generate_swift_openapi_types.py --check` (the openapi-contract workflow and the pre-push hook) now fails on main for anyone who touches the API. The generator now emits that decoder for every generated string enum: an exact raw-value match first, so a value that itself contained a dot would keep its meaning (none of the 160 enums in the app-client spec has one today), then the last dotted component, then _unknown. OmiApi.generated.swift is regenerated from the generator, and GoalType decodes exactly as #21091 intended.

Failure-Class: none

* docs(onboarding): add an Omi for Mac user guide to the public docs corpus (#21147)

The backend chat agent's product-info tool (get_omi_product_info_tool)
recursively reads docs/doc content from GitHub, and the corpus had no macOS
desktop user guide, so mobile/wearable chat could not answer desktop how-to
questions such as enabling Only Meetings. This page mirrors the product guide
shipped inside the Mac app (ProductKnowledge resources, #21081) and is
registered in docs.json Get Started.

* chore: consolidate changelog for v0.12.466

* fix(dream): tolerate invalid plan items and bound failed-record retries (#21154)

* fix(dream): retain valid plan items and bound failed-record retries

* perf(dream): reuse static fields for validation diagnostics

* feat(app): phone battery sample v2 — self-contained intervals + schema delivery fix (#21152)

* feat(app): phone battery sample v2 — self-contained drain intervals

* fix(app): reject clock-corrupted phone battery intervals

* fix(app): suspend-inclusive monotonic reference for Android battery intervals

* feat(app): pendant audio-loss integrity — unresolved outages + per-day provenance (#21151)

* feat(app): credible audio-loss accounting — persist unresolved outages, per-day provenance

* fix(app): fence durable audio evidence and reconcile daily outage exposure

* fix(app): rekey OmiDeviceHealthStore pbxproj entries to unique UUIDs

* test(app): gate depends_on_21152 delivery tests out of shared CI runs

* fix(app): buffer BLE outage writes when SharedPreferences binding is absent

* test(app): enable depends_on_21152 delivery tests after #21152 merge

* fix(dream): repair titles and protect multilingual feedback (#21160)

* fix(dream): repair conversation headings and protect multilingual feedback

* test(dream): account for explicit empty summary headers in queue excerpts

* fix(dream): enforce deterministic summary and feedback guardrails (#21161)

* fix(dream): enforce deterministic summary and feedback guardrails

Failure-Class: FC-generated-presentation-contract-bypass

* fix(dream): withhold feedback until privacy checks settle

Failure-Class: FC-generated-presentation-contract-bypass

* fix(dream): drop non-failure feedback deterministically

Failure-Class: FC-generated-presentation-contract-bypass

* fix(dream): guard task evidence, spelling targets and vocabulary noise (#21164)

* fix(dream): guard task evidence, spelling targets and vocabulary noise

* fix(dream): narrow validated task timestamps for type checking

* fix: clip embeddings gateway inputs that OpenAI rejects with 400

Sync backfill posted raw transcript batches to /v1/embeddings. Empty strings, NUL bytes, and over-long inputs make the gateway return 400, which httpx raises from _models.py:829.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: drop redundant str check in embeddings batching

Pyright rejects the isinstance guard because the batch items are already strings.

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(backend): switch the dream agent to on for the owner allowlist (#21171)

Owner-authorized promotion from shadow to on. Adds REVIEW_SURFACE_MODE=on
on backend-sync, where passes run, because mode=on admission requires it.
Canary leases stay forced to shadow.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* feat(backend): name identified people in general conversation titles (#3602) (#20646)

* feat(backend): name identified people in general conversation titles (#3602)

Notes v2 already binds speaker clusters to people through hard evidence
(voice profile / tagged person), but the general (non-meeting) notes path
never asked for those names in the title and never said which bound name is
the account owner, so titles rarely named who the conversation was with.

- SpeakerMap: the cluster->name map now also records the owner's clusters
  and words spoken per cluster. It stays a plain mapping for every caller and
  the rendered spk lines (shared prefix bytes) are unchanged.
- ConversationPromptPrefix.title_people / owner_names: non-owner people who
  said at least 10% of the words, most-spoken first, plus the owner's name.
  A bare mapping (owner unknown) names nobody. Rich meeting notes keep their
  roster rules and get neither.
- utils/llm/conversation_title_people.py: a static TITLE block inserted
  before the parser schema (cacheable, general path only, same pattern as
  rich_static_instructions) and a volatile PEOPLE IN THIS CONVERSATION block:
  name the one or two central people, never the account owner, never an
  unlisted or invented name.
- Presentation contract: a title that names none of the identified people
  is led by them ("Sarah Chen: Q2 Budget Review"), language-neutral, at most
  two names within 70 characters, never inventing a title or a name. Each
  repair is counted on omi_conversation_note_presentation_total
  (static_repair / title_people_lead).

* test(backend): preload conversation_title_people in isolated conversation_processing loads

test_conversation_structure_timezone and test_action_item_date_validation
stub utils.llm as an empty package and load each pure helper that
conversation_processing imports from its file. The new title module was
missing from that list, so both files failed at collection in CI.

* feat(backend): name title people only from scoped voice and tag bindings

Review follow-ups for #3602:

- SpeakerMap now carries per (speaker_id_scope, speaker_id) cluster
  evidence. Merged transcripts reuse ids across scopes, so an id whose
  clusters disagree about who is speaking (two people, or a person and
  the owner) names nobody. The rendered spk lines are unchanged.
- Title people come only from voice/tag bindings, never from a calendar
  name bound by elimination, so a guessed owner cluster cannot be named.
- Names in Thai, kana, CJK and Hangul match without word boundaries, a
  Latin name next to an unspaced script still matches, and a surname of
  three or more characters counts as a mention.
- Names are stripped before comparison and before leading a title, and
  MAX_LED_TITLE_CHARACTERS is renamed TWO_NAME_LEAD_MAX_CHARACTERS: it
  only decides when the second name is dropped.

* feat(backend): keep the owner out of titles without a voice match

Second review round for #3602:

- The owner line comes from the real profile name whenever one exists,
  with or without an is_user cluster, and a person record matching the
  owner's name or first name is treated as the owner.
- Talk share is speech time (word count only when no segment has a
  duration), so names in unspaced scripts reach the share threshold.
- Inflected mentions (Peters, Сарой, Anną) count as naming the person;
  titles and names are NFC-normalized and curly apostrophes match.
- A conflicting speaker id is dropped on its own instead of silencing the
  whole conversation, and a person named like a speaker placeholder is
  never a title person.
- The note presentation contract label is now v2: the title lead adds a
  static repair reason.

* feat(backend): a bare name stem is not a mention of the person

The inflection rule matched a name's stem with an empty suffix, so "and"
counted as naming Andy, "the" as Theo and "Joe" as Joel, and the title
was never led. Inflected forms now need a real suffix: a name plus up to
four letters, or, for a name ending in a vowel, its stem plus up to four.
Tests pin the bare-stem negatives, zero speech time for negative or
untimed segments, and that the owner's name stays out of the static
prompt.

* feat(backend): keep title names one line, from real talk, and free of what presentation strips

- A conversation with no speech time and no words names nobody: a share of
  nothing is no evidence of who spoke.
- Person and owner names reach the prompt as one line of plain text, so a
  stored name with a line break or control character cannot add a prompt line.
- The title lead skips a name holding a speaker placeholder or a transcript
  ID: it runs after presentation sanitization and must not bring either back.
- Pin the two-name lead to exactly TWO_NAME_LEAD_MAX_CHARACTERS.

* feat(backend): keep model titles that already name people

A title that already names any of the identified people is kept as
written, however many it names. The prompt asks for the one or two most
central people, and it stays the control for how many names appear: a
deterministic repair cannot remove names from the model's prose without
rewriting it, replacing the title loses its topic, and the name matcher's
inflection rule reads ordinary words as names ("Annual" for Anna,
"General" for Gene), so a count would rewrite titles that name no one
extra. The repair still leads a title that names nobody.

Tests pin that titles naming three listed people, and titles whose words
only look like names, come back unchanged, and that a title naming nobody
is still led. The test pinning the two-name threshold is renamed to say
what it asserts.

* test(backend): pin the general-path TITLE prompt bytes in the flag-off fixture

Review asked for the flag-off fixture to carry the general-path prompt with its
TITLE block. The previous commit built that expectation from the production
helper, so a change to the TITLE wording would still pass.

The fixture now has a general_static entry holding the literal general-path
static prompt, TITLE block included, and
test_actual_flag_off_request_uses_pinned_prompts compares the real request
against it. legacy_static stays pinned to main's base bytes, and
test_flag_off_prompt_bytes_pinned_to_base checks that general_static is
legacy_static with only the TITLE block inserted before the schema.

* fix(backend): normalize title identities in the shaped notes writer

Normalize owner and tagged-person identities to NFC before casefolding and combining scoped talk shares. Keep display spellings unchanged, and cover owner exclusion through the real speaker map, prompt builder, and notes writer.

Port the reviewed title prompt and deterministic lead to current shaped-only notes after rebasing upstream, preserving general-only behavior and the one-turn model budget.

Failure-Class: none

* feat: remove carried-speaker banner and keep inline correction

* feat: limit capture storage banner to disk admission failures

* feat: remove home header sync button

* chore: add banner cleanup mobile changelog

* docs(desktop): track the Silence Notifications setting UI evidence

Main's UI-evidence check needs a tracked screenshot for changed UI source.
Copy the existing settings screenshot from the docs-assets branch unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: keep owner and Omi labels out of inline correction

* fix: limit live speaker correction buttons to turn starts

* chore: include banner cleanup in both mobile store notes

* feat(dream): require Review for risky agent edits (#21176)

* feat(dream): require Review for risky agent edits

* refactor(dream): share typed entity evidence reads

* test(dream): exercise a guarded spelling proposal in transport fixture

* feat(review): version agent proposal wire contract for released clients

* docs(review): declare v2 route authentication and rate limits

* chore(api): regenerate TypeScript clients for Review v2

* chore(api): regenerate Swift operations for Review v2

* test(dream): fence tiered writers in shadow sweep regression

* fix: attribute live speaker budget blocks and schedule voices fairly

* chore: consolidate changelog for v0.12.467

* test: exercise profile retries through the coalescing speaker queue

* fix(messaging): restore Linq replies and release failed turns (#21172)

* fix(messaging): repair Linq send envelope and failed turn cleanup

* fix(messaging): initialize failure sink before worker handling

* fix(messaging): fence cancelled executor work during cleanup

* fix(messaging): log cancelled worker fencing

* fix(messaging): match fixture provider host exactly

* fix(messaging): preserve delivered replies and drain typed fallbacks

* fix(messaging): release settled turn failures without sticking the chat

A finished turn that raised CancelledError, and a failed deferred reentry, kept the surface lease after the work had unwound, so the next message could not run. Retain the lease only while the worker task is still cancelling, and fence a failed deferred reentry with an event so a retry does not run the turn again.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(app): show Dream suggestions in Review with Apply / Dismiss (#21183)

* feat(dream): require Review for risky agent edits

* refactor(dream): share typed entity evidence reads

* test(dream): exercise a guarded spelling proposal in transport fixture

* feat(review): version agent proposal wire contract for released clients

* docs(review): declare v2 route authentication and rate limits

* chore(api): regenerate TypeScript clients for Review v2

* chore(api): regenerate Swift operations for Review v2

* test(dream): fence tiered writers in shadow sweep regression

* feat(app): show Dream suggestions in Review with Apply and Dismiss

Review now reads the v2 queue, which adds Dream's agent_proposal items:
closing or retiring tasks, merging people or memories, and memory or
entity-summary edits that need the owner's consent. Cards show the
question and Dream's reason; the sheet shows current vs suggested text.
A 409 means the evidence changed, so the item stays gone with a clear
message. Ten new strings in all 49 locales via scripts/l10n.py.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(app): changelog fragment for Dream suggestions in Review

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* fix(debug-logs): serialize DebugLogManager appends to prevent interleaved lines (#21178)

* fix(diagnostics): include error detail and stack in RingStorageSync failure log (#21180)

* fix(sync): emit reconcile_completed_empty telemetry for empty job completions (#21182)

* fix(debug-logs): serialize DebugLogManager appends to prevent interleaved lines

* fix(sync): emit reconcile_completed_empty telemetry for empty job completions

* fix: preserve coalesced ranges and serialize numeric speaker work

* chore: consolidate changelog for v1.0.561

* test: retain the real speaker queue in teaching persistence coverage

* fix: stop channel settings test pinning a wall-clock date

The UTC+14 CI shard rolled to Oct 11 before the fixture's Oct 10 date could
render as 'Connected Oct 10, 2026' in its own local midnight window. Assert
the sentence shape (textContaining 'Connected') instead of a hardcoded
formatted date, matching how the product renders linkedAt via
OmiDateFormat.

* perf(ci): compile generated Swift PRs and scope Siri advisory (#21186)

* perf(ci): compile generated Swift PRs and scope Siri advisory

* docs(ci): mark desktop CI changes as internal

* test(ci): keep generated notification DTOs on compile-only PR lane

* fix(channels): order replies, scope Undo, and anchor reminders to message time (#21175)

* fix(channels): keep chat responsive and scope undo to action receipts

Failure-Class: new

* refactor(channels): load surface prompts at the shared mount boundary

* refactor(channels): make lease and receipt state contracts explicit

* fix(channels): preserve revocation and bound receipt retention

Failure-Class: new

* fix(channels): admit replies FIFO and anchor reminders to message time

Failure-Class: new

* refactor(channels): keep inbox index requirements in their owning module

Failure-Class: new

* fix(channels): preserve Telegram provider message timestamps

Failure-Class: new

* refactor(channels): narrow settled lease state after merge

* fix(channels): bound provider clocks and stop lease-miss wake spins

Linq reminders used processing time, and a busy surface lease requeued the inbox head and woke it again. Provider stamps are now bounded, a past relative reminder stays a missed phone reminder, and channel memory search uses the same empty-result wording as memory reads.

Failure-Class: new
Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(channels): accept only real ints for relative reminders

The annotated isinstance check failed pyright and blocked the push. Runtime still rejects bools and non-integers from the model.

Failure-Class: new
Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore: consolidate changelog for v0.12.468

* test(desktop): await the decided outcome in notification speech race tests

The race tests inferred the outcome from isSpeaking after twenty yields. The
playback service now reports where an audience-gated one-shot ended up
(played, fell back to the system voice, withheld, or dropped as stale) at the
point it is decided, and the tests await that. New cases cover a player
failure whose fallback is re-checked, a stopped synthesis that finishes late,
and a private room that still hears the notification. Only audience-gated
one-shots report; other speech is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(hygiene): exclude vendored tokenizer vocabulary from deferred-work markers (#21153)

A false positive surfaced by #20746, which vendors a BERT WordPiece vocabulary as
part of an on-device router. Line 20579 is the token "hack", between "##illy" and
"plaintiff", and the check read it as deferred work:

  FAIL: new deferred-work markers must reference a tracking issue (#123 or URL).
    - app/android/app/src/main/assets/vocab.txt:20579: hack

Why it happens: comment_fragment() treats a line whose first word matches
MARKER_RE as a comment fragment even with no comment syntax. That is deliberate
for a line that is just "TODO", but in a 30,522-row data file a bare token that
happens to start with a marker word is data, not a comment. Exactly one line of
30,522 is affected.

Why the fix is an exclusion rather than a change to the PR: the file cannot be
edited. Its line index is the token id, so removing or reordering a row silently
renumbers every token after it and changes what the tokenizer produces. Editing
the vendored asset to satisfy a linter would trade a false positive for a real
correctness bug.

This restores behaviour the check already documents -- --raw exists precisely so
that generated and vendored files are excluded by default, and the exclusion set
simply had no entry for vendored plain-text data. A glob over src/main/assets
would generalise further; a specific path is narrower and easier to review, so
this takes the narrow one and leaves the call to @Git-on-my-level.

Verified by running the real script, both directions:

  before: FAIL  app/android/app/src/main/assets/vocab.txt:20579: hack
  after:  OK

and the check is not weakened -- a "// HACK:" and a "// TODO:" with no issue
reference in normal source are both still flagged, while "// TODO: ... (#123)" is
still accepted.

Co-authored-by: palontologist <palontologist@users.noreply.github.com>

* fix(backend): drop a conversation's task search vectors when a cascade delete removes them (#15950)

Isolate the Pinecone batch deletion in a try/except block and run reminder
cancellation before vector cleanup to guarantee that vector failures do not
abort the delete or skip cancelling task reminders.

Co-authored-by: kduailabs <kiwidwnunder@gmail.com>

* fix: recover exhausted speaker voices from bounded fresh PCM

* fix: preserve fresh audio watermark across ring replacement

* docs: clarify recovery proof lifecycle and type audio helpers

* test: patch WAV encoding at its owning speaker audio module

* fix(sync): bound mobile job polls, follow active content owners, stop mislabeling edge throttles (#21206)

* fix(sync): bound mobile job polls and follow active content owners

Failure-Class: new

* fix(sync): recover expired owners and requeue retained phone audio

Failure-Class: FC-event-wakes-bypass-request-cadence

* fix(sync): generate declared owner and poll hint response decoders

Declare additive sync conflict and status cadence fields in OpenAPI and regenerate the app wire contract. Decode conflict responses and project status metadata through generated types, retaining compatibility with omitted optional fields.

Failure-Class: FC-event-wakes-bypass-request-cadence

* fix(sync): advance replay clocks through job poll deadlines

Keep terminal-state and retry-budget assertions intact while making replay time match the bounded polling contract. Reset shared test state and prove immediate restored-job admission, deferred upload hints, and singleton isolation.

Failure-Class: FC-event-wakes-bypass-request-cadence

* test(desktop): compare PTT and card surfaces like with like in notch sizing test

testHoldingPushToTalkAgainstAMountedCardKeepsTheCardSurface compared the exact
PTT surface size plus glow outsets with the mounted card's window frame. AppKit
snaps window frames to whole points, so on a display with a fractional notch
inset (37.5pt) the card's 197.5pt request lands as a 198pt frame and the
assertion fails by 0.5pt, while the panel itself never shrinks. Compare the PTT
surface with the card's own surface size instead; the check still fails when
the PTT surface drops the mounted card.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: reserve fresh recovery phrases and enforce tranche allowance

* refactor: keep matcher within the existing size ratchet

* fix(ios): memories fetch 503/429 no longer reported as fatal — retry + graceful degradation (#21226)

* fix(ios): memories fetch failures are recoverable — retry with backoff, non-fatal reporting

* fix(ios): bound memories retries by caller deadline and cancel on disposal

* fix: make owner recognition refusals attributable

* test: await queue matcher completions before asserting owner recovery

* feat(macos): continue MCP marketplace installs into connection setup (#20962)

* feat(macos): continue marketplace installs into connection setup

* test(macos): record marketplace setup and large-text evidence

* fix(macos): refresh committed installs after setup dismissal

* test(macos): record clean-head marketplace replay

* fix(backend): memories fetch resilience — bounded retry, admission cap, structured 503 telemetry (#21227)

* fix(backend): memories fetch resilience — bounded retry, structured 503 telemetry

* fix(backend): cap memories retry admission and preserve probe state

* fix: gate every memory read attempt through global admission

* feat(backend): route Dream spelling fixes to Review until they are grounded (#21232)

* feat(backend): route Dream spelling fixes to Review until they are grounded

Prod Dream passes no longer auto-apply spelling edits; they become Review
suggestions. Titles, overviews and vocabulary still auto-apply. Spelling
returns to auto once the agent must cite supporting context.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(backend): allow the Dream auto-apply override in the prod env contract

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* docs(backend): C025 runbook — regionalize syncing-local to us-central1

Cost ledger card C025: prod syncing-local is multi-region, paying replication
for <=8-minute staging blobs (+$46/day WoW). Runbook covers replacement
bucket provisioning, intake pause + job drain (queued raw audio can outlive
8 min), 15-min signed-URL cache waitout, env flip in all four chart values,
rollback, and old-bucket deletion gates.

* fix: bound diagnostic writes and update observation regression guards

* fix: narrow optional observer in diagnostic flush

* perf(memory): hydrate graph references instead of scanning the corpus

C008 cut 1+2: KG traversal hydration now reads only distinct edge-referenced
memory IDs in 500-doc batches (existing authoritative by-ID seam) instead of
two full memory_items corpus streams. 18k-doc scan -> 6 point reads in test.
Eligibility recheck, citation-time privacy check, probe labels preserved.
Privacy-critical exhaustive scans deliberately left unbounded (see report).

* test(memory): keep scan-guard test under the 0.30s timing guard

18k-item corpus reproduced the prod-scale scan but ran 0.33s against
test.sh's 0.30s per-test budget; 1.5k preserves the property (scan would
still enumerate the corpus; by-ID path reads 6 docs).

* test(memory): shrink batching test to 550 IDs under the timing guard

Batching/dup property preserved (500+50, dedupe before batching); the
implementation dedupes requested IDs first, so expected batches update to
[500, 50].

* test: isolate memory read admission between hermetic e2e cases

* fix(backend): per-UID ceiling on sync job status polling

C024: prod poll volume stepped to 40M req/day (85% this route). Cap = 40
polls/UID/20s via existing atomic Redis reservation, independent of
RATE_LIMIT_BOOST; 429 + Retry-After: 20 before job lookup; fail-open on
Redis outage with fallback telemetry; two counters added. Shipped Dart
poller cadence (<=18 req/min/instance) never hits the cap.

* test: cover diagnostics exemption and isolate synthetic cursor admission

* test: exhaust hydration budget after read admission

* chore: consolidate changelog for v0.12.469

* fix(app): fully-charged notification — require on-charger dwell + fresh battery reading (#21233)

* fix(app): fully-charged notification requires on-charger dwell and fresh battery reading

A cached 100% from before plug-in was enough to fire the fully-charged notification on the charging edge. Require a continuous on-charger dwell and a battery sample timestamped after that edge.

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(app): add changelog fragment for the fully-charged notification fix

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(app): scope fully-charged alerts per device and dwell on monotonic time

A provider-wide alert flag suppressed the next pendant, and wall-clock dwell
could be skipped by moving the phone clock. Mismatch logging now waits until
the notification permission check has passed.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(app): keep device provider under the line-count ratchet

The fully-charged presentation path had pushed the file past 1500 lines.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(memory): isolate the cursor suite from the shared read-admission allowance

#21227's process-wide RetryAllowance (8 tokens, 1/s refill) exhausts
deterministically when the cursor suite pages thousands of rows in under a
second. Fresh allowance per test + burst 64 via the existing env knob.

* fix(backend): first read admission gates on time headroom, not document headroom

#21227 regression: with an exhausted ListReadBudget document allowance
(max_documents spent), _headroom returns 0.0 and first-attempt admission
raises 503 Memory read admission exhausted — but an exhausted document
budget is a normal truncated page in the list-budget contract (the charge
path flags budget.truncated downstream). Restores truncated-page behavior;
the retry path keeps the full budget-aware headroom check.

* test(e2e): isolate the hermetic harness from the shared read-admission allowance

Same mechanism as the cursor suite: 113 in-process tests exhaust the
8-token process-wide RetryAllowance and shed legitimate bursts as 503
Memory read admission exhausted. Burst 64 + fresh allowance in the e2e
conftest; full harness green locally (126 passed, 3 skipped, 2 xfailed).

* feat: expose deterministic Dream summary eligibility (#21236)

* docs(backend): close the review blockers in the C025 cutover runbook

Grok review (read-only) found two blockers in the no-copy drain: Firestore
sync_backfill_pending docs carry raw_blob_paths and survive a Cloud Tasks +
Redis drain (expire as sync_staged_audio_expired post-flip); failed/partial
backfill jobs keep staged blobs and recover_dev_failed_sync_backfill.py
resolves paths against the current bucket env. Runbook now requires the
sequencer queue drained + recoverable failed/partial jobs finished or
copied before the flip, accounts chat voice-message staging in the URL-cache
clock, and documents the backfill clone-order hazard.

* Fix closed-generation diagnostic persistence and log kinds

* feat: chat edit receipts with undo, and h.omi.me item links (#21234)

* feat(app): render chat edit receipts with undo

Decode the new `editReceipt` chat content block (camelCase and snake_case)
and render it as a chip in the memory/task family: an existing icon per
kind, the summary, a tap that opens the conversation, memory, task or
person, and a compact Undo while the server allows it. Undo calls
POST /v1/chat/receipts/{id}/undo, treats already_undone as success, and
reports failure through OmiFeedback. Unknown kinds keep their summary and
undo but have no destination.

Also parse h.omi.me singular item links (conversation/task/memory) into
the Home routes that already open them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat: open h.omi.me item links in the app, content-free web fallback

https://h.omi.me/conversation/<id>, /task/<id> and /memory/<id> (singular;
the plural /conversations/ and /tasks/ public share pages are unchanged):

- iOS AASA and Android app-link intent filters claim the three paths, and
  the Flutter link handler routes them into Home.
- Without the app, h.omi.me serves a minimal Omi page: "Open in the Omi
  App", App Store / Google Play, and a web sign-in handoff. It never reads
  the item; preview metadata is generic Omi only and noindex.
- web/app login honours a same-origin `next` (and protected pages pass
  it), so the handoff lands on /conversations?id=<id> (tasks and memories
  land on their list pages; web/app has no single-item routes for them).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: UI evidence for edit receipts and item-link fallback; absolute title

The fallback page's title is now exactly "Omi" (the root template made it
"Omi | Omi").

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(app): treat refused receipt undo as terminal

Match the backend undo contract: `unavailable` (window closed or ambiguous
inverse), 404 and 409 report the failure and remove Undo instead of
showing a false "Undone" or inviting a retry that cannot succeed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(app): changelog entry for chat edit receipts and item links

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(app): keep a refused receipt undo from coming back

A rebuild from the original message dropped the terminal undo state, and the caption row overflowed once text scale grew past the fixed 32px line.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(web): reject sign-in return paths that normalize off-site

`/.//evil.example` and a percent-encoded double slash passed the prefix check and became a protocol-relative URL after the browser parsed them.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(chat): rich channel replies and shared edit receipts (#21240)

* feat(chat): publish shared edit receipt and owner undo contract

* feat(chat): render channel markdown and share audited edit tools

* docs(chat): clarify client integration and memory undo limits

* fix(chat): sanitize task snapshot failures before writes

* fix(memory): distinguish canonical audit keys in source inventory

* fix(chat): type shared edits and verify injected tool config

* fix(chat): align receipt summaries with native UI cards

* fix(chat): fence edits and persist undo in message history

* test(chat): cover receipt lookup query and record UI integration

* fix(chat): keep receipt message queries at database authority

* fix(chat): preserve protected notes across repeated corrections

* fix(chat): decode correction notes in manual speaker responses

* fix(chat): drop unsafe channel links and leftover citation markers

Fullwidth and footnote markers were still delivered, and non-https destinations survived as text or hrefs. Record the desktop proxy and non-ledger memory undo limits that are not contained fixes.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore: consolidate changelog for v0.12.470

* fix(ci): rebalance backend unit shards and synchronize fairness test (#21259)

* Retrieve bounded Dream neighbourhoods with target and context roles (#21237)

* feat: expose deterministic Dream summary eligibility

* feat: retrieve bounded Dream target neighbourhoods

* test: migrate Dream canary and report retrieval fixtures

* harden: enforce Dream context and speech admission contracts

* fix: wire Dream retrieval providers and isolate embedding work

* fix: retrieve nearest Dream context without photos and preserve vocabulary matches

* fix(dream): deduplicate context reads and tolerate unavailable search

* fix(dream): initialize the per-pass control cache before dispatch

* test(dream): migrate report fixtures to cached reads

* test(dream): validate Typesense bindings and preserve upstream app bytes

* fix(ci): distribute the growing backend suite across six shards

* chore(ci): refresh measured six-shard runner cost forecast

* chore(ci): record measured fanout cost in aggregate forecast caps

* fix(ci): run metadata preflight for Dream stack bases

* chore(dream): extract shared CI changes into standalone PR

* fix(dream): reclaim unused context budget and diagnose retrieval (#21263)

* fix(dream): reclaim unused context capacity and report retrieval paths

* fix(dream): type diagnostics and keep vector availability checks local

* fix(dream): bound reclaimed targets and retain unavailable queue liveness

* fix(dream): settle permanent misses and bound transient retries and reads

* fix(dream): retain user-authored titles in retrieval queries

* chore: consolidate changelog for v0.12.471

* fix(dream): run ordinary-text search and retrieve non-target neighbours (#21275)

* fix(dream): search ordinary speech and exclude temporal targets

* fix(dream): exclude pending targets and bound query matching

* fix(dream): read section evidence and reserve a context slice

* test(dream): bind verified reader and stop redundant budget probes

* perf(dream): avoid duplicate triage and schema sizing

* fix(dream): expire unreadable speech and inspect oldest available refs

* fix: restore live matcher FIFO scheduling and query cadence

* Require independent support for Dream spelling auto-apply (#21238)

* feat: expose deterministic Dream summary eligibility

* feat: retrieve bounded Dream target neighbourhoods

* feat: require independent support for Dream spelling auto-apply

* harden: type Dream dry-run diagnostic fields

* test: ground the synthetic Dream transport spelling fixture

* test: migrate Dream canary and report retrieval fixtures

* test: migrate Dream canary and report retrieval fixtures

* harden: enforce Dream context and speech admission contracts

* harden: enforce Dream context and speech admission contracts

* fix: wire Dream retrieval providers and isolate embedding work

* fix: wire Dream retrieval providers and isolate embedding work

* fix: retain actionable Dream spelling answers and canonical provenance

* fix: atomically admit new Dream spelling cards without wasting edit budget

* fix: retrieve nearest Dream context without photos and preserve vocabulary matches

* fix: retrieve nearest Dream context without photos and preserve vocabulary matches

* test: inject synthetic turns into Dream edit-budget regression

* fix(dream): deduplicate context reads and tolerate unavailable search

* fix(dream): prevent vocabulary laundering and preserve capped questions

* fix(dream): initialize the per-pass control cache before dispatch

* test(dream): migrate report fixtures to cached reads

* test(dream): use authored vocabulary and cached report fixtures

* fix(dream): require resolvable independent support chains

* test(dream): validate Typesense bindings and preserve upstream app bytes

* test(dream): make legacy agent support explicitly user authored

* fix(dream): defer capped spelling targets until UTC reset

* test(dream): witness deferred completion query branches

* fix(ci): distribute the growing backend suite across six shards

* chore(ci): refresh measured six-shard runner cost forecast

* chore(ci): record measured fanout cost in aggregate forecast caps

* fix(ci): run metadata preflight for Dream stack bases

* chore(dream): extract shared CI changes into standalone PR

* Sample older Dream cleanup targets with relevance and review cooldowns (#21239)

* feat: expose deterministic Dream summary eligibility

* feat: retrieve bounded Dream target neighbourhoods

* feat: require independent support for Dream spelling auto-apply

* harden: type Dream dry-run diagnostic fields

* feat: seed bounded older Dream cleanup with review cooldowns

* test: ground the synthetic Dream transport spelling fixture

* test: migrate Dream canary and report retrieval fixtures

* test: migrate Dream canary and report retrieval fixtures

* test: migrate Dream canary and report retrieval fixtures

* test: isolate Dream report backfill and review metadata

* harden: count dirty Dream targets in source diagnostics

* harden: enforce Dream context and speech admission contracts

* harden: enforce Dream context and speech admission contracts

* harden: enforce Dream context and speech admission contracts

* fix: wire Dream retrieval providers and isolate embedding work

* fix: wire Dream retrieval providers and isolate embedding work

* fix: retain actionable Dream spelling answers and canonical provenance

* fix: wire Dream retrieval providers and isolate embedding work

* fix: retain actionable Dream spelling answers and canonical provenance

* fix: bootstrap legacy Dream cleanup and bound cooldown reads

* fix: atomically admit new Dream spelling cards without wasting edit budget

* fix: atomically admit new Dream spelling cards without wasting edit budget

* fix: retrieve nearest Dream context without photos and preserve vocabulary matches

* fix: retrieve nearest Dream context without photos and preserve vocabulary matches

* fix: retrieve nearest Dream context without photos and preserve vocabulary matches

* test: inject synthetic turns into Dream edit-budget regression

* test: inject synthetic turns into Dream edit-budget regression

* fix(dream): deduplicate context reads and tolerate unavailable search

* fix(dream): prevent vocabulary laundering and preserve capped questions

* fix(dream): initialize the per-pass control cache before dispatch

* fix(dream): bound idle cleanup admission and historical sampling

* test(dream): migrate report fixtures to cached reads

* test(dream): use authored vocabulary and cached report fixtures

* fix(dream): return idle without run history and update admission fixtures

* fix(dream): require resolvable independent support chains

* test(dream): validate Typesense bindings and preserve upstream app bytes

* test(dream): make legacy agent support explicitly user authored

* test(dream): supply dirty versions to legacy admission fixtures

* fix(dream): defer capped spelling targets until UTC reset

* fix(dream): sample real record ID spaces and honor seed deferrals

* test(dream): witness deferred completion query branches

* fix(ci): distribute the growing backend suite across six shards

* chore(ci): refresh measured six-shard runner cost forecast

* chore(ci): record measured fanout cost in aggregate forecast caps

* fix(ci): run metadata preflight for Dream stack bases

* test(ci): await completed speaker matches in fairness regression

* chore(dream): extract shared CI changes into standalone PR

* chore(dream): move speaker fairness regression fix to CI PR

* chore: consolidate changelog for v0.12.472

* Schedule Dream fairly within shared reserved background capacity (#21241)

* feat: expose deterministic Dream summary eligibility

* feat: retrieve bounded Dream target neighbourhoods

* feat: require independent support for Dream spelling auto-apply

* harden: type Dream dry-run diagnostic fields

* feat: seed bounded older Dream cleanup with review cooldowns

* test: ground the synthetic Dream transport spelling fixture

* feat: schedule Dream fairly within reserved background capacity

* test: migrate Dream canary and report retrieval fixtures

* test: exercise full Dream fixtures through reserved admission

* test: migrate Dream canary and report retrieval fixtures

* test: migrate Dream canary and report retrieval fixtures

* test: isolate Dream report backfill and review metadata

* test: isolate manual Dream report from stochastic backfill

* harden: count dirty Dream targets in source diagnostics

* harden: retain dirty source counts through scheduling

* harden: enforce Dream context and speech admission contracts

* harden: enforce Dream context and speech admission contracts

* harden: enforce Dream context and speech admission contracts

* harden: enforce Dream context and speech admission contracts

* fix: wire Dream retrieval providers and isolate embedding work

* fix: wire Dream retrieval providers and isolate embedding work

* fix: retain actionable Dream spelling answers and canonical provenance

* fix: wire Dream retrieval providers and isolate embedding work

* fix: wire Dream retrieval providers and isolate embedding work

* fix: retain actionable Dream spelling answers and canonical provenance

* fix: retain actionable Dream spelling answers and canonical provenance

* fix: bootstrap legacy Dream cleanup and bound cooldown reads

* fix: bootstrap legacy Dream cleanup and bound cooldown reads

* fix: report Dream capacity waits as retryable manual responses

* fix: atomically admit new Dream spelling cards without wasting edit budget

* fix: atomically admit new Dream spelling cards without wasting edit budget

* fix: refresh Dream subscription priority and release declined slots

* fix: atomically admit new Dream spelling cards without wasting edit budget

* fix: retrieve nearest Dream context without photos and preserve vocabulary matches

* fix: retrieve nearest Dream context without photos and preserve vocabulary matches

* fix: retrieve nearest Dream context without photos and preserve vocabulary matches

* fix: retrieve nearest Dream context without photos and preserve vocabulary matches

* test: inject synthetic turns into Dream edit-budget regression

* test: inject synthetic turns into Dream edit-budget regression

* test: inject synthetic turns into Dream edit-budget regression

* harden: project only current plan fields for Dream scheduling

* fix(dream): deduplicate context reads and tolerate unavailable search

* fix(dream): prevent vocabulary laundering and preserve capped questions

* fix(dream): initialize the per-pass control cache before dispatch

* fix(dream): bound idle cleanup admission and historical sampling

* test(dream): migrate report fixtures to cached reads

* test(dream): use authored vocabulary and cached report fixtures

* fix(dream): return idle without run history and update admission fixtures

* fix(dream): require resolvable independent support chains

* fix(d…
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants