Skip to content

feat: blocklist display + introspection — *KB Sharing* Blocked view (closes #162) - #189

Merged
cuttlefisch merged 1 commit into
mainfrom
feat/162-blocklist-display
Jun 28, 2026
Merged

feat: blocklist display + introspection — *KB Sharing* Blocked view (closes #162)#189
cuttlefisch merged 1 commit into
mainfrom
feat/162-blocklist-display

Conversation

@cuttlefisch

Copy link
Copy Markdown
Owner

What

PR 3 of 3 for #162 (ADR-039 A2 local blocklist) — closes the issue. PR1 (#186, merged) = daemon enforcement; PR2 (#187) = human+AI action surface. This adds the display + introspection: a *KB Sharing* Blocked section, at-point unblock, and the blocked principals surfaced in kb_sharing_status / (kb-sharing-status).

Why a fetch (not the local replica)

Membership renders from the editor's local kbc: replica — but the blocklist is local-only on the daemon and never in the synced collection (that's the design). So the editor can't derive it; it must fetch the authoritative state. An optimistic local mirror would miss blocks set in a prior session or by another frontend on a shared daemon.

Daemon

  • DocStore::all_kb_blocklists() + a read-only kb/blocklist query RPC → { blocklist: { kb_id: [fp, …] } } (optional kb_id scopes to one). This is the ONLY way a client learns the local-only blocklist.

Editor

  • CollabState.kb_blocklists cache; a send_blocklist_fetch helper sent on connect (durable prior-session blocks) and after each block/unblock (the user's own changes); reply → PendingResponseKind::BlocklistCollabEvent::BlocklistUpdated → cache + repaint. Display-only; the daemon stays authoritative.
  • KbSharingEntry.blocked: Vec<BlockedView> in build_snapshot (label resolved best-effort from the member replica; a non-member block falls back to the short fingerprint).
  • *KB Sharing* buffer: a foldable Blocked (N) section after Pending; at-point B → unblock (kb-sharing-unblock, not owner-gated); diagnostic.error theme key.
  • Introspection: because all three actors share build_snapshot, the blocked list flows automatically into the kb_sharing_status MCP tool and (kb-sharing-status) Scheme primitive (CLAUDE.md chore(deps): Update crossterm requirement from 0.28 to 0.29 #3/ci(deps): Bump the ci-dependencies group with 2 updates #8).

Tests

  • Daemon: extends the block-fence test with the kb/blocklist query oracle (reports exactly the blocked principal).
  • Editor: blocklist_renders_blocked_view_with_member_label — bob stays a member (a local block is not a removal) and is listed Blocked with his label; a non-member block has an empty label; the buffer view emits a BlockedHeader + one Blocked row per principal.

Verification

  • Daemon: 120 lib + 51 handler tests, clippy clean.
  • Editor: fmt --check + clippy --workspace --all-targets -D warnings clean (incl GUI); cargo test --workspace green.

Closes #162.

🤖 Generated with Claude Code

…loses #162)

PR3 of the ADR-039 A2 local self-protection blocklist — closes #162. PR1 (#186)
landed daemon enforcement; PR2 (#187) the human+AI action surface. This adds the
display + introspection.

Why a fetch (not the local replica): membership renders from the editor's local
`kbc:` replica, but the blocklist is LOCAL-only on the daemon and never in the
synced collection — so the editor cannot derive it and must fetch the
authoritative state. An optimistic local mirror would miss blocks set in a prior
session or by another frontend on a shared daemon.

Daemon:
- DocStore::all_kb_blocklists() + a read-only `kb/blocklist` query RPC returning
  `{ blocklist: { kb_id: [fp, ...] } }` (optional kb_id scopes to one). The ONLY
  way a client learns the local-only blocklist.

Editor:
- CollabState.kb_blocklists cache; a `send_blocklist_fetch` helper sent ON CONNECT
  (durable prior-session blocks) and AFTER each block/unblock (the user's own
  changes); reply → PendingResponseKind::Blocklist → CollabEvent::BlocklistUpdated
  → cache + repaint. Display-only; the daemon stays authoritative.
- KbSharingEntry.blocked: Vec<BlockedView> in build_snapshot (label best-effort
  from the member replica; a non-member block falls back to the short fingerprint).
- *KB Sharing* buffer: a foldable "Blocked (N)" section after Pending; at-point
  `B` → unblock (kb-sharing-unblock, not owner-gated); diagnostic.error theme key.
- Introspection: all three actors share build_snapshot, so `blocked` flows into the
  kb_sharing_status MCP tool and (kb-sharing-status) Scheme primitive (#3/#8).

Tests: daemon block-fence test extended with the kb/blocklist query oracle;
editor blocklist_renders_blocked_view_with_member_label (bob stays a MEMBER and is
listed Blocked with his label; a non-member block has empty label; the view emits a
BlockedHeader + one Blocked row per principal).

Daemon 120+51 tests + clippy clean; editor fmt + clippy --workspace + test
--workspace green (incl GUI).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cuttlefisch cuttlefisch added the release:none Skip version bump on merge label Jun 28, 2026
@cuttlefisch
cuttlefisch merged commit b3988bb into main Jun 28, 2026
14 checks passed
@cuttlefisch
cuttlefisch deleted the feat/162-blocklist-display branch June 28, 2026 23:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release:none Skip version bump on merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ADR-039 A2: implement the local blocklist (self-protection deny-list) end-to-end

1 participant