Files
nanoclaw/docs/v1-vs-v2/sender-allowlist.md
gavrielc 47950671fa docs: add v1→v2 action-items analysis + SDK signal probe tool
- docs/v1-vs-v2/: full v1→v2 regression analysis (SUMMARY + 21 per-module
  docs + ACTION-ITEMS rollup with decisions + timezone recreation spec).
- container/agent-runner/scripts/sdk-signal-probe.ts: empirical harness
  used to characterise Claude Agent SDK event/hook/stderr timing for the
  stuck-detection design in item 9.
- src/channels/chat-sdk-bridge.ts: document the conversations Map staleness
  in a code comment; fix deferred to when dynamic group registration lands
  (ACTION-ITEMS item 17).

No runtime behavior change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 01:00:04 +03:00

3.6 KiB

sender-allowlist: v1 vs v2

Scope

  • v1: src/v1/sender-allowlist.ts (97 LOC), sender-allowlist.test.ts (217 LOC) — flat JSON config at ~/.config/nanoclaw/sender-allowlist.json
  • v2 counterparts: src/access.ts (116 LOC), src/router.ts (317 LOC), src/db/schema.ts (user_roles, agent_group_members, messaging_groups.unknown_sender_policy), src/container-runner.ts:291-295 (admin injection), src/types.ts (MessagingGroupAgent.response_scope)

Capability map

v1 behavior v2 location Status Notes
Per-chat entry (chats[chatJid]) messaging_groups.unknown_sender_policy replaced Policy per channel, not allowlist entries
Default entry Default unknown_sender_policy = 'strict' reversed v1 default-allow → v2 default-deny
allow: '*' wildcard Not present removed
allow: string[] (exact-match list) agent_group_members rows + user_roles replaced Role-based / membership-based
mode: 'trigger' (allow for processing) Implicit (access granted → routed) kept
mode: 'drop' (silent drop) recordDroppedMessage() (logs only) partially lost No silent-drop mode; denied = logged
Admin override owner / global_admin / scoped_admin new in v2 Richer privilege hierarchy
Static JSON file Central DB (users, user_roles, agent_group_members) changed Runtime-mutable, queryable
Exact-string sender Namespaced channel_type:handle user IDs enhanced Explicit channel scoping
logDenied flag implicit (log at decision point) kept

Access-model diff

v1: flat allowlist per chat → default-allow → binary allowed/denied. v2: entity model (users + roles + memberships) + per-messaging-group policy (strict | request_approval | public) → default-deny for unknowns.

Strictly more expressive: role hierarchy, per-agent-group scope, three-way unknown handling, user metadata (display_name/kind), runtime reconfig. Lost: per-message drop mode, default-allow posture, simple JSON editing.

Missing from v2

  1. request_approval flow — marked TODO in router.ts:295. Approval-on-first-contact for unknown senders is scaffolded but not wired
  2. response_scope enforcement — field exists ('all' | 'triggered' | 'allowlisted') but is not checked in router.ts or delivery.ts
  3. Trigger-rule matching on messaging_group_agentsrouter.ts:198 TODO ("Future: trigger rule matching"); currently only priority-based agent selection
  4. Silent-drop option for known-noisy senders — v1's mode: 'drop' allowed "I see you but I ignore you"; v2 can only log and drop

Behavioral discrepancies

  1. Default posture flipped: v1 open-by-default vs v2 closed-by-default — breaking for migrations that relied on default-allow
  2. Drop semantics: v1 silent drop; v2 recordDroppedMessage() always logs
  3. Admin bypass: v1 had no implicit bypass; v2 grants owners/admins access regardless of membership — more permissive for privileged users
  4. Scope resolution: v1 per-chat; v2 per-agent-group via user_roles.agent_group_id — misalignment if one chat routes to multiple agent groups with different admins

Worth preserving?

The v2 role-based model is architecturally superior. The gaps worth closing:

  • Finish request_approval flow — half-implemented scaffolding
  • Finish response_scope enforcement — exists in schema but unused
  • Finish trigger-rule matching in pickAgent — without it, every wired agent fires on every message
  • Consider silent-drop via a dedicated table ((agent_group_id, sender_pattern) with action=drop) — orthogonal to privilege