Files
my-pi/pi-permission-system/docs/plans/archive/0006-log-resolved-config-paths.md

9.6 KiB

issue, issue_title
issue issue_title
6 Log resolved config paths at startup so misconfiguration is debuggable

Log resolved config paths at startup

Problem Statement

There is no way for a user to see which permission-config files the extension actually loaded. When permissions do not work as expected, debugging requires reading source to figure out the search paths. A single review-log entry listing all resolved paths and their existence status would unblock most debugging.

Goals

  • At extension startup (and on each session_start), emit a single config.resolved review-log entry listing every config path the extension considers and whether each file exists.
  • Also emit the entry to the debug log when debug logging is enabled.
  • Expose a getResolvedConfigPaths() method on PermissionManager so the path set is testable without IO side effects.
  • The log entry appears regardless of whether any rules are ultimately matched.

Non-Goals

  • A pi config-style TUI view (mentioned in the issue as a bonus; separate work).
  • Changing any policy semantics, merge precedence, or on-disk identity.
  • Logging the contents of each config file (only paths and existence).

Background

Relevant modules

  • src/permission-manager.tsPermissionManager constructor receives globalConfigPath, agentsDir, projectGlobalConfigPath, projectAgentsDir, plus internally computes legacyGlobalSettingsPath and globalMcpConfigPath. The paths are stored as private fields but are not externally queryable.
  • src/extension-config.tsCONFIG_PATH is the extension's own config.json. loadPermissionSystemConfig loads it and returns a result with optional warning.
  • src/index.tspiPermissionSystemExtension wires everything together. refreshExtensionConfig() runs at module load and on every session_start. createPermissionManagerForCwd() constructs the PermissionManager with project-scoped paths derived from ctx.cwd.
  • src/logging.tsPermissionSystemLogger.review() and .debug() write structured JSONL lines.

Permission surface

This is a diagnostic/observability concern. No permission surface (tools / bash / mcp / skills / special / external_directory) is changed. No policy semantics or merge precedence is affected.

Prerequisites

  • Issue #4 (warn on misplaced permission keys in config.json) is already implemented and closed. This plan builds on the same startup path but is independent.

Design Overview

Data shape

interface ResolvedConfigPaths {
  extensionConfigPath: string;
  extensionConfigExists: boolean;
  globalConfigPath: string;
  globalConfigExists: boolean;
  projectConfigPath: string | null;
  projectConfigExists: boolean;
  agentsDir: string;
  agentsDirExists: boolean;
  projectAgentsDir: string | null;
  projectAgentsDirExists: boolean;
}

New method on PermissionManager

Add a public getResolvedConfigPaths(): ResolvedConfigPaths method that returns the struct above. It checks existsSync for each path (cheap, synchronous, already imported in the file). The method is a pure query — no caching side effects.

extensionConfigPath is not owned by PermissionManager; it will be passed in by the caller (or injected via a new optional constructor option) since the extension config path lives in extension-config.ts. To keep the method self-contained without adding a constructor dependency, the caller in index.ts will combine the PermissionManager paths with the extension config path when building the log entry.

Revised approach: add getResolvedPolicyPaths() to PermissionManager (returns only the policy-related paths it owns), and have the startup reporter in index.ts merge in the extension config path to produce the full config.resolved log entry.

interface ResolvedPolicyPaths {
  globalConfigPath: string;
  globalConfigExists: boolean;
  projectConfigPath: string | null;
  projectConfigExists: boolean;
  agentsDir: string;
  agentsDirExists: boolean;
  projectAgentsDir: string | null;
  projectAgentsDirExists: boolean;
}

Where the log entry is emitted

In index.ts, after permissionManager is (re)created in the session_start handler, call a new helper logResolvedConfigPaths(permissionManager) that:

  1. Calls permissionManager.getResolvedPolicyPaths().
  2. Adds extensionConfigPath / extensionConfigExists from CONFIG_PATH.
  3. Writes a single config.resolved entry to both writeReviewLog and writeDebugLog.

Log entry format

