Files
my-pi/pi-permission-system/docs/plans/archive/0018-drop-tool-call-limit.md
T

157 lines
10 KiB
Markdown

---
issue: 18
issue_title: "Drop unread special.tool_call_limit from permissions schema"
---
# Drop unread `special.tool_call_limit` from permissions schema
## Problem Statement
`schemas/permissions.schema.json` declares `special.tool_call_limit` with a `oneOf [permissionState, integer]` shape, but no runtime code reads it.
`SpecialPermissionName` in `src/types.ts` and `SPECIAL_PERMISSION_KEYS` in `src/permission-manager.ts` both omit `tool_call_limit`.
AGENTS.md is explicit: *"Treat any declared config field not read at runtime as a maintenance trap.*
*Remove it or document its purpose."*
The field also appears in the `README.md` special-permissions table with the note *"schema only, not enforced yet"*.
## Goals
- Remove `special.tool_call_limit` from `schemas/permissions.schema.json`.
- Remove the `tool_call_limit` row from the `README.md` special-permissions table.
- Add a tolerant-loader deprecation warning: if a user's parsed policy contains `special.tool_call_limit`, emit a single non-fatal config issue per occurrence and discard the value.
- Add tests covering the deprecation warning path.
## Non-Goals
- Implementing a tool-call-limit feature.
If we want one later, file a fresh issue with a real implementation, schema entry, example, and tests in lockstep.
- Changing any other permission surface or default policy state.
## Background
### Relevant modules
| File | Role |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `schemas/permissions.schema.json` | Declares `special.tool_call_limit` — the field to remove. |
| `src/permission-manager.ts` | `normalizePermissionRecord()` already silently discards integer values (they fail the `isPermissionState` check). If the user writes `"tool_call_limit": "allow"`, it would survive normalization but is never read by `SPECIAL_PERMISSION_KEYS`. The deprecation warning needs to fire *before* the value is discarded. |
| `src/types.ts` | `SpecialPermissionName` does not include `tool_call_limit` — no change needed. |
| `config/config.example.json` | Does not reference `tool_call_limit` — no change needed. |
| `README.md` | Contains one row in the special-permissions table for `tool_call_limit` marked as *schema only, not enforced yet*. |
### Permission surface
`special` — but only the schema and docs are affected.
No runtime permission decisions change because the key was never read.
## Design Overview
### Schema change
Remove the `tool_call_limit` property from `special` in `schemas/permissions.schema.json`.
The `special` object retains `doom_loop` and `external_directory`.
### Deprecation warning
AGENTS.md's Configuration rules require:
> When removing a previously accepted config field, keep the loader tolerant: accept the legacy key, emit a single non-fatal config issue per occurrence describing the deprecation, and discard the value.
The right place to emit the warning is inside `normalizeRawPermission()` in `src/permission-manager.ts`, since that is the single normalization gateway for both global and per-agent configs.
Today it returns a plain `AgentPermissions` object with no side channel for warnings.
**Approach:** Add a `configIssues` array to the return type (or use a parallel mechanism) so callers can surface deprecation messages.
Concretely:
```typescript
interface NormalizeResult {
permissions: AgentPermissions;
configIssues: string[];
}
```
`normalizeRawPermission()` checks for `tool_call_limit` in the `special` sub-object of the raw input.
If found, it pushes a message like:
```text
special.tool_call_limit is deprecated and ignored — remove it from your policy file.
```
The value is discarded as today (integer values already fail `isPermissionState`; string values would be stripped from the normalized output explicitly).
The `configIssues` array is threaded up through `loadGlobalConfig()`, `loadProjectGlobalConfig()`, `loadAgentPermissions()`, and exposed via a new `getConfigIssues(agentName?)` method on `PermissionManager`.
The extension entry point (`src/index.ts`) already has a warning-notification path (`notifyWarning`) used for misplaced-key detection; the deprecation issues can be surfaced through the same channel.
### Merge precedence
No change — global → project → per-agent remains the same.
The deprecation warning fires independently at each layer that contains the key.
## Module-Level Changes
### `schemas/permissions.schema.json` — changed
Remove the `tool_call_limit` property (and its `oneOf` definition) from the `special` object.
### `src/permission-manager.ts` — changed
- Extend `normalizeRawPermission()` to return config issues alongside the normalized permissions (new `NormalizeResult` type or equivalent).
- Detect `tool_call_limit` in the raw `special` sub-object and push a deprecation message.
- Explicitly strip `tool_call_limit` from the normalized `special` record (currently happens implicitly for integer values but not for valid PermissionState strings).
- Thread config issues through the load methods and cache them.
- Add `getConfigIssues(agentName?): string[]` to `PermissionManager`.
### `src/index.ts` — changed
- After loading permissions, call `getConfigIssues()` and surface any messages through the existing `notifyWarning` path (same pattern as misplaced-key detection).
### `README.md` — changed
- Remove the `tool_call_limit` row from the `### special` permissions table.
### `tests/` — new or changed test file
- Test that `normalizeRawPermission` (or the new wrapper) emits a deprecation issue when `special.tool_call_limit` is present (both integer and string forms).
- Test that the normalized output does not contain `tool_call_limit` in `special`.
- Test that configs without `tool_call_limit` produce no deprecation issues.
## TDD Order
1. **Red: deprecation detection for `special.tool_call_limit`.**
Write a test that calls the normalization function with `{ special: { tool_call_limit: 5 } }` and asserts a config-issue string is returned containing `"tool_call_limit"`.
Write a second case with `{ special: { tool_call_limit: "allow" } }`.
Write a third case with `{ special: { doom_loop: "deny" } }` asserting no issues.
Commit: `test: cover tool_call_limit deprecation warning (#18)`
2. **Green: implement deprecation detection in normalizer.**
Extend `normalizeRawPermission()` to return config issues.
Detect and warn on `tool_call_limit`; explicitly strip it from the output.
Commit: `feat: emit deprecation warning for special.tool_call_limit (#18)`
3. **Red → Green: `PermissionManager.getConfigIssues()` integration.**
Write a test constructing a `PermissionManager` with a temp config containing `special.tool_call_limit` and assert `getConfigIssues()` returns the deprecation message.
Implement `getConfigIssues()` on `PermissionManager` by threading issues through the load path.
Commit: `feat: surface config issues from PermissionManager (#18)`
4. **Schema and docs cleanup.**
Remove `tool_call_limit` from `schemas/permissions.schema.json`.
Remove the `tool_call_limit` row from `README.md`.
Commit: `docs: remove tool_call_limit from schema and README (#18)`
5. **Wire warning into extension entry point.**
In `src/index.ts`, call `getConfigIssues()` during initialization and surface messages via `notifyWarning`.
Commit: `feat: notify user of deprecated config fields at startup (#18)`
## Risks and Mitigations
| Risk | Mitigation |
| ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Could this silently weaken a permission?** | No. `tool_call_limit` was never enforced — removing it changes zero runtime decisions. The deprecation warning makes the removal *more* visible, not less. |
| **Users with `tool_call_limit` in their config get a schema-validation error.** | The schema drops the field, but the loader remains tolerant: it parses with `stripJsonComments` + `JSON.parse`, not schema validation. The deprecation warning tells the user to remove it. |
| **On-disk identity change.** | None. Config directory, log filenames, `/permission-system` slash command, and event channel names are untouched. |
| **`normalizeRawPermission` return-type change ripples through callers.** | The change is internal to `permission-manager.ts`. All call sites are in the same file. The public API gains only an additive `getConfigIssues()` method. |
## Open Questions
None — the scope and approach are unambiguous.