Skip to content

feat(flows): backend — surface flow-run approvals + per-flow trust (correlation, notify, socket)#4673

Merged
graycyrus merged 3 commits into
tinyhumansai:mainfrom
graycyrus:feat/flows-approval-surface-backend
Jul 7, 2026
Merged

feat(flows): backend — surface flow-run approvals + per-flow trust (correlation, notify, socket)#4673
graycyrus merged 3 commits into
tinyhumansai:mainfrom
graycyrus:feat/flows-approval-surface-backend

Conversation

@graycyrus

Copy link
Copy Markdown
Contributor

Backend for flow-approval surfacing + per-flow trust (part 1 of the approval-surfacing set). Makes a parked flow-run approval correlatable, resolvable, and answerable — no more silent deadlock — while KEEPING the safety gate.

Problem

A flow run parks a tool call (flow require_approval OR the Supervised-tier network/code escalation) with no thread_id/client_id, so nothing surfaces → the run hangs invisibly and TTL-denies.

What this adds

  • CorrelationPendingApproval.source_context {kind:"flow", flow_id, run_id, node_id?} + a FlowRunContext task-local set around the engine run; new source_context column + list_pending_for_flow_run.
  • Per-flow trust — a flow_tool_trust (flow_id, tool_name) table + a new ApproveAlwaysForFlow decision. The gate checks trust before parking a Workflow-origin call → auto-Allow if trusted, so once you "approve always" a workflow's action, future (incl. scheduled) runs don't re-park. approval_decide branches on source_context to insert_flow_trust for that decision.
  • Surface bridge — on a flow-origin park, emit a flow-gate-approval CoreNotification + a flow_approval_request socket event (neither needs a thread/client), so the frontend can surface + resolve it.
  • Chat / other-origin approval unchanged.

Contract (the frontend PR builds to this)

approval_decide decision ∈ approve_once | approve_always_for_flow | deny; pending-approval JSON carries source_context; socket flow_approval_request {request_id, flow_id, run_id, tool_name, summary}; notification kind flow-gate-approval.

Verification

cargo check/fmt clean · approval:: tests pass. Pairs with the frontend surfacing PR; the revert-defaults PR (#4672) merges after both.

…orrelation, notify, socket)

A flow run parks a tool call (flow require_approval OR the Supervised-tier network/code
escalation) with no thread_id/client_id, so nothing surfaces → silent deadlock. This makes
the pending approval correlatable + resolvable, adds per-flow "approve always" trust, and
bridges the park to a notification + a socket event the frontend can act on.

- approval/types.rs + store.rs: PendingApproval gains source_context {kind:"flow",flow_id,
  run_id,node_id?}; new source_context column; list_pending_for_flow_run.
- flows/ops.rs: FlowRunContext task-local (flow_id,run_id) around the engine run; gate reads
  it to populate source_context.
- approval/gate.rs: new ApproveAlwaysForFlow decision; a flow_tool_trust (flow_id,tool_name)
  check before parking a Workflow-origin call → auto-Allow if trusted.
- approval/store.rs: flow_tool_trust table + insert_flow_trust/is_flow_tool_trusted;
  approval_decide branches on source_context → insert_flow_trust for ApproveAlwaysForFlow.
- gate.rs + socketio.rs + events.rs + channels/host/adapters.rs: on a flow-origin park, emit
  a `flow-gate-approval` CoreNotification + a `flow_approval_request` socket event (no
  thread/client needed). Chat/other-origin approval unchanged.

Contract for the frontend: approval_decide accepts "approve_once"|"approve_always_for_flow"|
"deny"; pending-approval JSON carries source_context; socket `flow_approval_request`
{request_id,flow_id,run_id,tool_name,summary}; notification kind `flow-gate-approval`.

cargo check/fmt clean; approval:: tests pass.
@graycyrus graycyrus requested a review from a team July 7, 2026 20:55
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 44 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 06fbcbf7-5fce-4d22-9457-aad149c46b74

