chore: promote staging to staging-promote/e0e530e6-23703082447 (2026-03-29 10:07 UTC) - #1737
Merged
henrypark133 merged 22 commits intoMar 30, 2026
Conversation
…#1667) Add support for bundle layouts where directories without SKILL.md are recursed into to find nested skills (e.g., skills/my-org/skill-a/SKILL.md). - Add configurable max_scan_depth (SKILLS_MAX_SCAN_DEPTH env, default 3) - Recurse into subdirectories lacking SKILL.md up to depth limit - Share remaining discovery cap across recursive levels - Replace try_exists + read_dir with single read_dir (eliminates TOCTOU) - Box::pin recursive async calls for correct future sizing Closes #1664 Co-authored-by: Rajul Bhatnagar <brajul@amazon.com>
* Clarify message tool and channel setup guidance * Add target format hints to proactive messaging prompt * Clarify search and message tool edge cases * Fix stale tool_search e2e assertion * Update src/llm/reasoning.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Tighten prompt guidance for message replies * Format prompt guidance assertions --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* fix: pin staging ci jobs to a single tested sha * chore(ci): retrigger regression gate [skip-regression-check] --------- Co-authored-by: Firat Sertgoz <f@nuff.tech>
* fix: prevent UTF-8 panics in byte-index string truncation
Replace unsafe `&s[..n]` patterns with `floor_char_boundary(s, n)` at 3
production code sites where the truncation index could land mid-multibyte
character, panicking on non-ASCII input:
- src/llm/nearai_chat.rs: API response truncation in error message
- src/cli/memory.rs: memory content display truncation
- src/cli/config.rs: config value display truncation
All 3 sites operate on external or user-supplied strings that may contain
non-ASCII characters. The existing `crate::util::floor_char_boundary`
utility (used at 18 other call sites) walks back to the nearest char
boundary, preventing the panic.
Adds regression test with multi-byte characters (combining accents and
4-byte emoji) for truncate_content.
[skip-regression-check]
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: clarify test comment and use exact assertions
Address Gemini review feedback:
- Fix misleading comment: \u{00e9} is precomposed e-acute, not combining accent
- Replace weak assertions (ends_with/is_empty) with exact assert_eq!
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…nt (#1630) * fix(bedrock): strip tool blocks from messages when toolConfig is absent Bedrock's Converse API requires `toolConfig` whenever messages contain `toolUse` or `toolResult` content blocks. When the agentic loop reaches its force_text iteration (e.g. lightweight routine at max_iterations), it switches from `complete_with_tools()` to `complete()` — but the message history still carries tool blocks from prior iterations. `convert_messages()` faithfully converts these into Bedrock content blocks, and without `toolConfig` Bedrock rejects the request: "The toolConfig field must be defined when using toolUse and toolResult content blocks." Add `strip_tool_blocks()` that converts tool interaction data to text: - Assistant `tool_calls` → dropped (text content preserved) - `Role::Tool` → `Role::User` with `[Tool ... returned: ...]` text Wire it into: - `complete()`: unconditionally, since it never sends toolConfig - `complete_with_tools()`: when `build_tool_config()` returns None (empty tools or tool_choice="none") Closes #1629 * fix(bedrock): address review feedback on strip_tool_blocks - Add tracing::debug\! when tool blocks are stripped (zmanian suggestion) - Add test for tool_choice="none" path (zmanian suggestion) - Add inline comment on empty-content assistant behavior --------- Co-authored-by: Rajul Bhatnagar <brajul@amazon.com>
* feat: support custom LLM provider configuration via web UI Users can now define custom LLM providers through the web UI and have them take effect without modifying environment variables or config files. - Add `CustomLlmProviderSettings` struct and `llm_custom_providers` field to `Settings` so custom provider definitions are persisted and loaded from the DB settings table - Add `LlmConfig::resolve_custom_provider()` to build a `RegistryProviderConfig` from user-defined provider data (base_url, adapter, model, api_key) - Flip resolution priority to `db > env > default` so active provider set through the UI takes precedence over deployment env vars - Warn when a custom provider is missing base_url or model - Add startup info logs for backend source and provider creation - Add regression tests for custom provider resolution and DB priority * feat: add test connection for custom LLM providers - Add POST /api/llm/test_connection endpoint that validates connectivity and auth for OpenAI-compatible, Anthropic, and Ollama adapters (10s timeout, per-adapter request logic) - Add "Test" button next to Save/Cancel in the add-provider form; result shown inline with green/red styling - Hide delete button for the active provider instead of showing an error toast - Sort the active provider to the top of the provider list - Clear selected_model when switching providers to avoid model-not-supported errors on the new provider - Add i18n keys for test/testing states (en + zh-CN) * feat: add built-in provider API key and model configuration - Add Configure button on built-in provider cards (openai, anthropic, gemini, ollama, etc.) to set API key and default model via web UI - Store overrides as `llm_builtin_overrides` setting (per-provider key/model map) using the existing generic settings k/v API - Add LlmBuiltinOverride struct in settings.rs; resolve in resolve_registry_provider() with priority: env var > selected_model > llm_builtin_overrides[id] > default - Restore provider's configured model to selected_model on provider switch, so /model command always takes precedence at runtime - Fix fetch-models button in built-in configure mode: use hardcoded base_url from BUILTIN_PROVIDERS instead of the hidden form field - Add edit support for custom providers with pre-filled dialog - Show current model on active and configured provider cards - Convert add/edit provider form to a modal dialog - Sync selected_model when editing or deleting an active custom provider * feat: move Config tab into Settings as Providers subtab * feat(web): merge Providers into Inference tab with UX improvements * chore: resolve conflicts * fix(llm): address security and correctness issues in custom LLM provider * fix(llm): address security and correctness issues in custom LLM provider * feat(web): fall back to env vars for LLM provider config in UI * fix(llm): enforce db > env > default config priority for provider setting * fix: address review feedback on provider config priority * feat: extract BUILTIN_PROVIDERS into providers.js * fix(security): store LLM API keys in encrypted secrets store instead of plaintext * fix(security): harden LLM API key handling across settings and LLM endpoints * fix: test_connection sends actual chat completion * refactor(web): derive LLM Provider display from active Model Provider * fix(settings): language switch not working for llm provider * feat(web): add restart notice to LLM Provider settings * fix: review fixes for custom LLM provider PR - Add server-side validation of custom provider ID format (lowercase alphanumeric + hyphens, 1-64 chars) to match frontend regex - Tighten is_nearai_private_endpoint to exact-match private.near.ai or *.private.near.ai, rejecting lookalikes like private-evil.near.ai - Fix misleading priority doc comments in config/mod.rs and settings.rs to reflect the split model: LLM uses DB > env, others use env > DB - Clean up #1581 artifacts: remove TOML file creation from persist_selected_model (DB is sufficient), update stale priority comments in commands.rs, fix contradictory test assertions - Add 18 new tests for provider ID validation, adapter validation, and nearai private endpoint matching Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address PR review comments for custom LLM provider - Move LLM handlers (test_connection, list_models, env_defaults) from server.rs to handlers/llm.rs for consistency with other handler modules - Merge validate_custom_providers into single pass (ID + adapter check) - Allow underscores in custom provider IDs to match builtin naming - Add missing i18n key config.fetchingModels (en + zh-CN) - Fix optional_env().ok().flatten() error swallowing in config/llm.rs; propagate ConfigError with ? instead of silently discarding - Narrow settings.rs module docs to scope DB>env precedence to LLM - Add unit tests for hydrate_llm_keys_from_secrets Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: replace static providers.js with API endpoint from registry - Delete providers.js; serve provider list from /api/llm/providers endpoint that reads from the embedded ProviderRegistry (providers.json) - Centralize secret naming (builtin_secret_name, custom_secret_name) into settings.rs; replace 8 duplicated format! calls across 4 files - Extract JS API_KEY_UNCHANGED constant; replace 6 magic string literals - Replace hard-coded API key placeholder strings with i18n keys (config.apiKeyConfigured, config.apiKeyFromEnv, config.apiKeyEnter) - Simplify apiFetchVoid to delegate to apiFetch - Remove unnecessary Vec clones in guard_active_provider_not_removed Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Robert Yan <46699230+think-in-universe@users.noreply.github.com> Co-authored-by: Illia Polosukhin <ilblackdragon@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Handle empty tool completions in autonomous jobs * Address malformed tool recovery review comments * style: apply rustfmt to reasoning tests --------- Co-authored-by: Firat Sertgoz <f@nuff.tech>
…1463) * feat(gateway): add OIDC JWT authentication for reverse-proxy deployments Add an optional OIDC JWT auth path to the web gateway, enabling deployments behind identity-aware proxies like AWS ALB with Okta/Cognito. When GATEWAY_OIDC_ENABLED=true, the gateway reads a signed JWT from a configurable HTTP header (default: x-amzn-oidc-data), fetches the signing key from a JWKS endpoint, and verifies the signature + claims. Auth flow: Bearer token → OIDC JWT → query-string token → 401. Key design decisions: - Split signature verification from claim extraction to handle AWS ALB's non-standard base64 padding (ALB includes '=' padding in JWT segments, but jsonwebtoken's decode() strips it, changing the signing input). We verify against the original token text, then extract claims from a normalized copy. - JWKS keys cached for 1 hour with per-kid granularity. - Supports both ALB-style per-key PEM URLs ({kid} placeholder) and standard JWKS endpoints. - DER-to-raw ECDSA signature conversion for IdPs that use DER encoding. - Frontend auto-detects proxy auth via /api/gateway/status probe, skipping the login screen when OIDC is active. Configuration (env vars): GATEWAY_OIDC_ENABLED=true GATEWAY_OIDC_HEADER=x-amzn-oidc-data (default) GATEWAY_OIDC_JWKS_URL=https://public-keys.auth.elb.us-east-1.amazonaws.com/{kid} GATEWAY_OIDC_ISSUER=https://example.okta.com (optional) GATEWAY_OIDC_AUDIENCE=my-client-id (optional) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Address code review feedback on OIDC auth PR - Add EdDSA PEM key parsing support (was falling through to RSA) - Fix issuer validation: remove set_issuer(&[]) else branch that rejected all tokens when GATEWAY_OIDC_ISSUER is unset - Make missing `sub` claim a validation error instead of silently defaulting to "unknown" - Extract initApp() in app.js so OIDC auto-auth actually initializes the UI (was calling undefined function) - Add regression tests for sub claim and issuer validation fixes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Harden OIDC auth: address claude[bot] security review - SSRF: URL-encode kid before substituting into JWKS URL template - Cache bounds: cap key cache at 64 entries, evict expired + oldest - DER parsing: support long-form length encoding (>= 128 bytes), validate component lengths against expected curve size - Production safety: replace .expect() with Result in OidcState::from_config - Fetch backoff: cache failed JWKS fetches for 10s to prevent retry storms - Body limit: cap JWKS responses at 256 KB to prevent OOM from rogue endpoint - Add regression tests for DER long-form, kid encoding, cache bounds Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test(auth): add regression test for OIDC identity resolution Add two integration tests that exercise the full OIDC middleware path through to AuthenticatedUser extraction: - test_oidc_auth_inserts_user_identity_for_handler: sends a valid OIDC JWT through the middleware and verifies the handler receives the sub claim as user_id. Returns 401 if identity insertion is missing — verified by temporarily removing the insert and confirming failure. - test_oidc_auth_user_gets_member_role: confirms OIDC-authenticated users receive role=member (not admin). Uses a seed_key() test helper on OidcState to pre-populate the key cache with an HS256 secret, avoiding the need for an HTTP JWKS mock. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test(auth): comprehensive OIDC test coverage for edge cases Add 17 new OIDC tests covering middleware integration, auth priority, invalid JWTs, issuer/audience validation, and key cache behavior: Middleware auth priority & fallthrough: - Bearer works when OIDC configured but header absent - Bearer takes priority when both Bearer and OIDC header present - Bad OIDC signature returns 401 (not 500) - Invalid OIDC doesn't block valid bearer auth - No auth at all with OIDC configured → 401 Expired / invalid JWT edge cases: - Expired JWT (exp in the past) rejected - JWT without kid header rejected - Malformed JWTs rejected (empty, 2-part, 4-part, garbage) - Non-string sub claim (integer) rejected - Empty-string sub passes auth (documented behavior) - Missing sub rejected through full middleware path Issuer / audience validation: - Matching issuer accepted, wrong issuer rejected - Matching audience accepted, wrong audience rejected - Missing iss/aud when configured: passes (jsonwebtoken v9 behavior, documented with notes on potential hardening) Key cache: - Expired cache entries not served - Fetch failure backoff blocks retry within 10s - Backoff expiry allows retry - Cache max entries constant verified Also adds shared test helpers (encode_test_jwt, test_oidc_state, oidc_auth_state, oidc_test_app) to reduce boilerplate. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ci): resolve formatting and no-panics check failures - Run cargo fmt to wrap long assert lines in OIDC tests - Add // safety: test helper comments to suppress false positives from check_no_panics.py (unwraps in #[cfg(test)] helper fns) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: synner88 <29090601+synner88@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: ilblackdragon@gmail.com <ilblackdragon@gmail.com>
…ts (#1529) * fix(wasm): inject Content-Length: 0 for bodyless mutating requests [skip-version-check] The WASM host http_request now auto-injects Content-Length: 0 for POST/PUT/PATCH/DELETE requests with no body, unless the tool already provides the header. This fixes Gmail returning 411 on trash_message and proactively covers all other tools (Google Calendar DELETE, Google Drive DELETE, etc.). Extracted needs_content_length_zero() with 8 regression tests covering all HTTP methods and case-insensitive header detection. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(wasm): use eq_ignore_ascii_case to avoid allocation [skip-version-check] Replace matches!(method.to_uppercase().as_str(), ...) with eq_ignore_ascii_case() to avoid a per-request String allocation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: ilblackdragon@gmail.com <ilblackdragon@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(auth): make shared Google tool status scope-aware * fix(auth): simplify google docs auth status test * fix(auth): skip scope expansion for env-var tokens and add dual-source test Env-var-provided tokens are externally managed, so the scope-expansion check must not apply — otherwise tools regress to NeedsAuth when no scopes record exists in the secrets store. Split the token detection into managed vs env-var paths and only run scope checks for managed tokens. Also adds tests verifying: (1) env-var-only tokens return Ready without scope checks, and (2) when both a managed token and env var are present, the managed path with scope checks takes priority. [skip-regression-check] Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: ilblackdragon@gmail.com <ilblackdragon@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… gate regressions (#1746) * fix: resolve 11 test failures from multi-tenant bootstrap and sandbox gate regressions Three root causes fixed: 1. Per-user bootstrap greeting in tests: After the multi-tenant isolation PR, `tenant_ctx("test-user")` creates a per-user workspace that seeds BOOTSTRAP.md and triggers an unwanted bootstrap greeting. This threw off response counting and caused message-drain races in 7 e2e tests. Fix: pre-seed the "test-user" workspace in the test rig DB so the first tenant_ctx call finds existing documents. 2. Sandbox gate blocking full_job routines: The full_job reliability overhaul (#1650) intended to remove the SandboxReadiness gate from execute_full_job (since full_job routines dispatch through the scheduler, not Docker). The gate was accidentally re-added during rebase, breaking 4 routine tests. Fix: remove the gate and clean up the unused sandbox_readiness field from EngineContext. 3. Owner-gate tests expecting old failure path: Two tests expected RunStatus::Failed from the sandbox gate. With the gate removed, the tool is now blocked at execution time by the approval context and the job completes normally. Fix: update traces and assertions to match the new behavior (RunStatus::Ok, owner_gate_count == 0). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: keep DockerUnavailable gate for full_job routines Only remove the DisabledByConfig gate — when sandbox is enabled but Docker is unavailable, full_job routines should still fail rather than silently running without the expected sandbox isolation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: address PR review feedback - Remove unused `include_completion` param from `owner_gate_trace()` and update all 5 call sites - Use `.expect()` instead of `let _ =` on `seed_if_empty()` in test rig to surface seeding failures early - Rename owner-gate tests from `_blocks_` to `_denies_tool_` to clarify the denial-with-success semantics Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: let per-user bootstrap fire naturally, filter in TestRig Instead of pre-seeding the "test-user" workspace to prevent the per-user bootstrap greeting, let it happen naturally and make the TestRig resilient to it. `wait_for_responses` now transparently filters bootstrap greetings from the response stream: - Normal tests: all greetings filtered (bootstrap_greetings_to_keep=0) - `.with_bootstrap()` tests: 1 greeting kept (the startup greeting), additional per-user duplicates filtered Also updates owner-gate test section headers to match the denial-with-success semantics. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: assert tool denial event in owner-gate tests The owner-gate denial tests previously only checked RunStatus::Ok + owner_gate_count == 0, which could pass if the tool was never called at all. Now both tests also verify that a tool_result event with success=false exists for "owner_gate" in the job's event log, confirming the tool was attempted and blocked by the approval context. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style: apply rustfmt to collapsed function signature Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: use async lock in bootstrap filter loop, add DisabledByConfig unit test - Switch TestRig polling loop from `captured_responses()` (try_lock, panics on contention) to `captured_responses_async()` (async lock, safe under concurrent response pushing) - Add unit test asserting DisabledByConfig does NOT match the DockerUnavailable gate (verifies the intended behavior change) 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(slack): respond to thread replies in channels without requiring @mention Two fixes: 1. Host bug: `on_respond` callback never committed workspace writes or injected workspace reader, unlike all other WASM callbacks. Any WASM channel persisting state during on_respond silently lost data. 2. Slack WASM channel: track threads where the bot has participated via workspace storage. When a message event arrives in a channel thread the bot previously replied to, process it without requiring @mention. Closes #1404 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(slack): log workspace_write error instead of silently discarding Address code review feedback: handle the Result from workspace_write when tracking thread participation, logging a warning on failure instead of using `let _ =` which would silently swallow errors. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(slack): harden thread reply tracking --------- Co-authored-by: synner88 <29090601+synner88@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Firat Sertgoz <f@nuff.tech> Co-authored-by: firat.sertgoz <firat.sertgoz@near.ai>
…esh (#1756) * fix(routines): clone Arc before await in web handler event cache refresh (#1076) Address review: drop superseded ticker changes, keep only the .cloned() fix that prevents holding RwLockReadGuard across .await in toggle/delete handlers. Add regression test for web toggle disabling a system_event routine. Closes #1076 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: use explicit block to drop RwLockReadGuard before await Address review feedback: in Rust 2024, `if let` scrutinee temporaries live through the body, so the `.cloned()` approach still held the RwLockReadGuard across `refresh_event_cache().await`. Extract into an explicit block to ensure the guard is dropped, matching the existing pattern in `routines_trigger_handler`. Also add retry loop for `routine_by_name` in the integration test to avoid flakiness from potential race conditions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…1762) * test(e2e): align wasm reinstall expectations with uninstall cleanup * test(e2e): clarify wasm reinstall fixture semantics
…2238 chore: promote staging to staging-promote/d567d94c-23755250735 (2026-03-30 18:33 UTC)
…0735 chore: promote staging to staging-promote/d0f7862a-23733136790 (2026-03-30 16:19 UTC)
…6790 chore: promote staging to staging-promote/c75dea0e-23731217718 (2026-03-30 07:31 UTC)
…7718 chore: promote staging to staging-promote/8acdd080-23726030902 (2026-03-30 06:33 UTC)
…0902 chore: promote staging to staging-promote/368d2f52-23720185295 (2026-03-30 03:01 UTC)
…5295 chore: promote staging to staging-promote/70214c4a-23719079615 (2026-03-29 22:05 UTC)
…9615 chore: promote staging to staging-promote/86389dab-23706696435 (2026-03-29 21:07 UTC)
henrypark133
merged commit Mar 30, 2026
af7925e
into
staging-promote/e0e530e6-23703082447
14 checks passed
drchirag1991
pushed a commit
to drchirag1991/ironclaw
that referenced
this pull request
Apr 8, 2026
…3706696435 chore: promote staging to staging-promote/97129030-23703082447 (2026-03-29 10:07 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:
2f4eb08613cefff1af8b7b1a475fda00c84dd855..86389dab23ef4c49c56caf8eb0e9da451d916798Promotion branch:
staging-promote/86389dab-23706696435Base:
staging-promote/e0e530e6-23703082447Triggered by: Staging CI batch at 2026-03-29 10:07 UTC
Commits in this batch (16):
Current commits in this promotion (15)
Current base:
staging-promote/e0e530e6-23703082447Current head:
staging-promote/86389dab-23706696435Current range:
origin/staging-promote/e0e530e6-23703082447..origin/staging-promote/86389dab-23706696435Auto-updated by staging promotion metadata workflow
Waiting for gates:
Auto-created by staging-ci workflow