mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-08-28 08:35:57 +00:00
366 lines
7.6 KiB
Markdown
366 lines
7.6 KiB
Markdown
# Migration guide: legacy format → flat permission format
|
|
|
|
This guide covers migration from the pre-#66 config format to the flat `permission` format introduced in #66.
|
|
|
|
## Summary of changes
|
|
|
|
The old format had six top-level policy keys (`defaultPolicy`, `tools`, `bash`, `mcp`, `skills`, `special`).
|
|
The new format has a single `permission` key whose top-level entries map surface names to actions.
|
|
|
|
Runtime knobs (`debugLog`, `permissionReviewLog`, `yoloMode`) are **unchanged** and stay at the top level.
|
|
|
|
## Checklist
|
|
|
|
Go through each section below.
|
|
For each key present in your config, apply the translation and remove the old key.
|
|
|
|
- [ ] `defaultPolicy`
|
|
- [ ] `tools`
|
|
- [ ] `bash`
|
|
- [ ] `mcp`
|
|
- [ ] `skills`
|
|
- [ ] `special`
|
|
- [ ] Per-agent frontmatter
|
|
|
|
## Translation reference
|
|
|
|
### `defaultPolicy`
|
|
|
|
`defaultPolicy` set per-surface fallback actions.
|
|
In the flat format the universal fallback is `permission["*"]`; per-surface catch-alls are entries in the `permission` object.
|
|
|
|
```jsonc
|
|
// Before
|
|
{
|
|
"defaultPolicy": {
|
|
"tools": "ask",
|
|
"bash": "ask",
|
|
"mcp": "ask",
|
|
"skills": "ask",
|
|
"special": "ask"
|
|
}
|
|
}
|
|
|
|
// After — all surfaces default to "ask" via the universal fallback
|
|
{
|
|
"permission": {
|
|
"*": "ask"
|
|
}
|
|
}
|
|
```
|
|
|
|
If surfaces had **different** defaults, express each one explicitly:
|
|
|
|
```jsonc
|
|
// Before
|
|
{
|
|
"defaultPolicy": {
|
|
"tools": "allow",
|
|
"bash": "ask",
|
|
"mcp": "ask",
|
|
"skills": "ask",
|
|
"special": "deny"
|
|
}
|
|
}
|
|
|
|
// After
|
|
{
|
|
"permission": {
|
|
"*": "allow",
|
|
"bash": "ask",
|
|
"mcp": "ask",
|
|
"skill": "ask",
|
|
"external_directory": "deny"
|
|
}
|
|
}
|
|
```
|
|
|
|
### `tools`
|
|
|
|
Each entry in `tools` maps a tool name to a permission.
|
|
In the flat format, tool names are surface keys directly inside `permission`.
|
|
|
|
```jsonc
|
|
// Before
|
|
{
|
|
"tools": {
|
|
"read": "allow",
|
|
"write": "deny",
|
|
"edit": "ask"
|
|
}
|
|
}
|
|
|
|
// After
|
|
{
|
|
"permission": {
|
|
"read": "allow",
|
|
"write": "deny",
|
|
"edit": "ask"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Special case — `tools.bash` and `tools.mcp`:** These were catch-all overrides for their respective surfaces.
|
|
In the flat format, use a string shorthand or an explicit `"*"` pattern:
|
|
|
|
```jsonc
|
|
// Before
|
|
{
|
|
"tools": { "bash": "allow", "mcp": "deny" }
|
|
}
|
|
|
|
// After — string shorthand (equivalent to { "*": "allow" })
|
|
{
|
|
"permission": {
|
|
"bash": "allow",
|
|
"mcp": "deny"
|
|
}
|
|
}
|
|
```
|
|
|
|
### `bash`
|
|
|
|
Bash patterns translate directly; the surface name stays `bash`.
|
|
If you also had a `tools.bash` or `defaultPolicy.bash` value different from `defaultPolicy.tools`, add an explicit `"*"` catch-all pattern at the **start** of the object (so specific patterns placed after it override it via last-match-wins).
|
|
|
|
```jsonc
|
|
// Before
|
|
{
|
|
"defaultPolicy": { "tools": "allow", "bash": "ask" },
|
|
"bash": {
|
|
"git status": "allow",
|
|
"git diff": "allow",
|
|
"git *": "ask",
|
|
"rm -rf *": "deny"
|
|
}
|
|
}
|
|
|
|
// After
|
|
{
|
|
"permission": {
|
|
"*": "allow",
|
|
"bash": {
|
|
"*": "ask",
|
|
"git status": "allow",
|
|
"git diff": "allow",
|
|
"git *": "ask",
|
|
"rm -rf *": "deny"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
> **Note:** Pattern ordering matters within an object.
|
|
> `normalizeFlatConfig` preserves insertion order, and `evaluate` uses last-match-wins.
|
|
> Put broad catch-alls **first** and specific overrides **after** them.
|
|
|
|
### `mcp`
|
|
|
|
MCP patterns translate directly; the surface name stays `mcp`.
|
|
|
|
```jsonc
|
|
// Before
|
|
{
|
|
"mcp": {
|
|
"mcp_status": "allow",
|
|
"mcp_list": "allow",
|
|
"exa:*": "allow",
|
|
"dangerous-server": "deny"
|
|
}
|
|
}
|
|
|
|
// After
|
|
{
|
|
"permission": {
|
|
"mcp": {
|
|
"mcp_status": "allow",
|
|
"mcp_list": "allow",
|
|
"exa:*": "allow",
|
|
"dangerous-server": "deny"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### `skills`
|
|
|
|
The surface name changes from `skills` (plural) to `skill` (singular).
|
|
|
|
```jsonc
|
|
// Before
|
|
{
|
|
"skills": {
|
|
"*": "ask",
|
|
"librarian": "allow",
|
|
"dangerous-*": "deny"
|
|
}
|
|
}
|
|
|
|
// After — note: "skills" → "skill"
|
|
{
|
|
"permission": {
|
|
"skill": {
|
|
"*": "ask",
|
|
"librarian": "allow",
|
|
"dangerous-*": "deny"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### `special`
|
|
|
|
`special.external_directory` becomes a top-level surface key in `permission`.
|
|
Other deprecated keys (`doom_loop`, `tool_call_limit`) are simply dropped.
|
|
|
|
```jsonc
|
|
// Before
|
|
{
|
|
"special": {
|
|
"external_directory": "ask",
|
|
"doom_loop": "deny",
|
|
"tool_call_limit": "deny"
|
|
}
|
|
}
|
|
|
|
// After — doom_loop and tool_call_limit are removed entirely
|
|
{
|
|
"permission": {
|
|
"external_directory": "ask"
|
|
}
|
|
}
|
|
```
|
|
|
|
> **Note:** In the old format, `special.external_directory: "deny"` produced a rule with `matchedPattern: "external_directory"`.
|
|
> In the flat format, the string shorthand produces `pattern: "*"`, so `matchedPattern` is now `"*"` when the explicit rule matches.
|
|
|
|
## Full before/after example
|
|
|
|
```jsonc
|
|
// Before (legacy format)
|
|
{
|
|
"$schema": "...",
|
|
"debugLog": false,
|
|
"permissionReviewLog": true,
|
|
"yoloMode": false,
|
|
"defaultPolicy": {
|
|
"tools": "ask",
|
|
"bash": "ask",
|
|
"mcp": "ask",
|
|
"skills": "ask",
|
|
"special": "ask"
|
|
},
|
|
"tools": {
|
|
"read": "allow",
|
|
"write": "deny"
|
|
},
|
|
"bash": {
|
|
"git status": "allow",
|
|
"git *": "ask"
|
|
},
|
|
"mcp": {
|
|
"mcp_status": "allow"
|
|
},
|
|
"skills": {
|
|
"*": "ask"
|
|
},
|
|
"special": {
|
|
"external_directory": "ask"
|
|
}
|
|
}
|
|
```
|
|
|
|
```jsonc
|
|
// After (flat format)
|
|
{
|
|
"$schema": "...",
|
|
"debugLog": false,
|
|
"permissionReviewLog": true,
|
|
"yoloMode": false,
|
|
"permission": {
|
|
"*": "ask",
|
|
"read": "allow",
|
|
"write": "deny",
|
|
"bash": {
|
|
"git status": "allow",
|
|
"git *": "ask"
|
|
},
|
|
"mcp": {
|
|
"mcp_status": "allow"
|
|
},
|
|
"skill": { "*": "ask" },
|
|
"external_directory": "ask"
|
|
}
|
|
}
|
|
```
|
|
|
|
## Per-agent frontmatter
|
|
|
|
Frontmatter in agent `.md` files uses the same flat shape under the `permission` key.
|
|
|
|
```yaml
|
|
# Before (legacy nested shape)
|
|
---
|
|
permission:
|
|
defaultPolicy:
|
|
tools: allow
|
|
bash:
|
|
"git *": allow
|
|
tools:
|
|
mcp: deny
|
|
mcp:
|
|
exa_web_search_exa: allow
|
|
special:
|
|
external_directory: allow
|
|
---
|
|
```
|
|
|
|
```yaml
|
|
# After (flat shape)
|
|
---
|
|
permission:
|
|
"*": allow
|
|
bash:
|
|
"git *": allow
|
|
mcp:
|
|
"*": deny
|
|
exa_web_search_exa: allow
|
|
external_directory: allow
|
|
---
|
|
```
|
|
|
|
Key differences from the old frontmatter:
|
|
|
|
1. The `"*"` key (quoted in YAML) replaces `defaultPolicy.tools`.
|
|
2. `tools.bash` / `tools.mcp` catch-alls become `bash: <state>` or `mcp: { "*": <state>, ... }`.
|
|
3. `special.external_directory` becomes `external_directory` at the top level of `permission`.
|
|
4. Any surface key now works in frontmatter — extension tool names and `mcp` are no longer silently ignored.
|
|
|
|
## Behavioral differences
|
|
|
|
### Agent scope catch-alls override parent scope patterns
|
|
|
|
In the old format, `tools.bash: allow` (override layer) was lower priority than config-layer patterns from any scope, including global.
|
|
In the flat format, `bash: allow` in an agent scope is a config-layer catch-all with **higher** priority than global-scope patterns (last-match-wins, agent rules come later).
|
|
|
|
If you relied on global `rm -rf *: deny` surviving an agent's `tools.bash: allow`, you must now explicitly deny the pattern within the agent's own `bash` object:
|
|
|
|
```yaml
|
|
# Old agent frontmatter — global "rm -rf *": "deny" survived
|
|
permission:
|
|
tools:
|
|
bash: allow
|
|
|
|
# New agent frontmatter — must repeat the deny if you want it preserved
|
|
permission:
|
|
bash:
|
|
"*": allow
|
|
"rm -rf *": deny
|
|
```
|
|
|
|
### `matchedPattern` for `external_directory`
|
|
|
|
In the old format, an explicit `special.external_directory: "deny"` rule had `matchedPattern: "external_directory"`.
|
|
In the flat format, `external_directory: "deny"` (string shorthand) has `matchedPattern: "*"`.
|
|
Code that inspected `matchedPattern` to detect explicit external-directory config must be updated.
|