AGENTS.md@packages/client/src/lib/access-grants · git:20261021.3230393 · 2026-10-21 · sha256 5b03d126dda1839c

AGENTS.md@packages/client/src/lib/access-grants git:20261021.3230393A

Immutable. This exact content never changes and is served at /api/v1/blob/5b03d126dda1839c.

# DOX — packages/client/src/lib/access-grants

Files in this directory. One row per source file.

| File | Purpose |
|------|---------|
| `access-grants-api.ts` | Transport for the Settings → Access tab. `fetchAccessSnapshot()` (ONE aggregate `GET /api/access/grants` — the only request the tab issues; render performs zero store writes, task 7.7) + `revokeAccessEntry(entry)` routing per store to ITS OWN write path (task 7.5): path grants → `DELETE /api/access/grants`, worktree trust → `DELETE /api/access/worktree-trust`, KB trust → `DELETE /api/kb/source-trust`, project trust → `DELETE /api/access/project-trust` (the `persistTrustDecision` wrapper — DELETE, never a standing refusal, design D13), trustedNetworks → `DELETE /api/access/trusted-network`, corsOrigins → `DELETE /api/access/cors-origin`, bypassHosts → `DELETE /api/access/bypass-hosts`, pinned dirs → `DELETE /api/access/pinned-directory`. EVERY store is per-entry, so no client snapshot is passed: array-valued config stores are read-modify-written server-side, which is what makes concurrent revokes safe (task 4.5 #5 — previously a whole-array `PUT /api/config` computed from a rendered snapshot, so two revokes could resurrect each other). NEVER creates a grant (design D12). `GET /api/access/grants` is owned by the server side of the change; this module is the client contract. See change: add-access-grants-and-review. `previewEgress` revoke → `DELETE /api/access/preview-egress {project,origin}`. See change: improve-canvas-html-and-url-ux. |
| `access-grants-types.ts` | Types for the Access review surface. `ACCESS_STORES` — EXACTLY the eight in-scope stores (design D6): `pathGrants`, `worktreeTrust`, `kbTrust`, `projectTrust`, `trustedNetworks`, `bypassHosts`, `corsOrigins`, `pinnedDirectories`. Deliberately excluded (rationale in design D6, pinned by test): `paired-devices.json` (own management surface), `auth.bypassUrls` (route access → auth config surface). `AccessGrantSnapshot` (per-store raw shapes; kb entries carry optional additive `subject` beside `hash`), `AccessEntry` (labelled row; `legacyHashOnly` flag; path grants carry optional `via: "prompt"` + `widenedFrom` — See change: add-access-grant-dialog (tasks 8.2)), `emptyAccessGrantSnapshot()`. Session-scoped grants carry the same four fields as persisted ones (subject/scope/grantedAt/origin — design D17). See change: add-access-grants-and-review. See change: surface-denial-remedy-in-previews — Mirrors `PromptOutcome` (`PROMPT_OUTCOMES`) + `DenialRemedy`; `parseDenialBody` (no `denialId` → `refused`; absent/unknown outcome → none, never `unavailable`). `via` widened to `"prompt" | "agent-prompt" | (string & {})` (unknown future origin → generic prompt label). See change: ask-agent-file-access-in-chat. `AccessGrantEntry.via` / `via` mirror gain `"provision"`. See change: add-access-provisioning. Ninth store `previewEgress` (snapshot field optional for older servers; `AccessEntry.project`). See change: improve-canvas-html-and-url-ux. |
| `access-prompts-api.ts` | Transport for the Access page prompt surfaces. `fetchAccessPrompts` (GET `/api/access/prompts`), `answerPendingPrompt(promptId,{plane,subject,verdict})` (POST `/api/access/prompts/:promptId` — any pending entry, prompting on or off), `clearRefusal(plane,subject)` (DELETE `/api/access/refusals?plane=&subject=`), `setPromptEnabled(bool)` (PUT `/api/config` `{accessGrants:{promptEnabled}}`). Returns `ApiResult{ok,status,data?,error?}`. See change: add-access-grant-dialog (tasks 8.1, 8.3). YOLO: `fetchYoloRoots(base)` (GET `/api/access/yolo/roots?base=` → `{roots}`, base + ladder, narrowest first), `activateYolo(YoloActivation)` (POST `/api/access/yolo`; `{durationMinutes,base,root}` \| `{durationMinutes,unscoped:true}`; live session → server ADDS root, timer kept, `added:true`), `endYolo()` (DELETE). See change: add-access-grant-dialog (tasks 8b.7a, 8b.7b) `clearRefusal(plane: YoloSurfaceId, …)`. See change: yolo-covers-agent-path-gate. Provisioning: `ProvisionRequestView`, `provisionViewToMessage`, `fetchProvisionRequests` (GET `/api/access/provision`), `settleProvisionRequest(id, lines)` (POST `…/:id/settle`), `dismissAllProvisionRequests()` (POST `…/dismiss-all`). See change: add-access-provisioning. |

| `access-prompts-types.ts` | Client mirror of server `AccessPromptsView` (`routes/access-prompt-routes.ts`): `PromptingBlocker` (`report-mode`/`kill-switch`/`disabled`), `PendingPromptView`, `VerdictView` = `OperatorVerdictView` (store?, widenedFrom?) \| `YoloVerdictView` (no human answered), `RefusalView`, `AccessPromptsView` (carries `yolo: YoloView`). `YoloView {available, durationsMinutes, session}`; `YoloSessionView` mirrors server `YoloSession` (`expiresAt: null` = env session). See change: add-access-grant-dialog (tasks 8.1-8.3, 8b.7) `YoloVerdictView.plane` / `RefusalView.plane`: `YoloSurfaceId`. See change: yolo-covers-agent-path-gate. |
| `aggregate-access-entries.ts` | PURE aggregation: `aggregateAccessEntries(snapshot)` maps the eight-store snapshot to `AccessEntry[]` (every entry labelled with its origin store; bypassHosts never merged with trustedNetworks; legacy hash-only kb entries display the opaque hash and never error; stable `ACCESS_STORES`-order sort) + `groupAccessEntriesByStore(entries)` (non-empty groups in display order). Path-grant rows pass through `via`/`widenedFrom` when set (See change: add-access-grant-dialog (tasks 8.2)). Pure so fixture tests run without a live server. Tests: `__tests__/aggregate-access-entries.test.ts` (7.2/7.3/7.6/7.13), `__tests__/access-grants-api.test.ts` (7.5/7.8/7.12). See change: add-access-grants-and-review. `previewEgress` rows: id `project\0origin`, `project` field. See change: improve-canvas-html-and-url-ux. |
| `grant-channel.ts` | Browser half of the prompt capability. Module memory only (never web storage). Exports `GRANT_CHANNEL_HEADER` (`X-Pi-Grant-Channel`), `setGrantChannel` (replace on reconnect), `clearGrantChannel` (socket close), `getGrantChannel`, `useHasGrantChannel`, `installGrantChannelFetch` (idempotent global `fetch` wrapper, same seam as `installDeviceAuthFetch`; header only on same-origin `/api/*`, never overrides explicit header). Called from `main.tsx`. Media-element loads (`<img>/<video>` on `/api/file/raw`) cannot carry it → never held. See change: add-access-grant-dialog (tasks 7.1, 8.3) See change: surface-denial-remedy-in-previews — Adds `GRANT_OPT_OUT_HEADERS`, `fetchWithoutGrantPrompt` (empty header = declared ineligible, confers nothing), `canCarryGrantChannel(url)` (live capability ∧ `shouldAttach`). |
| `grant-prompt-store.ts` | Open-prompt queue. `GrantPromptStore` (`apply` grant_request/grant_dismiss, `claim` = answer-once gate, `expire(now)`, `snapshotIds` + `reconcile(pending, before)` — removes only ids queued before the fetch, `getQueue` stable ref, `getVersion` bump per change, `subscribe`) + singleton `grantPromptStore`. Closed ids remembered (cap 200) so late answers / re-delivered requests are inert. See change: add-access-grant-dialog (tasks 7.1-7.3) `grant_request` with `ttlMs` is re-based on receipt (`expiresAt = Date.now() + ttlMs`) so browser clock skew cannot expire a prompt early (review 9.5). |
| `preview-provenance.tsx` | `PreviewProvenance` provider + `usePreviewFetch()` — FAILS CLOSED (opted out unless a provider declares `autoOpened:false`). `previewFetchFor`, `eligibleFetch`, `PreviewFetch`. See change: surface-denial-remedy-in-previews. Adds `usePreviewOpener()` → `operator` (provider `autoOpened:false`) else `agent` (fail closed) for the live-preview egress policy. See change: improve-canvas-html-and-url-ux. |
| `provision-prompt-store.ts` | Open provisioning-request queue, keyed `requestId`. `ProvisionPromptStore` (`apply` provision_request/provision_dismiss, `claim` answer-once gate, `expire(now)`, `snapshotIds` + `reconcile`, `getQueue`, `getVersion`, `subscribe`) + singleton `provisionPromptStore`. Closed ids remembered (cap 200); `ttlMs` re-based on receipt. Mirrors `GrantPromptStore`. Tests: `__tests__/provision-prompt-store.test.ts`. See change: add-access-provisioning (task 3.2). |

| `yolo-status.ts` | ONE shared YOLO status for every indicator + activation surface. `YoloStatusStore` (`getSnapshot`, `publish(view)` notifies only on real change, `refresh()` keeps last view on failure, `subscribe` polls `GET /api/access/prompts` every 15 s only while subscribed) + singleton `yoloStatus`; `useYoloStatus()`, `useNow(active)` (1 s tick; moved to `lib/time/use-now.ts`, re-exported — see change: show-session-history-load-state). Pure: `isCwdInYoloScope(cwd, session)` (component-wise, `/` + `\\` separators; `/repo-secrets` NOT in `/repo`; unscoped = every cwd), `remainingMs` (`null` for env), `formatRemaining` (`m:ss` / `h:mm:ss`, seconds round up). See change: add-access-grant-dialog (tasks 8b.7, 8b.7a, 8b.7b) |