9.5 KiB
issue, issue_title
| issue | issue_title |
|---|---|
| 490 | pi-permission-system: floor other indirection wrappers (sudo/env/xargs/find -exec) to ask |
Retro: #490 — floor other indirection wrappers to ask
Stage: Planning (2026-07-12T17:44:03Z)
Session summary
Planned Phase 10 Step 5: floor indirection wrappers (sudo/env/xargs/find -exec/time/nohup/timeout/nice/fd -x) to ask, extending #481's opaque-payload floor.
This is an evaluation issue, so the direction was confirmed via ask_user — the operator chose floor-all (over re-target-vs-floor hybrid), all listed wrappers, find/fd gated on an exec flag, plus fd coverage with a follow-up for other modern rewrites.
Plan filed at docs/plans/0490-floor-indirection-wrappers.md; follow-up #575 filed for surveying other exec-capable CLI rewrites.
Observations
- The roadmap (
docs/architecture/architecture.mdPhase 10 Step 5) recorded an earlier 2026-07-10 hybrid direction ("re-target visible prefix wrappers, floorxargs/find -exec"). Today's floor-all decision supersedes it — the plan's Step 4 must rewrite the roadmap Step 5 note, the health-metrics "Indirection-wrapper coverage" row, and theS5Mermaid label, and mark the step✅in the implementation doc-update commit. - AST probe (tree-sitter-bash) confirmed every wrapper parses as a flat
commandnode with no boundary between the wrapper's own options and the inner command. This is why re-targeting was rejected: it needs a per-wrapper option-arity table (sudo -u X,env -u X,nice -n N,timeout N), and a wrong table silently under-matches — the bypass class the package warns against. The floor needs no such tables. - Design decision: generalize #481's
BashCommand.opaque?: booleanto awrapperKind?: "opaque-payload" | "indirection"discriminant (illegal{opaque, indirection}state unrepresentable), with a sibling<indirection-bash-wrapper>sentinel; the<opaque-bash-wrapper>string is preserved byte-for-byte so #481 tests/docs stay green. - The rename removes the
opaquefield, so allopaque: truetest literals (inprogram.test.ts,sync-commands.test.ts,bash-command.test.ts) become compile errors and must migrate in the same (Step 1 refactor) commit — sequenced as a behavior-preserving refactor before the twofix:behavior steps. find/fdare exec-flag-conditional (bare searches run no subcommand and are common); the always-invoke set floors bycommand_namebasename alone. Accepted edge: bareenv/time/sudo -lare floored too (least-privilege posture).- Release: ship independently (Step 5 is
Release: independent; afix:that cuts its own release; not part of the "tool-kind-dispatch" batch). - The advisory surface (
resolveBashAdvisoryCheck) reuses the sharedresolveBashCommandCheck, so the floor applies there for free — no separate change.
Stage: Implementation — TDD (2026-07-12T14:04:00Z)
Session summary
Implemented all four planned TDD cycles: a behavior-preserving refactor generalizing #481's BashCommand.opaque?: boolean to a wrapperKind?: "opaque-payload" | "indirection" discriminant, then two fix: commits adding the indirection floor (always-invoke INDIRECTION_WRAPPER_NAMES; find/fd exec-flag-gated EXEC_CONDITIONAL_WRAPPERS), then the docs/roadmap commit.
Test count went 2348 → 2374 (+26); check, root lint, and fallow dead-code all green.
Pre-completion reviewer returned PASS — ready for /ship-issue.
Observations
- The floor half needed no code change beyond Step 1: the
WRAPPER_SENTINELRecord<WrapperKind, string>was defined with both keys in the refactor, so once the classifier emitted"indirection",resolveBashCommandCheckfloored it automatically. This made Step 2's floor tests green on arrival — the only new code was the classifier arm. - Deviation 1 (test omissions): the plan floated an optional advisory indirection case in
bash-advisory-check.test.tsand afind/fdfloor case inbash-command.test.ts(Step 3). Omitted both as redundant — the advisory path reusesresolveBashCommandCheck, and thefind/fdfloor is the identicalwrapperKind: "indirection"path already covered by the Step 2sudofloor test. The distinguishing new behavior (exec-flag gating) is covered by theprogram.test.tsclassifier tests. Reviewer agreed the omissions are sound. - Deviation 2 (unplanned file): updated
docs/retro/phase-10-decide-once-dispatch.md— a grep found it recorded the superseded 2026-07-10 "re-target" direction as the roadmap's scheduling record; appended a supersession marker pointing to the plan. Reviewer classified it a legitimate reverse-documentation fix. - Infra note: the
rumdl-fmtpre-commit hook failed to reinstall on the docs commit (transientNo route to hostfetching setuptools). Ranpnpm exec rumdl fmt+rumdl checkmanually (clean), then committed the docs step with--no-verify. Code commits were unaffected (markdown hooks skip when no.mdfiles are staged). - Roadmap Step 5 marked
✅on both the heading and theS5Mermaid node in the docs commit (not deferred to ship), per the package skill. - Reviewer verdict: PASS. No warnings.
Stage: Final Retrospective (2026-07-13T01:16:50Z)
Session summary
Shipped #490 across three stages (plan → TDD → ship) with no rework: four commits (one refactor, two fix, one docs), +26 tests, PASS review, released as pi-permission-system-v20.4.1.
The session's spine was an evaluation-issue direction gate resolved via two ask_user rounds, then a mechanical, well-sequenced TDD execution that fell out cleanly because the design was grounded up front by an AST probe.
Friction was near-zero; the only agent-side slip was a wrong hand-typed timestamp in the TDD stage note, and the only external hiccup was a transient pre-commit-hook install failure that was handled correctly.
Observations
What went well
- Two-round
ask_userabsorbed a freeform operator addition. The wrapper-set question returned the multi-select plus freeform text ("fd, maybe other modern core tool rewrites"). The second round turned that into a crisp design decision (find/fdgated on an exec flag vs. always-invoke) and a filed follow-up (#575) — no guessing, no scope bleed. - Disposable AST probe grounded the design before committing to it.
A throwaway
tree-sitter-bashscript confirmed every wrapper parses as a flatcommandnode with no option/inner-command boundary — the concrete evidence that killed the re-target approach and justified floor-all. This is the testing skill's "write a disposable exploratory script first" pattern paying off. - Design sequencing made later steps free.
Defining
WRAPPER_SENTINELas aRecord<WrapperKind, string>with both keys during the Step 1 refactor meant Step 2's floor behavior fell out with zero new gate code once the classifier emitted"indirection". - Incremental verification throughout.
pnpm run check/vitestran after each TDD step, full suite + rootlint+fallow dead-codeafter the last — no end-of-session verification pile-up.
What caused friction (agent side)
other— the TDD stage note was written with a hand-typed timestamp (2026-07-12T14:04:00Z) that is earlier than the planning note (17:44Z) and the ship CI (18:19Z), so the breadcrumb ordering is internally inconsistent. Impact: none functional — a misleading timestamp in one stage note; no rework. Self-identified during this retro.
What caused friction (user side)
- None.
The operator's freeform "fd, maybe other modern rewrites" addition was a reasonable extension, cleanly resolved by a second
ask_userround rather than a correction.
External / infra
- The
rumdl-fmtprek hook could not reinstall during the twodocs:commits (transientNo route to hostfetchingsetuptoolsviauv). Handled correctly: ranpnpm exec rumdl fmt+rumdl checkmanually (clean), then committed with--no-verify. This is a hook install failure (infra), not a hook check failure (a real lint issue) — the distinction is what made--no-verifythe right call rather than a bypass of a real gate. - The release-please PR surfaced
UNSTABLEwith a CI check stillIN_PROGRESS(not the empty-rollup no-checks case); handled per/ship-issuestep 6.4 — waited for the check viaci_watch, then retriedrelease_pr_merge(succeeded), rather than falling back togh pr mergewhile the check ran.
Diagnostic details
- Model-performance correlation — one subagent dispatch:
pre-completion-revieweronanthropic/claude-sonnet-5, doing judgment-heavy review (design, cross-step invariants, deviation assessment). Appropriate match; no mismatch. - Escalation-delay tracking — no
rabbit-holefriction; no sequence approached 5 consecutive tool calls on one error. - Unused-tool detection — none;
colgrep(planning exploration) and the disposable AST probe were the right tools and were used. - Feedback-loop gap analysis — no gap; verification was incremental after every change, not deferred to the end.
Changes made
AGENTS.md(after thecommitted-hook sentence in the Commits section) — added a note distinguishing aprekhook install failure (network error building the hook env → run the gate manually, then commit--no-verify) from a hook check failure (a real gate → fix it, never--no-verifypast it).packages/pi-permission-system/docs/retro/0490-floor-indirection-wrappers.md— this Final Retrospective stage entry.