Skip to content

feat(flows): consolidated save+enable pre-authorization card (Approve all / Deny) - #5248

Merged
graycyrus merged 13 commits into
tinyhumansai:mainfrom
graycyrus:feat/flow-preauthorization-card
Jul 29, 2026
Merged

graycyrus merged 13 commits into
tinyhumansai:mainfrom
graycyrus:feat/flow-preauthorization-card

Conversation

@graycyrus

@graycyrus graycyrus commented Jul 28, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • One consolidated Approve all / Deny card at flow save+enable time, listing every ApprovalGate permission a run will need — replaces the first run parking node-by-node on per-tool prompts (each with a 10-min fail-closed TTL).
  • Dry runs stay quiet: dry_run_workflow output gains an informational permissions_manifest key; the card only fires at the save+enable commitment moment.
  • Approve all batch-grants flow-scoped trust via new idempotent approval.preauthorize_flow RPC; every new grant is a born-decided approve_always_for_flow row in the durable approval audit trail (Settings → Approval history).
  • Deny leaves the flow saved but disabled. Re-save after edits asks only for newly-needed grants (flows.approval_manifest joins against existing flow_tool_trust).
  • Safety boundaries unchanged: tier-Blocked classes are informational rows (no approve affordance), the Composio curation gate is untouched, curated Reads are excluded, dynamic = slugs and agent_ref steps are disclosed as "may still ask".

Problem

The first run of any new flow with side-effect nodes feels broken: the run parks mid-execution once per external-effect node, interrupts the user repeatedly, and silently dies (10-min TTL → cancelled) if nobody is watching. The flow-scoped trust primitive (approve_always_for_flow → flow_tool_trust) already existed but was only reachable reactively, one parked card at a time.

Solution

  • flows (gated): compute_approval_manifest(graph) statically enumerates the trust keys a run needs by reusing the exact runtime classifiers (classify_tool_call, classify_composio_action_for_tier — made pub(crate); never a parallel re-implementation, so the card can't drift from what actually gates). New flows.approval_manifest { id | graph } RPC returns { entries, missing, already_trusted, gate_installed }. flows_delete purges the flow's grants.
  • approval (always-on): list_flow_trust / delete_flow_trust / record_flow_preauthorization store fns + approval.preauthorize_flow RPC (succeeds with gate_installed: false when the gate is disabled — nothing parks in that mode, so the UX stays identical).
  • Frontend: useFlowPreauthorization hook orchestrates the three enable moments (Flows list toggle, canvas Save with deferred draft navigation, chat proposal "Save & enable"); FlowPreauthorizationCard + overlay variant; fail-open on manifest errors (falls back to plain enable — the runtime gate still parks per-node exactly as before).
  • Explicit HITL node gates (requires_approval → tinyflows interrupt → flows_resume) are deliberately untouched — pre-authorization clears only implicit tool prompts, never author-requested checkpoints.

Submission Checklist

  • Tests added or updated (happy path + at least one failure / edge case) per Testing Strategy
  • Diff coverage ≥ 80% — changed lines (Vitest + cargo-llvm-cov merged via diff-cover) meet the gate enforced by .github/workflows/ci-lite.yml. Run pnpm test:coverage and pnpm test:rust locally; PRs below 80% on changed lines will not merge.
  • Coverage matrix updated — added/removed/renamed feature rows in docs/TEST-COVERAGE-MATRIX.md reflect this change (or N/A: behaviour-only change)
  • All affected feature IDs from the matrix are listed in the PR description under ## Related
  • No new external network dependencies introduced (mock backend used per Testing Strategy)
  • Manual smoke checklist updated if this touches release-cut surfaces — N/A: no release-cut surface touched (no packaging, install, deep-link, or update path changes)
  • Linked issue closed via Closes #NNN in the ## Related section — N/A: no pre-existing issue; feature designed and implemented in-session

Impact

  • Desktop (all platforms): first-run UX for flows with side-effect nodes goes from N mid-run interruptions (or silent TTL cancellation) to one upfront card.
  • Security: Approve-all is flow-scoped (flow_tool_trust), auditable (born-decided audit rows), and cannot clear tier-Blocked classes or the curation allowlist. OPENHUMAN_APPROVAL_GATE=0 behaviour unchanged.
  • No new dependencies; --no-default-features --features tokenjuice-treesitter build checked clean (feature-gate drift catcher).

Related

  • Matrix feature IDs: 9.5.1, 9.5.2, 9.5.3
  • Closes: N/A (in-session feature request)
  • Follow-up PR(s)/TODOs: trust-shrink revocation when a re-saved graph drops tools; surface per-flow granted permissions in flow settings for manual revocation.

AI Authored PR Metadata (required for Codex/Linear PRs)

Linear Issue

  • Key: N/A
  • URL: N/A

Commit & Branch

  • Branch: feat/flow-preauthorization-card
  • Commit SHA: b90515b

Validation Run

  • pnpm --filter openhuman-app format:check (prettier run on all touched files)
  • pnpm typecheck
  • Focused tests: vitest related over all touched frontend files (46 files, 614 tests passed); cargo test --lib for approval store/gate/schemas + flows ops/schemas suites (243 tests passed)
  • Rust fmt/check (if changed): cargo fmt + cargo check (default and --no-default-features --features tokenjuice-treesitter)
  • Tauri fmt/check (if changed): N/A — no app/src-tauri changes

Validation Blocked

  • command: N/A
  • error: N/A
  • impact: N/A

Behavior Changes

  • Intended behavior change: enabling a flow with missing permission grants now shows one consolidated pre-authorization card instead of letting the first run park per-node.
  • User-visible effect: Approve all → first run executes without interruptions; Deny → flow saved but disabled.

Parity Contract

  • Legacy behavior preserved: with the card denied/never shown, runtime per-node parking behaves exactly as before; OPENHUMAN_APPROVAL_GATE=0 and readonly-tier blocking unchanged.
  • Guard/fallback/dispatch parity checks: manifest reuses runtime classifiers; fail-open manifest fetch falls back to plain enable; disabled-feature build verified.

Duplicate / Superseded PR Handling

  • Duplicate PR(s): none found on upstream/main (checked before building)
  • Canonical PR: this PR
  • Resolution (closed/superseded/updated): N/A

Summary by CodeRabbit

  • New Features
    • Added a save+enable pre-authorization prompt for workflows needing extra permissions, with Approve all and Deny.
    • Enabled the same approval-gated flow for Save & enable and Accept & enable, deferring completion via an overlay when needed.
    • Updated outcomes so Deny saves the workflow but keeps it disabled, with correct busy/error handling.
  • Localization
    • Added new workflow enable-approval UI translations (including hints and the “saved but disabled” message).
  • Tests
    • Expanded test coverage for the approval card/overlay and save/enable decision paths, including manifest and pre-authorization hook behavior.

… all / Deny)

