mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-08-28 16:45:22 +00:00
feat: vendor permission system source
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
---
|
||||
status: accepted
|
||||
date: 2026-07-09
|
||||
---
|
||||
|
||||
# 0006 — Grant-scope selection on forwarded approvals
|
||||
|
||||
## Status
|
||||
|
||||
Accepted.
|
||||
|
||||
## Context
|
||||
|
||||
A subagent with no UI escalates an `ask` up the tree; the serving (parent/root) session drains its inbox and a human decides ([#557], ADR-0005).
|
||||
When that human approves "for this session," the ruling could land only on the **requesting subagent**: the response rides back to the child, whose `GateRunner` records the pattern into the child's own `SessionRules`.
|
||||
The human had no way to record the ruling at the **serving scope**, so a grant meant to cover the parent and all its subagents had to be re-approved per child ([resolved direction](../architecture/architecture.md#resolved-direction) 4, Phase 9 Step 4).
|
||||
|
||||
## Decision
|
||||
|
||||
Offer the human a scope when approving a forwarded request "for this session," and record a whole-session grant on the serving node.
|
||||
|
||||
1. **The child rides its suggestion along.**
|
||||
`GateRunner` already computes a `SessionApproval` (surface + one-or-more patterns) for the ask.
|
||||
It now flows on `PromptPermissionDetails.sessionApproval` (via `SessionApproval.toForwardedData()`) into the `ForwardedPermissionRequest.sessionApproval` field the child writes.
|
||||
The field is optional and read tolerantly, so an older child (no suggestion) simply offers no scope choice.
|
||||
|
||||
2. **A two-step dialog.**
|
||||
The base four-option prompt is unchanged.
|
||||
Choosing "for this session" opens a second `select` — subagent-only (listed first, the least-privilege default) vs the whole session — but only for a forwarded ask that carries a suggestion.
|
||||
A cancelled scope select defaults to subagent-only.
|
||||
`LocalUserAuthorizer` builds the scope labels (`buildForwardedScopeLabels`) and is still the single `permissions:ui_prompt` emit site; the emit fires once before the first select, so the [#292] non-degraded broadcast is unaffected.
|
||||
|
||||
3. **Whole-session grants record on the serving node only.**
|
||||
The dialog returns a serving-node-internal `approved_for_serving_session` state.
|
||||
`ForwardedRequestServer.applyGrantScope` records the child's suggested pattern into the serving session's `SessionRules` — the same instance the resolver and gate runner read — then translates the response to a plain `approved`.
|
||||
The child records nothing; its next identical action re-forwards and resolves as recorded authority (the [#557] serve-time evaluation auto-approves it).
|
||||
A subagent-only grant (`approved_for_session`) passes through untouched — the child records, exactly as before.
|
||||
|
||||
The serving node is the single source of truth for a whole-session grant.
|
||||
Because the serving `SessionRules` is shared, the grant governs the parent's own actions immediately and future forwarded resolutions for free.
|
||||
|
||||
### The `approved_for_serving_session` state
|
||||
|
||||
It is serving-node-internal: produced by the dialog, consumed by `ForwardedRequestServer`, and translated to `approved` before any response is written, so it never reaches disk or the child.
|
||||
It is a member of `PermissionDecisionState` (and `isPermissionDecisionState`, for guard completeness); the on-disk `ForwardedPermissionResponse.state` stays within the four legacy values.
|
||||
|
||||
### Rejected alternatives
|
||||
|
||||
- **Record on both the serving node and the requesting child.**
|
||||
Rejected: two copies blur the scope, and the subagent-only vs whole-session distinction collapses to "does the parent also hold a copy."
|
||||
Serving-node-only keeps a single source of truth; the child re-forwards and auto-approves.
|
||||
- **A `grantScope` marker on an `approved_for_session` decision.**
|
||||
Rejected: the server must translate the response to `approved` for the whole-session case anyway (so the child does not double-record), and a state that says "subagent" while a marker says "serving" is less honest than a distinct state.
|
||||
- **Inline scope options in the base dialog (a five-option prompt).**
|
||||
Rejected in favor of the operator's two-step choice: the base prompt stays byte-identical for every local ask, and the scope question appears only when it applies.
|
||||
|
||||
## Consequences
|
||||
|
||||
- A human can grant a forwarded request for the whole serving session; the parent and its subagents then resolve it without a second prompt.
|
||||
- The default (subagent-only, pre-selected) preserves today's behavior exactly; this ships as `feat:`, not a breaking change.
|
||||
- The forwarded request and response formats gain one optional field each, read tolerantly — an upgrade needs no config edit and tolerates version skew.
|
||||
|
||||
### Accepted limitations
|
||||
|
||||
- **Cross-cwd / cross-surface re-resolution is best-effort.**
|
||||
A recorded whole-session path grant matches a child's later forward only when cwd and surface align — the pre-existing single-surface/cross-cwd limitation from ADR-0005 (`docs/decisions/0005-serving-authorizer-provenance.md`), tracked in [#565].
|
||||
An imperfect match lands on `ask` → prompt, never a silent grant.
|
||||
- **Three-way scope (root / parent / requesting subagent) is not shipped.**
|
||||
The tree is depth-2 today, so "parent" and "root" coincide and the dialog offers two scopes.
|
||||
The three-way split waits on multi-hop escalation — admitted-not-shipped, the same shape as the escalation chain.
|
||||
|
||||
[#292]: https://github.com/gotgenes/pi-packages/issues/292
|
||||
[#557]: https://github.com/gotgenes/pi-packages/issues/557
|
||||
[#565]: https://github.com/gotgenes/pi-packages/issues/565
|
||||
Reference in New Issue
Block a user