# Build the "Scope and tool permissions" pattern: `PermissionInventory`

## Stack (edit this block to match your project)
- React 18+ with TypeScript: one function component, typed props, no `any`.
- Tailwind CSS, using your design system's tokens if you have them.
- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.
- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.

## What the pattern is for
A visible inventory of the tools, data and destinations the agent may touch, editable by the user.

Use it when: Agents with connectors, and any enterprise deployment where access is the real risk.

## What to build
A panel listing every tool, data source and destination an agent may touch, grouped as read, act and send to, each with its own switch and recent use. It also surfaces in-context requests when the agent needs access it doesn't have, so consent resurfaces as capabilities expand. It renders in agent settings and inline in a run when a request is pending.

### Props
- `agent`: { id: string; name: string }, the identity permissions are bound to.
- `groups`: Array<{ name: string; items: Array<{ id: string; title: string; description: string; granted: boolean; risky?: boolean; lastUsed?: Date; offDescription?: string }> }>, where `offDescription` explains the fallback, e.g. "Off: drafts replies for a person to send".
- `onToggle(itemId: string, granted: boolean)`: requests a change. The parent enforces permissions server-side against the agent identity.
- `pendingRequests?`: Array<{ id: string; itemId: string; title: string; reason: string; context: string; scopeOptions: Array<{ id: string; label: string }> }>, e.g. a one-off "Allow for this ticket only" alongside "Always allow".
- `onGrantRequest(requestId: string, scopeOptionId: string)` and `onDenyRequest(requestId: string)`: callbacks.
- `grantedBy?`: { name: string; at: Date }, shown under the summary.
- `savingIds?`: string[], items whose change is in flight.

### States it must render
- Default: a summary line "Right now the agent can use 3 read sources, 2 actions, 1 destination", then grouped switches with each item's last use or "Not granted".
- Pending request: an amber card at the top, "The agent is asking for more access: Issue refunds", with the exact context ("For ticket #1042: refund $1,240"), why now ("The run paused at this step"), scope choices and Deny.
- Item turned off: the row shows its `offDescription`, the summary count drops, and a note confirms "Applies to runs already in progress".
- Saving: the switch shows its new position with a busy indicator; a failed save reverts it with an inline error.
- Empty: "This agent has no access yet" with each group still listed and all switches off.

### Interactions
- Each switch calls `onToggle`; granting a risky item asks for one confirmation that names the risk, revoking never does.
- Request buttons call `onGrantRequest` with the chosen scope; the narrowest scope (this item only) is the first option.
- Resolved requests collapse into a one-line note, e.g. "Allowed for #1042 only by you at 10:12".

### Edge cases
- A capability added after setup (new connector, new action) appears as a pending request, never as already granted.
- Items controlled by an admin policy: show the switch locked with who set it.
- Several pending requests: list them all, each with its own decision, oldest first.
- An item never used in 30 days: show "Not used in 30 days" so people can trim unused access.

## Must avoid
One bundled consent screen at install that never resurfaces as capabilities expand.

## Engineering constraint
Bind permissions to agent identity so checkpoints are enforceable; re-prompt on scope expansion.

## Accessibility
- Switches use `role="switch"` with `aria-checked`; each label includes the item title and its group, e.g. "Act: Issue refunds".
- Announce a new pending request politely and make the request card a labelled `region`.

## House rules
- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live="polite"`; respect `prefers-reduced-motion`.
- All user-facing text comes from props (or a `labels` prop) so it can be translated.
- One consistent accent marks anything the AI made or is doing; amber means "needs a person", red means risky, green means done. Never rely on colour alone.
- Fits a 360 px wide container without horizontal scrolling.

## Acceptance checks
- [ ] Every state above can be rendered by passing props, with no internal fake data.
- [ ] Every interaction and edge case above behaves as described.
- [ ] Nothing in "Must avoid" can happen with this component.

## Deliver
1. The component file with its exported prop types.
2. A short usage example that renders each state.