When a flow is saved and enabled, show ONE card listing every ApprovalGate
permission a run will need, with exactly two actions — Approve all / Deny —
instead of parking the first run node-by-node on per-tool approval prompts.

Core (approval domain, always-on):
- flow_tool_trust gains list_flow_trust / delete_flow_trust store fns +
  gate wrappers; flows_delete now purges a deleted flow's grants.
- New RPC approval.preauthorize_flow { flow_id, tool_names } batch-grants
  flow-scoped trust (idempotent; INSERT OR IGNORE) and writes one
  born-decided approve_always_for_flow audit row per new grant so blanket
  approvals stay inspectable in Settings -> Approval history. A missing
  gate (OPENHUMAN_APPROVAL_GATE=0) reports gate_installed=false and
  succeeds — nothing ever parks in that mode.

Core (flows domain, gated):
- compute_approval_manifest(graph) statically mirrors the runtime gating
  (caps.rs classifiers reused, never re-implemented): http_request ->
  flows_http_request, code -> flows_code, native oh: tools via
  classify_tool_call, Composio slugs via classify_composio_action_for_tier.
  Curated Read actions are excluded (never gated); tier-Blocked classes are
  "blocked" (informational); `=`-expression slugs are "dynamic"; agent_ref
  agent nodes are disclosed ("may still ask").
- New RPC flows.approval_manifest { id | graph } joins the manifest against
  existing trust -> { entries, missing, already_trusted, gate_installed }.
- dry_run_workflow output gains a quiet permissions_manifest key (both ok
  and failure shapes) so the builder can mention upcoming permissions
  without ever prompting during authoring dry runs.

Frontend:
- useFlowPreauthorization hook orchestrates the three enable moments:
  Flows list toggle, canvas Save (incl. deferred draft navigation so the
  card survives the /flows/:id route change), and the chat proposal card's
  "Save & enable". Approve all = preauthorize then enable; Deny = flow
  stays saved but disabled. Fail-open: a broken manifest RPC falls back to
  plain enable (runtime gate still parks per-node as before).
- FlowPreauthorizationCard renders approvable/blocked/dynamic/agent rows
  with exactly two actions; overlay variant for page contexts.
- i18n: flows.enableApproval.* added to en + all 13 locales.

Tests: approval store round-trips (list/delete/preauthorization audit),
manifest classification incl. curated-read exclusion + readonly-tier
blocking + dedupe + dynamic/agent disclosure, flows/approval schema
registries, hook behavior (grant-then-enable ordering, deny leaves
disabled, fail-open), and card rendering. Disabled-feature build
(--no-default-features --features tokenjuice-treesitter) checked clean.
@coderabbitai

coderabbitai Bot commented Jul 28, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9e7ad472-57f1-44dc-a129-3adf369bd622

📥 Commits

Reviewing files that changed from the base of the PR and between 1cf072d and df3dd92.

