- 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>
3.6 KiB
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
request_approvalflow — marked TODO inrouter.ts:295. Approval-on-first-contact for unknown senders is scaffolded but not wiredresponse_scopeenforcement — field exists ('all' | 'triggered' | 'allowlisted') but is not checked inrouter.tsordelivery.ts- Trigger-rule matching on
messaging_group_agents—router.ts:198TODO ("Future: trigger rule matching"); currently only priority-based agent selection - 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
- Default posture flipped: v1 open-by-default vs v2 closed-by-default — breaking for migrations that relied on default-allow
- Drop semantics: v1 silent drop; v2
recordDroppedMessage()always logs - Admin bypass: v1 had no implicit bypass; v2 grants owners/admins access regardless of membership — more permissive for privileged users
- 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_approvalflow — half-implemented scaffolding - Finish
response_scopeenforcement — 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