Fix main Reborn Playwright failures - #6546
ilblackdragon wants to merge 1 commit into
Conversation
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
🔎 IronLoop Review StatusHead: Current reviewers:
Reviewer summaries
Recent activity
Available commands
Run metadataAdmission: webhook accepted the request and IronLoop persisted reviewer state before this projection. |
📝 WalkthroughSummary by CodeRabbit
WalkthroughExtension lifecycle removal now uses the active registry package, WebUI lifecycle requests propagate client action IDs into explicit activity IDs, and frontend/E2E contracts update for lifecycle payloads, labels, limits, resolutions, and extension surfaces. ChangesExtension lifecycle and idempotency
WebUI activity and request contracts
Outbound type wiring
Frontend and E2E contract updates
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related issues
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
✅ IronLoop Review: reviewer
Review at a glance
| Verdict | Blocking | Notes | Inline | Head |
|---|---|---|---|---|
| ✅ Approved | 0 | 0 | 0 | 31f99cdf3717 |
Head: 31f99cdf3717aab003805bb1cd64b5332461ab47
Next: No reviewer action needed.
Run details
Status: Current
Needs human: no
Needs validation: no
Summary
Reviewed the complete 16-file (+365/-82) PR diff across extension lifecycle, ProductSurface, WebUI handlers, frontend, and E2E expectations. No actionable regression found.
Findings
None.
Developer follow-up
After fixing this feedback:
- Push the fix to this PR branch.
- Re-run this reviewer with
@ironloopai review --agent reviewerif you only changed this reviewer's findings. - Re-run all reviewers with
@ironloopai reviewwhen the fix may affect multiple areas.
31f99cd to
55828ad
Compare
|
🚅 Deployed to the ironclaw-pr-6546 environment in ironclaw-ci-preview
|
55828ad to
d907d9a
Compare
Coverage ratchetReborn integration-tier coverageLine coverage (Reborn crates): 86.33% — 309775 / 358834 lines Per-crate breakdown (62 crates, lowest-covered first)
This table itself is informational and never gates the PR on its own — not the percentage, not the per-crate holes, not the 0-coverage callout. A separate coverage ratchet (dry-run until enforce=true; see tests/integration/coverage-floor.toml) can fail the build on specific configured floors. Exemptions (3 entry/entries excluded from the accounting above)
|
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
There was a problem hiding this comment.
❌ IronLoop Review: reviewer
Review at a glance
| Verdict | Blocking | Notes | Inline | Head |
|---|---|---|---|---|
| ❌ Changes requested | 1 | 0 | 1 | d907d9aa866b |
Head: d907d9aa866b3c70688be2f494dfe6ab959fd722
Next: Fix the blocking findings, push the PR branch, then re-run this reviewer.
Run details
Status: Current
Needs human: no
Needs validation: no
Summary
One blocking idempotency regression: generic WebUI capability mutations now generate a new activity ID on every HTTP attempt.
Findings
Blocking: 1 / Notes: 0
Blocking findings
1. ❌ [MEDIUM] Preserve activity IDs across retries
Location: crates/ironclaw_webui/src/webui_v2/handlers.rs:2705
ActivityId is the ProductSurface mutation idempotency identity and must be preserved across retries. Generating it server-side for every request means a response-lost retry of a generic mutation (including install/activate/remove) is indistinguishable from a new gesture and receives a new identity. The extension frontend posts no client action key, so it cannot preserve one across retries. Generate a per-gesture key in the client and forward/reuse it for retries, while generating a distinct key for a later user gesture.
Developer follow-up
After fixing this feedback:
- Push the fix to this PR branch.
- Re-run this reviewer with
@ironloopai review --agent reviewerif you only changed this reviewer's findings. - Re-run all reviewers with
@ironloopai reviewwhen the fix may affect multiple areas.
| caller, | ||
| capability, | ||
| input, | ||
| ActivityId::new(), |
There was a problem hiding this comment.
ActivityId is the ProductSurface mutation idempotency identity and must survive retries. A fresh server-side ID makes a response-lost retry of any generic mutation a new operation; the extension client sends no action key that it can reuse. Please carry a per-gesture client key through this path, reusing it only for retries.
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
crates/ironclaw_reborn_composition/src/webui/product_capability.rs (1)
244-266: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winProduction extension-lifecycle web product calls use the local-dev lifecycle mount path.
ProductCapabilityMounts::Productionbranches only for skill-management inproduct_invocation_mounts;EXTENSION_INSTALL/ACTIVATE/REMOVEstill callcrate::local_dev_mounts::system_extensions_lifecycle_mount_view(), while production production-runnables get a productionProductResultFilesystemthroughRuntimeProductCapabilityInvoker::from_services. The current tests cover onlyLocalDev, so the path is not regression-tested for production invocations. Violates the composition/product boundary invariant: production capability wiring should not reuse local-dev-only product invocation mounts.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/ironclaw_reborn_composition/src/webui/product_capability.rs` around lines 244 - 266, The product_invocation_mounts function routes extension-lifecycle capabilities through the local-dev mount path regardless of ProductCapabilityMounts. Update this branch to select the production lifecycle mount implementation for ProductCapabilityMounts::Production while preserving the existing local-dev behavior, and add coverage for production extension-lifecycle invocations.Source: Path instructions
crates/ironclaw_webui/src/webui_v2/handlers.rs (1)
2725-2797: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicate caller/capability seed-prefix logic across the two new functions.
llm_provider_upsert_activity_idandoutbound_preferences_activity_idrepeat an identical 6-segment prefix (context label, tenant/user/agent/project, capability_id). Extract a shared helper so the two seed derivations can't silently diverge on caller-scope isolation.♻️ Proposed extraction
+fn seed_caller_prefix( + context: &'static str, + caller: &WebUiAuthenticatedCaller, + capability_id: &CapabilityId, +) -> Vec<u8> { + let mut seed = Vec::new(); + for segment in [ + context, + caller.tenant_id.as_str(), + caller.user_id.as_str(), + caller.agent_id.as_ref().map(|id| id.as_str()).unwrap_or(""), + caller + .project_id + .as_ref() + .map(|id| id.as_str()) + .unwrap_or(""), + capability_id.as_str(), + ] { + seed.extend_from_slice(&(segment.len() as u64).to_be_bytes()); + seed.extend_from_slice(segment.as_bytes()); + } + seed +}Then each function calls
seed_caller_prefix(...)and appends only its own request-specific fields.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/ironclaw_webui/src/webui_v2/handlers.rs` around lines 2725 - 2797, Extract the duplicated caller/capability seed-prefix construction from llm_provider_upsert_activity_id and outbound_preferences_activity_id into a shared seed_caller_prefix helper. Have both functions reuse it, passing their distinct context label and capability_id, then append only their request-specific fields while preserving the existing segment ordering and length-prefix encoding.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@crates/ironclaw_webui/frontend/src/pages/chat/components/auth-oauth-card.tsx`:
- Around line 45-54: Update providerDisplayName to verify that providerId is an
own key of PROVIDER_DISPLAY_NAMES before returning its value, preventing
inherited properties such as toString or constructor from entering the label
path; preserve the existing nearai and GitHub fallback behavior.
In `@crates/ironclaw_webui/src/webui_v2/handlers.rs`:
- Around line 2742-2759: Update the deterministic seed construction in the
UpsertLlmProviderRequest activity-ID path to encode presence separately from
string contents for name, base_url, default_model, model, and api_key. Preserve
distinct seed bytes for None and Some(""), following the presence-flag approach
used by outbound_preferences_activity_id while keeping existing values
deterministic.
- Around line 2753-2764: Update the ActivityId generation logic around
request.api_key so raw API-key bytes are never included in the seed passed to
Uuid::new_v5; use ActivityId::new() for a fresh identifier, unless an existing
server-side keyed HMAC mechanism is required for idempotency. Remove the
api_key-derived seed handling while preserving the function’s successful
ActivityId return behavior.
---
Outside diff comments:
In `@crates/ironclaw_reborn_composition/src/webui/product_capability.rs`:
- Around line 244-266: The product_invocation_mounts function routes
extension-lifecycle capabilities through the local-dev mount path regardless of
ProductCapabilityMounts. Update this branch to select the production lifecycle
mount implementation for ProductCapabilityMounts::Production while preserving
the existing local-dev behavior, and add coverage for production
extension-lifecycle invocations.
In `@crates/ironclaw_webui/src/webui_v2/handlers.rs`:
- Around line 2725-2797: Extract the duplicated caller/capability seed-prefix
construction from llm_provider_upsert_activity_id and
outbound_preferences_activity_id into a shared seed_caller_prefix helper. Have
both functions reuse it, passing their distinct context label and capability_id,
then append only their request-specific fields while preserving the existing
segment ordering and length-prefix encoding.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 888310a7-03c7-4c09-b218-b668f8115f56
📒 Files selected for processing (16)
crates/ironclaw_reborn_composition/src/extension_host/extension_lifecycle.rscrates/ironclaw_reborn_composition/src/extension_host/extension_lifecycle_capabilities.rscrates/ironclaw_reborn_composition/src/outbound/mod.rscrates/ironclaw_reborn_composition/src/runtime.rscrates/ironclaw_reborn_composition/src/runtime/tests/outbound_delivery.rscrates/ironclaw_reborn_composition/src/webui/facade/tests.rscrates/ironclaw_reborn_composition/src/webui/product_capability.rscrates/ironclaw_webui/frontend/src/pages/chat/components/auth-oauth-card.tsxcrates/ironclaw_webui/frontend/src/pages/chat/lib/attachments.tscrates/ironclaw_webui/src/webui_v2/handlers.rstests/e2e/scenarios/test_reborn_webui_v2_automation_trace_outbound_api.pytests/e2e/scenarios/test_reborn_webui_v2_legacy_approval.pytests/e2e/scenarios/test_reborn_webui_v2_legacy_attachments.pytests/e2e/scenarios/test_reborn_webui_v2_legacy_auth_flows.pytests/e2e/scenarios/test_reborn_webui_v2_legacy_extensions.pytests/e2e/scenarios/test_reborn_webui_v2_legacy_settings_search.py
💤 Files with no reviewable changes (1)
- tests/e2e/scenarios/test_reborn_webui_v2_automation_trace_outbound_api.py
d907d9a to
755534e
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
crates/ironclaw_webui/tests/webui_v2_handlers_contract.rs (1)
5226-5289: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winExtend the retry-idempotency assertion to activate/remove/setup.
install_extension's test (just above, lines 5017-5070) now proves a retried request with the sameclient_action_idyields the same ProductSurface activity id — the exact regression the prior review flagged.activate_and_remove_extension_decode_path_package_id_to_lifecycle_pathsandsetup_extension_invokes_product_surface_capabilityexercise the same sharedextension_lifecycle_activity_idhelper but don't assert this for their capabilities. Consider mirroring the install test's second-request +invoke_calls[i].2 == invoke_calls[j].2pattern for activate, remove, and setup so a regression in the shared helper is caught through all four production callers, not just one.Also applies to: 5327-5380
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/ironclaw_webui/tests/webui_v2_handlers_contract.rs` around lines 5226 - 5289, The lifecycle tests only verify capability payloads, not retry idempotency of the shared extension_lifecycle_activity_id behavior. Extend activate_and_remove_extension_decode_path_package_id_to_lifecycle_paths and setup_extension_invokes_product_surface_capability with repeated requests using the same client_action_id, then assert each retry produces the same activity ID in the corresponding invoke_calls entries, mirroring the install_extension test pattern for activate, remove, and setup.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tests/e2e/scenarios/test_reborn_webui_v2_legacy_extensions.py`:
- Around line 20-41: Update _assert_install_requests and
_assert_setup_submit_requests to collect each request’s client_action_id while
validating it, then assert the batch contains only unique IDs using len(ids) ==
len(set(ids)). Preserve the existing request-count and body/package assertions.
---
Outside diff comments:
In `@crates/ironclaw_webui/tests/webui_v2_handlers_contract.rs`:
- Around line 5226-5289: The lifecycle tests only verify capability payloads,
not retry idempotency of the shared extension_lifecycle_activity_id behavior.
Extend activate_and_remove_extension_decode_path_package_id_to_lifecycle_paths
and setup_extension_invokes_product_surface_capability with repeated requests
using the same client_action_id, then assert each retry produces the same
activity ID in the corresponding invoke_calls entries, mirroring the
install_extension test pattern for activate, remove, and setup.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: a4091f92-06f2-4517-9138-cad3df38d041
📒 Files selected for processing (29)
crates/ironclaw_product_workflow/src/lib.rscrates/ironclaw_product_workflow/src/reborn_services/extension_setup_credentials.rscrates/ironclaw_product_workflow/src/webui_inbound.rscrates/ironclaw_product_workflow/tests/reborn_services_contract.rscrates/ironclaw_reborn_composition/src/extension_host/extension_lifecycle.rscrates/ironclaw_reborn_composition/src/extension_host/extension_lifecycle_capabilities.rscrates/ironclaw_reborn_composition/src/outbound/mod.rscrates/ironclaw_reborn_composition/src/runtime.rscrates/ironclaw_reborn_composition/src/runtime/tests/core.rscrates/ironclaw_reborn_composition/src/runtime/tests/outbound_delivery.rscrates/ironclaw_reborn_composition/src/webui/facade/tests.rscrates/ironclaw_reborn_composition/src/webui/product_capability.rscrates/ironclaw_webui/frontend/src/lib/api.test.tscrates/ironclaw_webui/frontend/src/lib/api.tscrates/ironclaw_webui/frontend/src/pages/chat/components/auth-oauth-card.tsxcrates/ironclaw_webui/frontend/src/pages/chat/lib/attachments.tscrates/ironclaw_webui/frontend/src/pages/extensions/lib/extensions-api.test.tscrates/ironclaw_webui/frontend/src/pages/extensions/lib/extensions-api.tscrates/ironclaw_webui/src/webui_v2/handlers.rscrates/ironclaw_webui/tests/webui_v2_handlers_contract.rstests/e2e/scenarios/test_reborn_qa_trace_full_path.pytests/e2e/scenarios/test_reborn_slack_channel_e2e.pytests/e2e/scenarios/test_reborn_webui_v2_automation_trace_outbound_api.pytests/e2e/scenarios/test_reborn_webui_v2_extensions_api.pytests/e2e/scenarios/test_reborn_webui_v2_legacy_approval.pytests/e2e/scenarios/test_reborn_webui_v2_legacy_attachments.pytests/e2e/scenarios/test_reborn_webui_v2_legacy_auth_flows.pytests/e2e/scenarios/test_reborn_webui_v2_legacy_extensions.pytests/e2e/scenarios/test_reborn_webui_v2_legacy_settings_search.py
💤 Files with no reviewable changes (1)
- tests/e2e/scenarios/test_reborn_webui_v2_automation_trace_outbound_api.py
| def _assert_client_action_id(body: dict) -> None: | ||
| assert isinstance(body.get("client_action_id"), str) | ||
| assert body["client_action_id"] | ||
|
|
||
|
|
||
| def _assert_install_requests(requests: list[dict], *package_ids: str) -> None: | ||
| assert len(requests) == len(package_ids) | ||
| for request, package_id in zip(requests, package_ids, strict=True): | ||
| assert request.get("package_ref") == _package_ref(package_id) | ||
| _assert_client_action_id(request) | ||
|
|
||
|
|
||
| def _assert_setup_submit_requests( | ||
| requests: list[dict], expected: list[dict] | ||
| ) -> None: | ||
| assert len(requests) == len(expected) | ||
| for request, expected_request in zip(requests, expected, strict=True): | ||
| assert request["package_id"] == expected_request["package_id"] | ||
| body = dict(request["body"]) | ||
| _assert_client_action_id(body) | ||
| body.pop("client_action_id") | ||
| assert body == expected_request["body"] |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Assert client-action ID uniqueness.
Lines [20-41] verify only that each ID is non-empty. A regression that reuses one ID across repeated non-idempotent install/setup gestures would still pass these helpers, despite the PR objective requiring fresh activity IDs. Collect the IDs per request batch and assert len(ids) == len(set(ids)).
Proposed test fix
def _assert_install_requests(requests: list[dict], *package_ids: str) -> None:
assert len(requests) == len(package_ids)
+ client_action_ids = []
for request, package_id in zip(requests, package_ids, strict=True):
assert request.get("package_ref") == _package_ref(package_id)
_assert_client_action_id(request)
+ client_action_ids.append(request["client_action_id"])
+ assert len(client_action_ids) == len(set(client_action_ids))🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/e2e/scenarios/test_reborn_webui_v2_legacy_extensions.py` around lines
20 - 41, Update _assert_install_requests and _assert_setup_submit_requests to
collect each request’s client_action_id while validating it, then assert the
batch contains only unique IDs using len(ids) == len(set(ids)). Preserve the
existing request-count and body/package assertions.
|
Replacement PR opened at #6553 from a fresh branch because GitHub refused to reopen this PR after its pull ref stopped tracking the branch. |
Summary
Change Type
Linked Issue
None.
Validation
cargo fmt --all -- --checkcargo clippy --all --benches --tests --examples --all-features -- -D warningscargo buildcargo test --features integrationif database-backed or integration behavior changed: Not applicable: this fixes WebUI/product capability and browser CI behavior, not DB integration behavior.review-prorpr-shepherd --fixwas run before requesting reviewTest Strategy
User behavior:
WebUI users can install, activate, and remove extensions through ProductSurface-backed gestures without stale ProductResult collisions or missing lifecycle mounts. Browser-visible copy and route expectations now match current Reborn WebUI behavior.
Risk areas:
Tests added or updated:
product_invocation_mounts_*, extension lifecycle remove regressions, WebUI facade ProductSurface lifecycle regression, WebUI handler contract tests.ironclaw_reborn_compositionandironclaw_webuicargo tests plus clippy.What the tests prove:
ProductSurface extension lifecycle calls have the mount authority they need, repeated same-input user gestures do not collide on ProductResult activity ids, hosted MCP removal unpublishes discovered active packages, and all Reborn Playwright shards pass against current UI/API contracts.
Commands run:
cargo fmt --all -- --checkgit diff --checkcargo build -p ironclaw --bin ironclawcargo clippy -p ironclaw_reborn_composition -p ironclaw_webui --all-targets --all-features -- -D warningscargo test -p ironclaw_architecture --test reborn_extension_specificity reborn_generic_code_names_no_concrete_extensioncorepack pnpm test -- pages/chat/components/auth-oauth-card.test.ts pages/chat/lib/attachments.test.tscargo test -p ironclaw_reborn_composition product_invocation_mounts --libcargo test -p ironclaw_reborn_composition product_surface_extension_lifecycle_remove_succeeds_after_activation --libcargo test -p ironclaw_reborn_composition hosted_mcp_remove_unpublishes_discovered_active_package_after_absent_cleanup --libcargo test -p ironclaw_reborn_composition first_party_extension_remove_succeeds_after_absent_cleanup_reinstall_and_activate --libcargo test -p ironclaw_reborn_composition local_dev_extension_lifecycle_tools_manage_visible_extension_surface --libcargo test -p ironclaw_webui --test webui_v2_handlers_contract activate_and_remove_extension_decode_path_package_id_to_lifecycle_paths -- --nocapturecargo test -p ironclaw_webui --test webui_v2_handlers_contract settings_tool -- --nocapturecargo test -p ironclaw_webui --test webui_v2_handlers_contract set_outbound_preferences_dispatches_body_through_invoke -- --nocapturepytest tests/e2e/scenarios/test_reborn_webui_v2_smoke.py tests/e2e/scenarios/test_reborn_v2_file_download.py -v --timeout=120 --durations=25pytest tests/e2e/scenarios/test_reborn_webui_v2_automation_trace_outbound_api.py tests/e2e/scenarios/test_reborn_webui_v2_extensions_api.py tests/e2e/scenarios/test_reborn_webui_v2_filesystem_api.py tests/e2e/scenarios/test_reborn_webui_v2_operator_api.py tests/e2e/scenarios/test_reborn_webui_v2_product_auth_api.py tests/e2e/scenarios/test_reborn_webui_v2_session_api.py tests/e2e/scenarios/test_reborn_webui_v2_skills_api.py tests/e2e/scenarios/test_reborn_webui_v2_streaming_run_control_api.py -v --timeout=120 --durations=25pytest tests/e2e/scenarios/test_reborn_webui_v2_legacy_core.py tests/e2e/scenarios/test_reborn_webui_v2_legacy_rendering.py tests/e2e/scenarios/test_reborn_webui_v2_legacy_chat_actions.py tests/e2e/scenarios/test_reborn_webui_v2_legacy_dom_resource_limits.py -v --timeout=120 --durations=25pytest tests/e2e/scenarios/test_reborn_webui_v2_legacy_approval.py tests/e2e/scenarios/test_reborn_webui_v2_legacy_auth_flows.py tests/e2e/scenarios/test_reborn_webui_v2_legacy_attachments.py -v --timeout=120 --durations=25pytest tests/e2e/scenarios/test_reborn_webui_v2_legacy_extensions.py tests/e2e/scenarios/test_reborn_webui_v2_legacy_settings_search.py tests/e2e/scenarios/test_reborn_webui_v2_legacy_skills.py tests/e2e/scenarios/test_reborn_webui_v2_legacy_tool_permissions.py tests/e2e/scenarios/test_reborn_webui_v2_legacy_projects.py -k 'not test_reborn_legacy_always_approve_survives_reborn_restart' -v --timeout=120 --durations=25pytest tests/e2e/scenarios/test_reborn_webui_v2_legacy_message_persistence.py tests/e2e/scenarios/test_reborn_webui_v2_legacy_pending_messages.py tests/e2e/scenarios/test_reborn_webui_v2_legacy_sse_history.py tests/e2e/scenarios/test_reborn_webui_v2_legacy_tool_execution.py -v --timeout=120 --durations=25Security Impact
Changes ProductSurface mount grants for extension install/activate/remove so those lifecycle capabilities receive
/system/extensionsauthority through the existing lifecycle mount helper. No new network calls, secrets, auth bypasses, or sandbox policy changes.Reborn Trust-Boundary Checklist
serde(default)fields fail closed or have migration tests. Not applicable: no serde schema changes.Transient,Permanent,Misconfigured,PolicyDeniedor equivalent). Not applicable: no new error classes.Database Impact
None.
Blast Radius
Touches WebUI ProductSurface capability invocation, extension lifecycle removal, Reborn Playwright browser/API expectations, and small frontend fallback copy/limit behavior. A regression would most likely show up as extension install/activate/remove failure, repeated ProductSurface gesture result reuse, or stale WebUI test expectations.
Rollback Plan
Revert this PR. That restores the previous deterministic WebUI ProductSurface activity ids, previous extension lifecycle unpublish behavior, and prior Playwright expectations.
Review Follow-Through
Known follow-up: the full workspace clippy command was not run locally; targeted touched-crate clippy and the full Reborn Playwright matrix were run. Main branch currently has issue-comment-triggered
nearai-benchfailures unrelated to push CI; the scheduled Reborn Playwright failure being fixed was on pre-current-main SHA62c5ad08.Review track: C (runtime/permissions/browser CI)