📒 Files selected for processing (21)
  • app/src/components/chat/WorkflowProposalCard.test.tsx
  • app/src/components/chat/WorkflowProposalCard.tsx
  • app/src/components/flows/FlowPreauthorizationCard.test.tsx
  • app/src/components/flows/FlowPreauthorizationCard.tsx
  • app/src/hooks/useFlowPreauthorization.test.ts
  • app/src/hooks/useFlowPreauthorization.ts
  • app/src/lib/i18n/ar.ts
  • app/src/lib/i18n/bn.ts
  • app/src/lib/i18n/de.ts
  • app/src/lib/i18n/en.ts
  • app/src/lib/i18n/es.ts
  • app/src/lib/i18n/fr.ts
  • app/src/lib/i18n/hi.ts
  • app/src/lib/i18n/id.ts
  • app/src/lib/i18n/it.ts
  • app/src/lib/i18n/ko.ts
  • app/src/lib/i18n/pl.ts
  • app/src/lib/i18n/pt.ts
  • app/src/lib/i18n/ru.ts
  • app/src/lib/i18n/zh-CN.ts
  • tests/raw_coverage/tool_registry_approval_raw_coverage_e2e.rs

📝 Walkthrough

Walkthrough

Adds workflow approval-manifest computation, flow-scoped pre-authorization APIs and persistence, a reusable approval card, and integration across workflow saving, enabling, canvas navigation, and proposal handling.

Changes

Flow approval manifest and backend APIs

