Skip to content

feat(apps): review durable approvals on mobile#104913

Merged
steipete merged 2 commits into
mainfrom
codex/approval-native-clients
Jul 12, 2026
Merged

feat(apps): review durable approvals on mobile#104913
steipete merged 2 commits into
mainfrom
codex/approval-native-clients

Conversation

@steipete

Copy link
Copy Markdown
Contributor

Related: #103505
Carries #103912 to main after GitHub recorded that stacked PR as merged into its prerequisite branch.

What Problem This Solves

Operators using Android, iPhone, or Apple Watch cannot reliably review the same Gateway approval shown in the Control UI or a chat channel. Lost acknowledgements can leave a native card looking actionable or resolved without canonical proof, and switching Gateways can misroute stale approval state.

Why This Change Was Made

The official mobile clients now read and resolve the durable Gateway approval record. Android and iPhone use the kind-aware unified RPCs, Watch relays reviewer-safe exec prompts through its paired iPhone, and every client treats Gateway terminal state as authoritative. A bounded legacy exec-method fallback supports shipped Gateway v4 peers.

The clients preserve approval and Gateway IDs as opaque exact identifiers, bind credentials and cached prompts to the selected Gateway, and freeze ambiguous writes until canonical readback. Review also fixed a same-endpoint Android method-catalog race and made Watch show explicit loading or unavailable states instead of falsely reporting a pending approval as clear.

User Impact

Authorized operators can review pending exec approvals from Android, iPhone, or Apple Watch. The first answer from any authorized surface wins, every native surface displays the recorded result, and a lost response or reconnect cannot silently fabricate approval success or reopen execution rights.

Evidence

  • Blacksmith Testbox tbx_01kxa2qa51wqsm9njcjdzsk0qd (silver-prawn-aefb): the exact Android method-catalog race passes in Play and ThirdParty; full Play and ThirdParty unit suites plus app/benchmark ktlint pass.
  • Blacksmith Testbox: test/scripts/native-app-i18n.test.ts passes 7/7.
  • Xcode 26.6: the Watch target builds on watchOS 26.5, launches in the Series 11 simulator, and opens directly on the seeded high-risk approval face. The visually inspected 416x496 PNG has SHA-256 73b5ca0b7c1853e02fa5e8a9a8d5ed4cf5884eeeaae4f678a971c7af428c4cff; the local artifact publisher was unavailable, so no proof asset is committed.
  • Focused iOS run: 217/219 tests pass, including all approval and Watch source-guard tests. The two failures are current-main defects in untouched Talk status and typography-audit sources, not this diff.
  • Fresh autoreview reports no accepted or actionable findings after the Android reconciliation and Watch state fixes.

The paired physical iPhone/Watch interaction remains a manual release recipe; this PR does not claim that device run.

steipete added 2 commits July 11, 2026 19:20
Squash-rebased #103912 segment onto the deep-links tip on current main.
Native approval surfaces: iOS approval presentation with gateway-switch
lease preservation and resolution fencing, watchOS inbox + approval
actions with shipped-shape payload codec, Android approval notices with
publication-tokened dismissal. Native i18n inventory regenerated.

(cherry picked from commit 428a76670ffeede54248b7bd7aa4438e2589851b)
(cherry picked from commit 80225d5707c3645eeea5435f266131037b50ede6)
(cherry picked from commit 2a23b714dc30d773a294aa45adc617e46b80732e)
(cherry picked from commit 9ff1153827769e2cbab7c11c153f0f8634662c28)
(cherry picked from commit 5b25723525bd562e5f97478af492f7b46ad30fd1)
(cherry picked from commit 8c80e8467b5fe89aad0d4c74a0573f1600ed3af9)
(cherry picked from commit ad4037bc9846bf6f082b41c129ee68aa344576c3)
(cherry picked from commit fdf767dd662cff3c8a5a6d571f38f153410651ca)
(cherry picked from commit 00c120376ffd992ea68ce29254a9bc0a25ed1740)
(cherry picked from commit f36a952)
(cherry picked from commit e2c25cbe2baacab44d21871d8cb6734704f065ac)
(cherry picked from commit 7c4fda519080486d341a9f4df36d63f9e24b1235)
(cherry picked from commit 1b3d4eda3dc5988012124597f9454ae21fb187a1)
(cherry picked from commit 2a60619)
(cherry picked from commit 6f0c386)
(cherry picked from commit 784a585)
(cherry picked from commit cbf294e)
@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation app: android App: android app: ios App: ios scripts Repository scripts size: XL maintainer Maintainer-authored PR labels Jul 12, 2026
@steipete

