Prompt to approve external access rules on first use
- Status: Todo
- Kind: Feature
- Authors: jp
- Date: 2026-08-18
- Label: client=cli
- Label: package=jp_cli
- Label: package=jp_tool
- Label: type=feature
Finish the deferred half of the approval lifecycle in RFD D43 Phase 3: a hand-authored external = true access rule should prompt for approval on first use instead of being dropped.
Current behaviour
compile_tool_policy consults the approval store for every external rule and maps both outcomes to a refusal:
ApprovalLookup::Approved => ApprovalDecision::Approved,
ApprovalLookup::Retargeted { .. } | ApprovalLookup::Unknown => ApprovalDecision::Rejected,An unapproved rule is dropped with a warning, and compile_policy inserts a deny-all sentinel so the tool stays default-deny rather than degrading to unrestricted workspace access. Nothing is silently granted, so this is safe — just unusable without a bootstrap step.
The only way to seed an approval today is --mount, which creates a symlink and records the binding as a side effect. So a config-declared external rule cannot work until the user has run a --mount invocation for that same rule path.
Why it matters
It inverts the intended authoring model. The config is meant to declare intent and JP is meant to ask about the risky part once. Instead the CLI flag is load-bearing, and the config-only path silently does nothing (bar a warning).
The concrete case: a second project developed inside a JP workspace keeps its own JP config in an ignored directory, and wants a rule granting one tool access to a file outside the workspace. Declaring it is natural; having to remember an unrelated --mount invocation first is not.
What D43 already specifies
The design is settled, so this is implementation rather than a new decision:
- Approval is target-only. The user approves that a workspace-relative rule path may resolve to a specific canonical absolute target. Capability edits to the same rule (adding write, say) do not re-prompt while the target is unchanged; those are visible in git diff and
jp config show. Silent retargeting is the threat trust-on-first-use exists to catch. - The prompt is host-side and uses the terminal prompting UI, but is deliberately not recorded as
InquiryRequest/InquiryResponseevents in the conversation stream: the prompt text contains a canonical host path, which must not enter shared conversation state. The durable record is the user-local approval store only. - Retargeting is a distinct case from unknown, and the store already distinguishes them (
ApprovalLookup::Retargeted { previous }), so the prompt can say what the binding used to point at.
Scope
crates/jp_cli/src/access/compile.rs—compile_tool_policyneeds an approver that can prompt, rather than the current pure store lookup. Notecompile_fsalready takesapprove: impl FnMut(&str, &Utf8Path) -> ApprovalDecision, so the seam exists.crates/jp_cli/src/access/approvals.rs—recordandsavealready exist; the prompt path needs to persist on approval.- Decide the non-interactive behaviour: a prompt is impossible under
--no-interactiveor when detached, and the answer should be the current one (drop with a warning) rather than blocking or auto-approving.
Out of scope
The other unimplemented D43 phases: OS-level sandboxing (Phase 4, RFD 075), Windows junction fallback (Phase 6), --no-mount cleanup, and the broad-mount tool-scope confirmation prompt.
Note on RFD status
D43 is still a Draft, so implements is deliberately unset — that field is for phases of an accepted RFD's plan, and setting it would put a draft in the In Progress column. Phases 1, 2 and 5 of D43 shipped in PR #727 while it was a draft, so filing this against the draft matches how the RFD has actually been built out.