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

53 lines
3.4 KiB
Markdown

# 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:
```json
"$schema": "https://raw.githubusercontent.com/gotgenes/pi-packages/main/packages/pi-permission-system/schemas/permissions.schema.json"
```