7.1 KiB
issue, issue_title
| issue | issue_title |
|---|---|
| 509 | Bash bare-filename arguments bypass the path permission surface |
Retro: #509 — Bash bare-filename arguments bypass the path permission surface
Stage: Planning (2026-07-02T01:23:22Z)
Session summary
Planned the rule-driven promotion fix for bash bare-filename arguments (cat id_rsa) bypassing the path permission surface.
Confirmed direction with the operator via ask_user: rule-driven promotion only (accept fail-safe false prompts for search patterns/branches), defer backslash-relative Windows tokens, and fold case on Windows.
Wrote a 6-step TDD plan and committed it; filed follow-up #520 for the deferred backslash-relative concern.
Observations
- The layering choice was the crux: rather than threading raw
pathpatterns into the pure bash classifier, the plan hasPermissionManagerbuild aPathRuleTokenMatcherpredicate (it already owns the composed ruleset and the injectedplatform), which is threaded manager → session → pipeline →BashProgram.parse→BashPathResolver. This keeps the Windows case/separator fold in one place and keeps the classifier pure (predicate passed in). - Promotion must exclude the universal
"*"pattern before evaluation — the gate's existingmatchedPattern === undefinedguard only skips the synthesized default, not a real"*"config rule, so a"*"path rule would otherwise storm every bash argument. - Both interface widenings (
ScopedPermissionManager,ToolCallGateInputs) break their fakes at the type level, so the plan folds each fake update into the same commit as its interface change (TDD steps 2 and 4). - Promoted tokens reuse the unchanged
buildRuleCandidatePath, so the#393unknown-base literal-only rule and#418canonical/lexical matching carry over for free — noted as invariants with pins. - Not part of any release batch → ships independently.
- Docs to touch beyond
src:architecture.mdmodule-tree line, the packageSKILL.md(which currently documents the bare-token exclusion as intentional), andconfiguration.mdpath-surface prose.
Stage: Implementation — TDD (2026-07-02T22:14:00Z)
Session summary
Executed all 6 TDD steps exactly as planned: pure classifyPromotedRuleCandidate classifier, PermissionManager.getPromotablePathTokenMatcher, BashPathResolver/BashProgram.parse promotion wiring, ToolCallGatePipeline matcher threading, an end-to-end composition-root repro of the issue's literal cases, and docs.
Test count grew from 2207 to 2233 (26 new tests) in pi-permission-system; full monorepo suite, tsc --noEmit, root lint, and pnpm fallow dead-code all green throughout.
Pre-completion reviewer verdict: PASS.
Observations
- Two small deviations from the plan's exact Module-Level Changes list, both sensible and noted to the reviewer:
src/rule.ts's privatepathMatchOptionshad to be exported soPermissionManager.getPromotablePathTokenMatcherreuses the exact Windows case/separator foldevaluate()uses, instead of re-deriving it — the plan's Design Overview already implied this ("The Windows fold mirrorspathMatchOptions") but didn't listrule.tsas a changed file.test/permission-resolver.test.tshas its own inline fakeScopedPermissionManager(separate fromtest/helpers/session-fixtures.ts'smakeFakePermissionManager) that also needed the newgetPromotablePathTokenMatcherstub — the plan anticipated only the shared fixture needing the update.
- The Step 4 pipeline test (
ToolCallGatePipelinethreading) required updating one pre-existing assertion (toHaveBeenCalledWith("echo hello", expect.any(PathNormalizer))→ addexpect.any(Function)for the third arg) sincevi.fn().toHaveBeenCalledWithchecks the full argument list — a straightforward, anticipated-by-the-testing-skill breakage from adding an optional parameter that the pipeline now always supplies. - Step 5 (the end-to-end composition-root test) passed immediately on first run with no red phase — by design, since the plan sequenced it after all four implementation steps landed; it served as a confirmation/documentation commit rather than a traditional red→green cycle, matching the plan's framing.
- The
getPromotablePathTokenMatcherunit tests needed an explicitplatform: "win32"/"linux"injectedPermissionManager(not themakeInMemoryManagerhelper, which doesn't expose a platform override) to pin the Windows case-fold behavior — built directly vianew PermissionManager({ policyLoader: createInMemoryPolicyLoader(...), platform: "win32" }). - No architecture-roadmap step marker to flip — #509 was confirmed in planning to not be part of any roadmap phase.
Stage: Final Retrospective (2026-07-02T23:51:54Z)
Session summary
Shipped #509: synced, ran root pnpm run lint + pnpm fallow dead-code (both clean), pushed c8be2799, verified CI green, closed the issue with a per-commit summary, and merged release-please PR #522 to cut pi-permission-system-v18.1.0.
The one non-trivial moment was the release PR sitting at merge_state: UNSTABLE with a CI check still IN_PROGRESS; waited it out and retried the merge per the ship prompt's step 6.4, no fallback needed.
Across all three stages (Planning → TDD → Ship) the issue landed with zero rework and a PASS from the pre-completion reviewer.
Observations
What went well
- Plan → TDD fidelity was high: the 6-step plan produced 26 tests and shipped with only two minor, tsc-caught deviations (both recorded in the TDD stage entry).
The upfront
ask_userdesign gate at planning time (rule-driven promotion, defer backslash, Windows case fold) is what kept the later stages surprise-free — the layering decision was settled before any code was written. - The release-please
UNSTABLE-with-IN_PROGRESS-check path was handled cleanly on encounter:release_pr_mergerefused,statusCheckRollupshowed the in-progress check,ci_watchconfirmed success, retry merged. This is the ship prompt's documented step 6.4 exception working as designed — nogh pr mergefallback while a check was running.
What caused friction (agent side)
other(minor efficiency) — when the release PR's check wasIN_PROGRESS, the first wait was asleep 30; gh pr viewpoll before switching toci_watchon the run id. Impact: one redundant poll cycle (~30s), no rework —ci_watchon the check's run is the more direct wait and could have been used first.
What caused friction (user side)
- None — the ship stage was fully autonomous with no user intervention required, consistent with a clean plan and a green pre-completion review.
Changes made
packages/pi-permission-system/docs/retro/0509-bash-bare-filename-path-surface.md— appended this Final Retrospective stage entry. No prompt orAGENTS.mdchanges: the two TDD deviations are already covered by existing testing-skill guidance (grep all mocks, not just shared fixtures) and weretsc-caught with zero rework, and the lone ship-stage friction was a ~30s redundant poll — too situational to encode.