chore: promote staging to main (2026-03-26 01:33 UTC) - #1661
Merged
Merged
Conversation
* feat: complete multi-tenant isolation — per-user budgets, model selection, heartbeat cycling Finishes the remaining isolation work from phases 2–4 of #59: Phase 2 (DB scoping): Fix /status and /list commands to use _for_user DB variants instead of global queries that leaked cross-user job data. Phase 3 (Runtime isolation): Per-user workspace in routine engine's spawn_fire so lightweight routines run in the correct user context. Per-user daily cost tracking in CostGuard with configurable budget via MAX_COST_PER_USER_PER_DAY_CENTS. Multi-user heartbeat that cycles through all users with routines, auto-detected from GATEWAY_USER_TOKENS. Phase 4 (Provider/tools): Per-user model selection via preferred_model setting — looked up from SettingsStore on first iteration, threaded through ReasoningContext.model_override to CompletionRequest. Works with providers that support per-request model overrides (NearAI). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: use selected_model setting key to match /model command persistence The dispatcher was reading "preferred_model" but the /model command (merged from staging) persists to "selected_model". Since set_setting is already per-user scoped, using the same key makes /model work as the per-user model override in multi-tenant mode. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: heartbeat hygiene, /model multi-tenant guard, RigAdapter model override Three follow-up fixes for multi-tenant isolation: 1. Multi-user heartbeat now runs memory hygiene per user before each heartbeat check, matching single-user heartbeat behavior. 2. /model command in multi-tenant mode only persists to per-user settings (selected_model) without calling set_model() on the shared LlmProvider. The per-request model_override in the dispatcher reads from the same setting. Added multi_tenant flag to AgentConfig (auto-detected from GATEWAY_USER_TOKENS). 3. RigAdapter now supports per-request model overrides by injecting the model name into rig-core's additional_params. OpenAI/Anthropic/Ollama API servers use last-key-wins for duplicate JSON keys, so the override takes effect via serde's flatten serialization order. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address PR review — cost model attribution, heartbeat concurrency, pruning Fixes from review comments on #1614: - Cost tracking now uses the override model name (not active_model_name) when a per-user model override is active, for accurate attribution. - Multi-user heartbeat runs per-user checks concurrently via JoinSet instead of sequentially, preventing one slow user from blocking others. - Per-user failure counts tracked independently; users exceeding max_failures are skipped (matching single-user semantics). - per_user_daily_cost HashMap pruned on day rollover to prevent unbounded growth in long-lived deployments. - Doc comment fixed: says "routines" not "active routines". Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: /status ownership, model persistence scoping, heartbeat robustness Addresses second round of PR review on #1614: - /status <job_id> DB path now validates job.user_id == requesting user before returning data (was missing ownership check, security fix). - persist_selected_model takes user_id param instead of owner_id, and skips .env/TOML writes in multi-tenant mode (these are shared global files). handle_system_command now receives user_id from caller. - JoinSet collection handles Err(JoinError) explicitly instead of silently dropping panicked tasks. - Notification forwarder extracts owner_id from response metadata in multi-tenant mode for per-user routing instead of broadcasting to the agent owner. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: cost pricing, fire_manual workspace, heartbeat concurrency cap Round 3 review fixes: - Cost tracking passes None for cost_per_token when model override is active, letting CostGuard look up pricing by model name instead of using the default provider's rates (serrrfirat). - fire_manual() now uses per-user workspace, matching spawn_fire() pattern (serrrfirat). - Removed MULTI_TENANT env var — multi-tenant mode is auto-detected solely from GATEWAY_USER_TOKENS presence (serrrfirat + Copilot). - Multi-user heartbeat capped at 8 concurrent tasks to avoid flooding the LLM provider (serrrfirat + Copilot). - Fixed inject_model_override doc comment accuracy (Copilot). - Added comment explaining multi-tenant notification routing priority (Copilot). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: user-scoped webhook endpoint for multi-tenant isolation Adds POST /api/webhooks/u/{user_id}/{path} — a user-scoped webhook endpoint that filters the routine lookup by user_id, preventing cross-user webhook triggering when paths collide. The existing /api/webhooks/{path} endpoint remains unchanged for backward compatibility in single-user deployments. Changes: - get_webhook_routine_by_path gains user_id: Option<&str> param - Both postgres and libsql implementations add AND user_id = ? filter when user_id is provided - New webhook_trigger_user_scoped_handler extracts (user_id, path) from URL and passes to shared fire_webhook_inner logic - Route registered on public router (webhooks are called by external services that can't send bearer tokens) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add TenantCtx for compile-time tenant isolation Implements zmanian's architectural proposal from #1614 review: two-tier scoped database access (TenantScope/AdminScope) so handler code cannot accidentally bypass tenant scoping. TenantScope (default): wraps user_id + Arc<dyn Database>, auto-binds user_id on every operation. ID-based lookups return None for cross- tenant resources. No escape hatch — forgetting to scope is a compile error. AdminScope (explicit opt-in): cross-tenant access for system-level components (heartbeat, routine engine, self-repair, scheduler, worker). TenantCtx bundles TenantScope + workspace + cost guard + per-user rate limiting. Constructed once per request in handle_message, threaded through all command handlers and ChatDelegate. Key changes: - New src/tenant.rs (~920 lines): TenantScope, AdminScope, TenantCtx, TenantRateState, TenantRateRegistry - All command handlers: user_id: &str → ctx: &TenantCtx - ChatDelegate: cost check/record/settings via self.tenant - System components: store field changed to AdminScope - Config: TENANT_MAX_LLM_CONCURRENT, TENANT_MAX_JOBS_CONCURRENT env vars - Fixes bug: /status <job_id> cross-tenant leak (now auto-filtered) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(llm): filter XML tool-call recovery by context * fix: address review comments on PR #1641
Chore: Sync Main/Staging
…2140 chore: promote staging to staging-promote/ed4d9293-23582522167 (2026-03-26 18:18 UTC)
…override (#1681) * fix(extensions): channel-relay auth dead-end, add observability and relay URL override Fix a bug where clicking Activate on the Slack relay extension produces a dead-end "Authentication required" error with no OAuth URL. The root cause: `auth_channel_relay()` used `is_relay_channel()` to check auth status, but that function returns true as soon as the extension is *installed* (in-memory set), before OAuth completes. This short-circuits the OAuth flow so the authorization URL is never offered. Changes: 1. **Bug fix** — `auth_channel_relay()` now uses `has_stored_team_id()` which only checks the persistent settings store for an actual team_id. The extension list `authenticated` field uses the same check so the UI accurately reflects OAuth completion status. 2. **Observability** — Added debug/warn/info tracing to all channel-relay code paths that were previously silent on failure: - `activate_channel_relay`: team_id retrieval, relay config, signing secret fetch, hot_add, cache operations - `auth_channel_relay`: auth check, OAuth initiation, nonce storage - `extensions_activate_handler`: request entry, auth fallback flow - `slack_relay_oauth_callback_handler`: team_id persistence (was silently ignored with `let _`) - `RelayClient`: initiate_oauth, get_signing_secret, proxy_provider all log URL, status, and errors - `has_stored_team_id`: store read success/failure 3. **Per-extension relay URL override** — Users can now override the CHANNEL_RELAY_URL via Settings > Extensions > Reconfigure. Stored under `extensions.{name}.relay_url` in settings. Both auth and activate read this override before falling back to the env default. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style: cargo fmt Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address review feedback — clear relay_url override and improve log message 1. Allow clearing the relay_url override: when an optional setup field with a setting_path is submitted empty, delete the stored setting so the system reverts to the env/default value. Previously empty values were silently skipped, making it impossible to undo an override from the UI. 2. Improve the OAuth callback team_id persistence error log to be self-contained without referencing implementation details. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style: collapse nested if per clippy::collapsible_if Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address review feedback — security, scope consistency, and error handling 1. OAuth callback team_id persistence is now fatal: if set_setting fails, the callback returns an error instead of proceeding to activate (which would re-read from the store and fail anyway). 2. effective_relay_url uses owner scope (self.user_id) for reads, matching configure() which writes under the same scope. Prevents multi-user mismatch where an override saved via Reconfigure was invisible during auth/activation. 3. has_stored_team_id uses owner scope for the same reason — the OAuth callback stores team_id under state.owner_id (= self.user_id). 4. Security: effective_relay_url validates the override URL — only http/https without embedded credentials (userinfo) is accepted. This prevents API-key exfiltration if a user points relay_url at an attacker-controlled host. Logs only host portion, not full URL. 5. Fixed effective_relay_url docstring to match behavior (returns Option, callers handle the fallback). 6. get_setup_schema for ChannelRelay now logs a warning on settings store errors instead of silently returning None. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…1675 chore: promote staging to staging-promote/ed4d9293-23582522167 (2026-03-26 21:08 UTC)
…2167 chore: promote staging to staging-promote/4c043bf0-23573031775 (2026-03-26 07:26 UTC)
henrypark133
approved these changes
Mar 26, 2026
bkutasi
pushed a commit
to bkutasi/ironclaw
that referenced
this pull request
Mar 28, 2026
…3573031775 chore: promote staging to main (2026-03-26 01:33 UTC)
drchirag1991
pushed a commit
to drchirag1991/ironclaw
that referenced
this pull request
Apr 8, 2026
…3573031775 chore: promote staging to main (2026-03-26 01:33 UTC)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Auto-promotion from staging CI
Batch range:
86d11430640da22d8f890bb9b2df867dda1e668e..4c043bf05767d7e1ab74552eb010182ec44b3222Promotion branch:
staging-promote/4c043bf0-23573031775Base:
mainTriggered by: Staging CI batch at 2026-03-26 01:33 UTC
Commits in this batch (1):
Current commits in this promotion (4)
Current base:
mainCurrent head:
staging-promote/4c043bf0-23573031775Current range:
origin/main..origin/staging-promote/4c043bf0-23573031775Auto-updated by staging promotion metadata workflow
Waiting for gates:
Auto-created by staging-ci workflow