Files
my-pi/pi-permission-system/docs/migration/strict-config-validation.md

3.4 KiB

Migration guide: strict config validation

Starting with the release that closes #547, the permission-system config loader validates each config file against a JSON Schema derived from a zod source of truth. This is a breaking change in how malformed config is handled.

What changed

The loader used to be tolerant: it silently discarded a malformed field and loaded the rest. For example, a config with "debugLog": "yes" (a string, not a boolean) simply dropped debugLog and kept going; an unknown key like "debugLo": true was ignored.

The loader is now strict and fail-closed:

  • A config file with any invalid field is rejected as a whole scope (global or project).
  • The rejected scope contributes no permission rules.
  • Each problem is reported as a clear, path-qualified issue in the permission review log (and the debug log when debugLog is on).

Nothing about the config format changed — a config that was already valid keeps working unchanged.

Cross-scope hardening (fail closed on an invalid higher scope)

Rejecting a scope's rules is only half the story. Because a higher-precedence scope that contributes no rules leaves the lower scope's rules in place, an invalid higher scope used to silently inherit the lower scope's policy — including a permissive allow. For example, a global bash: allow remained effective even when a project config meant to deny bash but contained a typo.

The loader now fails closed across scopes as well: when a non-global scope (project config, global agent frontmatter, or project agent frontmatter) is present but invalid, the effective policy is floored so nothing resolves more permissively than ask. Every allow — including one inherited from a lower scope — is clamped to ask; deny and ask are unchanged. Alongside the per-problem validation issues, a distinct notice is reported: Invalid <scope> configuration detected — failing closed: 'allow' rules are clamped to 'ask' ….

An invalid global scope does not trigger the cross-scope clamp — it is the lowest precedence, so nothing more permissive is inherited when it fails. The clamp is deny-preserving, and (like yoloMode) applied at composition; when yoloMode is on it re-permits the floored ask back to allow. Fix the reported problems and reload to restore the intended policy.

What you need to do

If your config was valid, nothing.

If a scope stops taking effect after upgrading (surfaces start prompting with ask), open the permission review log and look for Invalid config value at '<path>': … or Unrecognized config key '<key>'. messages. Fix each reported problem, then reload.

Common fixes:

  • Wrong type — e.g. "toolInputPreviewMaxLength": "400" (string) → 400 (number); "debugLog": "true"true.
  • Unknown key — a typo ("debugLo""debugLog") or a legacy top-level policy key (defaultPolicy, tools, bash, …) that belongs under permission (see legacy-to-flat.md).
  • Invalid permission action — an action must be "allow", "deny", or "ask" (or a { "action": "deny", "reason": "…" } object).

Editor support

Add the hosted schema to your config for autocomplete and inline validation, so these problems surface as you type:

"$schema": "https://raw.githubusercontent.com/gotgenes/pi-packages/main/packages/pi-permission-system/schemas/permissions.schema.json"