Repository navigation
Finish the production Iroh transport rollout - #8484
Conversation
…at-iroh-final-integration
… feat-iroh-final-integration
…iroh-final-integration
…eat-iroh-final-integration
…nto feat-iroh-final-integration
…t-iroh-final-integration
…o feat-ios-iroh-debug-modes
Verified package-native iOS runtime and settings tests. Merges into the Iroh integration branch.
feat(iroh): publish signed direct UDP ports
test(iroh): verify custom relay round trips
|
Too many files changed for review. ( Bypass the limit by tagging |
📝 WalkthroughWalkthroughThis PR adds Iroh transport verification controls, relay-readiness handling, session diagnostics, direct-port propagation, UUID device-ID canonicalization, paired-Mac migration, relay catalog hardening, and debug-only iOS release-gate workflows with automated validation. ChangesTransport and diagnostics
Device identity and mobile integration
Relay services
Release gate and CI
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related issues
Possibly related PRs
Suggested reviewers: Important Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional. ❌ Failed checks (4 errors, 1 warning, 1 inconclusive)
✅ Passed checks (19 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/PairedMacRestore.swift (1)
64-80: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winCanonicalize the UUID in
tombstoneIDsand theliveRecordsfilter.While
cmxCanonicalDeviceIDis correctly applied during the upsert loop (line 139), it is missing from both thecanonicalPairingIDhelper and theliveRecordsfilter. If a backup contains a tombstone with a different UUID casing than the live record, the raw string mismatch will cause the.containscheck to fail and the live record will bypass the tombstone filter. The subsequentstore.removewill delete the local row, but the bypassed live record will be resurrected bystore.upsertIfNewer.Wrap the
macDeviceIDincmxCanonicalDeviceIDin both places to ensure tombstones reliably match their target records regardless of UUID casing.🐛 Proposed fix
func canonicalPairingID(_ value: String) -> String? { let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { return nil } let identity = MobilePairedMac.pairingIdentity(from: trimmed) return MobilePairedMac.pairingID( - macDeviceID: identity.macDeviceID, + macDeviceID: cmxCanonicalDeviceID(identity.macDeviceID), instanceTag: identity.instanceTag ) } let tombstoneIDs = Set(snapshot.deletedMacDeviceIDs.compactMap(canonicalPairingID)) .union(locallyDeletedMacDeviceIDs.compactMap(canonicalPairingID)) let liveRecords = snapshot.records.filter { record in !tombstoneIDs.contains(MobilePairedMac.pairingID( - macDeviceID: record.macDeviceID, + macDeviceID: cmxCanonicalDeviceID(record.macDeviceID), instanceTag: record.instanceTag )) }🤖 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 `@Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/PairedMacRestore.swift` around lines 64 - 80, Apply cmxCanonicalDeviceID to the macDeviceID passed to MobilePairedMac.pairingIdentity in canonicalPairingID, and to the record.macDeviceID passed to MobilePairedMac.pairingID in the liveRecords filter. Preserve the existing tombstone matching and filtering flow while ensuring UUID casing is normalized consistently.web/services/relay/repository.ts (1)
146-162: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winIdempotent same-sequence accept skips the catalog-body integrity check.
The sequence-advance branch verifies
relayCatalogDigest(previous) !== current.catalogDigestbefore trusting the persisted catalog body (lines 170-175), but the same-sequence branch (146-162) only relies onassertCatalogAdvance's digest-column comparison and never parses/re-hashescurrent.catalogitself. If the stored JSON body ever diverges fromcatalog_digestwithout the sequence changing, repeated idempotentacceptCatalogcalls at that sequence would silently miss the corruption that the very next rotation would catch.♻️ Proposed fix to also self-check the persisted body at same-sequence
if (catalog.sequence === current.catalogSequence) { - if (current.catalog === null) { - await tx - .update(irohRelayCatalogState) - .set({ catalog }) - .where(eq(irohRelayCatalogState.id, "managed")); - } + if (current.catalog === null) { + await tx + .update(irohRelayCatalogState) + .set({ catalog }) + .where(eq(irohRelayCatalogState.id, "managed")); + } else if ( + relayCatalogDigest(parseRelayCatalog(JSON.stringify(current.catalog))) !== + current.catalogDigest + ) { + throw new RelayCatalogIntegrityError({ + reason: "persisted_catalog_digest_mismatch", + }); + } return; }🤖 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 `@web/services/relay/repository.ts` around lines 146 - 162, Update the same-sequence branch of acceptCatalog around assertCatalogAdvance to validate the persisted current.catalog body by computing its relayCatalogDigest and comparing it with current.catalogDigest before accepting or updating the catalog. Preserve the existing idempotent behavior for valid data and ensure corrupted same-sequence state follows the same integrity-failure path as the sequence-advance branch.
🤖 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 @.github/workflows/iroh-release-gate.yml:
- Around line 3-19: add a workflow-level concurrency group for the manual gate
using the github.ref-based identifier and set cancel-in-progress to false,
ensuring concurrent dispatches queue rather than overlap while preserving the
existing workflow_dispatch inputs and behavior.
- Around line 67-76: Harden the workflow step using matrix.mode by assigning it
to a step-level environment variable such as MODE, then replace direct `${{
matrix.mode }}` interpolation in the case statement and release-gate command
with `$MODE`. Preserve the existing mode-to-TAG mapping and report filename
behavior while ensuring the expression is treated as runtime data rather than
shell code.
In `@ios/cmuxPackage/Sources/cmuxFeature/MobileIrohRuntimeComposition.swift`:
- Around line 2213-2237: Move the DEBUG-only CmxIrohDebugSettingsControlling
extension for MobileIrohRuntimeComposition into a dedicated
MobileIrohRuntimeComposition+DebugSettings.swift file, preserving
setIrohDebugTransportVerificationMode behavior and its `#if` DEBUG guard. Remove
the inlined extension and conditional block from the main production file.
In
`@Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/PairedMacBackupListResponse.swift`:
- Line 1: Import CMUXMobileCore and update the mapped device identifier used to
construct each backup pairing ID, applying cmxCanonicalDeviceID to
identity.macDeviceID before passing it to MobilePairedMac.pairingID. Preserve
the existing instance-tag mapping and ensure all affected mapping paths use the
canonicalized device ID so local deletion keys match the store.
In `@web/db/migrations/20260719120000_iroh_direct_ports/migration.sql`:
- Around line 1-13: Update the direct_port_v4 and direct_port_v6 CHECK
constraints in this migration to use NOT VALID when added, avoiding the blocking
table scan. Add subsequent VALIDATE CONSTRAINT statements, or a follow-up
migration matching the iroh_relay_status_validation pattern, so both constraints
are eventually validated.
---
Outside diff comments:
In `@Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/PairedMacRestore.swift`:
- Around line 64-80: Apply cmxCanonicalDeviceID to the macDeviceID passed to
MobilePairedMac.pairingIdentity in canonicalPairingID, and to the
record.macDeviceID passed to MobilePairedMac.pairingID in the liveRecords
filter. Preserve the existing tombstone matching and filtering flow while
ensuring UUID casing is normalized consistently.
In `@web/services/relay/repository.ts`:
- Around line 146-162: Update the same-sequence branch of acceptCatalog around
assertCatalogAdvance to validate the persisted current.catalog body by computing
its relayCatalogDigest and comparing it with current.catalogDigest before
accepting or updating the catalog. Preserve the existing idempotent behavior for
valid data and ensure corrupted same-sequence state follows the same
integrity-failure path as the sequence-advance branch.
🪄 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
Run ID: 8ef73fc9-8df6-4acd-9904-54299cf01f03
⛔ Files ignored due to path filters (4)
Packages/Shared/CmuxIrohTransport/Package.resolvedis excluded by!**/Package.resolvedcmux.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolvedis excluded by!**/Package.resolvedios/cmux.xcworkspace/xcshareddata/swiftpm/Package.resolvedis excluded by!**/Package.resolvedios/cmuxPackage/Package.resolvedis excluded by!**/Package.resolved
📒 Files selected for processing (169)
.github/workflows/ci-macos-compat.yml.github/workflows/iroh-release-gate.ymlPackages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/CmxByteTransportRequest.swiftPackages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/CmxDeviceIDCanonicalization.swiftPackages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/CmxIrohDebugSettingsControlling.swiftPackages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/CmxIrohSettingsSnapshot.swiftPackages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/CmxIrohTransportVerificationMode.swiftPackages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/CmxTransport.swiftPackages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/CmxTransportSessionPurpose.swiftPackages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/DiagnosticEventCode.swiftPackages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/DiagnosticReport.swiftPackages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/DiagnosticTaxonomy.swiftPackages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/MobileSyncProtocol.swiftPackages/Shared/CMUXMobileCore/Tests/CMUXMobileCoreTests/CmxDeviceIDCanonicalizationTests.swiftPackages/Shared/CMUXMobileCore/Tests/CMUXMobileCoreTests/CmxIrohSettingsSnapshotTests.swiftPackages/Shared/CMUXMobileCore/Tests/CMUXMobileCoreTests/DiagnosticLogTests.swiftPackages/Shared/CmuxIrohTransport/Package.swiftPackages/Shared/CmuxIrohTransport/README.mdPackages/Shared/CmuxIrohTransport/Sources/CmuxIrohTransport/CmxIrohAdmittedConnectionSupervisor.swiftPackages/Shared/CmuxIrohTransport/Sources/CmuxIrohTransport/CmxIrohBrokerModels.swiftPackages/Shared/CmuxIrohTransport/Sources/CmuxIrohTransport/CmxIrohClientRuntime+Policy.swiftPackages/Shared/CmuxIrohTransport/Sources/CmuxIrohTransport/CmxIrohClientRuntime+PolicyRefresh.swiftPackages/Shared/CmuxIrohTransport/Sources/CmuxIrohTransport/CmxIrohClientRuntime.swiftPackages/Shared/CmuxIrohTransport/Sources/CmuxIrohTransport/CmxIrohClientRuntimeConfiguration.swiftPackages/Shared/CmuxIrohTransport/Sources/CmuxIrohTransport/CmxIrohClientSessionPool.swiftPackages/Shared/CmuxIrohTransport/Sources/CmuxIrohTransport/CmxIrohDiagnosticFailure.swiftPackages/Shared/CmuxIrohTransport/Sources/CmuxIrohTransport/CmxIrohDirectPorts.swiftPackages/Shared/CmuxIrohTransport/Sources/CmuxIrohTransport/CmxIrohEndpointSupervisor.swiftPackages/Shared/CmuxIrohTransport/Sources/CmuxIrohTransport/CmxIrohEndpointSupervisorError.swiftPackages/Shared/CmuxIrohTransport/Sources/CmuxIrohTransport/CmxIrohHostRuntime+PolicyRefresh.swiftPackages/Shared/CmuxIrohTransport/Sources/CmuxIrohTransport/CmxIrohHostRuntime.swiftPackages/Shared/CmuxIrohTransport/Sources/CmuxIrohTransport/CmxIrohHostRuntimeConfiguration.swiftPackages/Shared/CmuxIrohTransport/Sources/CmuxIrohTransport/CmxIrohLANDiscoveryResolver.swiftPackages/Shared/CmuxIrohTransport/Sources/CmuxIrohTransport/CmxIrohLANPeerDiscovery.swiftPackages/Shared/CmuxIrohTransport/Sources/CmuxIrohTransport/CmxIrohLibEndpointFactory.swiftPackages/Shared/CmuxIrohTransport/Sources/CmuxIrohTransport/CmxIrohLiveDiscoveryRefreshOutcome.swiftPackages/Shared/CmuxIrohTransport/Sources/CmuxIrohTransport/CmxIrohPooledByteTransport.swiftPackages/Shared/CmuxIrohTransport/Sources/CmuxIrohTransport/CmxIrohRegistrationPayload.swiftPackages/Shared/CmuxIrohTransport/Sources/CmuxIrohTransport/CmxIrohRegistryContextProvider.swiftPackages/Shared/CmuxIrohTransport/Sources/CmuxIrohTransport/CmxIrohRelayDiagnosticsSnapshot.swiftPackages/Shared/CmuxIrohTransport/Sources/CmuxIrohTransport/CmxIrohRelayPolicyResolution.swiftPackages/Shared/CmuxIrohTransport/Sources/CmuxIrohTransport/CmxIrohRelayPolicyService.swiftPackages/Shared/CmuxIrohTransport/Tests/CmuxIrohTransportTests/CmxIrohAdmittedConnectionSupervisorTests.swiftPackages/Shared/CmuxIrohTransport/Tests/CmuxIrohTransportTests/CmxIrohClientRuntimeLifecycleRaceTests.swiftPackages/Shared/CmuxIrohTransport/Tests/CmuxIrohTransportTests/CmxIrohClientRuntimeTests.swiftPackages/Shared/CmuxIrohTransport/Tests/CmuxIrohTransportTests/CmxIrohClientSessionPoolTests.swiftPackages/Shared/CmuxIrohTransport/Tests/CmuxIrohTransportTests/CmxIrohCustomRelayLiveEnvironment.swiftPackages/Shared/CmuxIrohTransport/Tests/CmuxIrohTransportTests/CmxIrohCustomRelayLiveTests.swiftPackages/Shared/CmuxIrohTransport/Tests/CmuxIrohTransportTests/CmxIrohDirectPortsTests.swiftPackages/Shared/CmuxIrohTransport/Tests/CmuxIrohTransportTests/CmxIrohEndpointServerTests+Capacity.swiftPackages/Shared/CmuxIrohTransport/Tests/CmuxIrohTransportTests/CmxIrohEndpointServerTests.swiftPackages/Shared/CmuxIrohTransport/Tests/CmuxIrohTransportTests/CmxIrohEndpointSupervisorTests.swiftPackages/Shared/CmuxIrohTransport/Tests/CmuxIrohTransportTests/CmxIrohHostRuntimeLifecycleRaceTests.swiftPackages/Shared/CmuxIrohTransport/Tests/CmuxIrohTransportTests/CmxIrohHostRuntimeTests.swiftPackages/Shared/CmuxIrohTransport/Tests/CmuxIrohTransportTests/CmxIrohRegistrationSignerTests.swiftPackages/Shared/CmuxIrohTransport/Tests/CmuxIrohTransportTests/CmxIrohRegistryContextProviderFallbackTests.swiftPackages/Shared/CmuxIrohTransport/Tests/CmuxIrohTransportTests/CmxIrohRegistryContextProviderTests.swiftPackages/Shared/CmuxIrohTransport/Tests/CmuxIrohTransportTests/CmxIrohRelayPolicyCustomProfileTests.swiftPackages/Shared/CmuxIrohTransport/Tests/CmuxIrohTransportTests/CmxIrohRelayPolicyServiceTests+Preferences.swiftPackages/Shared/CmuxIrohTransport/Tests/CmuxIrohTransportTests/CmxIrohRelayPolicyServiceTests.swiftPackages/Shared/CmuxIrohTransport/Tests/CmuxIrohTransportTests/CmxIrohRuntimeConfigurationDeviceIDTests.swiftPackages/Shared/CmuxIrohTransport/Tests/CmuxIrohTransportTests/TestHangingDialEndpoint.swiftPackages/Shared/CmuxIrohTransport/Tests/CmuxIrohTransportTests/TestIrohProtocolConfiguration.swiftPackages/iOS/CmuxMobilePairedMac/Sources/CmuxMobilePairedMac/MobilePairedMac.swiftPackages/iOS/CmuxMobilePairedMac/Sources/CmuxMobilePairedMac/MobilePairedMacStore+DeviceIDCanonicalization.swiftPackages/iOS/CmuxMobilePairedMac/Sources/CmuxMobilePairedMac/MobilePairedMacStore.swiftPackages/iOS/CmuxMobilePairedMac/Tests/CmuxMobilePairedMacTests/MobilePairedMacDeviceIDCanonicalizationTests.swiftPackages/iOS/CmuxMobileRPC/Sources/CmuxMobileRPC/MobileCoreRPCClient.swiftPackages/iOS/CmuxMobileRPC/Sources/CmuxMobileRPC/MobileHostStatusResponse.swiftPackages/iOS/CmuxMobileRPC/Tests/CmuxMobileRPCTests/MobileTerminalDTODecodeTests.swiftPackages/iOS/CmuxMobileShell/Package.swiftPackages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/BackingUpPairedMacStore+AuthorizedRoutes.swiftPackages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/BackingUpPairedMacStore+ConditionalRestore.swiftPackages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/BackingUpPairedMacStore.swiftPackages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileMacInstanceTagAuthority.swiftPackages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+InstanceAuthority.swiftPackages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+ManualAttachTicket.swiftPackages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+PairedMacCoalescing.swiftPackages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+TerminalLane.swiftPackages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+ZeroTouchIroh.swiftPackages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swiftPackages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/PairedMacBackupListResponse.swiftPackages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/PairedMacBackupOpWire.swiftPackages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/PairedMacBackupRecord.swiftPackages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/PairedMacBackupRecordWire.swiftPackages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/PairedMacRestore.swiftPackages/iOS/CmuxMobileShell/Sources/CmuxMobileShellReleaseGateSupport/MobileIrohReleaseGateProbeFailure.swiftPackages/iOS/CmuxMobileShell/Sources/CmuxMobileShellReleaseGateSupport/MobileIrohReleaseGateProbeResult.swiftPackages/iOS/CmuxMobileShell/Sources/CmuxMobileShellReleaseGateSupport/MobileIrohReleaseGateResponseValidator.swiftPackages/iOS/CmuxMobileShell/Sources/CmuxMobileShellReleaseGateSupport/MobileShellComposite+IrohReleaseGate.swiftPackages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/MobileIrohReleaseGateResponseValidatorTests.swiftPackages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/MobileMacInstanceTagAuthorityTests.swiftPackages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/MobileShellCompositeForgottenMacRefreshTests.swiftPackages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/PairedMacBackupTests.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileIrohSettingsModel.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileIrohSettingsView.swiftPackages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/MobileIrohSettingsModelTests.swiftPackages/macOS/CmuxBrowser/Sources/CmuxBrowser/DesignMode/BrowserDesignModeAnnotationCapture.swiftPackages/macOS/CmuxBrowser/Sources/CmuxBrowser/DesignMode/BrowserDesignModeAnnotationCaptureRequest.swiftPackages/macOS/CmuxBrowser/Sources/CmuxBrowser/DesignMode/BrowserDesignModeEdit.swiftPackages/macOS/CmuxBrowser/Sources/CmuxBrowser/DesignMode/BrowserDesignModeEditKind.swiftPackages/macOS/CmuxBrowser/Sources/CmuxBrowser/DesignMode/BrowserDesignModePageURL.swiftPackages/macOS/CmuxBrowser/Sources/CmuxBrowser/DesignMode/BrowserDesignModePromptContext.swiftPackages/macOS/CmuxBrowser/Sources/CmuxBrowser/DesignMode/BrowserDesignModePromptFormatter.swiftPackages/macOS/CmuxBrowser/Sources/CmuxBrowser/DesignMode/BrowserDesignModePromptRun.swiftPackages/macOS/CmuxBrowser/Sources/CmuxBrowser/DesignMode/BrowserDesignModeRect.swiftPackages/macOS/CmuxBrowser/Sources/CmuxBrowser/DesignMode/BrowserDesignModeScript.swiftPackages/macOS/CmuxBrowser/Sources/CmuxBrowser/DesignMode/BrowserDesignModeSelection.swiftPackages/macOS/CmuxBrowser/Sources/CmuxBrowser/DesignMode/BrowserDesignModeSnapshot.swiftPackages/macOS/CmuxBrowser/Sources/CmuxBrowser/DesignMode/BrowserDesignModeViewport.swiftPackages/macOS/CmuxBrowser/Tests/CmuxBrowserTests/DesignMode/BrowserDesignModePromptPayload.swiftPackages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/IrohSettingsModelTests.swiftPackages/macOS/CmuxWorkspaces/Sources/CmuxWorkspaces/Values/WorkspaceChecklistAttachment.swiftPackages/macOS/CmuxWorkspaces/Sources/CmuxWorkspaces/Values/WorkspaceChecklistItem.swiftSources/AboutLicenseContent.swiftSources/AgentForkTimeoutResumeGate.swiftSources/IrohTransportDebugMenuButtons.swiftSources/Mobile/MobileHostIdentity.swiftSources/Mobile/MobileHostIrohRuntime+Activation.swiftSources/Mobile/MobileHostIrohRuntime+SettingsSnapshot.swiftSources/Mobile/MobileHostService.swiftSources/Panels/BrowserInsecureHTTPNavigationResolution.swiftSources/SessionPersistence+Todos.swiftSources/ShortcutRecorderRejectedAttempt.swiftSources/SidebarWorkspaceStatusPopover.swiftcmuxTests/MobileHostConnectionLifecycleTests.swiftcmuxTests/MobileHostIdentityTests.swiftcmuxTests/MobileHostIrohAdmissionTests.swiftcmuxTests/MobileHostServiceSettingsTests.swiftdocs/iroh-app-transport-architecture.mdios/cmux-ios.xcodeproj/project.pbxprojios/cmux/Resources/Localizable.xcstringsios/cmux/cmuxApp.swiftios/cmuxPackage/Package.swiftios/cmuxPackage/Sources/CmuxIrohReleaseGateSupport/MobileIrohReleaseGateHostView.swiftios/cmuxPackage/Sources/CmuxIrohReleaseGateSupport/MobileIrohReleaseGateRunner.swiftios/cmuxPackage/Sources/CmuxIrohReleaseGateSupport/MobileIrohReleaseGateScene.swiftios/cmuxPackage/Sources/cmuxFeature/CMUXMobileRootScene.swiftios/cmuxPackage/Sources/cmuxFeature/MobileIrohRouteCatalog.swiftios/cmuxPackage/Sources/cmuxFeature/MobileIrohRuntimeComposition.swiftios/cmuxPackage/Tests/cmuxFeatureTests/MobileIrohReleaseGateRunnerTests.swiftios/cmuxPackage/Tests/cmuxFeatureTests/MobileIrohRuntimeCompositionTests.swiftios/cmuxPackage/Tests/cmuxFeatureTests/MobileIrohTransportVerificationModeTests.swiftscripts/lib/mobile-attach.shscripts/lib/mobile-attach.test.mjsscripts/mobile-dev-launch.shscripts/run-iroh-release-gate.shweb/app/api/relay/token/route.tsweb/app/env.tsweb/db/migrations/20260718120000_iroh_relay_status_validation/migration.sqlweb/db/migrations/20260718121000_iroh_relay_catalog_body/migration.sqlweb/db/migrations/20260719120000_iroh_direct_ports/migration.sqlweb/db/schema.tsweb/services/iroh/README.mdweb/services/iroh/model.tsweb/services/iroh/repository.tsweb/services/iroh/trustBroker.tsweb/services/relay/catalog.tsweb/services/relay/errors.tsweb/services/relay/http.tsweb/services/relay/repository.tsweb/services/relay/workflows.tsweb/tests/client-config-env.test.tsweb/tests/iroh-db-behavior.test.tsweb/tests/iroh-model-crypto.test.tsweb/tests/iroh-trust-broker.test.tsweb/tests/relay-policy.test.tsweb/tests/relay-token-route.test.tsweb/tests/relay-workflows.test.ts
| on: | ||
| workflow_dispatch: | ||
| inputs: | ||
| ref: | ||
| description: Branch or SHA to verify | ||
| required: false | ||
| default: "" | ||
| mode: | ||
| description: Iroh transport mode | ||
| required: true | ||
| default: all | ||
| type: choice | ||
| options: | ||
| - all | ||
| - automatic | ||
| - relay-only | ||
| - direct-only |
There was a problem hiding this comment.
🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win
Consider a concurrency: group for the manual gate.
Nothing prevents two concurrent dispatches from overlapping and racing on the shared staging account (CMUX_DOGFOOD_STACK_EMAIL/PASSWORD) or relay-policy state. A simple concurrency: iroh-release-gate-${{ github.ref }} with cancel-in-progress: false would remove the risk cheaply.
🧰 Tools
🪛 zizmor (1.26.1)
[warning] 3-19: insufficient job-level concurrency limits (concurrency-limits): workflow is missing concurrency setting
(concurrency-limits)
🤖 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 @.github/workflows/iroh-release-gate.yml around lines 3 - 19, add a
workflow-level concurrency group for the manual gate using the github.ref-based
identifier and set cancel-in-progress to false, ensuring concurrent dispatches
queue rather than overlap while preserving the existing workflow_dispatch inputs
and behavior.
Source: Linters/SAST tools
| case "${{ matrix.mode }}" in | ||
| automatic) TAG=irgaut ;; | ||
| relay-only) TAG=irgrel ;; | ||
| direct-only) TAG=irgdir ;; | ||
| esac | ||
| ./scripts/run-iroh-release-gate.sh \ | ||
| --mode "${{ matrix.mode }}" \ | ||
| --tag "$TAG" \ | ||
| --report-output "$RUNNER_TEMP/iroh-release-gate-${{ matrix.mode }}.json" | ||
|
|
There was a problem hiding this comment.
🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win
Harden matrix.mode interpolation per zizmor's template-injection finding.
${{ matrix.mode }} is expanded directly into the case statement and CLI args inside run:. GitHub does validate choice inputs against the declared options, so this isn't exploitable today, but it's still the flagged anti-pattern — move it into a step env: var and reference $MODE from the script so it's treated as data, not code, consistent with standard defense-in-depth for run: blocks.
🔒 Proposed fix
- name: Run staging Iroh gate
+ env:
+ MODE: ${{ matrix.mode }}
run: |
set -euo pipefail
- case "${{ matrix.mode }}" in
+ case "$MODE" in
automatic) TAG=irgaut ;;
relay-only) TAG=irgrel ;;
direct-only) TAG=irgdir ;;
esac
./scripts/run-iroh-release-gate.sh \
- --mode "${{ matrix.mode }}" \
+ --mode "$MODE" \
--tag "$TAG" \
- --report-output "$RUNNER_TEMP/iroh-release-gate-${{ matrix.mode }}.json"
+ --report-output "$RUNNER_TEMP/iroh-release-gate-${MODE}.json"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| case "${{ matrix.mode }}" in | |
| automatic) TAG=irgaut ;; | |
| relay-only) TAG=irgrel ;; | |
| direct-only) TAG=irgdir ;; | |
| esac | |
| ./scripts/run-iroh-release-gate.sh \ | |
| --mode "${{ matrix.mode }}" \ | |
| --tag "$TAG" \ | |
| --report-output "$RUNNER_TEMP/iroh-release-gate-${{ matrix.mode }}.json" | |
| - name: Run staging Iroh gate | |
| env: | |
| MODE: ${{ matrix.mode }} | |
| run: | | |
| set -euo pipefail | |
| case "$MODE" in | |
| automatic) TAG=irgaut ;; | |
| relay-only) TAG=irgrel ;; | |
| direct-only) TAG=irgdir ;; | |
| esac | |
| ./scripts/run-iroh-release-gate.sh \ | |
| --mode "$MODE" \ | |
| --tag "$TAG" \ | |
| --report-output "$RUNNER_TEMP/iroh-release-gate-${MODE}.json" |
🧰 Tools
🪛 zizmor (1.26.1)
[warning] 67-67: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[warning] 73-73: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[warning] 75-75: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
🤖 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 @.github/workflows/iroh-release-gate.yml around lines 67 - 76, Harden the
workflow step using matrix.mode by assigning it to a step-level environment
variable such as MODE, then replace direct `${{ matrix.mode }}` interpolation in
the case statement and release-gate command with `$MODE`. Preserve the existing
mode-to-TAG mapping and report filename behavior while ensuring the expression
is treated as runtime data rather than shell code.
Source: Linters/SAST tools
|
|
||
| #if DEBUG | ||
| extension MobileIrohRuntimeComposition: CmxIrohDebugSettingsControlling { | ||
| public func setIrohDebugTransportVerificationMode( | ||
| _ mode: CmxIrohTransportVerificationMode | ||
| ) async throws { | ||
| guard transportVerificationMode != mode else { return } | ||
| guard let debugDefaults else { throw SettingsError.unavailable } | ||
|
|
||
| debugDefaults.set( | ||
| mode.rawValue, | ||
| forKey: CmxIrohTransportVerificationMode.debugDefaultsKey | ||
| ) | ||
| transportVerificationMode = mode | ||
| publishIrohSettingsUpdate() | ||
|
|
||
| guard let accountID = observedAccountID ?? activeAccountID else { return } | ||
| await scheduleReconcile( | ||
| targetAccountID: accountID, | ||
| eraseAccountState: false, | ||
| restartActiveRuntime: true | ||
| ).value | ||
| } | ||
| } | ||
| #endif |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
Move the #if DEBUG conformance into a dedicated debug file.
This whole extension is debug-only and inlined in the main production type. As per coding guidelines, "A genuinely unavoidable debug-only facility must be isolated in a dedicated debug file or folder, rather than inlined into the main production type." Consider extracting it to MobileIrohRuntimeComposition+DebugSettings.swift (also aligning with the TypeName+*.swift extension convention).
🤖 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 `@ios/cmuxPackage/Sources/cmuxFeature/MobileIrohRuntimeComposition.swift`
around lines 2213 - 2237, Move the DEBUG-only CmxIrohDebugSettingsControlling
extension for MobileIrohRuntimeComposition into a dedicated
MobileIrohRuntimeComposition+DebugSettings.swift file, preserving
setIrohDebugTransportVerificationMode behavior and its `#if` DEBUG guard. Remove
the inlined extension and conditional block from the main production file.
Source: Coding guidelines
| @@ -1,3 +1,4 @@ | |||
| internal import CmuxMobilePairedMac | |||
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Canonicalize the mapped device identifier to ensure reliable deletion.
The incoming pairingID strings from the server might contain legacy uncanonicalized device IDs. Since MobilePairedMac.pairingID(macDeviceID:instanceTag:) concatenates its inputs without altering them, identity.macDeviceID must be passed through cmxCanonicalDeviceID here; otherwise, the resulting local deletion keys won't match the new canonical format in the local store, leaving stale records orphaned.
Please import CMUXMobileCore and apply canonicalization to the device ID segment.
🐛 Proposed fix
+internal import CMUXMobileCore
internal import CmuxMobilePairedMac
struct PairedMacBackupListResponse: Decodable {
// ...
.map { pairingID in
let identity = MobilePairedMac.pairingIdentity(from: pairingID)
return MobilePairedMac.pairingID(
- macDeviceID: identity.macDeviceID,
+ macDeviceID: cmxCanonicalDeviceID(identity.macDeviceID),
instanceTag: identity.instanceTag
)
}Also applies to: 24-30
🤖 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
`@Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/PairedMacBackupListResponse.swift`
at line 1, Import CMUXMobileCore and update the mapped device identifier used to
construct each backup pairing ID, applying cmxCanonicalDeviceID to
identity.macDeviceID before passing it to MobilePairedMac.pairingID. Preserve
the existing instance-tag mapping and ensure all affected mapping paths use the
canonicalized device ID so local deletion keys match the store.
| ALTER TABLE "iroh_endpoint_bindings" | ||
| ADD COLUMN "direct_port_v4" integer; | ||
| --> statement-breakpoint | ||
| ALTER TABLE "iroh_endpoint_bindings" | ||
| ADD COLUMN "direct_port_v6" integer; | ||
| --> statement-breakpoint | ||
| ALTER TABLE "iroh_endpoint_bindings" | ||
| ADD CONSTRAINT "iroh_endpoint_bindings_direct_port_v4_check" | ||
| CHECK ("direct_port_v4" IS NULL OR "direct_port_v4" BETWEEN 1 AND 65535); | ||
| --> statement-breakpoint | ||
| ALTER TABLE "iroh_endpoint_bindings" | ||
| ADD CONSTRAINT "iroh_endpoint_bindings_direct_port_v6_check" | ||
| CHECK ("direct_port_v6" IS NULL OR "direct_port_v6" BETWEEN 1 AND 65535); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win
Use NOT VALID when adding constraints to avoid table locking.
Adding a CHECK constraint without NOT VALID acquires an ACCESS EXCLUSIVE lock and performs a full table scan, blocking all writes to iroh_endpoint_bindings until the scan finishes. This can cause severe production availability issues.
Append NOT VALID to the constraint definitions here, and execute a VALIDATE CONSTRAINT command in a subsequent statement or migration (similar to how the iroh_relay_status_validation migration is structured).
🔒️ Proposed fix to add NOT VALID
ALTER TABLE "iroh_endpoint_bindings"
ADD COLUMN "direct_port_v6" integer;
--> statement-breakpoint
ALTER TABLE "iroh_endpoint_bindings"
ADD CONSTRAINT "iroh_endpoint_bindings_direct_port_v4_check"
- CHECK ("direct_port_v4" IS NULL OR "direct_port_v4" BETWEEN 1 AND 65535);
+ CHECK ("direct_port_v4" IS NULL OR "direct_port_v4" BETWEEN 1 AND 65535) NOT VALID;
--> statement-breakpoint
ALTER TABLE "iroh_endpoint_bindings"
ADD CONSTRAINT "iroh_endpoint_bindings_direct_port_v6_check"
- CHECK ("direct_port_v6" IS NULL OR "direct_port_v6" BETWEEN 1 AND 65535);
+ CHECK ("direct_port_v6" IS NULL OR "direct_port_v6" BETWEEN 1 AND 65535) NOT VALID;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ALTER TABLE "iroh_endpoint_bindings" | |
| ADD COLUMN "direct_port_v4" integer; | |
| --> statement-breakpoint | |
| ALTER TABLE "iroh_endpoint_bindings" | |
| ADD COLUMN "direct_port_v6" integer; | |
| --> statement-breakpoint | |
| ALTER TABLE "iroh_endpoint_bindings" | |
| ADD CONSTRAINT "iroh_endpoint_bindings_direct_port_v4_check" | |
| CHECK ("direct_port_v4" IS NULL OR "direct_port_v4" BETWEEN 1 AND 65535); | |
| --> statement-breakpoint | |
| ALTER TABLE "iroh_endpoint_bindings" | |
| ADD CONSTRAINT "iroh_endpoint_bindings_direct_port_v6_check" | |
| CHECK ("direct_port_v6" IS NULL OR "direct_port_v6" BETWEEN 1 AND 65535); | |
| ALTER TABLE "iroh_endpoint_bindings" | |
| ADD COLUMN "direct_port_v4" integer; | |
| --> statement-breakpoint | |
| ALTER TABLE "iroh_endpoint_bindings" | |
| ADD COLUMN "direct_port_v6" integer; | |
| --> statement-breakpoint | |
| ALTER TABLE "iroh_endpoint_bindings" | |
| ADD CONSTRAINT "iroh_endpoint_bindings_direct_port_v4_check" | |
| CHECK ("direct_port_v4" IS NULL OR "direct_port_v4" BETWEEN 1 AND 65535) NOT VALID; | |
| --> statement-breakpoint | |
| ALTER TABLE "iroh_endpoint_bindings" | |
| ADD CONSTRAINT "iroh_endpoint_bindings_direct_port_v6_check" | |
| CHECK ("direct_port_v6" IS NULL OR "direct_port_v6" BETWEEN 1 AND 65535) NOT VALID; |
🧰 Tools
🪛 Squawk (2.59.0)
[warning] 2-2: Using 32-bit integer fields can result in hitting the max int limit. Use 64-bit integer values instead to prevent hitting this limit.
(prefer-bigint-over-int)
[warning] 5-5: Using 32-bit integer fields can result in hitting the max int limit. Use 64-bit integer values instead to prevent hitting this limit.
(prefer-bigint-over-int)
[warning] 8-9: By default new constraints require a table scan and block writes to the table while that scan occurs. Use NOT VALID with a later VALIDATE CONSTRAINT call.
(constraint-missing-not-valid)
[warning] 12-13: By default new constraints require a table scan and block writes to the table while that scan occurs. Use NOT VALID with a later VALIDATE CONSTRAINT call.
(constraint-missing-not-valid)
🤖 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 `@web/db/migrations/20260719120000_iroh_direct_ports/migration.sql` around
lines 1 - 13, Update the direct_port_v4 and direct_port_v6 CHECK constraints in
this migration to use NOT VALID when added, avoiding the blocking table scan.
Add subsequent VALIDATE CONSTRAINT statements, or a follow-up migration matching
the iroh_relay_status_validation pattern, so both constraints are eventually
validated.
Source: Linters/SAST tools
Summary
Verification
Need help on this PR? Tag
/codesmithwith what you need. Autofix is disabled.Summary by cubic
Make authenticated Iroh the default iOS transport with zero-touch same-account discovery. Adds managed/custom relay control, signed direct UDP ports, stronger lifecycle/diagnostics, and a simulator release gate.
New Features
CmxTransportSessionPurpose.CMUXMobileCore, pairing, backup/restore, discovery, and runtime configs; paired‑Mac store v7 migrates and dedupes case‑only aliases.CmuxMobileShellReleaseGateSupportand a GitHub workflow to validate control, terminal, workspace, events, notifications, chat, and artifacts.Bug Fixes
iroh-ffito1.0.2-cmux.3.Written for commit 0470a97. Summary will update on new commits.
Summary by CodeRabbit