Copy link
Copy Markdown
Contributor Author

Land-ready proof for exact head 1ae8ea503f7a614983c22edc273f661332c3e0c6:

  • OPENCLAW_TESTBOX=1 scripts/pr prepare-run 104913 passed the repository hosted exact-head/recent-rebase gate and verified the remote PR tree matches the prepared head.
  • Blacksmith Testbox tbx_01kxa2qa51wqsm9njcjdzsk0qd (silver-prawn-aefb): :app:testPlayDebugUnitTest, :app:testThirdPartyDebugUnitTest, :app:ktlintCheck, and :benchmark:ktlintCheck passed. The formerly failing methods-epoch reconciliation regression passes in both variants after automatic canonical readback was added.
  • Blacksmith Testbox: pnpm test test/scripts/native-app-i18n.test.ts passed 7/7.
  • Xcode 26.6 built the Watch target, launched the watchOS 26.5 Series 11 simulator, and opened directly on the high-risk approval face. The visually inspected 416x496 PNG SHA-256 is 73b5ca0b7c1853e02fa5e8a9a8d5ed4cf5884eeeaae4f678a971c7af428c4cff.
  • The focused iOS run passed 217/219 tests, including all approval and Watch source-guard tests. The two failures are unrelated current-main defects in untouched Talk-status and typography-audit sources; neither failing source is changed by this PR.
  • Fresh autoreview after the final Android and Watch fixes reported no accepted or actionable findings.

Known proof gaps: the paired physical iPhone/Watch recipe was not run. Crabbox artifact publishing was attempted but this host has neither a configured artifact broker nor S3 credentials, so the screenshot was visually inspected locally and was not committed or uploaded.

@steipete
steipete merged commit ac89350 into main Jul 12, 2026
125 of 140 checks passed
@steipete
steipete deleted the codex/approval-native-clients branch July 12, 2026 02:59
@steipete

Copy link
Copy Markdown
Contributor Author

Merged via squash.

fakoli added a commit to fakoli/openclaw that referenced this pull request Jul 12, 2026
- void the now-async submitToolResult calls in the anvil-voice test
- oxfmt scripts/sync-fakoli-fork-main.mjs
- sync control-ui i18n raw-copy baseline + locale meta (upstream
  openclaw#104768 left Interrupted strings out of the baseline; path-filtered
  upstream CI never ran the check)