📥 Commits

Reviewing files that changed from the base of the PR and between fac8b5f and 31fed2b.

📒 Files selected for processing (10)
  • src/core/event_bus/events.rs
  • src/core/socketio.rs
  • src/openhuman/approval/gate.rs
  • src/openhuman/approval/mod.rs
  • src/openhuman/approval/rpc.rs
  • src/openhuman/approval/schemas.rs
  • src/openhuman/approval/store.rs
  • src/openhuman/approval/types.rs
  • src/openhuman/channels/host/adapters.rs
  • src/openhuman/flows/ops.rs

Comment @coderabbitai help to get the list of available commands.

…ross-test flakiness

find_flow_approval_requested drained the process-wide event bus for the
first FlowApprovalRequested event, but other approval tests running
concurrently (cargo test runs in parallel by default) also publish
FlowApprovalRequested events with different flow_ids on the same
singleton bus. Filter by expected_flow_id so the test only matches its
own event instead of racing with unrelated ones.

@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: fb43f2f604

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

};

publish_core_notification(CoreNotificationEvent {
id: format!("{KIND}:{request_id}"),

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 Route flow gate actions to approval_decide

When this notification is emitted for a workflow-origin tool approval, its id starts with flow-gate-approval:, but the current notification UI only special-cases flow-pending-approval: in app/src/components/notifications/NotificationCenter.tsx; all other actionable core notifications are rendered by CoreNotificationCard, which sends clicks to openhuman.agent_meetings_notification_action. In that environment, clicking Approve/Deny/Always for this new notification calls the meeting-action RPC instead of approval_decide, so the flow-run tool call stays parked until it times out. This needs a matching frontend route/card or an existing supported notification contract.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Valid and correctly scoped to flow-gate-approval: (backend contract), but out of scope for this PR by design — this is explicitly part 1 of the approval-surfacing set (see PR description: "Pairs with the frontend surfacing PR"). The paired frontend PR is where NotificationCenter.tsx gains a flow-gate-approval: route (or CoreNotificationCard gains a generic action-routing path keyed off notification kind instead of hardcoding agent_meetings_notification_action). Tracking this here so it isn't lost: the frontend PR must route flow-gate-approval clicks to approval_decide, not the meeting-action RPC. Not fixing in this backend-only PR.

/// Only meaningful when `source_context` is `Some(Flow { .. })` — the
/// `approval_decide` RPC handler logs and no-ops the persistence step
/// otherwise (the decision itself still resolves the parked call).
ApproveAlwaysForFlow,

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 Teach approval history about flow approvals

Adding this new persisted decision value means approval_list_recent_decisions can now return "approve_always_for_flow", but the frontend approval API type and ApprovalHistoryPanel mappings still only cover approve_once, approve_always_for_tool, and deny. After a user chooses “Always allow for this workflow”, the Settings approval history will index those maps with an unknown decision and render an empty/invalid badge instead of a meaningful label, so the frontend decision union, badge mapping, and translations need to be extended with this variant.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Same scoping note as the sibling comment on gate.rs: this is correct and will matter, but the frontend decision union/badge mapping/i18n for approve_always_for_flow belongs in the paired frontend surfacing PR (this PR is backend-only — see PR description). approval_list_recent_decisions returning approve_always_for_flow is intentional groundwork for that PR to consume. Flagging so the frontend PR's checklist explicitly covers extending the decision union in approvalApi.ts/ApprovalHistoryPanel.tsx and adding translations. Not fixing here.

Picks up unrelated upstream fixes (meet mascots, CEF Windows GPU,
startup recovery, flows HITL, retrieve_memory fail-fast). No conflicts:
only src/core/event_bus/events.rs changed upstream since branch point.
@graycyrus graycyrus merged commit 771eeff into tinyhumansai:main Jul 7, 2026
15 checks passed
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.

1 participant