Files
my-pi/pi-permission-system/docs/retro/0511-retire-residual-getplatform-threading.md
T

101 lines
8.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
issue: 511
issue_title: "pi-permission-system: retire the residual getPlatform() threading (infra-read + skill-prompt sanitization)"
---
# Retro: #511 — Retire the residual `getPlatform()` threading (infra-read + skill-prompt sanitization)
## Stage: Planning (2026-06-28T23:10:40Z)
### Session summary
Produced a numbered plan to route the two residual `platform`-threading sites (external-directory infra-read and skill-prompt sanitization) through the session's `PathNormalizer`, adding two methods (`comparableValue`, `isInfrastructureRead`) and dropping the `platform` parameters from `describeExternalDirectoryGate`, `describeSkillReadGate`, and the skill-sanitizer functions.
Filed follow-up #513 for the final `getPlatform()` removal, which neither #511 nor #502 can complete alone (the pipeline reads `getPlatform()` once and threads it to three gates; the tool gate keeps it until #502 lands).
### Observations
- The issue raises a genuine fork for skill sanitization (carry `AccessPath`s vs. resolve through the normalizer).
Resolved decisively against carrying `AccessPath`s: `AccessPath.forPath` eagerly computes the canonical alias via `realpathSync` (`canonicalize-path.ts`), so it would add per-entry, per-turn filesystem access and shift lexical matching toward canonical — both behavior changes against a behavior-preserving refactor.
Chose a lexical `comparableValue` method instead.
Did not invoke `ask_user` because the behavior-preservation invariant removes the real choice (one option violates it).
- Confirmed `tcc.cwd === ctx.cwd === normalizer.cwd` (set in `permission-gate-handler.ts` and rebuilt on `session.activate`), which is what makes moving the `cwd` argument onto the baked normalizer behavior-preserving.
- `isInfrastructureRead` takes the already-built `AccessPath` (not a raw path) so the gate does not re-run `forPath`/`realpathSync` — Tell-Don't-Ask plus no double FS.
- Steps 14 of Phase 7 (#502#505) are all still OPEN, so this plan deliberately leaves `getPlatform()` in place rather than removing it; the dependency, not ambiguity, forces that.
- `getPlatform()` removal depends only on #511 + #502 (the only two `getPlatform()` readers are `before-agent-start.ts` and the pipeline; the pipeline's read survives for the tool gate until #502).
It does not depend on #503/#504, which touch `input-normalizer.ts` (that file gets `platform` elsewhere, not via `getPlatform()`).
- Doc surface to update: `architecture.md` (normalizer method list + the residual-threading subsection) and the package `SKILL.md` (normalizer method list in Debugging).
Plans/retros under `docs/` are historical and left untouched.
## Stage: Implementation — TDD (2026-06-28T19:32:00Z)
### Session summary
Completed all four planned TDD cycles: added `PathNormalizer.comparableValue` + `isInfrastructureRead`, routed the external-directory infra-read through the normalizer, routed skill-prompt sanitization through the normalizer, and updated the architecture doc + package `SKILL.md`.
Test count went 2189 → 2195 (+6 new `PathNormalizer` tests); full suite, `check`, `lint`, and `fallow dead-code` all green.
Pre-completion reviewer returned PASS.
### Observations
- Pre-completion reviewer: PASS.
No WARN findings.
- Deviation: also updated `test/handlers/external-directory-symlink-acceptance.test.ts` (not in the plan's listed test files) — it threaded the same `platform` arg into `describeExternalDirectoryGate`; `pnpm run check` caught it after the Step 2 production edit.
Noted in the Step 2 commit body.
- The plan's lexical-no-FS decision held up: `comparableValue` delegates to `normalizePathForComparison` (no `realpathSync`), and `forPath` is intentionally absent from all skill-entry code paths.
- Self-inflicted friction: a blind `sed 's/"linux"/normalizer/g'` over the two skill test files also rewrote the `"linux"` inside the `new PathNormalizer("linux", cwd)` const declarations themselves (→ `new PathNormalizer(normalizer, cwd)`), which surfaced as a `ReferenceError: Cannot access 'normalizer' before initialization`.
Fixed with a follow-up targeted `sed`.
Lesson: exclude the constructor lines (or add the const after the bulk replacement) when mass-replacing a platform literal.
- `getPlatform()` left in place as planned (still feeds `describeToolGate` until #502); the `ToolCallGatePipeline.evaluate` `const platform` read remains.
Final removal tracked in #513.
- biome's pre-commit hook reflowed the Step 3 sanitizer test (collapsed now-short multi-line calls); re-staged and committed cleanly.
## Stage: Final Retrospective (2026-06-28T23:53:50Z)
### Session summary
Shipped #511 end-to-end in one continuous session (plan → TDD → ship): a behavior-preserving refactor routing the two residual `platform`-threading sites through `PathNormalizer`, landing 4 commits, +6 tests (2189 → 2195), pre-completion PASS, CI green, issue closed.
No release was cut — every commit is `refactor:` (hidden changelog type) or `docs:` on an `exclude-paths` directory, so the work auto-batches into the next releasing commit, exactly as the plan's `**Release:** ship independently` rationale predicted.
The session was clean: the two friction points were minor, self-inflicted, and each caught immediately by a deterministic check (`tsc` / a test run).
### Observations
#### What went well
- **Hard-invariant resolution of an issue-flagged "decision."**
The issue explicitly forked skill sanitization (carry `AccessPath`s vs. route through the normalizer).
The behavior-preservation invariant (`AccessPath.forPath` eagerly calls `realpathSync` via `canonicalize-path.ts`) made one branch a behavior change, collapsing the fork to a single correct answer (a lexical `comparableValue` method) without spending an `ask_user` round.
Recognizing when a constraint — not preference — settles a flagged decision kept planning tight.
- **Proactive cross-issue follow-up (#513).**
The final `getPlatform()` removal cannot be completed by #511 or #502 alone (the pipeline reads it once and threads it to three gates; the tool gate keeps it until #502).
Filing #513 during planning gave that order-independent cleanup an explicit home rather than leaving an untracked dangling accessor.
- **Incremental feedback loop did its job.** `pnpm run check` ran after each signature-changing step and caught the one un-planned test file (`external-directory-symlink-acceptance.test.ts`) the moment Step 2's production edit landed — no end-of-session surprise.
- **Pre-completion reviewer PASS on first dispatch** (ran on `anthropic/claude-sonnet-4-6`, model well-matched to judgment-heavy review).
#### What caused friction (agent side)
- `missing-context` (self-identified) — the plan's Module-Level Changes listed only `external-directory.test.ts` for the external-directory gate, but the planning-stage `grep -rln describeExternalDirectoryGate test/` had already surfaced `external-directory-symlink-acceptance.test.ts` too.
The grep result was in hand; it just wasn't carried into the plan's test-file list.
Impact: one extra `sed` fix during TDD Step 2, caught instantly by `tsc`.
No rework beyond that; noted in the Step 2 commit body.
- `other` (self-identified) — a blind `sed 's/"linux"/normalizer/g'` over the two skill test files also rewrote the literal inside the `new PathNormalizer("linux", cwd)` const declarations I had just added (→ `new PathNormalizer(normalizer, cwd)`), producing `ReferenceError: Cannot access 'normalizer' before initialization`.
Impact: ~2 extra tool calls (one failed test run + one targeted `sed`).
Self-caught on the next test run.
Lesson: when mass-replacing a literal, guard the new symbol's own declaration (replace before introducing the const, or exclude the constructor line).
#### What caused friction (user side)
- None.
The session ran autonomously across all three stages with no user correction or redirect needed.
### Diagnostic details
- **Model-performance correlation** — one subagent dispatched (pre-completion-reviewer) on `anthropic/claude-sonnet-4-6`; appropriate for judgment-heavy review (acceptance criteria, design, invariants).
No mismatch.
- **Feedback-loop gap analysis** — no gap: `pnpm run check` ran after each shared-signature step, the affected test file ran per Red/Green cycle, and the full suite + `lint` + `fallow` ran once before the pre-completion dispatch.
Verification was incremental, not deferred.
- Escalation-delay and unused-tool lenses found nothing notable (no rabbit-hole exceeded 1 follow-up tool call; no missing-context warranted a subagent).
### Changes made
1. Added this Final Retrospective stage entry to `packages/pi-permission-system/docs/retro/0511-retire-residual-getplatform-threading.md`.
No `AGENTS.md` or prompt changes — the session's two friction points were minor, self-caught by deterministic checks, and already covered by existing guidance (confirmed with the operator).