Address PR #1590 review feedback:
1. Persist source_channel to DB: Add source_channel column to conversations
table in both PostgreSQL (V14 migration) and libSQL (incremental migration
+ base schema). Add get_conversation_source_channel trait method to
ConversationStore with both backend implementations.
2. Fix hydrate_thread_from_db: Read source_channel from DB instead of
stamping the requesting message's channel, preventing channel confusion
after server restart.
3. Reject reserved WASM channel names: Validate that WASM channels cannot
register as "web", "gateway", "cli", or "repl" to prevent authorization
bypass via name spoofing.
4. Require pending_approval exists: Authorization check now verifies
thread.pending_approval.is_some() before allowing approval-shaped messages
to target a thread.
5. Fail-closed for None source_channel: Use "__bootstrap__" sentinel for
bootstrap threads (authorized from any channel). None now means "deny by
default" instead of "allow by default".
6. Extract and test authorization predicate: is_approval_authorized() helper
with 6 unit tests covering same-channel, cross-channel blocked, web/gateway
always allowed, None denied, and bootstrap sentinel.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>