refactor(permissions): preserve pre-PR behavior in three spots

PR #5 review flagged three behavior changes that shouldn't have slipped
in. This commit reverts each to match the pre-refactor behavior exactly.

1. User upsert ordering. Split the router hook into two setters:
   setSenderResolver (runs before agent resolution) and setAccessGate
   (runs after). Restores the pre-PR sequence where the users row is
   upserted even if the message is dropped by wiring or trigger rules.

2. dropped_messages audit. Moved src/modules/permissions/db/dropped-messages.ts
   back to src/db/dropped-messages.ts. The table is core audit infra, not
   permissions-specific. Router re-writes rows for no_agent_wired and
   no_trigger_match; the access gate writes rows for policy refusals.

3. Permissionless container fallback. Dropped. poll-loop restores the
   original deny-all check when NANOCLAW_ADMIN_USER_IDS is empty.

Module contract doc updated with the two-hook shape.

Validation: host build clean, 137/137 host tests, 17/17 container
tests, typecheck clean, service boots to "NanoClaw running" with
permissions module registering both hooks and clean SIGTERM shutdown.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gavrielc
2026-04-18 18:00:10 +03:00
parent 7cc4ecc3be
commit 32bcc2c5ae
5 changed files with 142 additions and 75 deletions

View File

@@ -0,0 +1,44 @@
import { getDb } from './connection.js';
export interface UnregisteredSender {
channel_type: string;
platform_id: string;
user_id: string | null;
sender_name: string | null;
reason: string;
messaging_group_id: string | null;
agent_group_id: string | null;
message_count: number;
first_seen: string;
last_seen: string;
}
export function recordDroppedMessage(msg: {
channel_type: string;
platform_id: string;
user_id: string | null;
sender_name: string | null;
reason: string;
messaging_group_id: string | null;
agent_group_id: string | null;
}): void {
const now = new Date().toISOString();
getDb()
.prepare(
`INSERT INTO unregistered_senders (channel_type, platform_id, user_id, sender_name, reason, messaging_group_id, agent_group_id, message_count, first_seen, last_seen)
VALUES (@channel_type, @platform_id, @user_id, @sender_name, @reason, @messaging_group_id, @agent_group_id, 1, @now, @now)
ON CONFLICT (channel_type, platform_id) DO UPDATE SET
user_id = COALESCE(excluded.user_id, unregistered_senders.user_id),
sender_name = COALESCE(excluded.sender_name, unregistered_senders.sender_name),
reason = excluded.reason,
message_count = unregistered_senders.message_count + 1,
last_seen = excluded.last_seen`,
)
.run({ ...msg, now });
}
export function getUnregisteredSenders(limit = 50): UnregisteredSender[] {
return getDb()
.prepare('SELECT * FROM unregistered_senders ORDER BY last_seen DESC LIMIT ?')
.all(limit) as UnregisteredSender[];
}