feat: vendor permission system source

This commit is contained in:
云服务部-叶林立
2026-08-19 14:35:19 +08:00
parent 198584daf8
commit 410c50a3e5
809 changed files with 157793 additions and 139 deletions
@@ -0,0 +1,97 @@
---
issue: 336
issue_title: "Make the logger injectable; drop createSessionLogger(runtime)"
---
# Retro: #336 — Make the logger injectable; drop `createSessionLogger(runtime)`
## Stage: Planning (2026-06-06T21:30:54Z)
### Session summary
Produced a numbered implementation plan for Phase 4 Step 3 (Track B): repurpose `createSessionLogger` to take narrow deps (`globalLogsDir` + a `getConfig` thunk + a `notify` sink) instead of the whole `ExtensionRuntime`, fold the JSONL-writer composition + warning-dedup + `warn` into that one factory, expose the built logger as `runtime.logger`, and drop the five `.bind(runtime)` adapters in `index.ts`.
The plan is a two-step lift-and-shift (Step 1 adds `runtime.logger` while keeping the old runtime methods as thin delegators; Step 2 removes the old methods + the `.bind` adapters), keeping the repo green between commits.
### Observations
- The logger ↔ `ConfigStore` cycle (logger needs config toggle; `ConfigStore` writes through the logger) is broken cleanly with a lazy `getConfig: () => configStore.current()` thunk — the logger object is fully built before `ConfigStore`; only the config *value* is read lazily.
This replaces the existing stub-then-reassign forward reference, not just relocates it.
- Naming is intentionally left split: consumer deps keep `writeReviewLog` / `writeDebugLog` field names mapped to `logger.review` / `logger.debug` values.
Unifying the names is [#338]'s job — flagged as an Open Question.
- Logger construction stays in `createExtensionRuntime()` (not `index.ts`) because `ConfigStore` is built there until [#337] dissolves the runtime; moving it now would pre-empt and re-do [#337].
- The architecture doc's Step 3 target list (`session-logger.ts`, `logging.ts`, `index.ts`) omits `runtime.ts`, but removing the `writeDebugLog` / `writeReviewLog` fields and the inline logger construction unavoidably edits `runtime.ts` — noted in the plan.
`logging.ts` itself needs no edit (it already takes narrow options and has no runtime reference); it is merely composed by the new `createSessionLogger`.
- `SessionLogger` interface (`debug` / `review` / `warn`) is unchanged, so `decision-reporter.ts`, `handlers/lifecycle.ts` (sole `warn` caller), `permission-session.ts`, and the test fixtures need no edits — keeps blast radius small.
- Grep confirms `runtime.writeDebugLog` / `writeReviewLog` live only in `runtime.ts`, `index.ts`, `test/runtime.test.ts`; `createSessionLogger` only in `index.ts`, `session-logger.ts`, `test/session-logger.test.ts`.
- `[#335]` (ConfigStore) is complete and provides the `RuntimeContextRef` seam reused by the notify sink and the `ConfigReader` for the debug toggle.
[#337]: https://github.com/gotgenes/pi-packages/issues/337
[#338]: https://github.com/gotgenes/pi-packages/issues/338
## Stage: Implementation — TDD (2026-06-06T22:00:00Z)
### Session summary
Completed two TDD cycles (Step 1: inject `SessionLoggerDeps` + expose `runtime.logger`; Step 2: remove `writeDebugLog`/`writeReviewLog` from the interface and replace `.bind(runtime)` adapters).
Test count moved from 1840 → 1837 (3 net: 4 delegation tests removed, 11 new `session-logger.test.ts` tests added, some overlap with rewritten tests).
Architecture doc updated with Step 3 `✓ complete` mark and revised `session-logger.ts` description.
### Observations
- Two ESLint issues in Step 1: `prefer-const` on `let configStore` (fixed by initializing to `null as unknown as ConfigStore`) and `unbound-method` on bare `logger.debug`/`logger.review` references in the `ConfigStoreLogger` (fixed with arrow wrappers).
- Step 2 had the same `unbound-method` issue on the five `index.ts` adapter sites; same arrow-wrapper fix applied.
The type-level fix (`this: void` on `SessionLogger` interface) was blocked by `@typescript-eslint/no-invalid-void-type` which does not allow `allowAsThisParameterType` in this project's config.
Five unnecessary wrapper closures remain as an ESLint-compatibility workaround.
- Pre-completion reviewer: WARN (two observations, neither blocking).
### Reviewer warnings
- **WARN** — `src/index.ts` lines 4952, 61, 109: five `(event, details) => runtime.logger.review(event, details)` closures are unnecessary (bare references type-check under contravariance), but `@typescript-eslint/unbound-method` blocks bare references without a project-wide ESLint config change.
Deferred to [#338] which already owns the consumer deps bag cleanup.
- **WARN** — `src/runtime.ts` notify sink (`runtimeContext?.ui.notify`) is a transitional LoD seam; acknowledged in the plan, deferred to [#337].
## Stage: Final Retrospective (2026-06-06T22:18:35Z)
### Session summary
One continuous session carried #336 through plan → TDD → ship → retro across three models (`opus-4-8` planning, `sonnet-4-6` TDD, `deepseek-v4-flash` ship).
The injectable-`SessionLogger` refactor landed in two clean lift-and-shift commits plus a docs commit, CI passed, and the issue closed with no release (all `refactor:`/`docs:` commits).
The only notable friction was a mid-TDD path-confusion correction from the user and a one-round-trip ESLint rule conflict.
### Observations
#### What went well
- The lazy `getConfig: () => configStore.current()` thunk broke the logger ↔ `ConfigStore` construction cycle on the first try — no stub-then-reassign, no test flakiness.
The plan predicted this exactly; implementation matched.
- Two-step lift-and-shift kept the repo green between commits: Step 1 added `runtime.logger` with the old methods as thin delegators, Step 2 removed them.
`pnpm run check` / `lint` / full suite ran after every step, not just at the end — no feedback-loop gap.
- Pre-completion reviewer (fresh-context subagent) correctly flagged the five gratuitous wrapper closures as a non-blocking WARN, and the agent verified the contravariance claim with a throwaway `tsc` check before accepting it.
#### What caused friction (agent side)
- `instruction-violation` (user-caught) — during TDD the agent ran package-scoped commands as `cd packages/pi-permission-system && grep/sed/pnpm run …`, and in one case mixed that with a bare `grep "init-declar" ../../eslint.config.js` (no `cd`), which resolved *outside* the repo from the CWD and returned a misleading "not found".
`AGENTS.md` already says to run package scripts from the root via `pnpm --filter` / `pnpm -C`, not `cd packages/<pkg> && …`.
Impact: user intervened ("I don't trust that you are looking at the correct paths") at message 37; the agent re-verified with `pwd` + absolute paths.
No rework to committed code (the config facts happened to be correct), but a real trust dip and one wrong-path read.
- `rabbit-hole` (minor) — the `unbound-method` ESLint error on bare `logger.review` references was first "fixed" by annotating the `SessionLogger` interface with `this: void` (the rule's own suggestion), which `@typescript-eslint/no-invalid-void-type` then rejected.
Impact: ~5 tool calls and one revert before settling on arrow wrappers; self-resolved, no user involvement.
#### What caused friction (user side)
- The path-confusion correction at message 37 was a terse distrust signal rather than a pointer to the specific bad command.
A redirect naming the `cd`-into-subdir + `../../` mix (or the `AGENTS.md` root-run rule) would have shortened the recovery.
Framed as opportunity, not criticism — the signal was correct and well-timed.
### Diagnostic details
- **Model-performance correlation** — planning on `opus-4-8` (judgment-heavy design) and TDD on `sonnet-4-6` (implementation) were well-matched.
Ship ran on `deepseek-v4-flash`, a low-cost model, and handled a non-trivial judgment (detecting the `#335 → #338` stacked sequence, asking the user, diagnosing why no release-please PR existed) correctly — no quality mismatch in outcome.
The pre-completion-reviewer ran as a separate subagent (its model is set by agent frontmatter, not visible in the parent transcript).
- **Escalation-delay tracking** — the `unbound-method` / `this: void` / `no-invalid-void-type` sequence spanned ~5 consecutive tool calls (messages 6371) with one wrong turn; below the "dispatch a subagent" threshold and self-resolved.
- **Feedback-loop gap analysis** — none; verification ran incrementally after each change, plus full suite + `pnpm fallow dead-code` before the Step 2 commit.
### Changes made
1. `AGENTS.md` — added the `unbound-method` / `no-invalid-void-type` conflict (rule + arrow-wrapper fix) to the existing "Biome / ESLint linter conflicts" section.
2. `packages/pi-permission-system/docs/retro/0336-inject-session-logger.md` — appended this Final Retrospective stage entry.