{
  "event": "config.resolved",
  "extensionConfigPath": "/…/pi-permission-system/config.json",
  "extensionConfigExists": true,
  "globalConfigPath": "/…/.pi/agent/pi-permissions.jsonc",
  "globalConfigExists": false,
  "projectConfigPath": "/…/my-project/.pi/agent/pi-permissions.jsonc",
  "projectConfigExists": true,
  "projectConfigExists": true,
  "agentsDir": "/…/.pi/agent/agents",
  "agentsDirExists": true,
  "projectAgentsDir": "/…/my-project/.pi/agent/agents",
  "projectAgentsDirExists": false
}

Edge cases

  • projectConfigPath and projectAgentsDir are null when no cwd is available — logged as null with *Exists: false.
  • Extension config file missing (first run, ensurePermissionSystemConfig creates it) — extensionConfigExists reflects state after the ensure step, so it will be true.
  • Multiple session_start events (reload) — the entry is emitted each time, which is correct since cwd and thus project paths may change.

Module-Level Changes

src/permission-manager.ts

  1. Add ResolvedPolicyPaths interface (exported).
  2. Add public getResolvedPolicyPaths(): ResolvedPolicyPaths method that returns the five path pairs using existsSync.

src/index.ts

  1. Import existsSync (already imported) and CONFIG_PATH (already imported).
  2. Add logResolvedConfigPaths(pm: PermissionManager): void helper that assembles the full entry and writes to both review and debug logs.
  3. Call logResolvedConfigPaths(permissionManager) in the session_start handler after permissionManager is created.

tests/permission-manager.test.ts (existing or new)

  1. Add tests for getResolvedPolicyPaths() covering: all paths exist, none exist, mixed, null project paths.

tests/index.test.ts or tests/config-resolved-log.test.ts (new)

  1. Add tests for the log-entry assembly helper (if extracted as a pure function) or integration-level tests verifying the review log contains a config.resolved entry after startup.

No changes needed

  • schemas/permissions.schema.json — no policy-schema change.
  • config/config.example.json — no extension-config change.
  • README.md — could mention the diagnostic log entry, but optional and can be a follow-up.

TDD Order

  1. Red: test that getResolvedPolicyPaths() returns correct paths and existence flags when all policy files exist. test: getResolvedPolicyPaths returns paths and existence when files exist

  2. Green: implement ResolvedPolicyPaths interface and getResolvedPolicyPaths() on PermissionManager. feat: add getResolvedPolicyPaths to PermissionManager (#6)

  3. Red: test that getResolvedPolicyPaths() returns *Exists: false when files are missing and null for absent project paths. test: getResolvedPolicyPaths handles missing files and null project paths

  4. Green: already passes from step 2 (or adjust).

  5. Red: test the log-entry assembly helper produces the expected config.resolved shape combining extension config path with policy paths. test: config.resolved log entry merges extension and policy paths

  6. Green: implement buildResolvedConfigLogEntry() helper in index.ts (or a small src/config-reporter.ts if warranted) and wire into session_start. feat: emit config.resolved review-log entry at startup (#6)

  7. Red: integration test verifying the review log receives a config.resolved entry after a simulated startup sequence. test: config.resolved entry appears in review log after session_start

  8. Green: already passes from step 6 (or adjust wiring).

  9. Docs: mention the config.resolved log entry in README.md under a debugging/troubleshooting section. docs: document config.resolved diagnostic log entry (#6)

Risks and Mitigations

Risk Mitigation
existsSync on every session_start adds latency Five synchronous stat calls are negligible; no mitigation needed.
Could this silently weaken a permission? No — this change only adds a diagnostic log entry. Permission resolution logic is untouched.
Log entry leaks sensitive path information Paths are already visible in the debug log and are user-controlled config locations; no new exposure.
getResolvedPolicyPaths exposes internal paths of PermissionManager The paths are user-configured inputs, not secrets. Exposing them is the explicit goal.
Multiple session_start handlers already exist in index.ts The log call will be added to the existing handler, not a new duplicate.

Open Questions

  • Whether to extract the log-entry builder into a standalone src/config-reporter.ts module or keep it inline in index.ts. Decision can be made during implementation based on size.