Files
my-pi/pi-permission-system/docs/retro/0413-document-external-directory-allow-list.md

7.6 KiB

issue, issue_title
issue issue_title
413 Explicitly allow some external directories relative to the home directory

Retro: #413 — Explicitly allow some external directories relative to the home directory

Stage: Planning (2026-06-16T15:06:55Z)

Session summary

Investigated a third-party request (filed by michaelmior) to allow outside-CWD directories like ~/.cargo/registry without prompting. Found the capability already exists via the external_directory surface pattern map; the user's path-surface attempt failed because of most-restrictive-wins composition and a missing trailing *. After confirming direction with the operator, wrote a docs-only plan to make the external_directory allow-list discoverable across configuration.md, README.md, config.example.json, and the schema.

Observations

  • This is fundamentally a discoverability bug, not a missing feature. "external_directory": { "*": "ask", "~/.cargo/registry/*": "allow" } already does what the user wants.
  • Rejected the tempting "make a path allow suppress the external_directory gate" approach. The four layers compose with most-restrictive-wins, so a path allow loosening an external_directory: ask boundary would be a security regression, not a fix. The operator's instinct (keep external_directory as a separate, intentional layer modeled on OpenCode) confirmed this.
  • Operator correction on wildcard semantics: do not use ** in examples. A single * compiles to a greedy .* (with the s flag in wildcard-matcher.ts) and already crosses subdirectory boundaries, so ~/.cargo/registry/* matches every file beneath the directory.
  • Scope trimmed by operator: configuration.md + README.md + config.example.json/schema; troubleshooting.md deselected.
  • Next stage is /build-plan (docs-only), not /tdd-plan.
  • Alignment constraint applies: configuration.md, README.md, config.example.json, and schemas/permissions.schema.json must agree on the external_directory pattern-map form.

Stage: Implementation — Build (2026-06-16T15:30:00Z)

Session summary

Executed all three docs-only build steps from the plan, one commit each: configuration.md (cache-dir recipe, "which surface?" clarification, single-* note, most-restrictive-wins reaffirmation), README.md (clear external_directory surface description + pattern-map example), and config/config.example.json + schemas/permissions.schema.json (inline ~/.cargo/registry/* allow example, aligned across both). No src/ or test/ files were touched, so the test/typecheck suites were not required; the package lint passes and both JSON files parse.

Observations

  • The schema markdownDescription is a single physical JSON line with literal \n escape sequences, not real newlines. An Edit oldText built with actual newlines failed to match; matching the literal \n\n (and escaping the embedded JSON quotes as \\\") was required.
  • Operator mid-build question confirmed jsonc is the correct doc fence language: the docs use ```jsonc 21 times and ```json zero times, and the loader runs JSON.parse(stripJsonComments(raw)) (config-loader.ts:388), so // comments are supported (trailing commas are not). The config file itself stays .json and comment-free.
  • No deviations from the plan. The optional piInfrastructureReadPaths cross-reference (Open Question) was included as a single sentence in configuration.md only, as the plan leaned.
  • Pre-completion reviewer: PASS — ready for /ship-issue. No WARN findings.

Stage: Final Retrospective (2026-06-16T16:00:00Z)

Session summary

Shipped a docs-only fix for a third-party discoverability issue: the external_directory allow-list (e.g. ~/.cargo/registry/*) already does what the user wanted, so the work documented it across configuration.md, README.md, config.example.json, and the schema rather than changing behavior. The session ran clean end to end (plan → build → ship → release pi-permission-system v13.1.2), with the only notable inflection at planning: an initial mis-framing of the design that the operator's redirect and a most-restrictive-wins re-analysis corrected before any code was written.

Observations

What went well

  • The ask_user gate on a third-party issue earned its keep. It surfaced the design direction before implementation and prevented building the tempting-but-wrong "make a path allow suppress external_directory" change, which most-restrictive-wins makes a silent access-widening regression.
  • Incremental verification throughout build: lint ran after each of the three doc steps, and both JSON files were JSON.parse-checked after the schema/example edit — no end-only verification gap.
  • Operator mid-build spot-check ("jsonc or json?") was answered by verifying the convention (21 ```jsonc vs 0 ```json) and the loader (JSON.parse(stripJsonComments(raw))), not by assertion — and the answer held.

What caused friction (agent side)

  • missing-context — the first planning ask_user presented "make a path allow authoritative (suppress external_directory)" as a near-recommended fix, framed as correcting an "asymmetry," without first tracing the documented most-restrictive-wins composition where ask > allow. The operator redirected to discuss the interaction; a re-analysis then showed the option was a security regression. Self-corrected after the operator's nudge. Impact: one extra discussion round at planning; no rework (caught before any code), and the outcome improved.
  • other (tool-usage) — two consecutive Edit calls on configuration.md/schemas/permissions.schema.json were rejected: first for a stray newText_unused/newText2_unused key in the edits object, then because the oldText for the schema markdownDescription used real newlines while the file stores literal \n escapes inside a single-line JSON string. Impact: ~2 retries, no rework.

What caused friction (user side)

  • None blocking. The operator's two interventions (planning redirect, jsonc spot-check) were timely and strategic, and both improved the result. The only forward-looking opportunity: the most-restrictive-wins composition invariant lives in docs/configuration.md but not in the package skill, so it was not front-of-mind when I drafted the first ask_user — see the proposed skill addition.

Diagnostic details

  • Model-performance correlation — one subagent dispatch (pre-completion-reviewer) ran on anthropic/claude-sonnet-4-6 for judgment-heavy doc/consistency review; appropriate, no mismatch.
  • Escalation-delay tracking — no rabbit-hole; the longest stuck sequence was the two rejected Edit calls, resolved on the third attempt (under the 5-call threshold).
  • Unused-tool detection — none; grep/Read covered the planning context, and no missing-context point would have been closed by an unused agent or tool (the gap was an undocumented invariant, not unsearched code).
  • Feedback-loop gap analysislint ran after every build step and JSON parsing after the schema edit; no deferred-verification gap. check/test were correctly skipped (no src//test/ changes).

Changes made

  1. .pi/skills/package-pi-permission-system/SKILL.md — added a most-restrictive-wins cross-surface composition invariant after the last-match-wins bullet (Implementation Priorities), stating that a more-permissive rule on one surface cannot loosen a more-restrictive rule on another and that outside-CWD directories belong on external_directory, not path.
  2. Proposal 2 (JSON-escape Edit note in AGENTS.md) was considered and declined by the operator — not landed.