feat(jit): add owner-fenced ambient notification feedback - #12808
Conversation
The quality/cost corpus remains in the isolated replay evidence branch; ambient feedback only needs its production provenance path.
There was a problem hiding this comment.
3 issues found across 15 files
Confidence score: 2/5
JITProactivityDelivery.swiftcan present an ambient card using an outdated authorization snapshot after the owner’s cutover generation advances, creating a stale-authorization user-facing regression — revalidate authorization against the current generation before presentation.InterjectSuggestionFeedbackStore.swiftcan leave a stale feedback row when an account-generation fence is supplied without an authorization snapshot, even though analytics are suppressed — include the account-generation fence in stale-row cleanup handling.floating-bar-functional.yamlclaimsJITAmbientFeedbackcoverage without exercising it, which can hide regressions in that flow — remove the claim or add steps that actually validate the ambient feedback behavior.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="desktop/macos/Desktop/Sources/ProactiveAssistants/Core/JITProactivityDelivery.swift">
<violation number="1" location="desktop/macos/Desktop/Sources/ProactiveAssistants/Core/JITProactivityDelivery.swift:437">
P1: When the cutover generation advances for the same owner while this delivery is awaiting `completeDelivery`, the old authorization snapshot can remain current and this ambient card is still presented. Check the authoritative generation immediately before presentation, and drop the delivery when `execution.accountGeneration` is stale.</violation>
</file>
<file name="desktop/macos/Desktop/Sources/FloatingControlBar/Interject/InterjectSuggestionFeedbackStore.swift">
<violation number="1" location="desktop/macos/Desktop/Sources/FloatingControlBar/Interject/InterjectSuggestionFeedbackStore.swift:151">
P2: When a caller supplies an account-generation fence without an authorization snapshot, a generation change after the store write suppresses analytics but leaves the stale feedback row behind. Include the account-generation fence in the rollback condition so rejected mutations cannot remain in the canonical ledger.</violation>
</file>
<file name="desktop/macos/e2e/flows/floating-bar-functional.yaml">
<violation number="1" location="desktop/macos/e2e/flows/floating-bar-functional.yaml:47">
P2: This flow never exercises JITAmbientFeedback, so listing it in `covers:` claims coverage that doesn't exist. All steps (S1–S8) only assert the signed-in floating bar, open Ask Omi, send a stubbed typed turn, assert the stub marker echo, and close the bar. No step triggers an ambient JIT notification or taps the Useful/Not relevant actions routed by JITAmbientFeedbackActionRouter. Either remove the entry or add steps that drive an ambient card and its feedback actions.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| contextSummary: decision.reasoning, detail: execution.triggerID, | ||
| provenanceRef: deliveryID), | ||
| jitFeedbackContext: feedbackContext, | ||
| jitAmbientFeedbackContext: ambientFeedbackContext, |
There was a problem hiding this comment.
P1: When the cutover generation advances for the same owner while this delivery is awaiting completeDelivery, the old authorization snapshot can remain current and this ambient card is still presented. Check the authoritative generation immediately before presentation, and drop the delivery when execution.accountGeneration is stale.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At desktop/macos/Desktop/Sources/ProactiveAssistants/Core/JITProactivityDelivery.swift, line 437:
<comment>When the cutover generation advances for the same owner while this delivery is awaiting `completeDelivery`, the old authorization snapshot can remain current and this ambient card is still presented. Check the authoritative generation immediately before presentation, and drop the delivery when `execution.accountGeneration` is stale.</comment>
<file context>
@@ -400,6 +434,7 @@ actor JITProactivityDelivery {
contextSummary: decision.reasoning, detail: execution.triggerID,
provenanceRef: deliveryID),
jitFeedbackContext: feedbackContext,
+ jitAmbientFeedbackContext: ambientFeedbackContext,
onPresented: { [weak self] in
Task {
</file context>
| recordedAt: recordedAt | ||
| ) | ||
| ) | ||
| if !didEmitAnalytics, authorizationSnapshot != nil { |
There was a problem hiding this comment.
P2: When a caller supplies an account-generation fence without an authorization snapshot, a generation change after the store write suppresses analytics but leaves the stale feedback row behind. Include the account-generation fence in the rollback condition so rejected mutations cannot remain in the canonical ledger.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At desktop/macos/Desktop/Sources/FloatingControlBar/Interject/InterjectSuggestionFeedbackStore.swift, line 151:
<comment>When a caller supplies an account-generation fence without an authorization snapshot, a generation change after the store write suppresses analytics but leaves the stale feedback row behind. Include the account-generation fence in the rollback condition so rejected mutations cannot remain in the canonical ledger.</comment>
<file context>
@@ -58,26 +100,61 @@ enum InterjectSuggestionFeedbackMutation {
- recordedAt: recordedAt
- )
- )
+ if !didEmitAnalytics, authorizationSnapshot != nil {
+ // Do not leave a stale owner row behind when the transition won the
+ // race after the actor write but before the telemetry seam.
</file context>
| if !didEmitAnalytics, authorizationSnapshot != nil { | |
| if !didEmitAnalytics, authorizationSnapshot != nil || accountGeneration != nil { |
| - desktop/macos/Desktop/Sources/FloatingControlBar/Interject/InterjectReplyWindow.swift | ||
| - desktop/macos/Desktop/Sources/FloatingControlBar/Interject/InterjectSuggestionFeedbackStore.swift | ||
| - desktop/macos/Desktop/Sources/FloatingControlBar/Interject/InterjectVoiceFeedbackRouting.swift | ||
| - desktop/macos/Desktop/Sources/ProactiveAssistants/Core/JITAmbientFeedback.swift |
There was a problem hiding this comment.
P2: This flow never exercises JITAmbientFeedback, so listing it in covers: claims coverage that doesn't exist. All steps (S1–S8) only assert the signed-in floating bar, open Ask Omi, send a stubbed typed turn, assert the stub marker echo, and close the bar. No step triggers an ambient JIT notification or taps the Useful/Not relevant actions routed by JITAmbientFeedbackActionRouter. Either remove the entry or add steps that drive an ambient card and its feedback actions.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At desktop/macos/e2e/flows/floating-bar-functional.yaml, line 47:
<comment>This flow never exercises JITAmbientFeedback, so listing it in `covers:` claims coverage that doesn't exist. All steps (S1–S8) only assert the signed-in floating bar, open Ask Omi, send a stubbed typed turn, assert the stub marker echo, and close the bar. No step triggers an ambient JIT notification or taps the Useful/Not relevant actions routed by JITAmbientFeedbackActionRouter. Either remove the entry or add steps that drive an ambient card and its feedback actions.</comment>
<file context>
@@ -44,6 +44,7 @@ covers:
- desktop/macos/Desktop/Sources/FloatingControlBar/Interject/InterjectReplyWindow.swift
- desktop/macos/Desktop/Sources/FloatingControlBar/Interject/InterjectSuggestionFeedbackStore.swift
- desktop/macos/Desktop/Sources/FloatingControlBar/Interject/InterjectVoiceFeedbackRouting.swift
+ - desktop/macos/Desktop/Sources/ProactiveAssistants/Core/JITAmbientFeedback.swift
- desktop/macos/Desktop/Sources/FloatingControlBar/PTTContextVocabularyProvider.swift
- desktop/macos/Desktop/Sources/FloatingControlBar/PushToTalkManager.swift
</file context>
Add generation checks at direct, async, and queued notification presentation seams and derive ambient feedback identities from delivery provenance. Make audio permission setup deterministic and derive the streaming drift bound from chat typography. Focused tests: JITAmbientFeedbackTests (11), stale-generation presentation (1), ChatTranscriptGestureHarnessTests (1), combined Chat/FailLoud/Floating tests (55). Failure-Class: none
What
Ambient JIT notifications now expose explicit Useful and Not relevant feedback actions. The owner, authenticated-session, and account-generation fences are rechecked at the feedback write and telemetry seams, stale native banners cannot resurrect after a cutover, and a delayed tap cannot dismiss a replacement card.
Why
Ambient notifications have no standing trigger, so planned-only snooze/disable controls must not be fabricated. Feedback must remain tied to the exact admitted candidate while account transitions and relaunches invalidate stale work. This keeps the existing single chat timeline and canonical feedback mutation path intact.
Changes
Tests
xcrun swift test --package-path desktop/macos/Desktop --filter 'JITAmbientFeedbackTests|FloatingBarNotificationPreviewPolicyTests|ChatTranscriptGestureHarnessTests|FailLoudConfigTests|ChatDailySummaryTests|ProactiveNotificationKindTests'(104 passed: 16 JIT ambient, 23 floating policy, 21 transcript harness, 12 fail-loud, 25 daily summary, 7 notification kind)xcrun swift test --package-path desktop/macos/Desktop --filter JITAmbientFeedbackTests(16 passed)./desktop/macos/scripts/swiftlint-wrapper.sh lint(0 violations)./desktop/macos/scripts/check_desktop_test_quality.py(baseline ratchet passed)xcrun swift test --package-path desktop/macos/Desktop --filter FloatingBarNotificationPreviewPolicyTests(23 passed; current-generation presentation and mismatched-generation denial use the actual banner route)git diff --checkNo paid model calls or production flags were used.
Manual acceptance
floating-bar-functionalis a source-coverage declaration; this change has no executed bridge-flow receipt. Before release, run that flow against the non-production app: launch signed in, open Ask Omi, submit the marker query, wait for the floating-bar chat to become idle, verify the echoed marker, and close Ask Omi. Record the run SHA, bundle, and bridge result before treating the flow as functional evidence.Delivery ordering
The delivery actor records
policy_approvedbefore requesting the visible card so the durable row exists before presentation. It rechecks authenticated ownership immediately after that write, while the actual notification presenter rechecks authenticated ownership and account generation at its presentation seam. A generation change in the narrow interval afterpolicy_approvedand before presentation therefore rejects the card and invokes the existing dropped path, which terminalizes the row;policy_approvedis not a presentation-success receipt. The floating bar's delayed feedback check is a pre-mutation identity fence, while the existing feedback actor remains the authoritative owner and account-generation write fence.Chat drift evidence
The transcript harness derives the bound from the production 14 pt chat body:
14 * 1.25 + OmiMarkdownContent.chatLineSpacing(14)is 22.5 pt, with a 1.5 pt margin yielding a 24 pt bound. The same test asserts that the historical 48 pt multi-line glide is above that bound, while the live-edge harness exercises the real mounted transcript during 40 streaming flushes. This keeps the bound tied to typography and retains a negative control for the old regression.Failure class
Failure-Class: FC-split-mutation-authority
Invariants
INV-CUTOVER-1: account-generation checks remain derived from the server-authoritative cutover control through its synchronized authority.INV-AUTH-1: delayed ambient feedback remains bound to the original authenticated session at physical recording and response-publication boundaries.INV-CHAT-1: feedback actions continue to project onto the shared canonical chat/notification identity and do not introduce a second transcript or surface-local writer.INV-CHAT-2: the transcript gesture harness retains live-edge placement and explicit reader movement ownership while covering the measured typography bound.Follow-up boundary
The paired legacy-versus-JIT quality/cost fixture is tracked separately and remains blocked until the actual runtime provider/model/rate-card attribution is resolved and approved.
Line-Count-Exception: desktop/macos/Desktop/Sources/AnalyticsManager.swift | 1693 -> 1710 | provenance fields stay beside the existing suggestion feedback event until analytics event ownership is split
Line-Count-Exception: desktop/macos/Desktop/Sources/FloatingControlBar/FloatingControlBarView.swift | 3130 -> 3158 | ambient feedback action routing remains beside the existing notification presentation seam
Line-Count-Exception: desktop/macos/Desktop/Sources/FloatingControlBar/FloatingControlBarWindow.swift | 5794 -> 5818 | canonical mutation call-site return handling remains at the existing owner boundary
Line-Count-Exception: desktop/macos/Desktop/Sources/ProactiveAssistants/Services/NotificationService.swift | 1535 -> 1706 | native JIT feedback serialization and session-generation fencing share the existing notification boundary