- drop unused WatchInboxStore.shouldShowExecApprovalReviewStatus
  (upstream openclaw#104913, flagged by the iOS periphery scan)
steipete added a commit that referenced this pull request Jul 12, 2026
- android: capture createdAtMs on the pending exec-approval write at
  registration; canonical readback after a refresh that already replaced
  the visible rows was dropping the approval instead of surfacing the
  still-pending reconciliation message (#104913 merged without this)
- android: generous CI timeout ceilings in GatewayExecApprovalRuntimeTest
- agents: hermetic model-discovery test via the plugins/provider-runtime
  boundary (lazy plugin-runtime resolution hangs vitest workers since #104770)
- cli: usage-cost settle-budget test asserts the budget bound on every call
  instead of pinning the poll count (fast hosts fit a second poll in 50ms)
- protocol coverage: allowlist session.approval for ios/android (native
  review rides exec.approval push/nudge delivery)
- docs map, native i18n inventory, plugin-sdk api baseline regenerated
steipete added a commit that referenced this pull request Jul 12, 2026
- android: capture createdAtMs on the pending exec-approval write at
  registration; canonical readback after a refresh that already replaced
  the visible rows was dropping the approval instead of surfacing the
  still-pending reconciliation message (#104913 merged without this)
- android: generous CI timeout ceilings in GatewayExecApprovalRuntimeTest
- agents: hermetic model-discovery test via the plugins/provider-runtime
  boundary (lazy plugin-runtime resolution hangs vitest workers since #104770)
- cli: usage-cost settle-budget test asserts the budget bound on every call
  instead of pinning the poll count (fast hosts fit a second poll in 50ms)
- protocol coverage: allowlist session.approval for ios/android (native
  review rides exec.approval push/nudge delivery)
- docs map, native i18n inventory, plugin-sdk api baseline regenerated
steipete added a commit that referenced this pull request Jul 12, 2026
- android: capture createdAtMs on the pending exec-approval write at
  registration; canonical readback after a refresh that already replaced
  the visible rows was dropping the approval instead of surfacing the
  still-pending reconciliation message (#104913 merged without this)
- android: generous CI timeout ceilings in GatewayExecApprovalRuntimeTest
- agents: hermetic model-discovery test via the plugins/provider-runtime
  boundary (lazy plugin-runtime resolution hangs vitest workers since #104770)
- cli: usage-cost settle-budget test asserts the budget bound on every call
  instead of pinning the poll count (fast hosts fit a second poll in 50ms)
- protocol coverage: allowlist session.approval for ios/android (native
  review rides exec.approval push/nudge delivery)
- docs map, native i18n inventory, plugin-sdk api baseline regenerated
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 12, 2026
* feat(apps): Android, iPhone, and Watch approval clients

Squash-rebased openclaw#103912 segment onto the deep-links tip on current main.
Native approval surfaces: iOS approval presentation with gateway-switch
lease preservation and resolution fencing, watchOS inbox + approval
actions with shipped-shape payload codec, Android approval notices with
publication-tokened dismissal. Native i18n inventory regenerated.

(cherry picked from commit 428a76670ffeede54248b7bd7aa4438e2589851b)
(cherry picked from commit 80225d5707c3645eeea5435f266131037b50ede6)
(cherry picked from commit 2a23b714dc30d773a294aa45adc617e46b80732e)
(cherry picked from commit 9ff1153827769e2cbab7c11c153f0f8634662c28)
(cherry picked from commit 5b25723525bd562e5f97478af492f7b46ad30fd1)
(cherry picked from commit 8c80e8467b5fe89aad0d4c74a0573f1600ed3af9)
(cherry picked from commit ad4037bc9846bf6f082b41c129ee68aa344576c3)
(cherry picked from commit fdf767dd662cff3c8a5a6d571f38f153410651ca)
(cherry picked from commit 00c120376ffd992ea68ce29254a9bc0a25ed1740)
(cherry picked from commit f36a952)
(cherry picked from commit e2c25cbe2baacab44d21871d8cb6734704f065ac)
(cherry picked from commit 7c4fda519080486d341a9f4df36d63f9e24b1235)
(cherry picked from commit 1b3d4eda3dc5988012124597f9454ae21fb187a1)
(cherry picked from commit 2a60619)
(cherry picked from commit 6f0c386)
(cherry picked from commit 784a585)
(cherry picked from commit cbf294e)

* fix(apps): harden approval reconciliation and watch states
steipete added a commit that referenced this pull request Jul 12, 2026
- android: capture createdAtMs on the pending exec-approval write at
  registration; canonical readback after a refresh that already replaced
  the visible rows was dropping the approval instead of surfacing the
  still-pending reconciliation message (#104913 merged without this)
- android: generous CI timeout ceilings in GatewayExecApprovalRuntimeTest
- agents: hermetic model-discovery test via the plugins/provider-runtime
  boundary (lazy plugin-runtime resolution hangs vitest workers since #104770)
- cli: usage-cost settle-budget test asserts the budget bound on every call
  instead of pinning the poll count (fast hosts fit a second poll in 50ms)
- protocol coverage: allowlist session.approval for ios/android (native
  review rides exec.approval push/nudge delivery)
- docs map, native i18n inventory, plugin-sdk api baseline regenerated
steipete added a commit that referenced this pull request Jul 12, 2026
- android: capture createdAtMs on the pending exec-approval write at
  registration; canonical readback after a refresh that already replaced
  the visible rows was dropping the approval instead of surfacing the
  still-pending reconciliation message (#104913 merged without this)
- android: generous CI timeout ceilings in GatewayExecApprovalRuntimeTest
- agents: hermetic model-discovery test via the plugins/provider-runtime
  boundary (lazy plugin-runtime resolution hangs vitest workers since #104770)
- cli: usage-cost settle-budget test asserts the budget bound on every call
  instead of pinning the poll count (fast hosts fit a second poll in 50ms)
- protocol coverage: allowlist session.approval for ios/android (native
  review rides exec.approval push/nudge delivery)
- docs map, native i18n inventory, plugin-sdk api baseline regenerated
steipete added a commit that referenced this pull request Jul 12, 2026
- android: capture createdAtMs on the pending exec-approval write at
  registration; canonical readback after a refresh that already replaced
  the visible rows was dropping the approval instead of surfacing the
  still-pending reconciliation message (#104913 merged without this)
- android: generous CI timeout ceilings in GatewayExecApprovalRuntimeTest
- agents: hermetic model-discovery test via the plugins/provider-runtime
  boundary (lazy plugin-runtime resolution hangs vitest workers since #104770)
- cli: usage-cost settle-budget test asserts the budget bound on every call
  instead of pinning the poll count (fast hosts fit a second poll in 50ms)
- protocol coverage: allowlist session.approval for ios/android (native
  review rides exec.approval push/nudge delivery)
- docs map, native i18n inventory, plugin-sdk api baseline regenerated
steipete added a commit that referenced this pull request Jul 12, 2026
- android: capture createdAtMs on the pending exec-approval write at
  registration; canonical readback after a refresh that already replaced
  the visible rows was dropping the approval instead of surfacing the
  still-pending reconciliation message (#104913 merged without this)
- android: generous CI timeout ceilings in GatewayExecApprovalRuntimeTest
- agents: hermetic model-discovery test via the plugins/provider-runtime
  boundary (lazy plugin-runtime resolution hangs vitest workers since #104770)
- cli: usage-cost settle-budget test asserts the budget bound on every call
  instead of pinning the poll count (fast hosts fit a second poll in 50ms)
- protocol coverage: allowlist session.approval for ios/android (native
  review rides exec.approval push/nudge delivery)
- docs map, native i18n inventory, plugin-sdk api baseline regenerated
steipete added a commit that referenced this pull request Jul 12, 2026
- android: capture createdAtMs on the pending exec-approval write at
  registration; canonical readback after a refresh that already replaced
  the visible rows was dropping the approval instead of surfacing the
  still-pending reconciliation message (#104913 merged without this)
- android: generous CI timeout ceilings in GatewayExecApprovalRuntimeTest
- agents: hermetic model-discovery test via the plugins/provider-runtime
  boundary (lazy plugin-runtime resolution hangs vitest workers since #104770)
- cli: usage-cost settle-budget test asserts the budget bound on every call
  instead of pinning the poll count (fast hosts fit a second poll in 50ms)
- protocol coverage: allowlist session.approval for ios/android (native
  review rides exec.approval push/nudge delivery)
- docs map, native i18n inventory, plugin-sdk api baseline regenerated
steipete added a commit that referenced this pull request Jul 12, 2026
… gates (#104837)

* feat(gateway): propagate approvals to ancestor sessions with replay

Squash-rebased #103921 segment onto the native-clients tip on current main.
Session-scoped approval events publish sanitized pending/terminal
transitions to opted-in session audiences, with authoritative pending
replay on stream subscribe and durable-expiry reconciliation through the
owning manager. SessionApprovalEvent/Replay wire types export from the
approvals owner module; Swift models regenerated.

(cherry picked from commit 2d1dcf9747044710111d0c730fc46ba6013a165c)
(cherry picked from commit ccf88efd56b513d07599ffcee997bf0cddaf9adc)
(cherry picked from commit 8eb33f59f00ec9ee4625259b76bd7ae60d2cd480)
(cherry picked from commit c297cbc93c8401e9c79c20f242245a194f5dd236)
(cherry picked from commit 93d68f28601ff37d863da4db009504993a22533a)
(cherry picked from commit d285ccd8b2f212bb57e814107e7709c896e96b7d)
(cherry picked from commit 5c536982231402b1b58d1683794ea9948b32d077)
(cherry picked from commit 1646e5b6dc4dd20de54e10f110a5909be18d7d50)
(cherry picked from commit 35cf1b705b247fb282f4d56ffe74bd2fb238221f)
(cherry picked from commit 4f8ef36)
(cherry picked from commit 74fbdcc99d5fff062d67760a51dba4ee9e84479b)
(cherry picked from commit 222b285502681d2be14b1819798e9ee5555f431e)
(cherry picked from commit b9e744951f676b600e7c5322bfd04b0e99797c07)
(cherry picked from commit 9e904ef)
(cherry picked from commit 678f238)
(cherry picked from commit 72842e5)
(cherry picked from commit be74c25)
(cherry picked from commit 93ba8c4)
(cherry picked from commit 8f23761)

* feat(gateway): fail-closed plugin and tool approval gates

Squash-rebased #103932 segment onto the ancestor-propagation tip on
current main. Plugin node.invoke approvals claim a one-shot allow-once
decision before handing execution authority to the policy, so observation
or retry cannot replay a consumed approval; sibling tool gates bind
approval ids to their originating reviewer identity.

(cherry picked from commit 122df0d75281f572c012b6484ed5daf085d1d577)
(cherry picked from commit f23d8ac240a8dcf2a42c4daaae962263975a0ae8)
(cherry picked from commit 8632fb6436a224dac7a9a9ef0216884530de2c24)
(cherry picked from commit d9fe2dd5c53665e5732f5f3da5ed1907a686ade8)
(cherry picked from commit bb139f2c8aa36ddad70413e1ef79ff491a6f2ecd)
(cherry picked from commit 9b3e056b68ea515c7d4baef269289b6670570480)
(cherry picked from commit b11e66b59a7af1f3b08712de06864aed64d349e4)
(cherry picked from commit a12916ee592d03dfa35e86a2aaede4476a5d4e5d)
(cherry picked from commit d699de840fc8346892b9ae244fe3c3e8c4413032)
(cherry picked from commit 9b7e5a9)
(cherry picked from commit 4b507593f1b2f10b4496984c84a2803b853e5c5e)
(cherry picked from commit 56408a186733c4eeb5aa76bd5907cde7b0cd3d5b)
(cherry picked from commit a4051d6d8353d39d6fa0b5d69c36e06c3cd3e796)
(cherry picked from commit 7682980)
(cherry picked from commit a8b493f)
(cherry picked from commit aceb990)
(cherry picked from commit a29b0e7)
(cherry picked from commit 2e3be08)
(cherry picked from commit c9ae3d7)

* fix: main-gate repairs for the durable-approvals stack

- android: capture createdAtMs on the pending exec-approval write at
  registration; canonical readback after a refresh that already replaced
  the visible rows was dropping the approval instead of surfacing the
  still-pending reconciliation message (#104913 merged without this)
- android: generous CI timeout ceilings in GatewayExecApprovalRuntimeTest
- agents: hermetic model-discovery test via the plugins/provider-runtime
  boundary (lazy plugin-runtime resolution hangs vitest workers since #104770)
- cli: usage-cost settle-budget test asserts the budget bound on every call
  instead of pinning the poll count (fast hosts fit a second poll in 50ms)
- protocol coverage: allowlist session.approval for ios/android (native
  review rides exec.approval push/nudge delivery)
- docs map, native i18n inventory, plugin-sdk api baseline regenerated
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 13, 2026
… gates (openclaw#104837)

* feat(gateway): propagate approvals to ancestor sessions with replay

Squash-rebased openclaw#103921 segment onto the native-clients tip on current main.
Session-scoped approval events publish sanitized pending/terminal
transitions to opted-in session audiences, with authoritative pending
replay on stream subscribe and durable-expiry reconciliation through the
owning manager. SessionApprovalEvent/Replay wire types export from the
approvals owner module; Swift models regenerated.

(cherry picked from commit 2d1dcf9747044710111d0c730fc46ba6013a165c)
(cherry picked from commit ccf88efd56b513d07599ffcee997bf0cddaf9adc)
(cherry picked from commit 8eb33f59f00ec9ee4625259b76bd7ae60d2cd480)
(cherry picked from commit c297cbc93c8401e9c79c20f242245a194f5dd236)
(cherry picked from commit 93d68f28601ff37d863da4db009504993a22533a)
(cherry picked from commit d285ccd8b2f212bb57e814107e7709c896e96b7d)
(cherry picked from commit 5c536982231402b1b58d1683794ea9948b32d077)
(cherry picked from commit 1646e5b6dc4dd20de54e10f110a5909be18d7d50)
(cherry picked from commit 35cf1b705b247fb282f4d56ffe74bd2fb238221f)
(cherry picked from commit 4f8ef36)
(cherry picked from commit 74fbdcc99d5fff062d67760a51dba4ee9e84479b)
(cherry picked from commit 222b285502681d2be14b1819798e9ee5555f431e)
(cherry picked from commit b9e744951f676b600e7c5322bfd04b0e99797c07)
(cherry picked from commit 9e904ef)
(cherry picked from commit 678f238)
(cherry picked from commit 72842e5)
(cherry picked from commit be74c25)
(cherry picked from commit 93ba8c4)
(cherry picked from commit 8f23761)

* feat(gateway): fail-closed plugin and tool approval gates

Squash-rebased openclaw#103932 segment onto the ancestor-propagation tip on
current main. Plugin node.invoke approvals claim a one-shot allow-once
decision before handing execution authority to the policy, so observation
or retry cannot replay a consumed approval; sibling tool gates bind
approval ids to their originating reviewer identity.

(cherry picked from commit 122df0d75281f572c012b6484ed5daf085d1d577)
(cherry picked from commit f23d8ac240a8dcf2a42c4daaae962263975a0ae8)
(cherry picked from commit 8632fb6436a224dac7a9a9ef0216884530de2c24)
(cherry picked from commit d9fe2dd5c53665e5732f5f3da5ed1907a686ade8)
(cherry picked from commit bb139f2c8aa36ddad70413e1ef79ff491a6f2ecd)
(cherry picked from commit 9b3e056b68ea515c7d4baef269289b6670570480)
(cherry picked from commit b11e66b59a7af1f3b08712de06864aed64d349e4)
(cherry picked from commit a12916ee592d03dfa35e86a2aaede4476a5d4e5d)
(cherry picked from commit d699de840fc8346892b9ae244fe3c3e8c4413032)
(cherry picked from commit 9b7e5a9)
(cherry picked from commit 4b507593f1b2f10b4496984c84a2803b853e5c5e)
(cherry picked from commit 56408a186733c4eeb5aa76bd5907cde7b0cd3d5b)
(cherry picked from commit a4051d6d8353d39d6fa0b5d69c36e06c3cd3e796)
(cherry picked from commit 7682980)
(cherry picked from commit a8b493f)
(cherry picked from commit aceb990)
(cherry picked from commit a29b0e7)
(cherry picked from commit 2e3be08)
(cherry picked from commit c9ae3d7)

* fix: main-gate repairs for the durable-approvals stack

- android: capture createdAtMs on the pending exec-approval write at
  registration; canonical readback after a refresh that already replaced
  the visible rows was dropping the approval instead of surfacing the
  still-pending reconciliation message (openclaw#104913 merged without this)
- android: generous CI timeout ceilings in GatewayExecApprovalRuntimeTest
- agents: hermetic model-discovery test via the plugins/provider-runtime
  boundary (lazy plugin-runtime resolution hangs vitest workers since openclaw#104770)
- cli: usage-cost settle-budget test asserts the budget bound on every call
  instead of pinning the poll count (fast hosts fit a second poll in 50ms)
- protocol coverage: allowlist session.approval for ios/android (native
  review rides exec.approval push/nudge delivery)
- docs map, native i18n inventory, plugin-sdk api baseline regenerated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app: android App: android app: ios App: ios docs Improvements or additions to documentation maintainer Maintainer-authored PR scripts Repository scripts size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant