Skip to content

§1: in-chat approval modal + core mutating tools (install/engine/serve/services) - #32

Merged
michaelroy-amd merged 2 commits into
mainfrom
supergoal/phase-4-approval-core-mutating
Jun 24, 2026
Merged

§1: in-chat approval modal + core mutating tools (install/engine/serve/services)#32
michaelroy-amd merged 2 commits into
mainfrom
supergoal/phase-4-approval-core-mutating

Conversation

@michaelroy-amd

Copy link
Copy Markdown
Member

Stack 4/10 · base: supergoal/phase-3-readonly-and-nav

The defining capability: mutating ROCm actions behind explicit human approval.

  • Reuses ui/approval.rs; mutating tools surface ApprovalRequired via ClientMsg::ChatApprovalRequired → app-loop modal → on Approve execute_approved (runs as a captured subprocess, no TUI corruption) → exactly one follow-up turn.
  • Wires install/engine/serve/services + safety validators reused unchanged (no public bind / loopback-only / no CPU device).

Reviewed: rust + security + maintainability — approval gate is unbypassable. All cargo gates green.

@rominf rominf left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approve with minor comments. Reviewed this as the security-critical PR and the gate holds: the approval seam is enforced centrally — the LLM tool loop can only return ApprovalRequired, execution happens solely via execute_approved from the event-loop Approve verdict, and both paths re-validate before running. No bypass, no TOCTOU on the validated args, argv-vector spawning (no shell injection). Excellent test coverage proving approval-is-required from both entrypoints.

Minors:

  • launch_server arg-building pushes positional user values as bare argv; a value like --managed would be parsed as a flag by the child clap parser (arg-confusion, not injection — same gated binary). A -- separator before positionals would be good defense-in-depth (pre-existing, low risk).
  • approval.rs defaults the modal cursor to Approve, so Enter on a freshly-opened destructive modal approves. The two-key flow mitigates it, but defaulting destructive modals to Deny is the safer convention.

@michaelroy-amd
michaelroy-amd force-pushed the supergoal/phase-3-readonly-and-nav branch from 4d900ab to 09c172d Compare June 23, 2026 03:41
@michaelroy-amd
michaelroy-amd force-pushed the supergoal/phase-4-approval-core-mutating branch from c32a45f to 15633bf Compare June 23, 2026 03:41

@rominf rominf left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Requesting changes on signing grounds — this is separate from the code review above, which still stands.

Every commit on this PR is unsigned: GitHub reports verification.verified = false (reason unsigned) for all of them. Signed commits are required before this can merge.

To fix, configure commit signing and re-sign the branch history, e.g. with SSH signing:

git config gpg.format ssh
git config user.signingkey <your-signing-key.pub>
git config commit.gpgsign true
git rebase --exec 'git commit --amend --no-edit -S' <base-branch>
git push --force-with-lease

Since this is a stacked PR, re-signing is easiest from the bottom of the stack upward — re-signing a base branch rewrites its SHAs and the dependent branches will need rebasing/re-pushing on top.

Once the commits show as Verified I'll clear this.

@michaelroy-amd
michaelroy-amd force-pushed the supergoal/phase-3-readonly-and-nav branch from 09c172d to ecb4c53 Compare June 23, 2026 17:39
@michaelroy-amd
michaelroy-amd force-pushed the supergoal/phase-4-approval-core-mutating branch from 15633bf to 73fdb58 Compare June 23, 2026 17:39

@rominf rominf left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Signing gate cleared — all commits on this PR now show Verified (verification.verified = true, reason valid). Re-reviewed the current diff against this PR's own base after the re-sign; it's substantively unchanged from my prior review.

Approving on both grounds: code is sound and commits are signed.

@rominf
rominf force-pushed the supergoal/phase-3-readonly-and-nav branch from ecb4c53 to 0a23116 Compare June 24, 2026 07:38
@michaelroy-amd
michaelroy-amd force-pushed the supergoal/phase-3-readonly-and-nav branch from 0a23116 to 29eefc5 Compare June 24, 2026 16:53
@michaelroy-amd
michaelroy-amd force-pushed the supergoal/phase-4-approval-core-mutating branch from 73fdb58 to 8c50a09 Compare June 24, 2026 16:53
Base automatically changed from supergoal/phase-3-readonly-and-nav to main June 24, 2026 17:47
@rominf
rominf requested a review from Copilot June 24, 2026 17:50

Copilot AI 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.

Pull request overview

Adds an explicit human-approval gate for mutating ROCm actions invoked from Dash chat/slash flows, wiring the approval modal through the app loop and providing a safe “execute approved” seam that runs via captured subprocess output (to avoid TUI corruption).

Changes:

  • Introduces approval intent plumbing + execute_approved across the dash↔bin tool-exec seam, plus app-loop modal routing and result follow-up turn behavior.
  • Adds slash-command routing for both read-only executor-backed commands and approval-gated mutating commands (install/engine/serve/services), including concise summarization of tool results.
  • Registers read-only and mutating ROCm tools in the Rig agent clients, with mutating tools surfacing approval requests to the UI instead of executing in the tool loop.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
docs/dash-parity-map.md Adds a Phase/coverage parity map for legacy slash commands and their Dash surfaces/tests.
crates/rocm-dash-tui/src/ui/tabs/instances.rs Updates test helpers to include newly added AppState fields.
crates/rocm-dash-tui/src/ui/mod.rs Renders the approval modal last so it overlays all other UI surfaces.
crates/rocm-dash-tui/src/tool_exec.rs Extends the seam with an approval intent payload and an execute_approved method.
crates/rocm-dash-tui/src/client.rs Adds client message variants for slash-tool replies and approval request/result messaging.
crates/rocm-dash-tui/src/app.rs Implements slash-command handling, approval modal flow, executor-backed slash execution, and concise tool result summaries + tests.
crates/rocm-dash-tui/src/agent.rs Registers read-only and mutating ROCm tools; mutating tools surface approvals to the app via a channel + tests.
apps/rocm/src/main.rs Accepts doctor as an alias for examine and adds a guard test ensuring dash read-tool names are accepted by the bin.
apps/rocm/src/dash_seam.rs Implements execute_approved in the bin seam and updates approval intent payload to carry (name, arguments) for replay.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +674 to +683
RocmToolOutcome::ApprovalRequired(intent) => {
if let Some(tx) = self.approval_tx.as_ref() {
let _ = tx.send(ClientMsg::ChatApprovalRequired { intent });
}
Ok(json!({
"status": "surfaced_for_approval",
"note": "This action needs operator approval; it has been \
surfaced to the operator. Do not retry.",
}))
}
Comment on lines +699 to +707
{
"type": "object",
"properties": {
"channel": { "type": "string", "description": "Release channel: 'release' or 'nightly'." },
"format": { "type": "string", "description": "Artifact format: 'wheel' or 'tarball'." },
"prefix": { "type": "string", "description": "Install folder (required; never a system path)." },
"version": { "type": "string", "description": "Optional explicit wheel version selector." }
}
}
Comment thread docs/dash-parity-map.md
| clear | A | covered (Phase 3) | `/clear` slash → empties `chat` transcript | `slash_clear_empties_transcript` |
| quit | A | covered (Phase 3) | `/quit` slash → `should_quit` (event loop breaks) | `slash_quit_sets_should_quit` |
| exit | A | covered (Phase 3) | `/exit` slash → `should_quit` (event loop breaks) | `slash_exit_sets_should_quit` |
| doctor | B | covered (Phase 3) | `/doctor` slash → opens the doctor overlay (`doctor_manager`); the read-only `doctor` tool is covered separately via the LLM tool-call seam | `slash_doctor_opens_overlay` / `read_only_tool_round_trips_to_json` |
Comment thread docs/dash-parity-map.md
| logs | B | covered (Phase 3) | `/logs` slash → opens the logs overlay (`logs_view`); the read-only `service_logs` tool is covered separately via the LLM tool-call seam | `slash_logs_opens_overlay` / `read_only_tool_round_trips_to_json` |
| gpu | B | covered (Phase 3) | `/gpu` slash → `ActiveTab::Hardware`; tool `gpu_snapshot` via seam | `slash_gpu_switches_to_hardware` |
| daemon | B | covered (Phase 3) | `/daemon` slash → `slash_tool` `rocm_command ["daemon","status"]` (off-thread) | `slash_daemon_raises_executor_request` |
| install | D | covered (Phase 4) | `/install` slash → `install_sdk` mutating tool → approval modal → `execute_approved` (captured subprocess); also LLM tool-call seam | `slash_install_raises_install_sdk_request` / `approve_path_runs_execute_approved` |
…e/services)

Gate the four core mutating ROCm tools behind an in-chat approval modal.
Mutating tools are NOT executed inside the rig tool loop: the executor seam
returns an ApprovalRequired descriptor, the rig tool surfaces it to the app
over a ClientMsg channel, and the action runs only after the operator approves
the modal — via the bin's captured-subprocess path (TUI-safe, no stdout
corruption). The safety validators stay the single gate (re-validated on the
approved replay), so an unsafe call (public bind / non-loopback / CPU device)
fails to an error instead of opening the modal.

Seam (tool_exec.rs + dash_seam.rs):
- ApprovalIntent now carries name + arguments (replayable payload) plus the
  human-readable title/body.
- Re-add RocmToolExecutor::execute_approved; BinToolExecutor runs it via
  run_internal_mcp_call(name, args, allow_mutation=true).

Dash (agent.rs / client.rs / app.rs / ui):
- rocm_mutating_tool! macro + 4 tools (install_sdk, install_engine,
  launch_server, stop_server) that post ClientMsg::ChatApprovalRequired and
  return a "surfaced for approval" note (no execution); approval_tx threaded
  through both clients. agent.rs stays the sole rig namer.
- PendingApproval + approval modal flow: highest-priority key arm owns focus;
  Approve replays execute_approved off-thread (spawn_blocking) → concise result
  turn + exactly one automatic follow-up agent turn (chat_dispatch consumed
  once, no loop); Deny/Cancel → declined turn, no execution.
- Slash commands /install /engine /serve /services unified through the
  slash_tool path (now handles all three RocmToolOutcome variants, opening the
  modal on ApprovalRequired).

Parity map: install/engine/serve/services D-core rows → covered (Phase 4).

Tests (offline): approve drives execute_approved with expected args; deny runs
nothing + declined turn; exactly-one follow-up (no self-loop); validator
rejects public-bind/CPU before approval; slash dispatch for all four; mutating
rig tool surfaces approval without executing.
…, honest /services restart, single-approval guard

FIX A: /install slash now requires a prefix arg (mirrors /engine, /serve);
bare /install shows usage error and does not dispatch — the validator
REQUIRES a prefix, so the modal previously never opened.

FIX B: close_overlays now clears self.approval (was leaving a stale
approval modal / focus trap).

FIX C: /services restart is guided (not yet wired through the chat seam)
instead of silently running stop_server — the modal must never say
'restart' while executing 'stop'. Took the ELSE path: properly wiring
restart_server exceeds the contained threshold (4+ bin functions in
main.rs + agent.rs macro/array/register).

FIX D: open_approval guards against clobbering — a second pending
approval is discarded with an error turn so the operator never approves
args they did not see.

FIX E: PendingApproval doc reworded (struct is not Rust-generic).
@michaelroy-amd
michaelroy-amd force-pushed the supergoal/phase-4-approval-core-mutating branch from 8c50a09 to 724a76d Compare June 24, 2026 17:59
@michaelroy-amd
michaelroy-amd added this pull request to the merge queue Jun 24, 2026
Merged via the queue into main with commit c286021 Jun 24, 2026
7 checks passed
@michaelroy-amd
michaelroy-amd deleted the supergoal/phase-4-approval-core-mutating branch June 24, 2026 18:05
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.

3 participants