Skip to content

refactor(events): collapse three PairingRequired emission sites into one constructor - #2607

Merged
ilblackdragon merged 1 commit into
stagingfrom
refactor/onboarding-pairing-required-constructor
Apr 18, 2026
Merged

ilblackdragon merged 1 commit into
stagingfrom
refactor/onboarding-pairing-required-constructor

Conversation

@ilblackdragon

Copy link
Copy Markdown
Member

Summary

  • Three call sites (src/bridge/router.rs:2080, src/channels/web/server.rs:4010, src/extensions/manager.rs:7065) hand-construct AppEvent::OnboardingState { state: PairingRequired, ... } with identical 8-field payloads. Any new field added to OnboardingState requires touching all three, and they can silently disagree on invariants.
  • Add OnboardingStateDto::pairing_required(extension_name, request_id, thread_id, message, instructions, onboarding) to crates/ironclaw_common/src/event.rs as the single construction path. auth_url, setup_url, and state become invariant inside the constructor; callers only supply what genuinely varies between sites.
  • Follow-up to Unify gateway onboarding, auth gates, and pairing flows #2515, which flagged the leak in its own PR body: "backend onboarding transition knowledge still lives across server.rs, router.rs, and extension/pairing code". This is the smallest, most isolated slice of that consolidation.

Why now

After #2515 merged, #2574 (a6443dd) had to undo a duplication that landed 2 days later in pending_gate_extension_name — evidence the leak is live. The pair-emit path is the next-highest-pain boundary: requeue_pairing_pending_gate (router) and the /api/extensions/{name}/setup submit handler (server) compute request_id and thread_id via different helpers, so a field rename or new optional field lands in one place and not the other.

Notes

  • Naming: placed as OnboardingStateDto::pairing_required(...) -> AppEvent per the original consolidation proposal. Slightly unusual (an associated function on the state DTO returning an AppEvent), but it keeps the discoverability at the OnboardingStateDto:: path where the variant itself lives.
  • Scope intentionally narrow: does not touch AuthRequired / SetupRequired / Ready / Failed emissions. Those have fewer duplicate sites and less invariant surface; a follow-up can consolidate them once this pattern is established.
  • No wire-format change. Existing serialization tests in src/channels/web/types.rs (test_app_event_onboarding_state_pairing_required_serialize) continue to pass unchanged.

Test plan

  • cargo fmt
  • cargo clippy --all --benches --tests --examples --all-features — zero warnings
  • cargo test --lib -p ironclaw_common — 2 new constructor tests pass (field invariants + serialization)
  • cargo test --lib --features libsql — all 4,998 unit tests pass, including existing test_app_event_onboarding_state_pairing_required_serialize and the 10 onboarding/handler tests
  • Manual smoke: pairing flow end-to-end on a Telegram-style channel (not performed locally — relying on CI + staging)

Regression test

The commit adds pairing_required_constructor_sets_invariant_fields and pairing_required_constructor_serializes_to_onboarding_state_event in crates/ironclaw_common/src/event.rs. Per .claude/rules/testing.md the rule is to test through the caller for helper-gated side effects — here the existing test_app_event_onboarding_state_pairing_required_serialize in src/channels/web/types.rs continues to exercise the wire format, and the three call-site refactors are covered by existing integration tests that round-trip AppEvent::OnboardingState over SSE (e.g. test_chat_auth_token_handler_expired_auth_broadcasts_failed_onboarding_state pattern). No behavior change — pure compile-time refactor.

Three call sites (bridge::router, channels::web::server, extensions::manager)
hand-constructed AppEvent::OnboardingState with state=PairingRequired and
identical 8-field payloads. Any new field on OnboardingState required touching
all three sites and they could silently disagree.

Collapse emission through a single associated function in ironclaw_common
so auth_url/setup_url/state are invariant and the three sites only supply
the fields that genuinely vary (request_id, thread_id, message, instructions,
onboarding).
Copilot AI review requested due to automatic review settings April 18, 2026 03:36
@github-actions github-actions Bot added scope: channel/web Web gateway channel scope: extensions Extension management size: M 50-199 changed lines risk: medium Business logic, config, or moderate-risk modules contributor: core 20+ merged PRs labels Apr 18, 2026

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 pairing-required onboarding event emission to use a single constructor in ironclaw_common, reducing duplication and ensuring invariant fields (state, auth_url, setup_url) can’t drift across emit sites.

Changes:

  • Added OnboardingStateDto::pairing_required(...) -> AppEvent constructor in crates/ironclaw_common/src/event.rs.
  • Replaced three hand-built AppEvent::OnboardingState { state: PairingRequired, ... } call sites with the new constructor.
  • Added unit tests in ironclaw_common to assert invariant fields + serialization shape.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
crates/ironclaw_common/src/event.rs Adds pairing_required constructor + tests to centralize invariant pairing event construction.
src/bridge/router.rs Uses constructor when transitioning an auth-gated flow into pairing-required.
src/channels/web/server.rs Uses constructor in /api/extensions/{name}/setup submit handler when pairing becomes required.
src/extensions/manager.rs Uses constructor when activation detects a channel still needs pairing.

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

@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 canonical pairing_required constructor for OnboardingStateDto to standardize the creation of AppEvent::OnboardingState events during pairing transitions. By centralizing this logic, the PR ensures that invariant fields such as auth_url and setup_url are consistently set to None across the bridge router, web server, and extension manager. Additionally, unit tests have been implemented to verify the constructor's field assignments and serialization behavior. I have no feedback to provide.

@ilblackdragon
ilblackdragon merged commit afb0dcf into staging Apr 18, 2026
20 checks passed
@ilblackdragon
ilblackdragon deleted the refactor/onboarding-pairing-required-constructor branch April 18, 2026 03:45
@ilblackdragon ilblackdragon mentioned this pull request Apr 18, 2026
7 tasks
@ironclaw-ci ironclaw-ci Bot mentioned this pull request Apr 21, 2026
theredspoon pushed a commit to theredspoon/ironclaw that referenced this pull request Jun 21, 2026
nearai#2607)

Three call sites (bridge::router, channels::web::server, extensions::manager)
hand-constructed AppEvent::OnboardingState with state=PairingRequired and
identical 8-field payloads. Any new field on OnboardingState required touching
all three sites and they could silently disagree.

Collapse emission through a single associated function in ironclaw_common
so auth_url/setup_url/state are invariant and the three sites only supply
the fields that genuinely vary (request_id, thread_id, message, instructions,
onboarding).
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: medium Business logic, config, or moderate-risk modules scope: channel/web Web gateway channel scope: extensions Extension management size: M 50-199 changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants