Skip to content

Refactor owner scope across channels and fix default routing fallback - #1151

Merged
henrypark133 merged 14 commits into
stagingfrom
codex/owner-scope-refactor
Mar 16, 2026
Merged

henrypark133 merged 14 commits into
stagingfrom
codex/owner-scope-refactor

Conversation

@henrypark133

@henrypark133 henrypark133 commented Mar 13, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

This refactor makes IronClaw treat persistent state as belonging to one explicit instance owner instead of implicitly reusing whatever channel sender ID happened to arrive with a message.

That fixes the cross-channel inconsistency behind #994 and related issues where data created from one frontend could appear missing from another because some parts of the system stored state under the instance owner while other parts stored it under a channel-specific sender/chat identity.

Concretely, this PR:

  • introduces a stable owner identity via IRONCLAW_OWNER_ID
  • separates owner scope from sender identity and conversation scope in the runtime message model
  • re-keys persistent instance data to owner scope across supported channels
  • removes "default" as the stored routine/proactive delivery target sentinel, while preserving legacy owner broadcast metadata compatibility during transition
  • adds regression and Playwright coverage for the new owner model

Why This Change

Before this PR, user_id had two conflicting meanings depending on where a request came from:

  • the durable IronClaw instance owner
  • the external sender/chat identity from a channel like Telegram

That worked accidentally for some flows and failed badly for others. The most visible symptom was that routines, credentials, and other persistent state could look channel-local even though IronClaw is conceptually a single-user instance.

This PR establishes a cleaner model:

  • owner_id: who owns persistent instance state
  • sender_id: who sent the inbound message on that channel
  • conversation_scope_id: which thread/chat/session this interaction belongs to

Single-user IronClaw now becomes the simple case of one durable owner with many possible channel frontends.

What Changed

1. Config and runtime identity

  • added IRONCLAW_OWNER_ID config resolution, falling back to saved settings and then "default" only as the configured owner identity
  • extended inbound message/runtime handling to carry owner_id, sender_id, and conversation_scope_id
  • added requester_id to JobContext so job execution can distinguish the storage owner from the actor who initiated the request

2. Owner-scoped persistence

These now consistently use owner scope across the owner-capable channels covered in this PR:

  • routines
  • jobs
  • secrets / credentials
  • settings
  • MCP config
  • installed extensions / WASM artifacts
  • workspace memory / workspace-backed state

These remain channel/thread scoped:

  • conversations and thread history
  • approvals
  • undo/session state
  • delivery routing metadata
  • per-channel trust / guest handling

3. Channel behavior

This PR fully wires the owner model for:

  • gateway/http/web
  • CLI / REPL
  • Telegram via WASM channel owner matching
  • owner-aware WASM routing and credential lookup

Important behavior changes:

  • supported owner channels now access the same owner-scoped routines, jobs, settings, and credentials
  • Telegram/WASM only gets owner-global access when the configured owner actor matches; other senders remain guests
  • conversation/session state stays separated by conversation scope even when persistent state is shared

Signal and relay are intentionally not made owner-capable in this pass; they keep current behavior until they get explicit owner-actor configuration.

4. Fix for "default" routing sentinel (#994)

Previously, some proactive sends treated "default" as if it were an actual delivery target. That caused broken routing, especially for Telegram, where a real numeric chat_id was required.

This PR changes that model:

  • NotifyConfig.user is now optional instead of sentinel-driven
  • routines.notify_user is nullable in the database
  • legacy notify_user = 'default' values are normalized to NULL
  • CLI-created routines now default to notify_user = NULL unless the caller supplied a real explicit target
  • proactive sends resolve the owner's real last-seen channel target from stored owner broadcast metadata
  • if a routine explicitly targets a channel and that channel has no resolvable owner route, that delivery is skipped with a warning instead of falling back to broadcast_all
  • if no target can be resolved, we no longer try to deliver to "default"
  • legacy owner broadcast metadata stored under "default" is still read during transition for backwards compatibility

This is the core fix for the routine notification issue in #994.

5. Tests

Added regression coverage for:

  • owner-bound emitted messages
  • guest isolation from owner-global state
  • owner metadata routing fallback
  • legacy "default" broadcast compatibility
  • message-tool owner fallback behavior
  • routine notify target resolution and fallback behavior
  • notify target normalization

Added new Playwright/e2e coverage for:

  • creating a routine over HTTP and seeing it in the web Routines tab
  • creating a routine in web chat and listing it from multiple HTTP sender/thread contexts under the same owner
  • running an HTTP-created full-job routine from the web UI and verifying it appears in the Jobs tab

Reviewer Guide

Suggested review order:

  1. Identity model and config
    • src/config/mod.rs
    • src/channels/channel.rs
    • src/context/state.rs
  2. Owner routing and "default" cleanup
    • src/agent/agent_loop.rs
    • src/cli/routines.rs
    • src/channels/wasm/wrapper.rs
    • src/tools/builtin/message.rs
    • src/agent/routine.rs
    • src/db/libsql/mod.rs
  3. Channel integration
    • channels-src/telegram/src/lib.rs
    • src/channels/http.rs
    • src/main.rs
    • src/app.rs
  4. Migration and e2e validation
    • migrations/V13__owner_scope_notify_targets.sql
    • tests/e2e/scenarios/test_owner_scope.py

Change Type

  • Bug fix
  • New feature
  • Refactor
  • Documentation
  • CI/Infrastructure
  • Security
  • Dependencies

Linked Issue

Closes #994

Validation

  • cargo fmt --all --check
  • bash scripts/pre-commit-safety.sh
  • cargo clippy --all-features --all-targets -- -D warnings
  • cargo clippy --all-targets -- -D warnings
  • cargo clippy --no-default-features --features libsql --all-targets -- -D warnings
  • Focused owner-routing regressions:
    • cargo test -q resolve_routine_notification_user
    • cargo test -q cli_notify_config_defaults_to_runtime_target_resolution
    • cargo test -q normalize_notify_user_treats_legacy_default_as_missing
    • cargo test -q dispatch_emitted_messages
  • Additional branch validation run earlier in the PR:
    • cargo test -q
    • uv run --project tests/e2e python -m pytest tests/e2e/scenarios/test_owner_scope.py -q
    • uv run --project tests/e2e python -m pytest tests/e2e/scenarios/test_chat.py tests/e2e/scenarios/test_tool_execution.py tests/e2e/scenarios/test_owner_scope.py -q
  • Manual testing: Not run

Security Impact

Touches owner-vs-guest authorization and channel routing resolution for HTTP, Telegram, and WASM. No new external destinations or permissions were added. The primary behavior change is that owner-scoped persistence is now explicit, while non-owner external senders remain isolated to guest-scoped conversations and routing state.

Database Impact

Adds migrations/V13__owner_scope_notify_targets.sql and updates fresh-schema migrations/V6__routines.sql so routines.notify_user can be NULL.

Migration behavior:

  • converts legacy notify_user = 'default' rows to NULL
  • preserves compatibility for legacy owner broadcast metadata stored under "default" during transition
  • changes fresh installs to use the nullable notify target schema immediately

Blast Radius

Touches:

  • channel identity plumbing
  • owner/guest authorization boundaries
  • routine execution and notifications
  • message-tool fallback sends
  • app/bootstrap owner binding
  • extension manager owner binding
  • settings/secrets/workspace lookup paths
  • Telegram/WASM owner routing
  • e2e harness configuration

Main risks:

  • an owner-capable channel failing to resolve the correct owner target for proactive sends
  • guest senders accidentally getting owner-global access
  • regressions in conversation scoping where owner scope and thread scope intersect

Rollback Plan

  • revert the PR commits and redeploy
  • if V13__owner_scope_notify_targets.sql has already been applied, backfill routines.notify_user from NULL to "default" before running older binaries
  • operationally, a roll-forward patch is safer than running pre-refactor binaries against a migrated database

Review track: C

Copilot AI review requested due to automatic review settings March 13, 2026 20:27
@github-actions github-actions Bot added scope: agent Agent core (agent loop, router, scheduler) scope: channel Channel infrastructure scope: channel/cli TUI / CLI channel scope: channel/wasm WASM channel runtime scope: tool/builtin Built-in tools scope: tool/wasm WASM tool sandbox scope: db/postgres PostgreSQL backend scope: db/libsql libSQL / Turso backend scope: config Configuration scope: extensions Extension management scope: setup Onboarding / setup scope: docs Documentation size: XL 500+ changed lines risk: high Safety, secrets, auth, or critical infrastructure contributor: core 20+ merged PRs labels Mar 13, 2026
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly refactors the system's ownership model, moving from an implicit 'default' user concept to an explicit owner scope. This change ensures that persistent data and configurations are consistently tied to a single owner across all communication channels, while maintaining appropriate isolation for guest users and conversation-specific state. The update streamlines proactive message routing and enhances the system's robustness and clarity regarding data ownership.

Highlights

  • Explicit Owner Scoping: Introduced an explicit IRONCLAW_OWNER_ID and updated internal plumbing (owner_id, sender_id, conversation_scope_id) to ensure instance-global state is consistently owned across HTTP/web, Telegram, and WASM channels.
  • State Rescoping: Routines, jobs, settings, workspace memory, extensions, and credentials are now explicitly scoped to the owner, while conversations, approvals, undo state, and routing metadata remain scoped to the channel/thread.
  • Proactive Delivery Refinement: Removed the 'default' sentinel for proactive delivery. Routine notification users are now optional, with owner targets resolved from stored channel metadata, and a compatibility fallback for legacy records.
  • Database Migration: Added a new database migration (V13) to make the notify_user column in the routines table nullable and normalize existing 'default' values to NULL, reflecting the new optional nature of explicit notification targets.
  • Enhanced Testing: Added comprehensive regression coverage and new Playwright end-to-end tests to validate cross-channel routine visibility, owner-scoped HTTP/web flows, and routine/job execution.
Changelog
  • FEATURE_PARITY.md
    • Updated descriptions for 'Single-user system', 'Session-based messaging', 'WASM channels', and 'Telegram' to reflect the new owner scope model.
  • channels-src/telegram/src/lib.rs
    • Modified owner validation logic to treat non-owner senders as guests, allowing host/runtime layers to handle authorization.
    • Updated thread_id to use message.chat.id for more accurate conversation scoping.
  • migrations/V13__owner_scope_notify_targets.sql
    • Added a new migration to make the notify_user column in the routines table nullable.
    • Normalized existing 'default' values in notify_user to NULL during migration.
  • migrations/V6__routines.sql
    • Modified the notify_user column definition to be nullable and removed its default 'default' value.
  • src/agent/agent_loop.rs
    • Added an owner_id method to Agent to retrieve the current owner's ID.
    • Updated self-repair broadcast messages to use the repair_owner_id instead of 'default'.
    • Adjusted routine notification logic to resolve notify_user from owner_id if not explicitly set.
    • Introduced owner_id as a fallback for notify_user in send_notification metadata.
  • src/agent/commands.rs
    • Updated set_setting calls to use self.owner_id() for owner-scoped settings persistence.
  • src/agent/dispatcher.rs
    • Added requester_id to JobContext initialization for better tracking of the message sender.
  • src/agent/heartbeat.rs
    • Updated user_id resolution for heartbeat messages to use workspace.user_id().
    • Included owner_id in heartbeat message metadata.
  • src/agent/routine.rs
    • Changed the user field in NotifyConfig to Option<String> to allow for optional explicit notification targets.
    • Updated the default value for NotifyConfig.user to None.
  • src/agent/routine_engine.rs
    • Modified the send_notification function to accept and include owner_id in notification metadata.
    • Added owner_id to job metadata when executing full jobs from routines.
  • src/agent/thread_ops.rs
    • Added requester_id to JobContext when executing approved tools.
  • src/app.rs
    • Updated calls to migrate_disk_to_db, Config::from_db_with_toml, session.attach_store, re_resolve_llm, inject_llm_keys_from_secrets, Workspace::new_with_db, load_mcp_servers_from_db, and wasm_router.register_channel_host to consistently use config.owner_id instead of 'default'.
  • src/channels/channel.rs
    • Added owner_id, sender_id, and conversation_scope_id fields to the IncomingMessage struct.
    • Introduced new builder methods: with_owner_id, with_sender_id, and with_conversation_scope for IncomingMessage.
    • Added conversation_scope() and routing_target() methods to IncomingMessage for flexible conversation and routing target resolution.
    • Implemented routing_target_from_metadata function to extract channel-specific routing targets from message metadata.
  • src/channels/http.rs
    • Updated IncomingMessage creation to explicitly set owner_id and sender_id based on the HTTP request state.
  • src/channels/mod.rs
    • Exported the new routing_target_from_metadata function.
  • src/channels/repl.rs
    • Added a user_id field to ReplChannel to represent the owner scope.
    • Updated ReplChannel constructors (new, with_user_id, with_message, with_message_for_user) to manage the owner scope.
    • Modified message creation within the REPL channel to use the configured user_id.
  • src/channels/wasm/setup.rs
    • Modified credential retrieval (get_decrypted) and channel registration (inject_channel_credentials) to use config.owner_id for owner-scoped secrets.
    • Added owner_actor_id to WasmChannel binding during channel setup.
  • src/channels/wasm/wrapper.rs
    • Added owner_scope_id and owner_actor_id fields to the WasmChannel struct for explicit owner binding.
    • Updated do_update_broadcast_metadata to use owner_scope_id for persisting broadcast metadata.
    • Introduced resolve_message_scope and apply_emitted_metadata helper functions for message processing.
    • Modified WasmChannel constructors and methods to incorporate and utilize the owner scope.
    • Updated dispatch_emitted_messages to correctly handle owner/guest scoping and conditional broadcast metadata storage.
    • Changed broadcast logic to resolve the target based on stored owner metadata when broadcasting to the owner scope.
    • Updated resolve_channel_host_credentials to use owner_scope_id for credential resolution.
    • Added new unit tests to verify owner binding and guest isolation behavior in WASM channels.
  • src/cli/doctor.rs
    • Updated check_gateway_config to resolve owner_id from environment variables or settings, ensuring correct configuration validation.
  • src/cli/routines.rs
    • Changed the notify_user field in NotifyConfig to Some(user_id.to_string()) when creating routines via CLI.
  • src/config/channels.rs
    • Modified ChannelsConfig::resolve to accept an owner_id parameter and use it for setting http.user_id and gateway.user_id.
  • src/config/mod.rs
    • Added an owner_id field to the main Config struct.
    • Updated Config::default and Config::build methods to properly initialize and resolve the owner_id.
    • Introduced a resolve_owner_id function to determine the owner ID from environment or settings.
  • src/context/state.rs
    • Added a requester_id field to JobContext to store the channel-specific sender ID.
    • Provided a with_requester_id builder method for JobContext.
  • src/db/libsql/jobs.rs
    • Updated row_to_job_context_libsql to include requester_id: None for backward compatibility.
  • src/db/libsql/mod.rs
    • Added a normalize_notify_user function to handle legacy 'default' values as None.
    • Updated row_to_routine_libsql to use normalize_notify_user when loading routine notification users.
    • Added a unit test for normalize_notify_user.
  • src/db/libsql/routines.rs
    • Updated notify_user handling in routine persistence to use opt_text(routine.notify.user.as_deref()), accommodating the new Option<String> type.
  • src/db/libsql_migrations.rs
    • Added a new migration (V13) to modify the routines table, making notify_user nullable and converting 'default' values to NULL.
  • src/extensions/manager.rs
    • Updated WasmChannel creation within the extension manager to bind to self.user_id for owner scope.
  • src/history/store.rs
    • Updated row_to_job_context to include requester_id: None for backward compatibility.
  • src/main.rs
    • Updated ReplChannel instantiation to use config.owner_id for owner-scoped CLI interactions.
    • Changed ToolWebhookState.user_id to config.owner_id for consistent owner identification in webhooks.
    • Modified the SIGHUP signal handler to use sighup_owner_id for retrieving secrets and reloading configuration.
  • src/settings.rs
    • Added an owner_id field to the Settings struct.
    • Modified from_db_map and to_db_map methods to correctly handle owner_id as a bootstrap configuration that is not persisted in the database settings table.
  • src/setup/wizard.rs
    • Added an owner_id helper method to SetupWizard.
    • Updated various get_all_settings and set_setting calls to use self.owner_id() for owner-scoped database operations.
    • Modified SecretsContext::from_store calls to pass self.owner_id() for proper secret scoping.
  • src/testing/mod.rs
    • Updated NotifyConfig.user in test routines to Some("user1".to_string()) to align with the new Option<String> type.
  • src/tools/builtin/message.rs
    • Modified the message tool's target resolution logic to include a fallback to the JobContext's user_id (owner scope) when a channel is known but no explicit target is provided.
    • Updated the error message for unresolved targets to be more descriptive.
    • Added a new test case to verify the message tool's fallback behavior to ctx.user_id when a channel is known.
  • src/tools/builtin/routine.rs
    • Updated the description for notify_user in the routine creation tool to clarify its optional nature and owner-target resolution.
    • Changed the notify_user parameter type to Option<String>.
  • src/tools/wasm/wrapper.rs
    • Changed the credential_user_id for resolving host credentials from a hardcoded 'default' to ctx.user_id, ensuring credentials are resolved based on the job's owner scope.
  • tests/e2e/conftest.py
    • Added a server_ports fixture to reserve dynamic ports for the gateway and HTTP webhook channel.
    • Updated the ironclaw_server fixture to set IRONCLAW_OWNER_ID, configure HTTP channel host/port/secret, and enable routines.
  • tests/e2e/helpers.py
    • Added OWNER_SCOPE_ID and HTTP_WEBHOOK_SECRET constants.
    • Introduced signed_http_webhook_headers function for creating authenticated HTTP webhook requests.
    • Added new Playwright selectors for jobs and routines tables.
  • tests/e2e/mock_llm.py
    • Added new tool call patterns for creating lightweight and full-job owner routines, and for listing owner routines.
  • tests/e2e/scenarios/test_owner_scope.py
    • Added a new file containing end-to-end tests to validate owner scope functionality across the web UI, HTTP webhook channel, routine creation, and job execution.
  • tests/e2e_routine_heartbeat.rs
    • Updated IncomingMessage creation in tests to explicitly set owner_id, sender_id, and conversation_scope_id.
  • tests/telegram_auth_integration.rs
    • Updated comments to reflect the new owner-scope model for Telegram, clarifying that non-owner senders are treated as guests rather than being hard-dropped.
Activity
  • The pull request introduces a significant refactoring to establish explicit owner scoping across various components.
  • New database migrations were added to support the updated data model for routine notifications.
  • Extensive unit and end-to-end tests, including Playwright scenarios, were implemented to ensure the correctness and stability of the owner scope changes and proactive routing.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a significant and well-executed refactoring to establish a clear owner scope across various channels, which is a crucial improvement for multi-channel and multi-user scenarios. The changes consistently replace hardcoded "default" user scopes with a resolved owner ID, properly scoping routines, jobs, settings, and credentials. The logic for routing notifications, especially the fallback for owner-targeted messages, has been fixed and made more robust. The introduction of new fields in IncomingMessage to distinguish between owner, sender, and conversation scope is a solid architectural enhancement. The addition of new E2E tests covering these cross-channel owner-scoped flows provides confidence in the correctness of this large-scale change. Overall, this is an excellent refactoring that greatly improves the application's architecture and maintainability.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Refactors “owner scope” handling so instance-global state (routines/jobs/settings/secrets/extensions/workspace) is consistently keyed to a stable owner ID across channels, while channel interactions remain scoped to sender/thread; also fixes routine notification routing by removing the legacy "default" notify target sentinel and resolving proactive delivery targets from stored channel metadata.

Changes:

  • Introduces explicit IRONCLAW_OWNER_ID / owner_id / sender_id / conversation_scope_id plumbing across channels, agent context, and tool execution.
  • Makes routines.notify_user nullable (migrations + DB adapters) and updates routine/message tooling to resolve owner targets from persisted channel metadata instead of "default".
  • Adds/updates regression coverage and new Playwright E2E scenarios for cross-channel owner-scoped routine/job visibility and HTTP/web owner flows.

Reviewed changes

Copilot reviewed 42 out of 42 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tests/telegram_auth_integration.rs Updates Telegram auth expectations for owner-scope/guest behavior.
tests/e2e_routine_heartbeat.rs Extends test messages to include new owner/sender/scope fields.
tests/e2e/scenarios/test_owner_scope.py Adds E2E coverage for owner-scoped HTTP/web routine + job flows.
tests/e2e/mock_llm.py Adds mock tool-call patterns for routine create/list used by new E2E tests.
tests/e2e/helpers.py Adds signed HTTP webhook helper and selectors/constants for jobs/routines UI.
tests/e2e/conftest.py Boots gateway + HTTP webhook with owner scope env and new fixtures.
src/tools/wasm/wrapper.rs Switches WASM tool host-credential resolution to use job context scope.
src/tools/builtin/routine.rs Makes notify_user optional and updates tool param handling accordingly.
src/tools/builtin/message.rs Adds target resolution fallback for owner-scoped proactive sends + tests.
src/testing/mod.rs Updates test fixtures for NotifyConfig.user becoming optional.
src/setup/wizard.rs Keys settings/secrets persistence off resolved owner scope instead of hardcoded default.
src/settings.rs Adds bootstrap-only owner_id and prevents persisting it to the DB settings map.
src/main.rs Threads owner scope through REPL/webhooks and SIGHUP config/secrets reload.
src/history/store.rs Initializes new JobContext.requester_id field when hydrating jobs.
src/extensions/manager.rs Binds WASM channels to owner scope and optional owner actor IDs.
src/db/libsql_migrations.rs Makes notify_user nullable in fresh schema and adds incremental migration 13.
src/db/libsql/routines.rs Writes notify_user as NULL-able via opt_text(...).
src/db/libsql/mod.rs Normalizes legacy "default"/empty notify_user values to None on read + tests.
src/db/libsql/jobs.rs Initializes new JobContext.requester_id field when hydrating jobs.
src/context/state.rs Adds requester_id to JobContext for channel-actor attribution.
src/config/mod.rs Adds Config.owner_id and resolves it from env/settings, feeding channel config.
src/config/channels.rs Resolves HTTP/gateway channel user IDs from the resolved owner scope.
src/cli/routines.rs Updates CLI routine creation for optional notify user.
src/cli/doctor.rs Updates doctor gateway config resolution to include owner scope.
src/channels/wasm/wrapper.rs Adds owner binding + guest isolation, conversation scope extraction, and routing fallback for broadcasts.
src/channels/wasm/setup.rs Loads channel secrets and injects credentials under owner scope; binds owner actor IDs.
src/channels/repl.rs Adds owner scope to REPL channel messages and single-message mode.
src/channels/mod.rs Re-exports routing_target_from_metadata for routing fallback usage.
src/channels/http.rs Sets owner_id/sender_id on HTTP webhook messages while keeping owner scope as channel identity.
src/channels/channel.rs Adds owner/sender/scope fields and routing helpers to IncomingMessage.
src/app.rs Threads owner scope through bootstrap, session, workspace, MCP, and WASM setup.
src/agent/thread_ops.rs Propagates requester identity into tool-execution job contexts.
src/agent/routine_engine.rs Adds owner_id into routine/job metadata for downstream routing decisions.
src/agent/routine.rs Makes routine notify target optional (NotifyConfig.user: Option<String>).
src/agent/heartbeat.rs Uses workspace owner scope as notify default and records owner_id in metadata.
src/agent/dispatcher.rs Propagates requester identity into tool-execution job contexts.
src/agent/commands.rs Persists model selection under owner scope.
src/agent/agent_loop.rs Uses routing_target helper for message-tool default target and carries owner_id through notification routing.
migrations/V6__routines.sql Updates fresh SQL schema to allow NULL notify_user.
migrations/V13__owner_scope_notify_targets.sql Adds Postgres migration to drop default sentinel + normalize legacy "default" to NULL.
channels-src/telegram/src/lib.rs Keeps non-owner senders as guests and sets thread_id to chat ID for conversation scoping.
FEATURE_PARITY.md Updates documentation to reflect explicit owner scope and routing model.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/agent/agent_loop.rs
Comment thread tests/e2e/conftest.py Outdated
Comment thread src/db/libsql_migrations.rs
Comment thread src/config/mod.rs Outdated
Comment thread src/cli/doctor.rs Outdated
Copilot AI review requested due to automatic review settings March 13, 2026 21:21

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Refactors IronClaw’s identity/persistence model to use an explicit, stable owner scope (via IRONCLAW_OWNER_ID) rather than implicitly keying persistent state off the channel sender ID, and fixes the "default" proactive routing sentinel by resolving real channel targets from stored owner metadata instead.

Changes:

  • Introduces owner/sender/conversation scope plumbing across channels (HTTP/REPL/WASM/Telegram) and job execution (requester_id).
  • Makes routine notify targets nullable (removing the "default" sentinel) with DB migrations + normalization and updated routing fallback logic.
  • Adds/updates regression + Playwright/E2E coverage for owner scope behavior and routing fallbacks.

Reviewed changes

Copilot reviewed 43 out of 43 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
tests/telegram_auth_integration.rs Updates auth behavior expectations for owner vs guest Telegram senders.
tests/e2e_routine_heartbeat.rs Adapts tests to new IncomingMessage identity fields.
tests/e2e_builtin_tool_coverage.rs Updates routine notify assertions for optional notify targets.
tests/e2e/scenarios/test_owner_scope.py New E2E scenarios validating owner-global routines/jobs across web + HTTP.
tests/e2e/mock_llm.py Adds mock tool-call patterns for owner-scope routine scenarios.
tests/e2e/helpers.py Adds HTTP webhook signing helpers and test constants.
tests/e2e/conftest.py Wires owner ID + HTTP channel into the E2E harness.
src/tools/wasm/wrapper.rs Switches host-credential lookup to context-scoped user ID.
src/tools/builtin/routine.rs Makes notify_user optional and stops defaulting to "default".
src/tools/builtin/message.rs Adds owner-scope fallback behavior when channel is known but target omitted.
src/testing/mod.rs Updates test routines to use NotifyConfig.user: Option<String>.
src/setup/wizard.rs Re-keys settings/secrets operations off the resolved owner scope (no longer hardcoded "default").
src/settings.rs Adds non-persisted bootstrap owner_id and filters it out of DB settings maps.
src/main.rs Passes configured owner ID into CLI/REPL and tool webhook state.
src/history/store.rs Initializes loaded job contexts with requester_id: None.
src/extensions/manager.rs Binds WASM channels to owner scope + optional owner actor ID.
src/db/libsql_migrations.rs Makes routines.notify_user nullable + adds migration to normalize legacy "default".
src/db/libsql/routines.rs Writes notify_user as nullable for libSQL routines.
src/db/libsql/mod.rs Adds notify-user normalization helper and applies it to routine row mapping.
src/db/libsql/jobs.rs Initializes loaded job contexts with requester_id: None.
src/context/state.rs Adds requester_id to JobContext and builder method.
src/config/mod.rs Resolves owner_id from env/settings and threads it into channel config resolution.
src/config/channels.rs Makes HTTP/gateway channel configs owner-scoped (user_id derived from owner).
src/cli/routines.rs Makes CLI routine notify config rely on runtime target resolution (no explicit user).
src/cli/doctor.rs Updates gateway config check to resolve owner ID before channel config resolution.
src/channels/wasm/wrapper.rs Adds owner binding, owner-scope routing metadata persistence, and owner-aware broadcast routing.
src/channels/wasm/setup.rs Injects owner-scoped secrets/credentials and owner binding when registering WASM channels.
src/channels/repl.rs Makes REPL messages owner-scoped and plumbs user_id through message construction.
src/channels/mod.rs Re-exports routing_target_from_metadata.
src/channels/http.rs Creates owner-scoped HTTP messages with separate sender_id and stable conversation scope.
src/channels/channel.rs Extends IncomingMessage with owner/sender/conversation-scope and routing target helpers.
src/app.rs Re-keys bootstrap/migration/session/workspace/MCP loading to owner scope.
src/agent/thread_ops.rs Sets requester_id when executing tools from approvals.
src/agent/routine_engine.rs Carries owner_id in notification/job metadata and routes notifications with owner scope.
src/agent/routine.rs Makes NotifyConfig.user optional and updates defaults.
src/agent/heartbeat.rs Defaults heartbeat notify target to workspace/owner scope and carries owner_id in metadata.
src/agent/dispatcher.rs Sets requester_id for interactive chat tool execution contexts.
src/agent/commands.rs Persists selected model under owner scope (not hardcoded "default").
src/agent/agent_loop.rs Adds owner-aware notification routing fallback logic and uses routing_target helper.
migrations/V6__routines.sql Updates fresh schema to make notify_user nullable.
migrations/V13__owner_scope_notify_targets.sql Postgres migration to drop sentinel default + normalize legacy rows.
channels-src/telegram/src/lib.rs Treats non-owner senders as guests; sets thread_id to chat ID for scoping.
FEATURE_PARITY.md Updates parity notes to reflect explicit owner scope model.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/cli/doctor.rs Outdated
Comment thread src/agent/agent_loop.rs Outdated
Comment thread src/channels/wasm/wrapper.rs Outdated
Comment thread src/channels/http.rs Outdated
Comment thread src/db/libsql_migrations.rs
Comment thread src/setup/wizard.rs
Copilot AI review requested due to automatic review settings March 13, 2026 22:33

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Refactors runtime identity and persistence to use an explicit, stable instance owner_id (configurable via IRONCLAW_OWNER_ID) rather than implicitly keying durable state off channel sender IDs, and fixes routine notification routing by removing the legacy "default" notify-user sentinel.

Changes:

  • Introduces owner/sender/conversation-scope identity plumbing across channels, agent runtime, and job context.
  • Makes routine notify targets nullable (migrations + stores) and updates routing to resolve owner last-seen targets instead of sending to "default".
  • Adds/updates unit + integration + Playwright E2E coverage for owner scoping and routing fallback behavior.

Reviewed changes

Copilot reviewed 45 out of 45 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/telegram_auth_integration.rs Updates Telegram WASM auth integration expectations for owner-vs-guest behavior.
tests/support/test_rig.rs Passes resolved owner_id into AgentDeps in test harness builder.
tests/support/gateway_workflow_harness.rs Passes resolved owner_id into AgentDeps in gateway workflow harness.
tests/e2e_routine_heartbeat.rs Adapts test messages to new IncomingMessage identity fields.
tests/e2e_builtin_tool_coverage.rs Updates assertions for NotifyConfig.user becoming Option.
tests/e2e/scenarios/test_owner_scope.py New Playwright E2E scenarios validating owner-scoped routines/jobs across web + HTTP.
tests/e2e/mock_llm.py Adds mock LLM patterns for owner-scope routine create/list flows.
tests/e2e/helpers.py Adds signed webhook helper + new selectors/constants for owner-scope E2E.
tests/e2e/conftest.py Boots gateway + HTTP webhook with owner-scope env and distinct ports; enables routines.
src/tools/wasm/wrapper.rs Switches host-credential lookup to use JobContext.user_id (scope-aware).
src/tools/builtin/routine.rs Makes notify_user optional in tool schema + creation logic.
src/tools/builtin/message.rs Adds owner-scope fallback routing when channel is known; adds regression test.
src/testing/mod.rs Updates test harness defaults for AgentDeps.owner_id and nullable notify users.
src/setup/wizard.rs Loads bootstrap settings (env/TOML) and threads resolved owner scope into setup flows.
src/settings.rs Adds bootstrap-only owner_id field (not persisted in per-user DB settings table).
src/main.rs Uses TOML-aware setup wizard constructor; binds CLI/REPL + webhooks + SIGHUP reload to owner scope.
src/history/store.rs Initializes new JobContext.requester_id when hydrating jobs from DB.
src/extensions/manager.rs Binds WASM channels to owner scope + optional owner-actor mapping.
src/db/libsql_migrations.rs Makes routines.notify_user nullable; adds migration to normalize legacy 'default'.
src/db/libsql/routines.rs Writes notify_user as nullable for libSQL routines store.
src/db/libsql/mod.rs Adds normalize_notify_user() and applies it when reading routines.
src/db/libsql/jobs.rs Initializes new JobContext.requester_id when hydrating jobs from DB.
src/context/state.rs Adds requester_id to JobContext plus builder method.
src/config/mod.rs Adds owner_id to config + bootstrap settings loader + owner-id resolver.
src/config/channels.rs Resolves gateway/HTTP channel configured user_id from owner scope.
src/cli/routines.rs Defaults CLI-created routine notify target to “resolve at runtime” (user: None).
src/cli/doctor.rs Uses shared owner-id resolution before resolving channel config.
src/channels/wasm/wrapper.rs Implements owner binding, emitted-message scope resolution, owner-target routing metadata persistence, and owner-route broadcast resolution.
src/channels/wasm/setup.rs Looks up channel secrets under owner scope; binds channels to owner scope/actor mapping; injects credentials owner-scoped.
src/channels/repl.rs Adds REPL user binding so REPL operates under configured owner scope.
src/channels/mod.rs Re-exports routing_target_from_metadata for shared routing resolution.
src/channels/http.rs Separates HTTP owner scope from sender identity; populates owner/sender fields + conversation scope.
src/channels/channel.rs Extends IncomingMessage with owner_id, sender_id, conversation_scope_id and routing helpers.
src/app.rs Re-keys bootstrap migration, session attach, workspace, MCP server load, and WASM setup to owner scope.
src/agent/thread_ops.rs Sets JobContext.requester_id from inbound sender for approval/tool flows.
src/agent/routine_engine.rs Ensures event triggers respect routine user_id scope; propagates owner_id into notifications/job metadata.
src/agent/routine.rs Makes NotifyConfig.user optional and updates default.
src/agent/heartbeat.rs Defaults heartbeat notify user to workspace owner scope; annotates owner_id in metadata.
src/agent/dispatcher.rs Sets JobContext.requester_id for interactive chat execution.
src/agent/commands.rs Persists selected model setting under resolved owner scope.
src/agent/agent_loop.rs Adds owner-id handling, routine notification target resolution, fallback logic, and conversation-scope usage.
migrations/V6__routines.sql Updates fresh schema: notify_user nullable (no 'default' sentinel).
migrations/V13__owner_scope_notify_targets.sql Postgres migration: drops NOT NULL/default on notify_user, normalizes 'default' to NULL.
channels-src/telegram/src/lib.rs Changes owner handling to treat non-owner as guest (authorization applies); emits chat id as thread scope.
FEATURE_PARITY.md Updates parity notes to document explicit owner scope and identity separation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/agent/agent_loop.rs Outdated
Comment thread tests/telegram_auth_integration.rs Outdated
Comment thread src/setup/wizard.rs Outdated
@henrypark133
henrypark133 requested a review from zmanian March 13, 2026 22:57

@zmanian zmanian left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review

The architecture is sound -- cleanly separating owner_id / sender_id / conversation_scope_id addresses a real cross-channel identity inconsistency. CI is green across all checks. Three issues to address before merge.

Blocking

1. HTTP channel debug log is misleading (src/channels/http.rs)

The handler now uses req.user_id as sender_id while state.user_id remains the storage scope, but the existing debug log still says the provided user_id is "being ignored." This will mislead operators debugging webhook behavior. Update the log message to reflect the new semantics.

2. WASM tool credential lookup change needs verification (src/tools/wasm/wrapper.rs)

The change from let credential_user_id = "default" to let credential_user_id = &ctx.user_id reverses a previous explicit fix. The old comment explained: ExtensionManager stores OAuth tokens under user_id "default".

Now that ctx.user_id is the owner scope for owner-originated messages, this should work for the owner. But if ExtensionManager still stores credentials under one key (e.g., config.owner_id) while ctx.user_id resolves to something different, credential lookup silently fails with no error -- the tool just gets no credentials.

Please add:

  • A trace log when credential lookup finds no match (to aid debugging)
  • A test case verifying that a WASM tool invoked from an owner-scoped message can resolve credentials stored under the owner scope

3. owner_scope_id defaults to "default" silently (src/channels/wasm/wrapper.rs)

WasmChannel::new() sets owner_scope_id: "default".to_string() before with_owner_binding() is called. If any code path constructs a WasmChannel and forgets to call with_owner_binding(), it silently operates under the wrong scope. Consider either:

  • Making owner_scope_id a required constructor parameter, or
  • Using Option<String> with explicit None-handling to force callers to bind ownership

Non-blocking

  • Duplicate index in libSQL migration 13: idx_routines_event_triggers and idx_routines_user both index routines(user_id). The event triggers index should probably be a composite index (e.g., (user_id, trigger_type)).
  • Telegram thread_id behavioral change: thread_id changed from None to Some(chat_id). Confirm conversation dedup and history lookup work correctly with this -- previously Telegram messages had no thread_id.
  • "default" owner warning: If IRONCLAW_OWNER_ID=default is set explicitly, it silently gets legacy behavior. Consider logging a warning.
  • E2E _find_distinct_free_ports TOCTOU race: Known limitation, acknowledged by author.

Security

  • Owner-vs-guest model is correct: resolve_message_scope() checks owner_actor_id == sender_id. Non-owners remain guest-scoped.
  • uses_owner_broadcast_target() is a simple equality check -- no "default" bypass vulnerability.
  • Migrations correctly normalize "default" to NULL in both backends.
  • No injection risks or sensitive data logging issues identified.

@henrypark133
henrypark133 force-pushed the codex/owner-scope-refactor branch from 4c4852e to 6ebf011 Compare March 16, 2026 15:21

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors IronClaw’s identity model to separate durable owner-scoped persistence from per-channel sender/conversation scope, and removes the legacy "default" notify routing sentinel by making routine notification targets nullable and resolved at send time.

Changes:

  • Introduces explicit owner_id plumbing across config, channels, job context, and agent loop (owner vs sender vs conversation scope).
  • Updates routine notification behavior (notify_user nullable; resolve last-seen owner routing target; avoid sending to "default").
  • Adds/updates integration + E2E coverage for owner scoping across HTTP/web/REPL/WASM/Telegram and routine/job flows.

Reviewed changes

Copilot reviewed 49 out of 49 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/wasm_channel_integration.rs Updates WASM channel construction for new owner-scope parameter.
tests/telegram_auth_integration.rs Strengthens Telegram owner/guest behavior tests and thread scoping expectations.
tests/support/test_rig.rs Passes owner_id into AgentDeps in test rig.
tests/support/gateway_workflow_harness.rs Passes owner_id into AgentDeps in gateway harness.
tests/e2e_routine_heartbeat.rs Updates message fixtures for new IncomingMessage fields and adds owner/guest trigger regression.
tests/e2e/scenarios/test_owner_scope.py New E2E scenarios validating owner-global routines/jobs across web + HTTP sender contexts.
tests/e2e/mock_llm.py Adds mock LLM tool patterns for routine create/list used by E2E owner-scope scenarios.
tests/e2e/helpers.py Adds HTTP webhook signing helper and new UI selectors for routines/jobs.
tests/e2e/conftest.py Configures owner id + HTTP webhook channel for E2E runs; reserves ports.
src/tools/wasm/wrapper.rs Resolves host credentials using job context scope; adds tests around owner-scoped credential lookup.
src/tools/builtin/routine.rs Makes notify_user optional and stops defaulting to "default".
src/tools/builtin/message.rs Adds owner-scope fallback target resolution when channel is known; adds regression test.
src/testing/mod.rs Updates test harness deps and NotifyConfig option type.
src/setup/wizard.rs Refactors wizard bootstrap owner scope + backend-specific DB/secrets handling and model discovery helpers.
src/settings.rs Adds bootstrap owner_id field and ensures it’s excluded from DB settings map serialization.
src/main.rs Wires owner_id into REPL, webhooks, agent deps, and SIGHUP config reload paths.
src/history/store.rs Extends loaded job context shape with requester_id field (currently None from store).
src/extensions/manager.rs Binds loaded WASM channels to an optional per-channel owner actor id.
src/error.rs Adds structured ChannelError::MissingRoutingTarget for routing-aware fallback decisions.
src/db/libsql_migrations.rs Makes routines.notify_user nullable and adds migration to normalize legacy 'default'; improves routine trigger index.
src/db/libsql/routines.rs Persists nullable notify_user in libSQL routine store.
src/db/libsql/mod.rs Adds normalize_notify_user() and applies it when reading routines.
src/db/libsql/jobs.rs Extends loaded job context shape with requester_id field (currently None from store).
src/context/state.rs Adds requester_id to JobContext and a builder setter.
src/config/mod.rs Adds bootstrap settings loader + resolve_owner_id(); adds Config.owner_id; updates channels resolve call signature.
src/config/channels.rs Refactors channels config resolution to use owner_id and owner-aware channel IDs.
src/cli/routines.rs Makes CLI-created routine notify target default to runtime resolution (user: None).
src/cli/doctor.rs Uses shared resolve_owner_id() before resolving channels config.
src/channels/wasm/wrapper.rs Adds owner scope + owner actor binding; owner-scoped broadcast metadata storage and routing target resolution.
src/channels/wasm/setup.rs Injects owner scope into WASM loader and secrets lookup; binds owner actor id per channel.
src/channels/wasm/router.rs Updates test helper to pass owner-scope argument to WASM channel constructor.
src/channels/wasm/mod.rs Updates module docs to reflect new loader constructor signature.
src/channels/wasm/loader.rs Stores owner_scope_id on loader and passes it into constructed channels; updates tests.
src/channels/repl.rs Adds REPL user_id/owner-scope binding (including single-message mode).
src/channels/mod.rs Re-exports routing_target_from_metadata.
src/channels/http.rs Treats request user_id as sender_id (trimmed), keeps owner scope fixed, and adds regressions.
src/channels/channel.rs Extends IncomingMessage with owner_id, sender_id, and conversation_scope_id; adds routing_target helpers.
src/app.rs Re-keys bootstrap/DB config, session attach, MCP loading, workspace scope, and secrets injection to owner_id.
src/agent/thread_ops.rs Sets requester_id in JobContext for approval tool execution.
src/agent/routine_engine.rs Filters event triggers by message scope; includes owner_id in routine notification metadata and full-job metadata.
src/agent/routine.rs Makes NotifyConfig user target optional (nullable notify).
src/agent/heartbeat.rs Defaults heartbeat notify user_id to workspace owner scope rather than "default".
src/agent/dispatcher.rs Sets requester_id in JobContext for interactive chat tool execution.
src/agent/commands.rs Persists selected model under resolved owner_id scope.
src/agent/agent_loop.rs Adds owner_id to AgentDeps; adds structured routine notification target resolution and routing fallback behavior; uses conversation_scope().
migrations/V6__routines.sql Updates fresh schema to make notify_user nullable.
migrations/V13__owner_scope_notify_targets.sql New migration to drop NOT NULL/DEFAULT and normalize 'default' → NULL.
channels-src/telegram/src/lib.rs Updates Telegram WASM channel behavior for owner/guest authorization and thread scoping; removes topic-thread routing fields.
FEATURE_PARITY.md Updates parity notes to reflect explicit owner scoping and routing behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/config/channels.rs
Comment thread tests/e2e/helpers.py
Comment thread channels-src/telegram/src/lib.rs
@henrypark133
henrypark133 requested a review from zmanian March 16, 2026 16:00

@zmanian zmanian left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a large, foundational refactor (6200+ lines) introducing explicit owner identity (IRONCLAW_OWNER_ID) across the entire system. CI is green. The PR description and reviewer guide are excellent -- one of the most thorough I've seen.

Assessment:

This is clearly Track C (security/runtime/DB) and deserves careful review. Key observations:

1. The owner model is well-designed

The separation of owner_id / sender_id / conversation_scope_id is the right abstraction. The fallback chain (env var -> saved settings -> "default") is reasonable.

2. Database migration V13

Converting notify_user = 'default' to NULL is a one-way migration. The rollback plan correctly notes that downgrade requires backfilling. This is acceptable for a Track C change.

3. Security boundary concern

The PR touches owner-vs-guest authorization boundaries. The description says "Telegram/WASM only gets owner-global access when the configured owner actor matches; other senders remain guests." This is critical -- please ensure there are tests that verify a non-owner Telegram sender cannot access owner-scoped routines, credentials, or secrets.

4. Blast radius

Touching channel identity plumbing, routine execution, message tool fallback, settings/secrets lookup, and Telegram routing in one PR is high risk. The E2E test coverage (test_owner_scope.py) helps, but the manual testing checkbox is unchecked.

Recommendation: This needs dedicated review time given its scope. The design is sound but the security implications of getting the owner/guest boundary wrong are significant. Would benefit from a focused security review of the authorization boundaries.

@zmanian zmanian left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-Review: APPROVE

Both previously requested changes addressed in 2f474c7.

I1: Credential resolution test -- RESOLVED

Two well-designed tests added:

  • test_resolve_host_credentials_owner_scope_bearer -- verifies credential lookup under owner scope
  • test_execute_resolves_host_credentials_from_owner_scope_context -- uses RecordingSecretsStore to capture all get_decrypted calls and explicitly asserts lookup does NOT use "default". This is the right pattern -- it catches the silent credential miss failure mode directly.

I2: Agent::owner_id() double source of truth -- RESOLVED

  • owner_id() now returns &self.deps.owner_id as the canonical source
  • debug_assert_eq! verifies workspace.user_id() stays aligned in debug builds
  • Clear assert message for future debugging

Note

This PR overlaps with #1211 on event trigger user_id filtering. Whichever merges second should reconcile to avoid duplicate checks in check_event_triggers().

CI all green. Ready to merge.

@henrypark133
henrypark133 merged commit 878a67c into staging Mar 16, 2026
19 checks passed
@henrypark133
henrypark133 deleted the codex/owner-scope-refactor branch March 16, 2026 20:31
bkutasi pushed a commit to bkutasi/ironclaw that referenced this pull request Mar 28, 2026
…nearai#1151)

* refactor: add explicit owner scope across channels

* fix: tighten routine owner target routing

* fix: address owner scope review feedback

* Fix owner-scope onboarding and event trigger isolation

* Tighten routing fallback and wizard owner validation

* fix: address owner-scope follow-up review

* fix: tighten owner-scope follow-up details

* fix: import Channel trait in telegram test

* fix: normalize http webhook sender ids

* fix: address remaining owner-scope review issues

* fix: reconcile config rebase fallout

* fix: reconcile extension manager rebase drift

* fix: address current copilot review regressions

* fix: restore clippy matrix after rebase
@mschfh

mschfh commented Apr 6, 2026

Copy link
Copy Markdown

@henrypark133 @zmanian This change broke all updates from 0.18.0 (and earlier releases), see #1328

ilblackdragon added a commit that referenced this pull request Apr 7, 2026
…on (#1328)

PR #1151 modified the already-released migrations/V6__routines.sql in
place, causing refinery's checksum validation to abort startup on every
existing PostgreSQL deployment upgrading to v0.19.0.

Revert V6 to its v0.18.0 content (V13 already applies the schema change
incrementally and is idempotent for fresh installs that received the
modified V6). Add a runtime checksum realignment step that rewrites
refinery_schema_history rows whose stored checksum disagrees with the
embedded SQL — this handles both populations of databases in the wild
(pre-#1151 originals and post-#1151 fresh installs).

Add migrations/checksums.lock pinning every migration's SipHasher13
checksum and a `released_migrations_are_immutable` cargo test that
fails if any migration is modified or added without a matching lockfile
entry. A second hard-coded sentinel test pins V6's literal v0.18.0
checksum so the guard cannot be defeated by editing both the migration
and the lockfile in the same commit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ilblackdragon added a commit that referenced this pull request Apr 7, 2026
Per @serrrfirat's review, the previous IS DISTINCT FROM-based realignment
would silently rewrite *any* non-canonical V6 checksum, masking unrelated
corruption or manual tampering instead of narrowly exempting the one
historical released mismatch.

Add a `known_bad_checksums: &'static [u64]` field to `KnownDivergence`
listing the exact historical bad value(s), and rewrite only rows whose
stored checksum is in that whitelist via `WHERE checksum = ANY($4)`.
Anything else is left alone so refinery still aborts startup loudly.

The single known-bad V6 value (`11230857244097235596`) is the SipHasher13
of `git show 878a67c:migrations/V6__routines.sql` (the post-#1151
content) and is pinned by a new sentinel test
`v6_known_bad_checksum_matches_post_1151_content` so the whitelist
cannot drift or be silently widened.

Also adds an ignored bootstrap helper `compute_checksum_for_external_file`
for computing checksums of external SQL files when adding future entries.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
drchirag1991 pushed a commit to drchirag1991/ironclaw that referenced this pull request Apr 8, 2026
…nearai#1151)

* refactor: add explicit owner scope across channels

* fix: tighten routine owner target routing

* fix: address owner scope review feedback

* Fix owner-scope onboarding and event trigger isolation

* Tighten routing fallback and wizard owner validation

* fix: address owner-scope follow-up review

* fix: tighten owner-scope follow-up details

* fix: import Channel trait in telegram test

* fix: normalize http webhook sender ids

* fix: address remaining owner-scope review issues

* fix: reconcile config rebase fallout

* fix: reconcile extension manager rebase drift

* fix: address current copilot review regressions

* fix: restore clippy matrix after rebase
ilblackdragon added a commit that referenced this pull request Apr 9, 2026
…on (#1328) (#2101)

* fix(db): repair V6 migration checksum and guard against re-modification (#1328)

PR #1151 modified the already-released migrations/V6__routines.sql in
place, causing refinery's checksum validation to abort startup on every
existing PostgreSQL deployment upgrading to v0.19.0.

Revert V6 to its v0.18.0 content (V13 already applies the schema change
incrementally and is idempotent for fresh installs that received the
modified V6). Add a runtime checksum realignment step that rewrites
refinery_schema_history rows whose stored checksum disagrees with the
embedded SQL — this handles both populations of databases in the wild
(pre-#1151 originals and post-#1151 fresh installs).

Add migrations/checksums.lock pinning every migration's SipHasher13
checksum and a `released_migrations_are_immutable` cargo test that
fails if any migration is modified or added without a matching lockfile
entry. A second hard-coded sentinel test pins V6's literal v0.18.0
checksum so the guard cannot be defeated by editing both the migration
and the lockfile in the same commit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(db): address review feedback on migration_fixup (#2101)

- Drop hard-coded `public.` schema qualifier from the existence probe
  so PostgreSQL resolves `refinery_schema_history` via the active
  search_path, matching how refinery itself locates the table and how
  the subsequent UPDATE statement is written. Without this, deployments
  using a non-default schema would silently skip the realignment.
- Use `IS DISTINCT FROM` instead of `<>` so a corrupted row with a NULL
  checksum is repaired rather than silently skipped.
- Add `explanation` field to `KnownDivergence` and use it in the
  realignment warning so future entries are not coupled to the V6/#1328
  wording.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(db): narrowly whitelist V6 known-bad checksum (#2101)

Per @serrrfirat's review, the previous IS DISTINCT FROM-based realignment
would silently rewrite *any* non-canonical V6 checksum, masking unrelated
corruption or manual tampering instead of narrowly exempting the one
historical released mismatch.

Add a `known_bad_checksums: &'static [u64]` field to `KnownDivergence`
listing the exact historical bad value(s), and rewrite only rows whose
stored checksum is in that whitelist via `WHERE checksum = ANY($4)`.
Anything else is left alone so refinery still aborts startup loudly.

The single known-bad V6 value (`11230857244097235596`) is the SipHasher13
of `git show 878a67c:migrations/V6__routines.sql` (the post-#1151
content) and is pinned by a new sentinel test
`v6_known_bad_checksum_matches_post_1151_content` so the whitelist
cannot drift or be silently widened.

Also adds an ignored bootstrap helper `compute_checksum_for_external_file`
for computing checksums of external SQL files when adding future entries.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(db): assert in release + add postgres integration test (#2101)

Address two follow-up review comments from @serrrfirat:

1. The defensive `debug_assert!` guarding against the canonical
   checksum being listed in `known_bad_checksums` is stripped in
   release builds, so the safety net was absent in production.
   `KNOWN_DIVERGENCES` has at most a handful of entries — switch to
   `assert!` so the guard runs in release too. Cost is one constant-
   time slice lookup per startup.

2. The `realign_diverged_checksums` SQL path was never exercised
   against a real database. Refactor into a thin pub wrapper plus an
   injectable `realign_diverged_checksums_with` inner helper, and add
   a `#[cfg(feature = "integration")]` test that:
   - skips gracefully if no DATABASE_URL is reachable
   - creates `refinery_schema_history` if missing
   - seeds a synthetic V99999 row with a deliberately-wrong checksum
   - calls the realignment with a custom divergence list (no collision
     with real V6 rows in shared CI databases)
   - asserts the row now holds the canonical checksum
   - re-runs the realignment and asserts a no-op

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(db): replace assert! with returned error to satisfy no-panics check (#2101)

The previous commit changed `debug_assert!` → `assert!` to keep the
canonical-in-known-bad-list guard active in release builds, but this
trips the project's "No panics in production code" CI check (the
regex matches `assert!` outside test attributes).

Replace with an early `return Err(DatabaseError::Migration(...))` so
the guard still runs in release builds — startup refuses to proceed
with a misconfigured `KNOWN_DIVERGENCES` table — without using a
panicking macro. This is also more idiomatic for a function that
already returns `Result`.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(db): address review follow-ups on migration_fixup (#2101)

- parse_lockfile() now panics on duplicate migration keys instead of
  silently overwriting earlier entries — a stray duplicate could mask
  the actual pinned checksum and weaken the immutability guard
  (Copilot review).
- Add `rejects_canonical_in_known_bad_checksums` integration test
  exercising the defensive Err path that refuses startup when a
  KnownDivergence has its canonical checksum listed in its own
  known_bad_checksums list (serrrfirat review).
- Document why `tracing::warn!` is intentional in the realignment
  fix-up despite CLAUDE.md's warning about info!/warn! corrupting the
  TUI: this code runs at startup before any channel/REPL/TUI is
  initialized, so terminal-rendering interference is impossible. If
  the call site ever moves later in startup, downgrade to debug! or
  pre-buffer (illblackdragon review).
- Cross-reference comments in src/history/store.rs and
  src/setup/wizard.rs pointing each other out so future changes to
  the migration fix-up call site stay in sync (illblackdragon review).
- Sort migrations/checksums.lock by parsed migration version (V1, V2,
  ..., V10, V11, ...) instead of lex order (V10 before V2). The
  resulting file reads in numeric order which makes review diffs
  easier to scan (illblackdragon review).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(db): consolidate migration entry points + advisory lock + no leaks (#2101)

Address three Medium-severity findings from @serrrfirat's review:

1. **Duplicate call sites** — extract
   `run_postgres_migrations_with_fixup(client)` in
   `crate::db::migration_fixup` that bundles fix-up + refinery into a
   single function. Both `Store::run_migrations` and
   `SetupWizard::run_migrations_postgres` now call it. Eliminates the
   class of bug where a future entry point could forget the fix-up.
   The previous comment-based coupling was an interim measure.

2. **Concurrent startup race** — the new helper acquires
   `pg_advisory_lock(1328)` (issue number, easy to grep in `pg_locks`)
   before realignment and releases it after refinery returns, on every
   exit path including errors. Serializes concurrent migration runs
   across replicas — also hardens the pre-existing refinery race that
   has always existed for multi-replica starts. Uses session-level
   advisory lock (not `pg_advisory_xact_lock`) because refinery's
   `run_async` opens its own internal transactions.

3. **`Box::leak` in tests** — refactor `KnownDivergence` to be
   lifetime-generic (`KnownDivergence<'a>`). Production
   `KNOWN_DIVERGENCES` is `&[KnownDivergence<'static>]` — no external
   API change. Both integration tests now use stack-allocated
   `&[u64]` slices, no `Box::leak`. Removes the leak-sanitizer false
   positive.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
theredspoon pushed a commit to theredspoon/ironclaw that referenced this pull request Jun 21, 2026
…on (nearai#1328) (nearai#2101)

* fix(db): repair V6 migration checksum and guard against re-modification (nearai#1328)

PR nearai#1151 modified the already-released migrations/V6__routines.sql in
place, causing refinery's checksum validation to abort startup on every
existing PostgreSQL deployment upgrading to v0.19.0.

Revert V6 to its v0.18.0 content (V13 already applies the schema change
incrementally and is idempotent for fresh installs that received the
modified V6). Add a runtime checksum realignment step that rewrites
refinery_schema_history rows whose stored checksum disagrees with the
embedded SQL — this handles both populations of databases in the wild
(pre-nearai#1151 originals and post-nearai#1151 fresh installs).

Add migrations/checksums.lock pinning every migration's SipHasher13
checksum and a `released_migrations_are_immutable` cargo test that
fails if any migration is modified or added without a matching lockfile
entry. A second hard-coded sentinel test pins V6's literal v0.18.0
checksum so the guard cannot be defeated by editing both the migration
and the lockfile in the same commit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(db): address review feedback on migration_fixup (nearai#2101)

- Drop hard-coded `public.` schema qualifier from the existence probe
  so PostgreSQL resolves `refinery_schema_history` via the active
  search_path, matching how refinery itself locates the table and how
  the subsequent UPDATE statement is written. Without this, deployments
  using a non-default schema would silently skip the realignment.
- Use `IS DISTINCT FROM` instead of `<>` so a corrupted row with a NULL
  checksum is repaired rather than silently skipped.
- Add `explanation` field to `KnownDivergence` and use it in the
  realignment warning so future entries are not coupled to the V6/nearai#1328
  wording.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(db): narrowly whitelist V6 known-bad checksum (nearai#2101)

Per @serrrfirat's review, the previous IS DISTINCT FROM-based realignment
would silently rewrite *any* non-canonical V6 checksum, masking unrelated
corruption or manual tampering instead of narrowly exempting the one
historical released mismatch.

Add a `known_bad_checksums: &'static [u64]` field to `KnownDivergence`
listing the exact historical bad value(s), and rewrite only rows whose
stored checksum is in that whitelist via `WHERE checksum = ANY($4)`.
Anything else is left alone so refinery still aborts startup loudly.

The single known-bad V6 value (`11230857244097235596`) is the SipHasher13
of `git show 958a747:migrations/V6__routines.sql` (the post-nearai#1151
content) and is pinned by a new sentinel test
`v6_known_bad_checksum_matches_post_1151_content` so the whitelist
cannot drift or be silently widened.

Also adds an ignored bootstrap helper `compute_checksum_for_external_file`
for computing checksums of external SQL files when adding future entries.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(db): assert in release + add postgres integration test (nearai#2101)

Address two follow-up review comments from @serrrfirat:

1. The defensive `debug_assert!` guarding against the canonical
   checksum being listed in `known_bad_checksums` is stripped in
   release builds, so the safety net was absent in production.
   `KNOWN_DIVERGENCES` has at most a handful of entries — switch to
   `assert!` so the guard runs in release too. Cost is one constant-
   time slice lookup per startup.

2. The `realign_diverged_checksums` SQL path was never exercised
   against a real database. Refactor into a thin pub wrapper plus an
   injectable `realign_diverged_checksums_with` inner helper, and add
   a `#[cfg(feature = "integration")]` test that:
   - skips gracefully if no DATABASE_URL is reachable
   - creates `refinery_schema_history` if missing
   - seeds a synthetic V99999 row with a deliberately-wrong checksum
   - calls the realignment with a custom divergence list (no collision
     with real V6 rows in shared CI databases)
   - asserts the row now holds the canonical checksum
   - re-runs the realignment and asserts a no-op

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(db): replace assert! with returned error to satisfy no-panics check (nearai#2101)

The previous commit changed `debug_assert!` → `assert!` to keep the
canonical-in-known-bad-list guard active in release builds, but this
trips the project's "No panics in production code" CI check (the
regex matches `assert!` outside test attributes).

Replace with an early `return Err(DatabaseError::Migration(...))` so
the guard still runs in release builds — startup refuses to proceed
with a misconfigured `KNOWN_DIVERGENCES` table — without using a
panicking macro. This is also more idiomatic for a function that
already returns `Result`.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(db): address review follow-ups on migration_fixup (nearai#2101)

- parse_lockfile() now panics on duplicate migration keys instead of
  silently overwriting earlier entries — a stray duplicate could mask
  the actual pinned checksum and weaken the immutability guard
  (Copilot review).
- Add `rejects_canonical_in_known_bad_checksums` integration test
  exercising the defensive Err path that refuses startup when a
  KnownDivergence has its canonical checksum listed in its own
  known_bad_checksums list (serrrfirat review).
- Document why `tracing::warn!` is intentional in the realignment
  fix-up despite CLAUDE.md's warning about info!/warn! corrupting the
  TUI: this code runs at startup before any channel/REPL/TUI is
  initialized, so terminal-rendering interference is impossible. If
  the call site ever moves later in startup, downgrade to debug! or
  pre-buffer (illblackdragon review).
- Cross-reference comments in src/history/store.rs and
  src/setup/wizard.rs pointing each other out so future changes to
  the migration fix-up call site stay in sync (illblackdragon review).
- Sort migrations/checksums.lock by parsed migration version (V1, V2,
  ..., V10, V11, ...) instead of lex order (V10 before V2). The
  resulting file reads in numeric order which makes review diffs
  easier to scan (illblackdragon review).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(db): consolidate migration entry points + advisory lock + no leaks (nearai#2101)

Address three Medium-severity findings from @serrrfirat's review:

1. **Duplicate call sites** — extract
   `run_postgres_migrations_with_fixup(client)` in
   `crate::db::migration_fixup` that bundles fix-up + refinery into a
   single function. Both `Store::run_migrations` and
   `SetupWizard::run_migrations_postgres` now call it. Eliminates the
   class of bug where a future entry point could forget the fix-up.
   The previous comment-based coupling was an interim measure.

2. **Concurrent startup race** — the new helper acquires
   `pg_advisory_lock(1328)` (issue number, easy to grep in `pg_locks`)
   before realignment and releases it after refinery returns, on every
   exit path including errors. Serializes concurrent migration runs
   across replicas — also hardens the pre-existing refinery race that
   has always existed for multi-replica starts. Uses session-level
   advisory lock (not `pg_advisory_xact_lock`) because refinery's
   `run_async` opens its own internal transactions.

3. **`Box::leak` in tests** — refactor `KnownDivergence` to be
   lifetime-generic (`KnownDivergence<'a>`). Production
   `KNOWN_DIVERGENCES` is `&[KnownDivergence<'static>]` — no external
   API change. Both integration tests now use stack-allocated
   `&[u64]` slices, no `Box::leak`. Removes the leak-sanitizer false
   positive.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: core 20+ merged PRs risk: high Safety, secrets, auth, or critical infrastructure scope: agent Agent core (agent loop, router, scheduler) scope: channel/cli TUI / CLI channel scope: channel/wasm WASM channel runtime scope: channel Channel infrastructure scope: config Configuration scope: db/libsql libSQL / Turso backend scope: db/postgres PostgreSQL backend scope: docs Documentation scope: extensions Extension management scope: setup Onboarding / setup scope: tool/builtin Built-in tools scope: tool/wasm WASM tool sandbox size: XL 500+ changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Routine broadcast to Telegram fails with "Invalid chat_id 'default'"

4 participants