mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-08-28 08:35:57 +00:00
feat: vendor permission system source
This commit is contained in:
@@ -0,0 +1,365 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user