Skip to content

feat: Approval Paths — pluggable manual-approval delivery (OneCLI SDK + ntfy push) - #395

Open
nbmorgan wants to merge 5 commits into
onecli:mainfrom
nbmorgan:feat/approval-paths
Open

feat: Approval Paths — pluggable manual-approval delivery (OneCLI SDK + ntfy push)#395
nbmorgan wants to merge 5 commits into
onecli:mainfrom
nbmorgan:feat/approval-paths

Conversation

@nbmorgan

Copy link
Copy Markdown
Contributor

Closes #394.

What

Adds Approval Paths — a Settings section with independently-toggleable delivery channels for manual_approval decisions:

  • OneCLI SDK long-poll (existing path, now gated by an explicit, default-on flag)
  • ntfy push with Approve/Deny action buttons → a token-guarded gateway callback

Both channels resolve the same in-memory PendingApproval store, so any combination can be enabled.

Gateway (OSS modules)

  • db reads + decrypt for the new config, notify.rs (ntfy publish + confirmation note + short-TTL resolved-decision memory), forward.rs wiring, and callback routes POST /v1/approvals/{id}/approve|deny.
  • Callbacks are idempotent + conflict-aware: same decision → 200, opposite → 410, timed-out/unknown → 408 (mobile push clients retry and users double-tap).
  • Per-channel hold timeout (max across enabled channels). Variable expansion ({agentId}, {agentName}, {method}, {host}, {path}) in ntfy Tags/Priority.
  • Configurable resolved-decision window via APPROVAL_RESOLVED_TTL_SECS.

Data model

New project-scoped ApprovalPath model. The migration is purely additive (one CREATE TABLE + FKs) — backward-compatible / rollback-safe. Multiple secret fields are stored in one encrypted blob with merge-on-save so re-entering one secret doesn't wipe the others.

Dashboard

Settings → Approval Paths with per-channel cards, a connection test (exercises publish → device → callback without an agent), a recent-events debug log, password reveal gated by ONECLI_ALLOW_SECRET_REVEAL, and a default-on Report Selection to Topic confirmation note.

Incidental fix (bundled, happy to split)

/settings is routed via next.config redirects() instead of an in-render server redirect(), which avoids a React #310 AppRouter crash on client soft-navigation and is required for the new settings page to be reachable.

Testing

  • cargo clippy -- -D warnings clean; gateway unit tests pass (incl. resolved-decision logic).
  • pnpm check (lint + types + format) green.
  • Verified end-to-end against a self-hosted deployment: test approval published to ntfy, Approve/Deny round-trip through the callback, and the idempotent/conflict (200/410/408) responses across devices.

Notes for reviewers

This came out of a self-hosted deployment, so some defaults/copy lean toward that use case — glad to adjust naming, split the next.config fix into its own PR, or scope the ntfy specifics differently. Opening this to make the discussion concrete per #394.

nbmorgan and others added 3 commits June 26, 2026 18:09
…SDK + ntfy push)

Add an Approval Paths settings section with independently-toggleable delivery
channels for manual-approval decisions:

- OneCLI SDK long-poll (existing path, now gated by an explicit, default-on flag)
- ntfy push with Approve/Deny action buttons → a token-guarded gateway callback

Both channels resolve the same in-memory PendingApproval store, so any
combination can be enabled. New project-scoped ApprovalPath model (additive
migration), per-channel hold timeout (max across enabled channels), and an
encrypted credentials blob with merge-on-save so multiple secret fields don't
stomp each other.

Gateway (OSS modules): db reads + decrypt, notify.rs (ntfy publish + status
note + resolved-decision memory), forward.rs wiring, and callback routes
POST /v1/approvals/{id}/approve|deny that are idempotent and conflict-aware
(same decision -> 200, opposite -> 410, timed-out/unknown -> 408). Variable
expansion ({agentId},{agentName},{method},{host},{path}) in Tags/Priority.
Configurable resolved-decision window (APPROVAL_RESOLVED_TTL_SECS).

Dashboard: Settings -> Approval Paths with per-channel cards, a connection
test that exercises publish -> device -> callback without an agent, a recent-
events debug log, password reveal gated by ONECLI_ALLOW_SECRET_REVEAL, and a
default-on "Report Selection to Topic" confirmation note (security signal +
iOS feedback for silent action buttons).

Also routes /settings via next.config redirects() instead of an in-render
redirect() to avoid the React onecli#310 AppRouter crash on soft-navigation, so the
new settings page is reachable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
# Conflicts:
#	apps/gateway/src/gateway/forward.rs
… ntfy channel

Align the ntfy push channel with upstream onecli#393's approval summaries + bell:

- ntfy notification now renders the gateway's structured ApprovalSummary
  (the same "Send email · To: … · Subject: …" the dashboard bell shows) as the
  title + body, falling back to the legacy body preview then a generic line. A
  footer always names the agent + target.
- The "Report Selection to Topic" confirmation note now fires on ANY resolution
  via a shared helper — not just the ntfy tap but also a dashboard/SDK decision
  (labelled "via dashboard"), so ntfy subscribers learn when someone ELSE
  approved. Carries the decision + original request name/time.
- Settings copy: the OneCLI SDK card now names the dashboard approvals bell as
  its surface.

Keeps the Approval Paths channel framework as the home for delivery channels
(bell = onecli channel, ntfy = push channel). A generalized notifier registry
is left for upstream to decide.

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

Copy link
Copy Markdown
Contributor Author

Synced this branch with the latest main, which now includes #393 ("live approval notifications, gateway summaries, and fixes").

The branch is MERGEABLE with no conflicts against current main. Verified end-to-end on a self-hosted deployment: a real Gmail send held at the gateway → ntfy approval push (with the summary) → approved on phone → forwarded (HTTP 200).

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.

Feature: Approval Paths — pluggable manual-approval delivery channels (ntfy push)

1 participant