17 KiB
issue, issue_title
| issue | issue_title |
|---|---|
| 527 | pi-permission-system: delete dead yolo arms from the prompt path; dissolve yolo-mode.ts |
Delete the dead yolo arms from the prompt path; dissolve yolo-mode.ts
Release Recommendation
Release: ship now — batch "yolo-recorded-authority" tail (this issue completes the batch)
This issue is Phase 8 Step 3, the tail of the two-step "yolo-recorded-authority" release batch (Steps 2, 3).
Step 2 (#526) relocated the yolo decision to the composition stage with observable review-log/decision-event field changes and left its release-please PR open per a mid-batch — defer marker; this cleanup completes the batch, so shipping it releases the batch.
The commits here are refactor: (a hidden changelog type that does not cut a release on its own), but merging them to main finalizes the open release-please PR that Step 2's feat/fix opened.
Problem Statement
Step 2 (#526) moved yolo-mode from the prompt path into the composed ruleset: PermissionManager.check now applies rewriteAsksToYolo so every ask becomes an allow (tagged origin: "yolo") before it ever leaves the manager, and GateRunner writes the permission_request.auto_approved review entry from a yolo fast-path.
As a result, evaluate() never returns ask under yolo, so two decision-path branches became unreachable:
- the auto-approve arm at the top of
PermissionPrompter.prompt()(viashouldAutoApprovePermissionState), and - the yolo arm inside
PromptingGateway.canConfirm()(viacanResolveAskPermissionRequest).
This issue deletes those dead arms, dissolves src/yolo-mode.ts, and reduces canConfirm() to the two Authorizer-selection predicates the Phase 9 spine will consume.
Goals
- Remove the unreachable auto-approve arm from
PermissionPrompter.prompt(). - Reduce
PromptingGateway.canConfirm()tohasUI ∨ isSubagent; deletecanResolveAskPermissionRequestand itsAskPermissionResolutionOptionsinterface. - Dissolve
src/yolo-mode.ts: moveisYoloModeEnablednext to its config insrc/extension-config.ts; deleteshouldAutoApprovePermissionState. - Keep the forwarded-inbox serve arm's yolo check in place, re-pointed at
isYoloModeEnabled, with a code comment noting it dissolves in the Phase 9 spine work whenprocessInboxis refactored ontoevaluate(). - Drop the now-unused
configdependency fromPermissionPrompterDepsandPromptingGatewayDeps. - Non-breaking: no config, schema, review-log, or decision-event output changes.
Non-Goals
- Refactoring
processInbox/ serving-as-resolution ontoevaluate()— that is Phase 9 spine work, and the serve arm's yolo check stays until then. - Any change to
PermissionManager.check,rewriteAsksToYolo,GateRunner's yolo fast-path, orderiveResolution— Step 2 owns those and they are unchanged here. - Any change to the yolo status footer semantics in
src/status.ts(only its import path moves). - Changing the
yoloModeconfig field, its schema, its example, or/permission-system showdisplay.
Background
Relevant modules and their current yolo coupling:
src/yolo-mode.ts— three exports:isYoloModeEnabled(config)(readsconfig.yoloMode),shouldAutoApprovePermissionState(state, config)(=state === "ask" && isYoloModeEnabled), andcanResolveAskPermissionRequest({config, hasUI, isSubagent})(=hasUI || isSubagent || isYoloModeEnabled), plus theAskPermissionResolutionOptionsinterface used only by the last.src/permission-prompter.ts—prompt()opens withif (shouldAutoApprovePermissionState("ask", this.deps.config.current())) { … return autoApproved };configis its only other use ofthis.deps.config.src/prompting-gateway.ts—canConfirm()delegates tocanResolveAskPermissionRequest({ config, hasUI, isSubagent });configis its only use ofthis.deps.config.src/forwarded-permissions/permission-forwarder.ts:509— the serve arm callsshouldAutoApprovePermissionState("ask", this.config.current()); this check is intentionally retained.src/status.ts— the yolo footer callsisYoloModeEnabled(config); retained, import path moves.src/index.ts— importsisYoloModeEnabledfrom./yolo-mode(wires the manager'sisYoloEnabledreader), and passesconfig: configStoreinto both thePermissionPrompterandPromptingGatewayconstructors.
Constraints from AGENTS.md / package skill:
- The roadmap step completion marker (
✅on the Step 3 heading and its Mermaid node, plus stale metric rows) must land in the implementation doc-update commit, not a deferred ship commit. docs/architecture/architecture.mdnames internal symbols in narrative prose and a module-layout tree — both must be swept for the removedyolo-mode.ts/canResolveAskPermissionRequest/shouldAutoApprovePermissionState.- The #526 retro documented that
permission-prompter.md(which still describes the prompter's yolo-mode arm) updates ride with this issue.
Design Overview
The change is a pure narrowing: it deletes two unreachable branches and the config dependency they required, and relocates the one surviving predicate (isYoloModeEnabled) next to its config.
No new collaborator, no new interface, no behavior on the reachable path.
Decision model after the change
PermissionPrompter.prompt()— no yolo branch; always writes thewaitingentry, emits the UI-prompt event whenctx.hasUI, and delegates toforwarder.requestApproval.PromptingGateway.canConfirm()—this.context !== null && (hasUI || isSubagent).- The composition-stage rewrite (
PermissionManager.check, unchanged) remains the sole yolo decision point on the ask path; the serve arm remains the sole yolo decision point on the forwarded serving path.
isYoloModeEnabled moves to extension-config.ts
isYoloModeEnabled is a one-line reader over PermissionSystemExtensionConfig, which is defined in extension-config.ts — its natural home.
The move eliminates the yolo-mode.ts module entirely.
The serve arm's shouldAutoApprovePermissionState("ask", config) collapses to isYoloModeEnabled(config) — with the prompter arm gone, the only surviving caller always passed the literal "ask", so the state parameter is dead and shouldAutoApprovePermissionState is deleted rather than moved.
Serve-arm call site after the change (permission-forwarder.ts):
// Yolo serve-arm: auto-approve a forwarded request under yolo mode.
// This is the last yolo check outside the composed ruleset; it dissolves
// when `processInbox` is refactored onto evaluate() + Authorizer selection
// in the Phase 9 spine work (#530 seeds this; the spine consumes it).
if (isYoloModeEnabled(this.config.current())) {
this.logger.review("forwarded_permission.auto_approved", details);
decision = { approved: true, state: "approved" };
}
Dependency narrowing
Both PermissionPrompterDeps and PromptingGatewayDeps lose their config: ConfigReader field, because the only reader of config in each class was the deleted yolo branch.
index.ts drops config: configStore from both constructor calls.
This is a dependency-width improvement, not a widening — no design-review smell is introduced.
Design-review checklist (applied)
- Dependency width: the change removes a field (
config) from two dependency bags — narrowing, not widening. - Law of Demeter: no new reach-through;
this.deps.config.current()chains are deleted, not added. - Output arguments / scattered resets / parameter relay: none introduced.
- Test mock depth:
makeDepshelpers in the prompter and gateway tests shrink (drop theconfigfield).
No structural smell is added; the checklist confirms the change is a clean narrowing, so the fixes are inline (this PR), not a follow-up.
Module-Level Changes
Source:
src/extension-config.ts— addisYoloModeEnabled(config: PermissionSystemExtensionConfig): boolean(moved verbatim, including itsno-unnecessary-type-conversiondisable comment).src/yolo-mode.ts— deleted.src/permission-prompter.ts— remove the auto-approve arm fromprompt(); removeconfigfromPermissionPrompterDeps; remove theConfigReaderimport and theshouldAutoApprovePermissionStateimport; update the class/deps doc comments to drop the "Yolo-mode auto-approval check" step and the "config access" mention.src/prompting-gateway.ts—canConfirm()returnsthis.context !== null && (this.context.hasUI || isSubagentExecutionContext(...)); removeconfigfromPromptingGatewayDeps; remove theConfigReaderandcanResolveAskPermissionRequestimports; update the deps andcanConfirm()doc comments to drop the yolo-mode branch.src/forwarded-permissions/permission-forwarder.ts— switch the serve arm toisYoloModeEnabled(this.config.current()); change the import from#src/yolo-modeto#src/extension-config; add the retention comment shown above; update theconfigdeps JSDoc that says "yolo-mode auto-approve check".src/status.ts— change theisYoloModeEnabledimport from./yolo-modeto./extension-config.src/index.ts— change theisYoloModeEnabledimport from./yolo-modeto./extension-config; dropconfig: configStorefrom thePermissionPrompterandPromptingGatewayconstructor calls.
Tests:
test/permission-prompter.test.ts— delete thedescribe("yolo-mode auto-approve")block (4 tests for removed behavior); drop theconfigfield from themakeDepshelper (and removemakeConfigReaderif it becomes unused).test/prompting-gateway.test.ts— delete the "returns true when yolo mode is enabled (no UI, not subagent)" test; simplify the two remainingyoloMode-parameterizedcanConfirmtests to drop the now-irrelevant yolo config; dropconfigfrom the gatewaymakeDepshelper.test/yolo-mode.test.ts— deleted: its two subjects (shouldAutoApprovePermissionState,canResolveAskPermissionRequest) are removed, and its loneresolvePermissionForwardingTargetSessionIdassertion is already covered bytest/permission-forwarding.test.ts("isSubagent=true, no candidates set returns null").test/extension-config.test.ts— add anisYoloModeEnableddescribe block (on/off/undefinedyoloMode), giving the relocated function direct unit coverage at its new home.test/permission-forwarder.test.ts— no change: the serve-arm yolo test (yoloMode: true→forwarded_permission.auto_approved) stays green becauseisYoloModeEnabledis behavior-identical to the oldshouldAutoApprovePermissionState("ask", …); it pins the retained serve arm.
Docs (in the implementation doc-update commit):
docs/architecture/architecture.md— mark Step 3✅on both the step heading and its Mermaid node; remove theyolo-mode.tsline from the module-layout tree; update theprompting-gateway.tstree description to drop "yolo-mode" from the can-prompt policy; flip the "yolo checks on the ask path" and "canConfirm() predicates" metric rows to their post-Step-3 values.docs/architecture/permission-prompter.md— remove the yolo-mode step (item 1), thegetConfig()yolo comment, and the "Yolo-mode is handled at the prompter level" paragraph so the doc reflects the arm's removal.
Test Impact Analysis
- New tests enabled: a direct
isYoloModeEnabledunit test intest/extension-config.test.ts. Previously the function had no direct test — it was exercised only transitively throughshouldAutoApprovePermissionState/canResolveAskPermissionRequestin the now-deletedyolo-mode.test.ts. - Redundant tests removed: the prompter
yolo-mode auto-approveblock (the behavior moved toGateRunnerin Step 2 and is tested there); the wholeyolo-mode.test.tsfile (its subjects are deleted and its forwarding-target assertion duplicates existingpermission-forwarding.test.tscoverage). - Tests that must stay: the
permission-forwarder.test.tsserve-arm yolo test (genuinely exercises the retained serve-arm check) and the Step 2permission-manager/GateRunneryolo tests (pin the composition-stage invariant this cleanup must not regress).
Invariants at risk
Step 2 (#526) landed three documented outcomes that this step must not regress:
evaluate()is the only yolo decision point on the ask path; yoloask→allowhappens inPermissionManager.checkviarewriteAsksToYolo. Pinned by thePermissionManageryolo-rewrite tests — untouched here (the manager is not modified).- A yolo-origin
allowreports resolutionauto_approvedviaGateRunner's yolo fast-path and thepermission_request.auto_approvedreview entry. Pinned by theGateRunneryolo tests — untouched here (the runner is not modified). - The forwarded-inbox serve arm auto-approves under yolo and logs
forwarded_permission.auto_approved. Pinned bytest/permission-forwarder.test.ts(yoloMode: true) — this step re-points the arm fromshouldAutoApprovePermissionStateto the behavior-identicalisYoloModeEnabled, and the test stays green, confirming no regression.
The removal of the prompter arm is safe because the #526 retro recorded an exhaustive reachability trace: every ask-producing surface (tool / bash / mcp / path / external_directory / skill-input via manager.check, and skill-read via the yolo-aware sanitizer) resolves to allow under yolo before the prompter is reached, so no ask reaches PermissionPrompter.prompt() under yolo.
TDD Order
-
Remove the prompter auto-approve arm. Test surface:
test/permission-prompter.test.ts. Delete thedescribe("yolo-mode auto-approve")block and dropconfigfrommakeDeps; then remove the arm and theconfigfield frompermission-prompter.ts, dropconfig: configStorefrom the prompter constructor inindex.ts. (shouldAutoApprovePermissionStatestill exists for the serve arm, soyolo-mode.tsstill compiles.) Verify:pnpm --filter @gotgenes/pi-permission-system run testgreen;grep -n "config" src/permission-prompter.tsshows noConfigReader. Commit:refactor(pi-permission-system): remove dead yolo arm from PermissionPrompter. -
Reduce
canConfirm()and deletecanResolveAskPermissionRequest. Test surface:test/prompting-gateway.test.ts,test/yolo-mode.test.ts. Delete/simplify the gateway yolo tests and dropconfigfrom itsmakeDeps; remove thecanResolveAskPermissionRequestdescribe and catch-all tests fromyolo-mode.test.ts; then setcanConfirm()tohasUI ∨ isSubagentand dropconfigfromprompting-gateway.ts, deletecanResolveAskPermissionRequest+AskPermissionResolutionOptionsfromyolo-mode.ts, and dropconfig: configStorefrom the gateway constructor inindex.ts. Because removing theconfigfield fromPromptingGatewayDepsbreaks its constructor call site and itsmakeDepsat the type level in the same commit, all three land together. Verify: suite green. Commit:refactor(pi-permission-system): reduce canConfirm to hasUI or isSubagent. -
Dissolve
yolo-mode.ts. Test surface:test/extension-config.test.ts(newisYoloModeEnabledblock), deletetest/yolo-mode.test.ts. MoveisYoloModeEnabledintoextension-config.ts; re-point the serve arm inpermission-forwarder.tstoisYoloModeEnabled(import from#src/extension-config) with the retention comment; updatestatus.tsandindex.tsimports to./extension-config; deleteshouldAutoApprovePermissionStateand the now-emptyyolo-mode.ts. Deletingyolo-mode.tsbreaks every importer at the type level in this commit, so all import updates land together. Verify: suite green;grep -rn "yolo-mode" src/ test/returns nothing;pnpm --filter @gotgenes/pi-permission-system run checkandpnpm fallow dead-codeclean. Commit:refactor(pi-permission-system): dissolve yolo-mode.ts into extension-config. -
Doc updates + roadmap completion marker. No test surface. Mark Step 3
✅(heading + Mermaid node) inarchitecture.md, remove theyolo-mode.tstree line, update theprompting-gateway.tstree description, flip the two metric rows; strip the yolo-mode content frompermission-prompter.md. Verify:pnpm --filter @gotgenes/pi-permission-system run lint(rumdl) clean; Mermaid renders. Commit:docs(pi-permission-system): mark Phase 8 Step 3 complete; drop yolo-mode from prompt-path docs.
Risks and Mitigations
- Risk: an
askstill reaches the prompter under yolo, making the removed arm load-bearing. Mitigation: the #526 reachability trace proves noaskreaches the prompter under yolo; the serve-arm and composition-stage tests continue to pin the two surviving yolo decision points. - Risk:
import type { ConfigReader }orshouldAutoApprovePermissionStateleft dangling after an interface-field removal (a dropped edit passestscbecause unused type imports are not errors). Mitigation: runpnpm run checkunpiped and re-read each edited file;pnpm fallow dead-codein Step 3 gates unused exports. - Risk: a stale
yolo-mode.ts/canResolveAskPermissionRequestreference left in a live doc. Mitigation: Step 4 sweepsdocs/architecture/architecture.mdandpermission-prompter.md; historicaldocs/plans/*anddocs/retro/*are frozen and intentionally not edited.
Open Questions
None. No follow-up issues are filed by this plan — the retained serve-arm yolo check already has its Phase 9 dissolution tracked by the spine steps (#530 and the Phase 9 roadmap).