Merged
Size
XL
Change Breakdown
Feature75%
Security15%
Testing5%
Docs5%
#60239feat(ios): add exec approval notification flow

iOS exec approval notifications now trigger in-app modal

iOS exec approval notifications now trigger in-app modal

iPhone users receive a generic push when exec approval is needed, tap to open the app, review command details securely, and resolve directly from a modal — without requiring a separate operator interface.

Before this change, iOS devices paired as operator consoles received no native notification path for exec approvals. Requests still had to be handled through a separate surface, breaking the mobile workflow.

Now, when an exec approval request arrives, iOS devices get a generic "Exec approval required" push. Tapping that notification opens the app and presents a modal after an authenticated fetch retrieves the command details. The modal shows the command text, host, node, agent, and expiration — with buttons for Allow Once, Allow Always (when permitted), Deny, and Cancel.

The push payload stays generic to avoid exposing command details on the lock screen. The actual command text is fetched via a new exec.approval.get RPC only after the operator reconnects and authenticates. When an approval resolves — whether allowed, denied, or expired — a cleanup push dismisses the stale notification state.

This flow targets devices with active operator tokens that have the operator.approvals scope. Cleanup pushes are limited to devices that originally received the request, avoiding over-broadcast. The implementation integrates with the existing approval backend, extending it with iOS-specific push delivery and modal resolution rather than building a parallel system.

View Original GitHub Description

Summary

  • Problem: iOS had no native exec approval notification flow after onboarding, so approval requests still required another operator surface.
  • Why it matters: iPhone users could receive generic pushes, but there was no secure, well-scoped notification-to-approval UX for exec requests.
  • What changed: added lock-screen-safe exec approval APNs delivery, a scoped exec.approval.get fetch-on-open RPC, and an in-app iOS modal that resolves through the existing approval backend flow.
  • What did NOT change (scope boundary): no approval inbox/history UI, no reconnect-time reconciliation/list flow, no notification action buttons, and no QR bootstrap auth redesign in this PR.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes #
  • Related #59732
  • This PR fixes a bug or regression

Root Cause / Regression History (if applicable)

  • Root cause: iOS never had a native exec approval push + modal path; approval semantics existed, but not an iOS delivery/fetch/resolve UX.
  • Missing detection / guardrail: there was no mobile-specific delivery bridge, and no scoped RPC for authenticated fetch-on-open approval details.
  • Prior context (git blame, prior PR, issue, or refactor if known): split out from the broader iOS exec approval work previously carried on ios/exec-approval-push / PR #59732.
  • Why this regressed now: N/A; this is a new iOS capability split out from the combined branch.
  • If unknown, what was ruled out: the branch intentionally excludes QR bootstrap/auth handoff changes, which live in the stacked base PR.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file:
    • src/gateway/exec-approval-ios-push.test.ts
    • src/gateway/server-methods/server-methods.test.ts
    • src/infra/push-apns.test.ts
    • src/gateway/method-scopes.test.ts
    • apps/ios/Tests/ExecApprovalNotificationBridgeTests.swift
    • apps/ios/Tests/NodeAppModelInvokeTests.swift
  • Scenario the test should lock in: a generic exec approval push opens the app, the app fetches details via exec.approval.get, and the modal resolves via the existing approval decision flow while resolved pushes dismiss stale notification state.
  • Why this is the smallest reliable guardrail: the behavior crosses APNs payloads, gateway delivery targeting, a new RPC, and iOS modal state.
  • Existing test that already covers this (if any): none covered the new mobile notification flow before this PR.
  • If no new test is added, why not: N/A.

User-visible / Behavior Changes

  • iOS receives a generic "Exec approval required" push for exec approval requests.
  • Tapping that push opens the app and presents a modal after authenticated detail fetch.
  • The modal supports Allow Once, Allow Always (when permitted), Deny, and Cancel.
  • Resolved/expired cleanup pushes dismiss stale approval notification state.

Diagram (if applicable)

Before:
[exec approval requested] -> [other operator surface needed]

After:
[exec approval requested] -> [generic iOS push] -> [tap notification] -> [exec.approval.get] -> [modal] -> [exec.approval.resolve]

Security Impact (required)

  • New permissions/capabilities? (Yes/No) Yes
  • Secrets/tokens handling changed? (Yes/No) No
  • New/changed network calls? (Yes/No) Yes
  • Command/tool execution surface changed? (Yes/No) No
  • Data access scope changed? (Yes/No) Yes
  • If any Yes, explain risk + mitigation:
    • Risk: mobile approval delivery fetches command details and grants an operator-approval UX on iOS.
    • Mitigation: push payloads remain lock-screen-safe and generic; command text is fetched only after authenticated operator connection via exec.approval.get; the method is gated by operator.approvals; push delivery targets active operator tokens with effective approval scope; cleanup pushes are skipped when original target sets are unknown.

Repro + Verification

Environment

  • OS: macOS 26.3.1 and iOS device/simulator validation paths
  • Runtime/container: Node 24.x, local gateway
  • Model/provider: N/A
  • Integration/channel (if any): iOS app / APNs / gateway exec approvals
  • Relevant config (redacted): stacked on top of ios/qr-bootstrap-fixes

Steps

  1. Trigger an exec approval request on a gateway paired with iOS.
  2. Confirm the iOS device receives a generic approval push.
  3. Tap the notification, fetch details with exec.approval.get, and resolve from the modal.

Expected

  • Push payload is generic and does not expose command text on the lock screen.
  • The app fetches approval details after authenticated open.
  • Modal resolution uses existing approval semantics and cleanup dismisses stale state.

Actual

  • Matches expected with the changes in this PR.

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

  • Verified scenarios:
    • reviewed the branch scope against the intended exec-notification-only split
    • validated targeted gateway/APNs tests
    • validated targeted iOS simulator tests for notification bridge parsing and modal prompt state
  • Edge cases checked:
    • stale/expired approval fetch handling
    • cleanup push dismissal
    • approval-id ambiguity hardening
    • generic payload without command-text exposure
  • What you did not verify:
    • a fresh real-phone rerun after splitting this branch from the original combined branch

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? (Yes/No) Yes
  • Config/env changes? (Yes/No) No
  • Migration needed? (Yes/No) No
  • If yes, exact upgrade steps:

Risks and Mitigations

  • Risk: approval details could leak through push payloads.
    • Mitigation: requested APNs payload is generic and only includes the opaque approval id.
  • Risk: stale cleanup pushes could over-broadcast to devices that did not receive the original request.
    • Mitigation: cleanup delivery is limited to remembered original targets and skipped when that target set is unknown.
  • Risk: mobile clients without active approval scope could still be targeted.
    • Mitigation: iOS push targeting follows active operator token scopes rather than historical paired-device metadata.
© 2026 · via Gitpulse