feat: blocklist display + introspection — *KB Sharing* Blocked view (closes #162) - #188
Closed
cuttlefisch wants to merge 1 commit into
Closed
feat: blocklist display + introspection — *KB Sharing* Blocked view (closes #162)#188cuttlefisch wants to merge 1 commit into
cuttlefisch wants to merge 1 commit into
Conversation
…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>
Owner
Author
This was referenced Jul 3, 2026
Merged
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>
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
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 inkb_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-onlykb/blocklistquery RPC →{ blocklist: { kb_id: [fp, …] } }(optionalkb_idscopes to one). This is the ONLY way a client learns the local-only blocklist.Editor
CollabState.kb_blocklistscache; asend_blocklist_fetchhelper 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>inbuild_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-pointB→ unblock (kb-sharing-unblock, not owner-gated);diagnostic.errortheme key.build_snapshot, theblockedlist flows automatically into thekb_sharing_statusMCP 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
kb/blocklistquery oracle (reports exactly the blocked principal).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 aBlockedHeader+ oneBlockedrow per principal.Verification
fmt --check+clippy --workspace --all-targets -D warningsclean (incl GUI);cargo test --workspacegreen.Closes #162.
🤖 Generated with Claude Code