feat(client): policy & audit UI — surface the per-contact + agent policy engine#27
Merged
Conversation
…icy engine
The F2 ask_user gate and F4 per-contact policy engine shipped server-side with
no client surface, so the policy engine was invisible to users. This wires three
already-tested-but-unconsumed endpoints into UI, as one coherent policy & audit
cluster (packages/client only, no backend change, no migration):
- Per-contact standing-policy editor: a contact detail panel opened from the
contacts list reads GET /contacts/:id and saves the per-contact default
decision (allow/ask_user/deny, or inherit) via POST /contacts/:id/policies.
Existing rules shown read-only (full rule editor deferred, TODO).
- Agent-level default policy editor: a new Settings "policy" tab reads
GET /agents/me and writes the agent default via PUT /agents/me/policies.
- Permission decision history: a new Settings "history" tab lists past decisions
from GET /permissions/history (action + decision + decided-at).
Correctness:
- Both editors use the engine PolicyOverrides vocabulary from @confer/shared
({default?, rules?} with allow/ask_user/deny) — never the AgentFacts
policyConfigSchema (do-not-bridge); the client type is the only shape guard
for PUT /me/policies (server takes z.record).
- Policy writes are whole-object PUT semantics (preserve existing rules, swap
only default).
- PATCH/POST-policies responses omit the peer join; the store merges them into
the cached contact preserving peer (mergePeerlessUpdate) so peer info survives
a save.
- i18n added across zh/en/ja (structural parity is build-enforced).
Verified: client `bun run build` (strict tsc -b + vite) PASS, client store
tests 105/0, lint clean (247 files).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…guard openDetail race Code-review follow-ups (all packages/client): - permissions store: loadHistory no longer silently swallows failures. The history tab is user-triggered, so a fetch failure now sets `historyError` (surfaced in PermissionHistory) instead of rendering an empty list that reads as "no history". Corrected the stale "endpoint might not exist yet" comment (the endpoint shipped in PR #24). loadPending left as the background poll it is. - contacts store: openDetail discards a stale GET response if the user switched to another contact mid-fetch (cancellation guard on selectedContactId), so a slow response can't overwrite the newer selection. - PermissionHistory: drop the dead `decision === 'allow'` branch (the backend only writes allow_once/allow_always); use `startsWith('allow')`. - lib/policy: make decisionFromSelect module-private (only mergePolicyDefault uses it) — no speculative public surface. Verified: client `bun run build` PASS, store tests 105/0, lint clean (247 files). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Owner
Author
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
F5 is the first client code to import @confer/shared. The client tsconfig had no `paths`, so CI's `npx tsc --noEmit` (flat, not `tsc -b`) could not resolve the workspace package and failed with TS2307 on PolicyEditor/lib-policy/contacts/ settings. Local `bun run build` masked it (node_modules symlink + prior dist). Mirror the root tsconfig: map @confer/shared to ../shared/src so resolution is explicit and build-state-independent — the same mechanism the backend type-check already uses. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The F2 ask_user gate (#24) and F4 per-contact policy engine (#26) shipped server-side with no client surface, so the policy engine was invisible to users. This wires three already-tested-but-unconsumed endpoints into UI as one coherent "policy & audit" cluster. packages/client only — no backend change, no migration.
GET /contacts/:idand saves the per-contact default decision (allow / ask_user / deny, or inherit) viaPOST /contacts/:id/policies. Existing rules shown read-only (full rule editor deferred, TODO).GET /agents/meand writes the agent default viaPUT /agents/me/policies.GET /permissions/history(action + decision + decided-at).Correctness
PolicyOverridesvocabulary from@confer/shared({default?, rules?}withallow/ask_user/deny) — never the AgentFactspolicyConfigSchema(do-not-bridge). The client type is the only shape guard forPUT /me/policies(server takesz.record).rules, swap onlydefault).PATCH/POST /policiesresponses omit thepeerjoin; the store merges them into the cached contact preservingpeer(mergePeerlessUpdate) so peer info survives a save.peer_name/description— unlike/pending); null-safe.Resources = typeof zh).Review notes
Pure client over already-tested endpoints → no a2a-contract-reviewer, no migration-reviewer. Reviewer-QA confirmed: all six cross-boundary shapes match the live gateway handlers field-by-field, peer-join reconciliation correct, policy vocabulary pure engine-shape, whole-object semantics preserve rules, i18n complete. One review round pruned an unconsumed
patchContactstore method + 5 unused i18n keys (speculative surface).Verified
bun run build(stricttsc -b+vite build): PASSsetContactPolicyengine-shape body + peer preservation;loadHistory)http://localhost/ → 200.🤖 Generated with Claude Code