Skip to content

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

Closed
cuttlefisch wants to merge 1 commit into
feat/162-blocklist-editor-surfacefrom
feat/162-blocklist-display
Closed

feat: blocklist display + introspection — *KB Sharing* Blocked view (closes #162)#188
cuttlefisch wants to merge 1 commit into
feat/162-blocklist-editor-surfacefrom
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 deleted the branch feat/162-blocklist-editor-surface June 28, 2026 23:14
@cuttlefisch

Copy link
Copy Markdown
Owner Author

Superseded by #189 (rebased onto main after #187 merged; this PR was auto-closed when its base branch was deleted). Same commit, base = main.

cuttlefisch added a commit that referenced this pull request Jul 3, 2026
…ults + config honesty (#188) (#264)

From the pre-dogfood system review (6-subsystem trace, #188). This is the
verified, tested set; the deeper daemon-security items are handed off with
precise diagnoses (see the PR body) and the architecture/scale items are
tracked in #263. Dogfood is hub-scoped (maintainer decision), so mesh-only
findings are deferred.

Fixes:
- Batch-collapse (data loss, verified): the Scheme/AI surface can lower
  several KB lifecycle actions in ONE apply cycle (bulk member onboarding),
  but queue_kb_collab_action wrote a single pending_intent slot, so all but
  the LAST were silently dropped with no error. Now the overflow fans out
  through the existing one-per-tick reconnect_intents queue (FIFO, order
  preserved). Test batched_kb_collab_actions_do_not_collapse_to_the_last.

- Scale defaults for a 472 MB / ~2,800-node KB: max_documents 1000 -> 4096
  (one yrs doc PER node -> 1000 thrashes a 2,800-node KB; pure LRU cap, so
  raising it only costs memory when exceeded). max_update_size_bytes 1 MiB ->
  4 MiB (a large node's full-state push on reseal/share was silently REJECTED;
  4 MiB keeps a DoS bound with headroom). DEFAULT_MAX_UPDATE_SIZE kept in
  sync. DAEMON_ADMIN.md documents the doc-per-node model + the tuning knobs.

- Config honesty (false control surfaces, verified zero readers): marked
  RESERVED (see #263) the dead options collab_batch_update_ms,
  collab_max_pending_updates, kb_backup_interval, kb_backup_retention
  (backup module has no callers) and the dead daemon fields sync_interval_secs,
  decay_interval_secs, heartbeat_interval_secs (the editor's
  collab_heartbeat_interval is the real client keepalive). Removed the
  misleading sync_interval_secs line from DAEMON_ADMIN.md. (Marked, not
  deleted: removing a registered option errors on config-load.)

Refs #188, #263.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
cuttlefisch added a commit that referenced this pull request Jul 3, 2026
…the review (#265) (#266)

The daemon-security bugs deferred from the pre-dogfood review (#188), each done
in a careful pass. Dogfood is hub-scoped, so these are the hub-relevant
correctness/security fixes.

1. Owner rotation -> self-lockout on an un-anchored (owned/hub) KB [FIXED + tested].
   The rotation-successor -> member_roles mirror ran ONLY on the member
   self-service branch; the owner (Manage=Allow) left rebind_pairs empty, so
   after the documented rotate+reconnect the owner got role_of(new_fp)=None ->
   Deny on their own KB. Added owner_self_rebind_pairs: it extracts ONLY the
   authenticated principal's OWN self-Rebinds (author==principal, self-signed,
   fresh successor bound to its key) from the update and mirrors them, inheriting
   the caller's role. It can NOT reuse verify_member_self_service_update (the
   owner's rotation update also carries E2E content-key re-wraps, which that
   strict gate rejects), and it can NOT inject an arbitrary member (predecessor
   is always the caller). Refactored the branch to a match expression.
   Tests: owner_self_rebind_is_mirrored_into_the_roster_no_lockout (the fix) +
   owner_path_does_not_mirror_a_rebind_authored_by_another_principal (adversarial:
   a Rebind authored by another principal yields no pair for a non-author caller).

2. Keyless add on an E2E KB [FIXED]. Adding a member by FINGERPRINT to an
   encrypted KB cannot wrap the content key (no published wrap key to seal to),
   so they were admitted keyless with success UX. The add now emits a prominent
   warning steering to the join->approve path (which seals the key); the add
   still proceeds so the member can be re-keyed on a later approve. Advisory at
   the authoritative point (the network task holds content_keys); not
   unit-tested (network-loop, not a security gate).

3. Import silently became an unpersisted in-memory KB [FIXED]. import_org_dir_to_store
   `?`-aborted on the first bad node (partial persistent state) and the editor
   silently swapped to an in-memory copy that vanished on restart. Now per-node
   error-tolerant (records into report.errors, continues; only tracks a node once
   it persisted), and a catastrophic store failure surfaces a prominent
   message-log ERROR instead of a silent ephemeral swap.

Verification: daemon lib 144 + mae-core 2349 + mae-kb 217 green; workspace clippy
--all-targets clean (both workspaces).

Deferred from #265 (follow-ups): the links:by_dst index (perf, needs a covering
Cozo index rewrite), and the heavier tests (>=3-member mesh/hub convergence,
recovery-on-anchored, leaked-recovery-supersede, offline-across-rotation) — the
e2e-harness + membership-fixture items. Tracked on #265.

Refs #265, #188.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

1 participant