Layer / File(s) Summary
Manifest computation and RPC surface
src/openhuman/flows/*, src/openhuman/tinyflows/caps.rs, app/src/services/api/flowsApi.ts
Workflow graphs now produce deduplicated approval manifests, exposed through a new RPC and included in dry-run output.
Flow trust persistence and pre-authorization
src/openhuman/approval/*, app/src/services/api/approvalApi.ts
Flow-scoped trust listing, revocation, audit records, and batch pre-authorization are implemented with validation, normalization, and tests.

Approval UI and workflow integration

Layer / File(s) Summary
Pre-authorization hook and UI
app/src/hooks/*, app/src/components/flows/*, app/src/lib/i18n/*
A hook coordinates manifest checks, approval, denial, busy/error states, and localized card or overlay rendering.
Enablement and save flows
app/src/pages/FlowsPage.tsx, app/src/pages/FlowCanvasPage.tsx, app/src/components/chat/WorkflowProposalCard.tsx
Flow toggles, canvas saves, copilot acceptance, and workflow proposals defer completion or navigation until approval decisions settle.
Shared test environment isolation
src/api/config.rs, src/core/cli_tests.rs, src/openhuman/medulla/*, tests/raw_coverage/*
Backend environment mutations use a crate-wide test lock, and environment-dependent test expectations are updated.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant FlowCanvasPage
  participant useFlowPreauthorization
  participant ApprovalManifestRPC
  participant PreauthorizeRPC
  User->>FlowCanvasPage: Save or enable workflow
  FlowCanvasPage->>useFlowPreauthorization: beginEnable or checkAfterSave
  useFlowPreauthorization->>ApprovalManifestRPC: request manifest
  ApprovalManifestRPC-->>useFlowPreauthorization: return missing grants
  useFlowPreauthorization-->>FlowCanvasPage: show approval overlay
  User->>FlowCanvasPage: Approve all or deny
  FlowCanvasPage->>useFlowPreauthorization: approveAll or deny
  useFlowPreauthorization->>PreauthorizeRPC: grant missing tools
  PreauthorizeRPC-->>useFlowPreauthorization: return approval result
  useFlowPreauthorization-->>FlowCanvasPage: settle outcome and continue navigation or enablement
Loading

Possibly related PRs

Suggested labels: feature, bug

Suggested reviewers: oxoxdev

Poem

I’m a rabbit with grants in my hat,
Approving each tool with a tap-tap-tap.
Save it, enable it, keep worries away,
Or deny and leave it safely astray.
The workflow now hops through approval all day!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Accurately summarizes the main change: a consolidated flow pre-authorization card with Approve all/Deny actions.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

@graycyrus

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 28, 2026 •

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Cover the new API clients (getApprovalManifest / preauthorizeFlow), the
hook's checkAfterSave and deny failure paths, the page overlay wrapper,
and the WorkflowProposalCard inline card branches (card shown on missing
grants, Approve all -> terminal saved view without a redundant enable,
Deny -> saved-but-disabled note). Diff coverage was 68%, gate needs 80%.
@graycyrus

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 28, 2026 •

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot added feature Net-new user-facing capability or product behavior. rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. labels Jul 28, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/src/components/chat/WorkflowProposalCard.tsx (1)

244-260: 🗄️ Data Integrity & Integration | 🔴 Critical | 🏗️ Heavy lift

Duplicate flow creation: Save stays clickable while the pre-auth card is pending.

When createFlow returns an already-enabled flow and checkAfterSave shows the card, save() returns without ever calling setSavedFlowId(flow.id), and setSaving(false) (Line 253) re-enables the Save button while the decision is still pending. A repeat click on Save re-enters save() with flowId = savedFlowId = null and calls createFlow again, persisting a second flow server-side.

Fix both the missing state and the UI guard:

🛡️ Proposed fix
         if (flow.enabled) {
           // Already live — surface the pre-authorization card when grants
           // are missing; otherwise this is the terminal success state.
           log('save: createFlow returned enabled id=%s — running pre-auth check', flow.id);
+          // Persist the id now: the Save button stays clickable while the
+          // card is pending below, and without this a retry click would
+          // call `createFlow` again and create a duplicate flow.
+          setSavedFlowId(flow.id);
           const cardShown = await preauth.checkAfterSave(flow.id, true);

Also gate the Save button itself so a repeat click can't re-enter save() at all while a decision is pending:

<Button
  variant="primary"
  size="sm"
  data-analytics-id="workflow-proposal-save"
  onClick={() => void save()}
  disabled={saving || !!preauth.pending}>

A regression test for "click Save again while the preauth card is showing → createFlow is called only once" would be worth adding alongside this fix.

Also applies to: 376-383

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/chat/WorkflowProposalCard.tsx` around lines 244 - 260,
Prevent duplicate flow creation while pre-authorization is pending: in save(),
persist the created flow ID with setSavedFlowId(flow.id) before handling the
enabled-flow preauth path, and keep the saving state disabled while
preauth.checkAfterSave displays a decision card. Update the Save Button’s
disabled condition to include preauth.pending alongside saving, ensuring repeat
clicks cannot re-enter save() during the pending decision.
🧹 Nitpick comments (3)
src/openhuman/flows/ops.rs (1)

7191-7264: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add RPC-level tests for flows_approval_manifest

src/openhuman/flows/ops_tests.rs covers compute_approval_manifest, but not the wrapper’s flow_tool_trust join or the gate_installed = false short-circuit. Add cases for a saved flow with partial trust (missing + already_trusted) and for the no-gate path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/flows/ops.rs` around lines 7191 - 7264, Add RPC-level tests for
flows_approval_manifest covering a saved flow with partial flow_tool_trust,
asserting trusted tools appear in already_trusted and untrusted tools in
missing, plus a no-gate case asserting gate_installed is false and missing is
empty. Reuse the existing test setup and fixtures in ops_tests.rs, exercising
the wrapper rather than only compute_approval_manifest.

Source: Coding guidelines

app/src/components/flows/FlowPreauthorizationCard.tsx (1)

119-133: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Consider modal semantics for the overlay's alertdialog.

FlowPreauthorizationOverlay renders a full-screen modal but the inner role="alertdialog" has no aria-modal="true" and nothing moves focus into it on mount, so screen-reader users may not get correct modal semantics or keyboard focus. Note FlowPreauthorizationCard is also reused inline (non-modal) in chat per its own doc comment, so aria-modal shouldn't be hardcoded on the card itself — it belongs on/near the overlay wrapper (or behind a modal prop passed to the card).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/flows/FlowPreauthorizationCard.tsx` around lines 119 -
133, The FlowPreauthorizationOverlay needs complete modal accessibility
semantics while FlowPreauthorizationCard remains reusable inline. Update the
overlay/card integration to mark only the overlay instance as modal with
aria-modal="true" and move keyboard focus into the alertdialog when it mounts,
using an appropriate focusable target or ref; do not hardcode aria-modal on
FlowPreauthorizationCard itself.
app/src/components/chat/WorkflowProposalCard.tsx (1)

409-409: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Hardcoded translation key ignores preauth.errorKey's actual value.

errorMsg={preauth.errorKey ? t('flows.enableApproval.error') : null} discards whatever key the hook returns and always renders the same literal key. Works today only because the hook currently ever sets one error key.

♻️ Proposed fix
-              errorMsg={preauth.errorKey ? t('flows.enableApproval.error') : null}
+              errorMsg={preauth.errorKey ? t(preauth.errorKey) : null}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/chat/WorkflowProposalCard.tsx` at line 409, Update the
errorMsg expression in WorkflowProposalCard to translate the actual
preauth.errorKey value when present, rather than always using the hardcoded
flows.enableApproval.error key; retain null when no error key is returned.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/components/chat/WorkflowProposalCard.tsx`:
- Around line 216-230: Add grep-friendly diagnostics in WorkflowProposalCard
around the approved onSettled transition and the preauth.beginEnable external
call: log entry/arguments and completion or error outcomes for beginEnable, and
log the approved path before markSourceMessageConsumed/markCompleted. Preserve
the existing no-card and denied behavior and avoid changing flow semantics.

In `@app/src/components/flows/FlowPreauthorizationCard.tsx`:
- Around line 66-91: Update the row key in the entries.map callback within
FlowPreauthorizationCard to use entry.node_id alone, since it is always present
and uniquely identifies each node. Remove the tool_name/kind-based key
construction while preserving the existing rendering and test identifiers.

In `@app/src/lib/i18n/de.ts`:
- Line 4462: Update the translation value for the
“flows.enableApproval.approveAll” key to use “genehmigen,” matching the
terminology used by the related approval strings while preserving the existing
meaning.

In `@app/src/lib/i18n/fr.ts`:
- Around line 4447-4450: Update the French translations for
flows.enableApproval.dynamicHint and flows.enableApproval.agentHint so both
explicitly state that approval may be requested, replacing the ambiguous “il
vous demandera” phrasing and completing the grammatical construction around
“peut demander séparément.”

In `@app/src/lib/i18n/hi.ts`:
- Line 4337: Update the Hindi translation for the
`flows.enableApproval.approveAll` key from `सभी स्वीकृत करें` to the more
natural `सभी को अनुमति दें`, preserving the existing key and translation
structure.

In `@app/src/pages/FlowCanvasPage.tsx`:
- Around line 613-628: The new deferred-navigation flow lacks grep-friendly
diagnostics. Add verbose logs in the preauth onSettled callback around the
parked-navigation match and navigation transition, and in the
wasDraft/preauthCardShown branch around the deferred versus immediate navigation
decision; include the flow ID and selected path, while preserving the existing
navigation behavior.
- Around line 640-655: Update onCanvasSave to catch failures from
preauth.checkAfterSave in both branches. In the result.wasDraft path, ensure
navigation to /flows/${result.flowId} still occurs when the preauthorization
check throws, while preserving the existing cardShown behavior; in the non-draft
path, attach the established logging catch pattern to the fire-and-forget
promise to prevent unhandled rejections.

In `@app/src/pages/FlowsPage.tsx`:
- Around line 141-161: Add a verbose, grep-friendly diagnostic in handleToggle
immediately after preauth.beginEnable returns, recording whether enabling
occurred immediately or was deferred to the pre-authorization card; preserve the
existing listFlows refresh only for the immediate-enable outcome.

In `@src/openhuman/flows/ops.rs`:
- Around line 7117-7147: In the native-tool handling branch, update the result
of classify_tool_call before push_gated to skip entries whose class is
CommandClass::Read. Add a continue for read-only tools, matching the existing
Composio branch behavior, while preserving gating for all other command classes.

In `@src/openhuman/flows/schemas.rs`:
- Around line 1046-1076: Update the "approval_manifest" ControllerSchema output
definition to declare the four flat response fields returned by
ops::flows_approval_manifest: entries, missing, already_trusted, and
gate_installed. Remove the manifest wrapper and align the field types, required
flags, and descriptions with the existing flat "validate" schema pattern.

---

Outside diff comments:
In `@app/src/components/chat/WorkflowProposalCard.tsx`:
- Around line 244-260: Prevent duplicate flow creation while pre-authorization
is pending: in save(), persist the created flow ID with setSavedFlowId(flow.id)
before handling the enabled-flow preauth path, and keep the saving state
disabled while preauth.checkAfterSave displays a decision card. Update the Save
Button’s disabled condition to include preauth.pending alongside saving,
ensuring repeat clicks cannot re-enter save() during the pending decision.

---

Nitpick comments:
In `@app/src/components/chat/WorkflowProposalCard.tsx`:
- Line 409: Update the errorMsg expression in WorkflowProposalCard to translate
the actual preauth.errorKey value when present, rather than always using the
hardcoded flows.enableApproval.error key; retain null when no error key is
returned.

In `@app/src/components/flows/FlowPreauthorizationCard.tsx`:
- Around line 119-133: The FlowPreauthorizationOverlay needs complete modal
accessibility semantics while FlowPreauthorizationCard remains reusable inline.
Update the overlay/card integration to mark only the overlay instance as modal
with aria-modal="true" and move keyboard focus into the alertdialog when it
mounts, using an appropriate focusable target or ref; do not hardcode aria-modal
on FlowPreauthorizationCard itself.

In `@src/openhuman/flows/ops.rs`:
- Around line 7191-7264: Add RPC-level tests for flows_approval_manifest
covering a saved flow with partial flow_tool_trust, asserting trusted tools
appear in already_trusted and untrusted tools in missing, plus a no-gate case
asserting gate_installed is false and missing is empty. Reuse the existing test
setup and fixtures in ops_tests.rs, exercising the wrapper rather than only
compute_approval_manifest.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9fdee637-cfc1-4b79-9953-dc48e2aa1c98

📥 Commits

Reviewing files that changed from the base of the PR and between 4ce2a8c and 893f7d9.

📒 Files selected for processing (37)
  • app/src/components/chat/WorkflowProposalCard.test.tsx
  • app/src/components/chat/WorkflowProposalCard.tsx
  • app/src/components/flows/FlowPreauthorizationCard.test.tsx
  • app/src/components/flows/FlowPreauthorizationCard.tsx
  • app/src/hooks/useFlowPreauthorization.test.ts
  • app/src/hooks/useFlowPreauthorization.ts
  • app/src/lib/i18n/ar.ts
  • app/src/lib/i18n/bn.ts
  • app/src/lib/i18n/de.ts
  • app/src/lib/i18n/en.ts
  • app/src/lib/i18n/es.ts
  • app/src/lib/i18n/fr.ts
  • app/src/lib/i18n/hi.ts
  • app/src/lib/i18n/id.ts
  • app/src/lib/i18n/it.ts
  • app/src/lib/i18n/ko.ts
  • app/src/lib/i18n/pl.ts
  • app/src/lib/i18n/pt.ts
  • app/src/lib/i18n/ru.ts
  • app/src/lib/i18n/zh-CN.ts
  • app/src/pages/FlowCanvasPage.tsx
  • app/src/pages/FlowsPage.tsx
  • app/src/services/api/approvalApi.test.ts
  • app/src/services/api/approvalApi.ts
  • app/src/services/api/flowsApi.test.ts
  • app/src/services/api/flowsApi.ts
  • docs/TEST-COVERAGE-MATRIX.md
  • src/openhuman/approval/gate.rs
  • src/openhuman/approval/rpc.rs
  • src/openhuman/approval/schemas.rs
  • src/openhuman/approval/store.rs
  • src/openhuman/approval/types.rs
  • src/openhuman/flows/builder_tools.rs
  • src/openhuman/flows/ops.rs
  • src/openhuman/flows/ops_tests.rs
  • src/openhuman/flows/schemas.rs
  • src/openhuman/tinyflows/caps.rs

Comment thread app/src/components/chat/WorkflowProposalCard.tsx
Comment thread app/src/components/flows/FlowPreauthorizationCard.tsx
Comment thread app/src/lib/i18n/de.ts Outdated
Comment thread app/src/lib/i18n/fr.ts
Comment thread app/src/lib/i18n/hi.ts Outdated
Comment thread app/src/pages/FlowCanvasPage.tsx
Comment thread app/src/pages/FlowCanvasPage.tsx
Comment thread app/src/pages/FlowsPage.tsx
Comment thread src/openhuman/flows/ops.rs
Comment thread src/openhuman/flows/schemas.rs
- Guard the draft-save preauth check so a failed manifest fetch can never
  strand the route on /flows/draft (a Save retry would createFlow again and
  duplicate the flow); catch the fire-and-forget check on the non-draft path.
- Declare flows.approval_manifest's real flat output shape (entries/missing/
  already_trusted/gate_installed) instead of a phantom 'manifest' wrapper.
- Key manifest rows by node_id (tool_name is a trust key, not a row id).
- Add grep-friendly logs on the approved settle, beginEnable boundaries, and
  the deferred-vs-immediate navigation branches.
- Locale polish: de 'Alle genehmigen', fr approval-hint wording, hi
  'सभी को अनुमति दें'.
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 28, 2026
@graycyrus
graycyrus marked this pull request as ready for review July 29, 2026 09:51
@graycyrus
graycyrus requested a review from a team July 29, 2026 09:51

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/src/lib/i18n/zh-CN.ts (1)

4108-4108: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use the established “召回” terminology.

memory.recall is already translated as “召回” elsewhere in this locale. Using “回忆” here makes the memory retrieval operation sound like personal recollection rather than a technical recall action.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/lib/i18n/zh-CN.ts` at line 4108, Update the translation value for
flows.nodeConfig.memory.operation_recall in the zh-CN locale to use the
established “召回” terminology, matching the existing memory.recall translation
elsewhere in the locale.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@app/src/lib/i18n/zh-CN.ts`:
- Line 4108: Update the translation value for
flows.nodeConfig.memory.operation_recall in the zh-CN locale to use the
established “召回” terminology, matching the existing memory.recall translation
elsewhere in the locale.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 35e3e8ff-9262-44ae-84b1-662fde510b02

📥 Commits

Reviewing files that changed from the base of the PR and between 893f7d9 and b23a5ee.

📒 Files selected for processing (22)
  • app/src/components/chat/WorkflowProposalCard.tsx
  • app/src/components/flows/FlowPreauthorizationCard.tsx
  • app/src/lib/i18n/ar.ts
  • app/src/lib/i18n/bn.ts
  • app/src/lib/i18n/de.ts
  • app/src/lib/i18n/en.ts
  • app/src/lib/i18n/es.ts
  • app/src/lib/i18n/fr.ts
  • app/src/lib/i18n/hi.ts
  • app/src/lib/i18n/id.ts
  • app/src/lib/i18n/it.ts
  • app/src/lib/i18n/ko.ts
  • app/src/lib/i18n/pl.ts
  • app/src/lib/i18n/pt.ts
  • app/src/lib/i18n/ru.ts
  • app/src/lib/i18n/zh-CN.ts
  • app/src/pages/FlowCanvasPage.tsx
  • app/src/pages/FlowsPage.tsx
  • docs/TEST-COVERAGE-MATRIX.md
  • src/openhuman/flows/builder_tools.rs
  • src/openhuman/flows/schemas.rs
  • src/openhuman/tinyflows/caps.rs
🚧 Files skipped from review as they are similar to previous changes (21)
  • app/src/lib/i18n/de.ts
  • app/src/lib/i18n/ru.ts
  • app/src/lib/i18n/en.ts
  • app/src/lib/i18n/it.ts
  • app/src/lib/i18n/bn.ts
  • app/src/lib/i18n/id.ts
  • app/src/lib/i18n/pt.ts
  • app/src/lib/i18n/ko.ts
  • app/src/lib/i18n/hi.ts
  • app/src/components/flows/FlowPreauthorizationCard.tsx
  • app/src/lib/i18n/fr.ts
  • app/src/lib/i18n/es.ts
  • app/src/pages/FlowsPage.tsx
  • app/src/lib/i18n/ar.ts
  • app/src/lib/i18n/pl.ts
  • docs/TEST-COVERAGE-MATRIX.md
  • src/openhuman/flows/builder_tools.rs
  • app/src/components/chat/WorkflowProposalCard.tsx
  • src/openhuman/tinyflows/caps.rs
  • src/openhuman/flows/schemas.rs
  • app/src/pages/FlowCanvasPage.tsx

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

ℹ️ 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".

Comment on lines +253 to 254
const cardShown = await preauth.checkAfterSave(flow.id, true);
setSaving(false);

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 Record the saved flow before showing preauth

When createFlow returns an already-enabled flow and checkAfterSave opens the pre-authorization card, this branch sets saving back to false and returns without storing flow.id in savedFlowId. The proposal controls are only disabled while saving, so clicking Save again while the card is pending starts from savedFlowId === null and calls createFlow a second time, creating duplicate saved flows instead of retrying the enable/preauth path for the first one.

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.

This was fixed in 3ee5498 (also flagged by greptile): setSavedFlowId(flow.id) now runs immediately after createFlow persists, before any branch releases saving, so a second Save click while the card is pending reuses the persisted id — a regression test pins createFlow at one call across a double click.

Comment on lines +64 to +65
if (!manifest || !manifest.gate_installed || manifest.missing.length === 0) {
await setFlowEnabled(flowId, true);

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 Do not skip blocked-only manifests

For a manifest that contains only blocked rows, such as a readonly user enabling a workflow with only HTTP/code actions, missing is empty because blocked entries are intentionally not approvable; this branch therefore enables the flow without showing the informational card. The runtime tier gate still returns Block, so the user gets no save-time warning and only discovers the refusal when the run fails; the repo’s autonomy model treats Block as a distinct gate decision that should not be collapsed into the no-card success path.

AGENTS.md reference: AGENTS.md:L101-L108

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.

Fixed in df3dd92 — the card now surfaces whenever the manifest contains blocked rows, not only when grants are missing. For a blocked-only manifest the primary action reads 'Enable anyway' (nothing to grant, so the preauthorize RPC is skipped) and the ⛔ rows carry the 'blocked by your agent access settings' hint, so the user sees the tier Block at save time instead of a failing run. Deny still leaves the flow disabled. Covered by new hook + card tests.

@greptile-apps

greptile-apps Bot commented Jul 29, 2026 •

Copy link
Copy Markdown

Greptile Summary

This PR replaces the per-node mid-run parking UX (N interruptions + 10-min TTL failures) with a single consolidated "Approve all / Deny" card shown once at flow save+enable time. Approval batch-grants flow-scoped trust via a new approval.preauthorize_flow RPC, producing durable audit rows; Deny leaves the flow saved-but-disabled; re-saving after edits only surfaces newly-needed grants.

  • Backend: new flows.approval_manifest RPC statically enumerates permissions by reusing the exact runtime classifiers (classify_tool_call, classify_composio_action_for_tier) so the card can never drift from what actually gates; approval.preauthorize_flow batch-writes idempotent flow_tool_trust rows plus born-decided audit entries; flows_delete now purges the flow's grants as best-effort cleanup.
  • Frontend: useFlowPreauthorization hook handles three enable surfaces (list toggle, canvas save, chat proposal) with correct fail-open behavior; FlowPreauthorizationCard / FlowPreauthorizationOverlay are presentational with no-backdrop-dismiss by design.
  • Test infrastructure: backend_env_test_lock extracted as a crate-wide mutex to eliminate cross-module env-var races that broke coverage runs; medulla assertion updated to MedullaNoSessionToken (correct since the hosted backend URL always resolves).

Confidence Score: 4/5

Safe to merge once the non-draft canvas save deny path is resolved — the flow goes live on the backend with missing grants and the canvas toggle shows stale enabled state until navigation.

The non-draft onCanvasSave arm fires checkAfterSave without awaiting it, so when the user denies, setFlowEnabled(false) succeeds on the backend but FlowEditor's local flow.enabled is never updated. The canvas enable/disable toggle therefore shows the flow as enabled (incorrectly) until the user navigates away. This was called out in the prior review and remains unaddressed in the current diff.

Files Needing Attention: app/src/pages/FlowCanvasPage.tsx — the fire-and-forget checkAfterSave in the non-draft else branch needs a state-sync mechanism for the denied outcome before merging.

Important Files Changed

Filename Overview
app/src/hooks/useFlowPreauthorization.ts Core orchestration hook: two entry points (beginEnable / checkAfterSave), fail-open on manifest errors, clean approve/deny state machine. The checkAfterSave path called fire-and-forget in the non-draft canvas save arm means a Deny outcome never syncs local flow.enabled state (existing unresolved P1 comment).
app/src/pages/FlowCanvasPage.tsx Integrates useFlowPreauthorization for both draft-create (deferred navigation via preauthNavRef) and copilot-proposal accept paths. Non-draft checkAfterSave remains fire-and-forget; a Deny in that path disables the flow on the backend without updating FlowEditor's local enabled state (unresolved P1 from prior review).
app/src/pages/FlowsPage.tsx handleToggle correctly splits enable/disable paths; disable is direct, enable routes through beginEnable. Overlay blocks UI while pending. Post-settle list refresh via onSettled is correct.
app/src/components/chat/WorkflowProposalCard.tsx setSavedFlowId now set before any branch releases saving (addressed the prior P1 duplicate-flow bug). preauth card rendered inline below the proposal body; onSettled routes approved/denied outcomes correctly.
app/src/components/flows/FlowPreauthorizationCard.tsx Presentational component with correct role=alertdialog, per-kind icons and hints, blocked-only Enable anyway label swap, and disabled state during busy.
src/openhuman/flows/ops.rs compute_approval_manifest reuses runtime classifiers for correctness at save time. flows_approval_manifest joins against existing flow_tool_trust grants. flows_delete_impl now purges flow trust grants as best-effort cleanup.
src/openhuman/approval/store.rs Three new store functions: record_flow_preauthorization (born-decided audit row), list_flow_trust (sorted, flow-scoped), delete_flow_trust (all or named, idempotent). All backed by thorough unit tests.
src/openhuman/approval/rpc.rs approval_preauthorize_flow validates flow_id, enforces MAX_PREAUTHORIZE_TOOLS=100, returns gate_installed:false when gate absent, writes idempotent INSERT OR IGNORE per tool plus one audit row per new grant.
src/api/config.rs Extracts backend_env_test_lock() as a crate-wide process-global mutex to prevent cross-module test races on BACKEND_URL env vars.

Reviews (7): Last reviewed commit: "fix(flows): surface the card for blocked..." | Re-trigger Greptile

The vendored sdk bump (tinyhumansai#5260) added futures + tokio to tinyhumans-sdk's
dependency set without regenerating either Cargo.lock, so every --locked
cargo invocation (Rust Quality lane's TLS-policy check, coverage lanes)
fails with 'cannot update the lock file'. Also picks up the tinyflows
reqwest rename normalization from the same merge. No version changes.
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 29, 2026
…ome; unify backend env test locks

The two medulla::ops tests asserted MedullaNoBaseUrl from a cleared env,
but base_url() falls back to the hosted backend default BY DESIGN
(resolve.rs's an_unconfigured_install_falls_back_to_the_hosted_backend
pins it), so NoBaseUrl is unreachable that way and the tests failed
deterministically whenever the full suite ran them (our PR always runs
the full suite via the Cargo.lock fallback; per-area lanes never did).
They now assert the state a cleared env actually produces - the missing
session token - against a temp workspace so a signed-in dev machine
cannot flip the outcome.

Also collapses FOUR module-local env locks (api::config, core::cli_tests,
medulla::ops, medulla::resolve) into one crate-wide
backend_env_test_lock(): std::env is process-global, so module-local
locks cannot prevent cross-module set_var/remove_var races on
BACKEND_URL/VITE_BACKEND_URL/app-env.
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 29, 2026
…bility routing

The raw-coverage voice e2e expected vision state 'ondemand' for the
fixture id 'vision-ready', which the pre-tinyhumansai#5253 MVP allowlist rejected
(fallback model absent from mock tags -> ondemand). tinyhumansai#5253's
is_vision_capable heuristic accepts the id (vision marker in the name),
the mock's /api/tags advertises it, and a present Ondemand-mode vision
model correctly reports 'ready'. Stale expectation, intentional behavior
change — only surfaced by the full-suite fallback this PR triggers.
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 29, 2026
Comment thread app/src/components/chat/WorkflowProposalCard.tsx
…or preauthorize_flow

The raw-coverage e2e pins the approval namespace's controller list, which
now includes preauthorize_flow; also exercises the new handler's param
validation (missing flow_id / missing + non-array + mixed-type
tool_names). Success-path semantics stay pinned by the approval::rpc /
approval::store unit tests — asserting them here would be order-dependent
on the process-global gate installed by a sibling test.
… flow on re-click

When createFlow returned enabled and the pre-authorization card was
shown, save() released the saving lock without recording savedFlowId; a
second 'Save & enable' click then re-ran createFlow and duplicated the
flow (greptile P1). The id is now recorded immediately after persist,
before any branch can release the lock, so a re-click reuses the
persisted flow. Regression test pins createFlow at one call across a
double click.
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 29, 2026
A manifest with only tier-Blocked rows (e.g. readonly tier + http/code
nodes) had missing=[] and enabled silently; the user discovered the
Block only when the run failed. The card now also shows when blocked
rows exist — informational rows plus a primary action that reads
'Enable anyway' when nothing is approvable (the grant RPC is skipped;
the runtime tier gate keeps blocking). Deny still leaves the flow off.
New flows.enableApproval.enableAnyway key in all 14 locales.
(chatgpt-codex-connector P2)
@graycyrus

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 29, 2026 •

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@graycyrus
graycyrus merged commit d125b85 into tinyhumansai:main Jul 29, 2026
24 of 44 checks passed
@coderabbitai coderabbitai Bot added bug and removed rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. labels Jul 29, 2026
senamakel pushed a commit to nocstah/openhuman that referenced this pull request Sep 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug feature Net-new user-facing capability or product behavior.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant