Skip to content

Prove broker-bound Iroh relay round trip - #8488

Merged
azooz2003-bit merged 2 commits into
mainfrom
feat-iroh-live-relay-gate
Jul 19, 2026
Merged

azooz2003-bit merged 2 commits into
mainfrom
feat-iroh-live-relay-gate

Conversation

@azooz2003-bit

@azooz2003-bit azooz2003-bit commented Jul 19, 2026 •

Copy link
Copy Markdown
Collaborator

What

  • registers two disposable endpoint identities through the staging trust-broker challenge flow
  • mints independent endpoint-bound relay credentials
  • requires a bidirectional relay-only Iroh stream over one exact server-provided relay URL
  • verifies both observed paths are relay and revokes all disposable bindings
  • bounds connection and stream phases so a live failure cannot hang the gate
  • fixes the live harness ordering so the first byte is sent before awaiting the peer's lazily materialized QUIC stream

Verification

  • swift test: 408 tests passed
  • staging live gate: passed in 4.496s with two fresh endpoint-bound JWTs
  • post-run broker query: zero disposable bindings remained

Test-only; no app runtime or user-facing strings changed.


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Summary by cubic

Adds a live test that proves a broker-bound Iroh relay can carry a bidirectional stream round trip over a single server-provided relay URL. Tightens timeouts and isolates each run so the gate never hangs.

  • New Features

    • Registers two disposable endpoints via the staging trust-broker, mints endpoint-bound relay tokens, and verifies both paths are relay-only on the same URL.
    • Adds bounded stream round trip with incoming stream limits and a deadline; force-closes connections on timeout.
    • Isolates each live run with a unique tag and auto-revokes stale and post-run bindings. Supports broker env vars: CMUX_IROH_CUSTOM_RELAY_BROKER_URL, CMUX_IROH_CUSTOM_RELAY_ACCESS_TOKEN, CMUX_IROH_CUSTOM_RELAY_REFRESH_TOKEN.
  • Bug Fixes

    • Fixes live harness ordering so the first byte is sent before awaiting the peer’s lazily materialized QUIC stream.

Written for commit 1b403a7. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Tests
    • Added live coverage for broker-credential–bound custom relay token flows.
    • Added end-to-end bidirectional relay round-trip validation using credentials issued at runtime.
    • Introduced timeout handling and progress reporting for streamed request/response exchanges.
    • Enhanced cleanup by revoking temporary relay bindings on both success and failure.

@coderabbitai

coderabbitai Bot commented Jul 19, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds broker-credential detection and live coverage for broker-bound custom relay tokens. Tests register endpoints, perform bidirectional relay exchanges, clean up bindings, log progress, and bound stream round trips with concurrent timeout handling.

Changes

Broker relay live tests

Layer / File(s) Summary
Broker credential setup and token flow
Packages/Shared/CmuxIrohTransport/Tests/CmuxIrohCustomRelayLiveEnvironment.swift, Packages/Shared/CmuxIrohTransport/Tests/CmuxIrohCustomRelayLiveTests.swift
Detects required broker credentials and adds live setup for endpoint registration, token issuance, relay testing, and binding cleanup.
Relay round-trip orchestration
Packages/Shared/CmuxIrohTransport/Tests/CmuxIrohCustomRelayLiveTests.swift
Adds progress logging and routes bidirectional relay assertions through the bounded stream helper.
Concurrent stream exchange and timeout
Packages/Shared/CmuxIrohTransport/Tests/CmuxIrohCustomRelayLiveTests.swift
Runs stream exchange and timeout handling concurrently, closing connections and cancelling remaining tasks when complete.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant LiveTest
  participant TrustBroker
  participant EndpointOne
  participant EndpointTwo
  participant CustomRelay
  LiveTest->>TrustBroker: Register endpoint bindings
  TrustBroker-->>LiveTest: Return relay tokens
  EndpointOne->>CustomRelay: Connect with first token
  EndpointTwo->>CustomRelay: Connect with second token
  EndpointOne->>EndpointTwo: Exchange bidirectional stream data
  LiveTest->>TrustBroker: Revoke bindings
Loading

Possibly related PRs

  • manaflow-ai/cmux#8484: Adds related custom-relay live test and environment harness work in the same files.
🚥 Pre-merge checks | ✅ 24 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning It covers what changed and verification, but misses required template sections like Summary, Demo Video, Review Trigger, and Checklist. Rewrite the PR description using the repo template sections: Summary, Testing, Demo Video, Review Trigger, and Checklist.
✅ Passed checks (24 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Swift Actor Isolation ✅ Passed PASS: The diff only touches test targets and adds live-test helpers; no production Swift, MainActor, or actor-isolation regression was introduced.
Cmux Swift Blocking Runtime ✅ Passed Only test files changed; the new sleeps are test-only scaffolding, and no semaphores, sync waits, or locks were added in production code.
Cmux Browser Automation Off-Main ✅ Passed Diff only changes CmuxIrohTransport live relay tests; no browser.* routing, WebKit/AppKit main-lane code, or policy-test gaps are present.
Cmux Expensive Synchronous Load ✅ Passed Diff only changes a test file and adds env gating/async live-test logic; no production main-actor synchronous agent-history load was added or moved.
Cmux Cache Substitution Correctness ✅ Passed Changes are confined to Tests; the new env helper and live relay flow don’t replace any fresh authoritative read in a persistence/history/snapshot path.
Cmux No Hacky Sleeps ✅ Passed The PR diff only changes live-test tagging and cleanup; no new sleep or timer logic is introduced, and existing waits are test-only scaffolding.
Cmux Algorithmic Complexity ✅ Passed Only test scaffolding changed; the new scans are over tiny fixed-size arrays (3 env vars, 2 bindings/credentials), not scalable production collections.
Cmux Swift Concurrency ✅ Passed Only test code changed, and it uses structured concurrency (withThrowingTaskGroup, async let) with no new Dispatch/Combine/completion-handler or fire-and-forget task patterns.
Cmux Swift @Concurrent ✅ Passed No changed async helper is actor-isolated or misannotated; the new live-test methods are plain test methods, and no UI-bound hop issue is introduced.
Cmux Swift Package Boundaries ✅ Passed Only test-target Swift files changed under Packages/Shared/CmuxIrohTransport/Tests, which are explicitly allowed fixtures/live tests, not production app code.
Cmux Swiftpm Lockfiles ✅ Passed HEAD only changes a Swift test file; no Package.swift, .gitignore, workflow, or Package.resolved files were touched, so the lockfile rule is not violated.
Cmux Swift Logging ✅ Passed Logging changes are confined to a test-only live suite; the new prints are harness progress messages and expose no secrets or personal data.
Cmux User-Facing Error Privacy ✅ Passed Only test code changed, and the rule explicitly allows tests/docs/runbooks outside user-facing surfaces.
Cmux Full Internationalization ✅ Passed PASS: the changes are confined to test-only live harness files under Tests/, with no production UI, catalog, or user-facing text changes.
Cmux Swiftui State Layout ✅ Passed PASS: the PR only touches test-only Iroh live harness code; no SwiftUI views, state objects, GeometryReader, lazy row store refs, or render-time mutations appear.
Cmux Architecture Rethink ✅ Passed Changes are confined to test-only Swift files; the added polling/sleeps are explicit live-test synchronization, which the rule allows.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR only changes a test-only live relay harness; no NSWindow/NSPanel/WindowGroup code or cmuxAuxiliaryWindowIdentifiers registration was introduced.
Cmux Source Artifacts ✅ Passed PASS: The changed files are handwritten Swift test/source files; no logs, caches, build outputs, temp folders, or other artifact paths were added.
Cmux No Test Or Debug Seam In Production Source ✅ Passed Only a Tests/ Swift file changed; no production Sources/ file added a test/debug seam.
Cmux No Ambient Global State ✅ Passed PASS: The diff is test-only; no production Sources files or new top-level globals/singletons were added, and the new helper is just a computed test gate.
Title check ✅ Passed Concise and accurately summarizes the main change: a broker-bound Iroh relay round-trip proof.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-iroh-live-relay-gate

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jul 19, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This test-only PR proves a broker-bound Iroh relay round trip end-to-end: it registers two disposable endpoint identities via the staging trust-broker, mints independent endpoint-bound relay tokens, verifies a bidirectional QUIC stream traverses a single server-provided relay URL, and revokes all disposable bindings on success or failure. It also fixes a pre-existing harness ordering bug where the peer's lazily materialised QUIC stream was awaited before the first byte was sent.

  • New live test brokerBoundTokensCarryBidirectionalRoundTrip: registers two endpoints with per-run UUID tags (avoiding cross-runner collisions), discovers a common relay URL across both credential sets using uniquingKeysWith, and delegates the stream phase to the new carryBoundedStreamRoundTrip helper.
  • carryBoundedStreamRoundTrip: wraps the stream exchange in a withThrowingTaskGroup race between the work task and a ContinuousClock timeout; defer { group.cancelAll() } ensures the timeout task is cancelled immediately on success rather than waiting for the full deadline.
  • Ordering fix: outgoingStream.sendStream.send + finish are now called before await acceptedStream, so the incoming peer's QUIC stream is materialised by the time it is awaited.

Confidence Score: 5/5

Test-only change with no production source modifications; safe to merge.

Both changed files live entirely within the test target. No production sources, app runtime, user-facing strings, or SwiftPM dependency changes are touched. The new test uses per-run UUID tags to avoid cross-runner binding collisions, properly cleans up disposable broker bindings on both success and failure paths, and the timeout/cancellation pattern in carryBoundedStreamRoundTrip is structurally sound.

No files require special attention.

Important Files Changed

Filename Overview
Packages/Shared/CmuxIrohTransport/Tests/CmuxIrohTransportTests/CmxIrohCustomRelayLiveEnvironment.swift Adds hasBrokerCredentials static property that checks for the three new broker env vars; straightforward addition consistent with existing checks.
Packages/Shared/CmuxIrohTransport/Tests/CmuxIrohTransportTests/CmxIrohCustomRelayLiveTests.swift Adds the brokerBoundTokensCarryBidirectionalRoundTrip live test with per-run UUID tags, endpoint registration, credential minting, stream round-trip, and full cleanup; refactors the stream phase into carryBoundedStreamRoundTrip with a proper task-group timeout pattern; fixes the QUIC stream materialisation ordering bug.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Test as brokerBoundTokensCarryBidirectionalRoundTrip
    participant Broker as CmxIrohTrustBrokerClient
    participant E1 as Endpoint 1 (Iroh)
    participant E2 as Endpoint 2 (Iroh)
    participant Relay as Iroh Relay Server

    Test->>Broker: "register(secretKey1, tag=runTag-first)"
    Broker-->>Test: "RegisteredEndpoint{bindingID1, endpointID1}"
    Test->>Broker: "register(secretKey2, tag=runTag-second)"
    Broker-->>Test: "RegisteredEndpoint{bindingID2, endpointID2}"

    Test->>Broker: issueRelayToken(bindingID1, endpointID1)
    Broker-->>Test: firstToken credentials
    Test->>Broker: issueRelayToken(bindingID2, endpointID2)
    Broker-->>Test: secondToken credentials

    Note over Test: Find commonRelayURL in both credential sets

    Test->>E1: bind(secretKey1, relayProfile[token1])
    Test->>E2: bind(secretKey2, relayProfile[token2])
    E1->>Relay: connect relay-only
    E2->>Relay: connect relay-only

    Test->>E1: openBidirectionalStream, send request, finish
    E2->>Test: acceptBidirectionalStream, receiveAll request
    E2->>E1: send response, finish
    E1->>Test: receiveAll response

    Test->>E1: verifyPath relay
    Test->>E2: verifyPath relay

    Test->>Broker: revoke(bindingID1)
    Test->>Broker: revoke(bindingID2)
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Test as brokerBoundTokensCarryBidirectionalRoundTrip
    participant Broker as CmxIrohTrustBrokerClient
    participant E1 as Endpoint 1 (Iroh)
    participant E2 as Endpoint 2 (Iroh)
    participant Relay as Iroh Relay Server

    Test->>Broker: "register(secretKey1, tag=runTag-first)"
    Broker-->>Test: "RegisteredEndpoint{bindingID1, endpointID1}"
    Test->>Broker: "register(secretKey2, tag=runTag-second)"
    Broker-->>Test: "RegisteredEndpoint{bindingID2, endpointID2}"

    Test->>Broker: issueRelayToken(bindingID1, endpointID1)
    Broker-->>Test: firstToken credentials
    Test->>Broker: issueRelayToken(bindingID2, endpointID2)
    Broker-->>Test: secondToken credentials

    Note over Test: Find commonRelayURL in both credential sets

    Test->>E1: bind(secretKey1, relayProfile[token1])
    Test->>E2: bind(secretKey2, relayProfile[token2])
    E1->>Relay: connect relay-only
    E2->>Relay: connect relay-only

    Test->>E1: openBidirectionalStream, send request, finish
    E2->>Test: acceptBidirectionalStream, receiveAll request
    E2->>E1: send response, finish
    E1->>Test: receiveAll response

    Test->>E1: verifyPath relay
    Test->>E2: verifyPath relay

    Test->>Broker: revoke(bindingID1)
    Test->>Broker: revoke(bindingID2)
Loading

Reviews (2): Last reviewed commit: "test(iroh): isolate live relay runs" | Re-trigger Greptile

Comment on lines +176 to +178
let firstCredentials: [String: String] = Dictionary(
uniqueKeysWithValues: firstToken.credentials.map { ($0.relayURL, $0.token) }
)

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.

P2 Dictionary(uniqueKeysWithValues:) crashes on duplicate relay URLs

If the broker ever returns two credentials entries with the same relayURL (e.g., a server-side bug or a future multi-token-per-relay design), Dictionary(uniqueKeysWithValues:) hits a fatal precondition and crashes the process rather than throwing, which bypasses the catch block that revokes disposable bindings. Replace with init(_:uniquingKeysWith:) to keep the latest token per URL and convert a potential crash into a recoverable state.

Comment on lines +271 to +280
private func revokeStaleLiveTestBindings(
broker: CmxIrohTrustBrokerClient
) async throws {
let staleBindingIDs = try await broker.discover().bindings
.filter { ["relay-live-first", "relay-live-second"].contains($0.tag) }
.map(\.bindingID)
for bindingID in staleBindingIDs {
try await broker.revoke(bindingID: bindingID)
}
}

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.

P2 Stale-cleanup tags are not run-isolated

revokeStaleLiveTestBindings filters the account's bindings by the hardcoded tags "relay-live-first" and "relay-live-second". If two live runs execute concurrently against the same staging broker account (different CI runners, parallel developer machines), one run's stale-cleanup step will revoke the other run's in-flight bindings mid-test, producing confusing failures that look like broker or relay errors.

@azooz2003-bit
azooz2003-bit merged commit 725da9f into main Jul 19, 2026
6 checks passed
@azooz2003-bit
azooz2003-bit deleted the feat-iroh-live-relay-gate branch July 19, 2026 16:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant