Repository navigation
feat: deckard_send MCP tool + value-bearing auto-allow safety (#135 PR2) - #161
Merged
Merged
Conversation
Adds an 8th mcp.v0.1 tool, deckard_send, mirroring deckard_shield: it parses a decimal-ETH amount and a strict 0x recipient (ENS is not resolved here), builds a native-ETH Send intent (token: None, empty calldata — the daemon's calldata_ok requires it), and proposes it with ProposalOrigin::Agent. Within the policy's Send rule it auto-allows; over the cap it returns needs_approval. The sidecar is key-less and only proposes — there is no self-approve path. This is the agent-channel side of the first value-bearing auto-allow: a within-cap Send carrying real ETH can now execute on the agent's own authority within the Rules. Updates every tool-count drift guard: acceptance T1 (now the eight-tool profile + two new send tests), the quickstart tool table, the mcp-shape doc, README, and the clap/about/module copy.
…ouble-spend (#135 PR2) Adds an anvil e2e regression for the #108 reserve-before-sign + execute-time re-evaluate path, now that a within-cap native-ETH Send auto-allows and moves real funds. Two distinct within-cap Sends (0.03 each; per-tx 0.04, daily 0.05) both return Decision::Allow at propose time while spent_today is still 0 — the TOCTOU window. After the first commits, the second is Denied (cap_exceeded) at the execute-time re-evaluate (daemon.rs:1312), and the non-funded recipient's on-chain balance stays 0 — no double-spend past the daily cap. The test is guard-pinning: it fails if the re-evaluate is removed. durable_cap_e2e.rs only covered restart recovery, not this single-session close.
The linux job died with "No space left on device" during the Swatinem/rust-cache@v2 restore: extracting the multi-GB cached target dir exhausted the runner's disk before the "Free up disk space" step ran. The step's own comment says to reclaim space "first", but it was ordered AFTER the cache restore. Move it before the restore so neither the restore nor the build runs out of space. No code change; macOS is unaffected (it has the headroom and runs no free-space step).
hellno
added a commit
that referenced
this pull request
Jul 1, 2026
The #135 keystone shipped end to end — the v2 Rules schema (PR1, #160), agent send over MCP (PR2, #161), and the named starter presets (PR4, #162) — with evaluate/evaluate_order mock⇄daemon parity green and the send approval card (PR3) already in the app. Flip the status Proposed → Accepted and reword the closing section to record what shipped and that in-app authoring (#29) is next.
hellno
added a commit
that referenced
this pull request
Jul 1, 2026
#162) * feat(policy): starter Rules presets + DECKARD_POLICY_PRESET launch selection (#135 PR4) Ship three named starter postures for the agent's Rules, all default-deny on the v1 schema, selectable at daemon launch: - shield-only: shield auto-allowed, send and swap denied (the safest useful agent) - ask-me-everything: shield, send, and swap all human-approved - locked: no rules, every action denied (a frozen rulebook, distinct from the runtime revoke STOP) Selection is launch-time via DECKARD_POLICY_PRESET, consulted only when there is no policy.json (an authored file always wins; a malformed file still falls back to the loud deny-all, and a preset can never revive it). An unknown value warns and uses the friendly default. The app spawns signerd with env_clear() + FORWARDED_ENV_ALLOWLIST (finding C1), so DECKARD_POLICY_PRESET is added to that allowlist or it would be stripped at spawn. Safe: every preset is same-or-more-restrictive than the no-env default, so forwarding it can only narrow the agent's authority, never escalate it. Live in-app preset switching is deferred to #29 (needs the SetPolicy RPC; the daemon reads policy only at startup). DoD: fmt clean; just check green (both configs); cargo test --workspace 396 passed. * docs(adr): promote ADR-0005 (policy & authorization model) to Accepted The #135 keystone shipped end to end — the v2 Rules schema (PR1, #160), agent send over MCP (PR2, #161), and the named starter presets (PR4, #162) — with evaluate/evaluate_order mock⇄daemon parity green and the send approval card (PR3) already in the app. Flip the status Proposed → Accepted and reword the closing section to record what shipped and that in-app authoring (#29) is next.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
deckard_send, the 8thmcp.v0.1tool: a key-less, agent-channel propose path for a native-ETH transfer that flows through the daemon's policyevaluateexactly likedeckard_shield. Within the policy's Send rule it auto-allows; over the cap it returnsneeds_approval; the sidecar only proposes (no self-approve). This makes a within-cap Send carrying real ETH the first value-bearing action the agent can take on its own authority within the Rules, so it also adds an anvil e2e regression that proves the #108 reserve-before-sign + execute-time re-evaluate path has no single-session double-spend — two within-cap auto-allows that together exceed the daily cap cannot both move funds. The palette is verify-only (the human Send action already has its ⌘K command;deckard_sendis agent-channel), sodeckard-appis untouched.Linked issue
Part of #135 — PR2 of the policy-v2 keystone (PR1 was #160). Does not close the epic.
Definition of Done
cargo fmt --all --checkis cleanjust checkis green — clippy-D warningson both the default config and--features tray(anddeckard-signerd --features dev-signerd-bin)cargo test --workspaceis greenCargo.toml/Cargo.lockDESIGN.md— N/A: no UI change (deckard-appuntouched; the human Send ⌘K command already exists)Zeroizingand are never logged orDebug-printed — thedeckard-mcpsidecar is key-less and holds no secrets; the new tool's response echoes onlydecision/request_id/to/amount_eth/nextEvidence (paste command output here)
Notes for reviewers
executenow gates real fund movement. The path was reviewed sound:executeis fully serialized under the singledaemon.lock().await.handle()dispatch (server.rs:169), and it re-evaluates an auto-allow against the live post-commitspent_todaybefore reserving (daemon.rs:1312). The new test is guard-pinning — it fails (panicsDOUBLE-SPEND) if that re-evaluate is removed.Sendreserves 0 against the daily cap (the cap is ETH-wei; token atoms aren't comparable) —deckard_sendonly ever builds native ETH, so no token path is introduced here. A per-token cap is a separate follow-up.TMPDIR=/tmpto keep Unix-socket paths underSUN_LEN; the new test runs (not skipped) whereveranvilis on PATH.