Skip to content

fix(ci): make iOS post-login E2E hermetic - #486

Merged
jason931225 merged 25 commits into
mainfrom
codex/ios-ci-hermetic-selfhost
Jul 23, 2026
Merged

fix(ci): make iOS post-login E2E hermetic#486
jason931225 merged 25 commits into
mainfrom
codex/ios-ci-hermetic-selfhost

Conversation

@jason931225

@jason931225 jason931225 commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • replace the secret-dependent/all-skip iOS UI workflow with a hermetic real-backend XCUITest gate on fixed GitHub-hosted macos-15
  • build checksum-pinned PostgreSQL 18.4 and the exact candidate Rust backend under one owned runner-temp root
  • mint a fresh one-use OTP session per 12-minute test-class shard, below the 15-minute access-token TTL
  • add production rotating refresh-token handling with serialized refresh, delete-before-use persistence, stale-401 arbitration, and truthful invalidation/logout failure state
  • prove post-login work-order, location-consent, messenger durability, camera dismissal, exact Korean validation, and strict accessibility behavior without skip/fallback branches
  • aggregate structured .xcresult evidence against the source-discovered XCTest set, scan retained artifacts for raw session material, then prove identity-aware teardown

Trust and portability boundary

Public/untrusted pull-request code runs only on GitHub-hosted macOS. It is never routed to a reusable self-hosted runner. The workflow's backend, PostgreSQL, session, paths, and cleanup contracts are portable seams; a future self-hosted/Oyatie Cloud lane must supply a separately governed ephemeral/JIT runner with teardown attestation rather than weakening this gate. Apple distribution signing/TestFlight remains a separate release boundary.

Fail-closed evidence

  • 31 Node mutation/unit tests and 18 iOS workflow contract checks
  • exact Xcode 16.4 build 16F6, iOS 18.5 runtime, XcodeGen 2.46.0, PostgreSQL 18.4
  • zero external backend/session secrets, XCTSkip, optional fork suite, or conditional accessibility path
  • exact per-shard test-set aggregation with no missing, duplicate, skipped, failed, or errored tests
  • raw OTP/access/refresh artifact scan before upload
  • backend/PostgreSQL/Simulator/job-root cleanup proof after upload

Local verification

  • swift build --package-path ios
  • swift test --package-path ios
  • swift run --package-path ios MaintenanceFieldCoreBehaviorTests
  • npm run check:swift
  • npm run test:api-client-contract:swift
  • npm run check:api-drift:swift
  • npm run check:ios-ui-test-fail-closed
  • iOS localization gate, actionlint, workflow YAML/shell parsing, Swift UI-test parse, production-hardening checks/tests, and git diff --check

Independent comprehensive review: APPROVE. Live Xcode/Simulator CI for this exact head remains the mandatory merge gate.

Closes #434

Summary by CodeRabbit

  • New Features
    • Added automatic session token refresh with single-flight recovery when authorization expires.
  • Bug Fixes
    • Improved login/logout and session-refresh failure handling, including a new user-facing message when session invalidation fails.
    • Improved camera capture behavior in automated real-session runs to reduce flakiness.
  • Quality
    • Updated accessibility identifiers for messenger rows and work-order status to improve UI test reliability.

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@jason931225, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 49 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 90319179-6621-4427-b82a-c1f529b84d93

📥 Commits

Reviewing files that changed from the base of the PR and between f390e92 and 0326ee5.

📒 Files selected for processing (36)
  • .github/workflows/ci.yml
  • .github/workflows/ios-ui-tests.yml
  • HANDOFF.md
  • backend/crates/workorder/rest/src/lib.rs
  • docs/CI-GATES.md
  • docs/parity-checklist.md
  • docs/program/console-program-ledger.md
  • e2e/harness/seed-mobile-ci.sql
  • ios/Config/App.xcconfig
  • ios/Config/README.md
  • ios/Sources/MaintenanceFieldApp/CameraCaptureView.swift
  • ios/Sources/MaintenanceFieldApp/FieldAccessibilityID.swift
  • ios/Sources/MaintenanceFieldApp/FieldViews.swift
  • ios/Sources/MaintenanceFieldApp/Info.plist
  • ios/Sources/MaintenanceFieldApp/Resources/ko.lproj/Localizable.strings
  • ios/Sources/MaintenanceFieldCore/APIGateway.swift
  • ios/Sources/MaintenanceFieldCore/PersistenceStores.swift
  • ios/Sources/MaintenanceFieldCoreBehaviorTests/main.swift
  • ios/UITests/AccessibilityAuditUITests.swift
  • ios/UITests/CameraCaptureUITests.swift
  • ios/UITests/DynamicTypeRuntimeUITests.swift
  • ios/UITests/FieldCriticalPathUITests.swift
  • ios/UITests/LoginValidationUITests.swift
  • ios/UITests/MessengerUITests.swift
  • ios/UITests/PreflightUITests.swift
  • ios/UITests/Support/FieldUITestCase.swift
  • ios/project.yml
  • scripts/check-android-e2e-fail-closed.mjs
  • scripts/check-android-e2e-fail-closed.test.mjs
  • scripts/check-i18n.mjs
  • scripts/check-ios-ui-test-fail-closed.mjs
  • scripts/check-ios-ui-test-fail-closed.test.mjs
  • scripts/check-production-hardening.test.mjs
  • scripts/patch-ios-xctestrun.py
  • scripts/verify-xcresult-test-results.mjs
  • scripts/verify-xcresult-test-results.test.mjs
📝 Walkthrough

Walkthrough

This PR makes iOS UI tests hermetic and fail-closed, adds deterministic fixtures and strict result/artifact checks, introduces token refresh and throwing session persistence, standardizes REST timestamps as RFC3339, and adds explicit E2E backend port-conflict handling.

Changes

iOS authentication and persistence

Layer / File(s) Summary
Refresh gateway and session persistence
ios/Sources/MaintenanceFieldCore/APIGateway.swift, ios/Sources/MaintenanceFieldCore/PersistenceStores.swift
Adds serialized token rotation, proactive refresh, one-time 401 recovery, bootstrap-client routing, and throwing Keychain/session-store operations.
Login/logout failure handling
ios/Sources/MaintenanceFieldCore/AuthRepository.swift, ios/Sources/MaintenanceFieldCore/LoginStateMachine.swift, ios/Sources/MaintenanceFieldApp/FieldViewModel.swift, ios/Sources/MaintenanceFieldApp/Resources/ko.lproj/Localizable.strings
Preserves authenticated state when invalidation fails, propagates logout errors, and displays the new session-invalidation message.
Authentication behavior tests
ios/Sources/MaintenanceFieldCoreBehaviorTests/main.swift
Covers refresh concurrency, retry behavior, token deletion failures, proactive expiry, passkey bootstrap, timestamp decoding, and session preservation.

Hermetic iOS UI-test gate

Layer / File(s) Summary
Workflow provisioning and cleanup
.github/workflows/ios-ui-tests.yml, ios/project.yml, ios/Config/*
Pins toolchain and Simulator inputs, builds PostgreSQL and the backend locally, provisions the seeder app, shards UI tests, scans artifacts, and proves cleanup.
Fail-closed validators and result verification
scripts/check-ios-ui-test-fail-closed.mjs, scripts/verify-xcresult-test-results.mjs, scripts/*test*.mjs, package.json
Adds mutation-tested workflow checks and exact XCTest result validation across shards.
Deterministic fixtures and session seeding
e2e/harness/seed-*.sql, ios/UITests/Support/*, ios/Sources/MaintenanceFieldUITestSeeder/*
Adds seeded work-order and messenger data, validates fixture IDs and tokens, and seeds sessions through a dedicated helper app.
UI critical paths and accessibility
ios/UITests/*, ios/Sources/MaintenanceFieldApp/FieldAccessibilityID.swift, ios/Sources/MaintenanceFieldApp/FieldViews.swift
Replaces conditional discovery and skips with seeded flows, validates persisted work-order, messenger, location, camera, login, and accessibility behavior, and adds identifiers for status and message rows.
Hermetic gate documentation
HANDOFF.md, docs/CI-GATES.md, docs/parity-checklist.md, docs/program/console-program-ledger.md
Documents pinned execution, deterministic sessions, strict result and artifact requirements, and cleanup verification.

E2E backend port handling

Layer / File(s) Summary
Port conflict policy and launcher tests
e2e/harness/boot-backend.sh, scripts/boot-backend-port-conflict.test.mjs, scripts/boot-ios-ui-backend.mjs, scripts/boot-ios-ui-backend.test.mjs
Adds explicit fail and reclaim modes, direct socket probing, a validated iOS backend launcher, and coverage for occupied ports, reclamation, environment validation, and exit propagation.

Backend timestamp contracts

Layer / File(s) Summary
RFC3339 serialization and validation
backend/crates/*/rest/src/lib.rs, backend/crates/*/Cargo.toml, backend/app/tests/*, backend/crates/workorder/rest/tests/*
Applies explicit RFC3339 codecs to REST timestamps and adds tests for fractional-second parsing, serialization, optional values, and mobile sync payloads.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.30% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: making iOS post-login E2E CI hermetic.
Linked Issues check ✅ Passed The workflow and supporting backend/seeding changes satisfy #434 by replacing secret-dependent runs with a dedicated hermetic real-session gate.
Out of Scope Changes check ✅ Passed The changes are tied to the hermetic iOS CI gate, session/backend seeding, or supporting docs/tests, with no clearly unrelated code standing out.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/ios-ci-hermetic-selfhost

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.

@jason931225
jason931225 force-pushed the codex/ios-ci-hermetic-selfhost branch from efcf451 to 979e637 Compare July 22, 2026 07:22
@jason931225

Copy link
Copy Markdown
Collaborator Author

The first real hosted Simulator run reached the exact-SHA backend setup and failed before Xcode because the source-built PostgreSQL 18.4 installation omitted the bundled pg_trgm module required by migrations 0020 and 0118.

Fixed in 979e637a by:

  • building/installing contrib/pg_trgm from the same checksum-pinned PostgreSQL 18.4 source tree;
  • load-testing CREATE EXTENSION / DROP EXTENSION before the expensive backend build;
  • extending the fail-closed workflow gate with mutation coverage for omitted, reordered, and commented-out commands.

Verification: exact PostgreSQL 18.4 source build + live create/drop smoke passed locally; 32 iOS CI contract/result tests, 19 fail-closed checks, actionlint, production hardening (250 checks + 97 Node + 18/16 Python), Swift build/behavior, i18n/parity, and independent code review all pass. New exact-head CI is the remaining proof.

@jason931225

Copy link
Copy Markdown
Collaborator Author

Follow-up database prerequisite closure at cff0850e654cade1788ae24114b7b69524d22768:

  • audited all backend migrations: the complete required extension set is pgcrypto (0021/0023) plus pg_trgm (0020/0118);
  • PostgreSQL 18.4 is now configured with OpenSSL, both selected contrib modules are built/installed, and both are create/drop load-tested under ON_ERROR_STOP=1 before the backend build;
  • fail-closed mutations cover omitted/commented SSL setup, each build/install/create/drop command, and load-before-start ordering;
  • local verification passed: 32 focused Node tests, 19 workflow checks, actionlint/YAML/bash syntax, 250 production-hardening checks, 97 Node + 18/16 Python hardening tests, canonical lock check, and an exact PostgreSQL 18.4 source-build smoke executing both digest() and similarity();
  • independent review: APPROVE, zero findings.

Exact-head hosted validation is running at https://github.com/jason931225/maintenance/actions/runs/29900569211.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🤖 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/ios-ui-tests.yml:
- Around line 41-43: Remove the “Cache Rust dependencies + build artifacts” step
using Swatinem/rust-cache because its CARGO_HOME and CARGO_TARGET_DIR contents
are deleted by the final rm -rf "$D"; alternatively, relocate those Cargo
directories outside "$D" before retaining the cache step.

In `@docs/CI-GATES.md`:
- Around line 680-684: Update the CI-GATES documentation paragraph describing
OTP and session-material handling to distinguish database storage from temporary
files: state that the database fixture stores only the SHA-256 OTP digest, while
plaintext OTP and access/refresh tokens are briefly written to mode-0700
runner-local files, masked and scanned, then deleted.

In `@ios/Sources/MaintenanceFieldApp/FieldViews.swift`:
- Around line 565-568: Update the accessibility identifiers for the
search-result and open-thread rows in the messenger view so they cannot collide
when the same message appears in both sections. Add a distinct dynamic
identifier to FieldAccessibilityID, mirror it in FieldUITestCase.AID, and use
the section-specific identifiers at the ForEach blocks around
MessengerMessageRow.

In `@ios/Sources/MaintenanceFieldCoreBehaviorTests/main.swift`:
- Around line 2205-2211: Update the jwt(expiration:) helper to be throwing by
removing try! and propagating JSONSerialization errors; then mark both jwt call
sites around the existing throws contexts to use try. Preserve the current JWT
header, payload, and signature formatting.

In `@ios/UITests/MessengerUITests.swift`:
- Around line 63-66: Add the offline-fallback string under the
`messenger_send_pending` key in the shared `KO` localization helper, then update
the assertion in `MessengerUITests` to retrieve that key instead of hardcoding
the Korean text. Preserve the existing negative assertion and failure message.

In `@ios/UITests/Support/FieldUITestCase.swift`:
- Around line 157-196: Remove the redundant UITestFixture.workOrderID wrapper
and update its call sites to invoke requiredID directly with the same key and
environment parameters. Keep requiredID as the shared validation path for all
fixture identifiers.

In `@scripts/boot-backend-port-conflict.test.mjs`:
- Around line 1-97: Add coverage for the default reclaim behavior by creating a
stale loopback listener without SO_REUSEADDR, invoking boot() with
E2E_PORT_CONFLICT_MODE set to reclaim and the marker app configured, then assert
the boot succeeds, the stale listener exits, the port becomes available, and the
marker confirms backend startup.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: bc60f034-d163-47ce-bb7e-0be3b3197e71

📥 Commits

Reviewing files that changed from the base of the PR and between 87241f3 and 979e637.

📒 Files selected for processing (38)
  • .github/workflows/ios-ui-tests.yml
  • HANDOFF.md
  • docs/CI-GATES.md
  • docs/parity-checklist.md
  • docs/program/console-program-ledger.md
  • e2e/harness/boot-backend.sh
  • e2e/harness/seed-mech.sql
  • e2e/harness/seed-mobile-ci.sql
  • ios/Config/App.xcconfig
  • ios/Config/README.md
  • ios/Sources/MaintenanceFieldApp/AppContainer.swift
  • ios/Sources/MaintenanceFieldApp/FieldAccessibilityID.swift
  • ios/Sources/MaintenanceFieldApp/FieldViewModel.swift
  • ios/Sources/MaintenanceFieldApp/FieldViews.swift
  • ios/Sources/MaintenanceFieldApp/Resources/ko.lproj/Localizable.strings
  • ios/Sources/MaintenanceFieldCore/APIGateway.swift
  • ios/Sources/MaintenanceFieldCore/AuthRepository.swift
  • ios/Sources/MaintenanceFieldCore/LoginStateMachine.swift
  • ios/Sources/MaintenanceFieldCore/PersistenceStores.swift
  • ios/Sources/MaintenanceFieldCoreBehaviorTests/main.swift
  • ios/UITests/AccessibilityAuditUITests.swift
  • ios/UITests/CameraCaptureUITests.swift
  • ios/UITests/FieldAccessibilityIDParityTests.swift
  • ios/UITests/FieldCriticalPathUITests.swift
  • ios/UITests/LoginValidationUITests.swift
  • ios/UITests/MessengerUITests.swift
  • ios/UITests/PreflightUITests.swift
  • ios/UITests/Support/FieldUITestCase.swift
  • ios/UITests/Support/RealBackendSession.swift
  • ios/UITests/Support/RealSessionSeed.swift
  • ios/project.yml
  • package.json
  • scripts/boot-backend-port-conflict.test.mjs
  • scripts/check-i18n.mjs
  • scripts/check-ios-ui-test-fail-closed.mjs
  • scripts/check-ios-ui-test-fail-closed.test.mjs
  • scripts/verify-xcresult-test-results.mjs
  • scripts/verify-xcresult-test-results.test.mjs

Comment thread .github/workflows/ios-ui-tests.yml Outdated
Comment thread docs/CI-GATES.md Outdated
Comment thread ios/Sources/MaintenanceFieldApp/FieldViews.swift
Comment thread ios/Sources/MaintenanceFieldCoreBehaviorTests/main.swift Outdated
Comment thread ios/UITests/MessengerUITests.swift
Comment thread ios/UITests/Support/FieldUITestCase.swift
Comment thread scripts/boot-backend-port-conflict.test.mjs
@jason931225

Copy link
Copy Markdown
Collaborator Author

Implemented and locally verified the final iOS CI fixes at exact head 2b07cde57d84846cbe80dfe356989337fcc29825.

Root cause of the remaining simulator failure: the candidate backend bound loopback correctly, but the WebAuthn relying-party origin/ID used the IP literal 127.0.0.1; webauthn-rs requires a valid effective domain. The workflow now continues to bind 127.0.0.1 while the structured launcher supplies http://localhost:<port> and localhost for WebAuthn.

Hardening included:

  • pinned Node 24.16.0 from the setup-node toolcache with startup-injection variables cleared and locked;
  • structured, exact-environment backend launcher with no shell interpretation or wrapper-secret forwarding;
  • SHA-256 seals for the launcher and exact backend execution step;
  • mutation tests against alternate Node and shell launch paths, including post-PID insertion;
  • all seven review findings addressed in atomic commits, including removing the ineffective Rust cache.

Fresh evidence: 39/39 focused Node tests, 21/21 iOS contract checks, actionlint, YAML parse, bash syntax for all workflow run blocks, iOS string/i18n gates, Swift build and core behavior tests, 250 production-hardening checks, 97 Node production-hardening tests, 34 Python tests, package-lock integrity, and two independent APPROVE reviews.

@jason931225

Copy link
Copy Markdown
Collaborator Author

CI follow-up: main advanced during this run when #484 merged release 0.2.0. Its web/package.json change left the root lock entry at 0.1.65, so GitHub's synthetic merge failed check:package-lock. I merged current main without rewriting PR history and committed the single generated lock correction in 115ffcbf. Independent diagnosis reproduced the exact one-line mismatch. Fresh local package-lock, root-workspace, iOS contract, Swift, and production-hardening suites are green.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 (2)
ios/Sources/MaintenanceFieldCore/PersistenceStores.swift (1)

162-181: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Propagate rollback failures from restore(...). If one delete succeeds and a later restore write fails, the caller only sees the original delete error while the store can be left partially deleted. Surface the rollback failure too, and add coverage for delete failure followed by restore-write failure.

🤖 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/Sources/MaintenanceFieldCore/PersistenceStores.swift` around lines 162 -
181, Update the private static restore(...) method to propagate write failures
instead of suppressing them with try?, and make its callers preserve/report both
the original delete error and any rollback failure. Add coverage for a delete
failure followed by a restore-write failure, asserting that the surfaced error
includes the rollback failure.
ios/UITests/FieldAccessibilityIDParityTests.swift (1)

3-43: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Class name no longer matches its test content.

FieldAccessibilityIDParityTests now only tests hermetic-runner environment parsing (RealBackendSession, UITestFixture) — AID string parity moved to the host-side check-ios-ui-test-fail-closed.mjs gate per the updated doc comment in FieldUITestCase.swift. Keeping the old name here is misleading for anyone searching for AID-parity coverage.

Consider renaming the class/file (e.g. HermeticRunnerConfigurationTests) to reflect its actual purpose.

🤖 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/UITests/FieldAccessibilityIDParityTests.swift` around lines 3 - 43,
Rename the test class FieldAccessibilityIDParityTests and its file to reflect
their current purpose, such as HermeticRunnerConfigurationTests, while
preserving testHermeticRunnerConfigurationRequiresMintedPairAndNamedFixtures and
its existing assertions unchanged.
🤖 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 `@ios/Sources/MaintenanceFieldCore/PersistenceStores.swift`:
- Around line 284-289: Update the deferred cleanup in
KeychainAccessGroup.resolveShared to capture the status returned by
SecItemDelete. Treat errSecItemNotFound as expected, but log or assert when
deletion returns any other failure status so probe-item cleanup problems are
visible.

---

Outside diff comments:
In `@ios/Sources/MaintenanceFieldCore/PersistenceStores.swift`:
- Around line 162-181: Update the private static restore(...) method to
propagate write failures instead of suppressing them with try?, and make its
callers preserve/report both the original delete error and any rollback failure.
Add coverage for a delete failure followed by a restore-write failure, asserting
that the surfaced error includes the rollback failure.

In `@ios/UITests/FieldAccessibilityIDParityTests.swift`:
- Around line 3-43: Rename the test class FieldAccessibilityIDParityTests and
its file to reflect their current purpose, such as
HermeticRunnerConfigurationTests, while preserving
testHermeticRunnerConfigurationRequiresMintedPairAndNamedFixtures and its
existing assertions unchanged.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ddcd4a36-9234-463e-b950-eb1ca46798df

📥 Commits

Reviewing files that changed from the base of the PR and between 979e637 and ef61fad.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (26)
  • .github/workflows/ios-ui-tests.yml
  • docs/CI-GATES.md
  • ios/Config/App.xcconfig
  • ios/Config/MaintenanceFieldApp.entitlements
  • ios/Config/MaintenanceFieldUITestSeeder.entitlements
  • ios/Config/MaintenanceFieldUITests.entitlements
  • ios/Config/README.md
  • ios/Sources/MaintenanceFieldApp/FieldAccessibilityID.swift
  • ios/Sources/MaintenanceFieldApp/FieldViews.swift
  • ios/Sources/MaintenanceFieldCore/PersistenceStores.swift
  • ios/Sources/MaintenanceFieldCoreBehaviorTests/main.swift
  • ios/Sources/MaintenanceFieldUITestSeeder/UITestSeederApp.swift
  • ios/UITests/FieldAccessibilityIDParityTests.swift
  • ios/UITests/FieldCriticalPathUITests.swift
  • ios/UITests/LoginValidationUITests.swift
  • ios/UITests/MessengerUITests.swift
  • ios/UITests/PreflightUITests.swift
  • ios/UITests/Support/FieldUITestCase.swift
  • ios/UITests/Support/RealSessionSeed.swift
  • ios/project.yml
  • package.json
  • scripts/boot-backend-port-conflict.test.mjs
  • scripts/boot-ios-ui-backend.mjs
  • scripts/boot-ios-ui-backend.test.mjs
  • scripts/check-ios-ui-test-fail-closed.mjs
  • scripts/check-ios-ui-test-fail-closed.test.mjs
💤 Files with no reviewable changes (1)
  • ios/Config/MaintenanceFieldUITests.entitlements

Comment thread ios/Sources/MaintenanceFieldCore/PersistenceStores.swift Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 (3)
ios/Sources/MaintenanceFieldCore/APIGateway.swift (1)

182-187: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reject an empty rotated access token.

Only refreshToken is validated here. A response with accessToken == "" is saved and promoted, causing retries to send Bearer instead of invalidating the session. Treat either empty token as invalidSession, and add the corresponding behavior case.

Proposed fix
 case let .ok(response):
     let tokens = try response.body.json
-    guard let refreshToken = tokens.refreshToken, !refreshToken.isEmpty else {
+    guard !tokens.accessToken.isEmpty,
+          let refreshToken = tokens.refreshToken,
+          !refreshToken.isEmpty
+    else {
         throw SessionRefreshError.invalidSession
     }
🤖 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/Sources/MaintenanceFieldCore/APIGateway.swift` around lines 182 - 187,
Update the .ok response handling in APIGateway to validate that both
tokens.accessToken and tokens.refreshToken are non-empty before constructing
AuthTokens; throw SessionRefreshError.invalidSession when either is missing or
empty, and add the corresponding behavior test case for an empty access token.
ios/UITests/Support/FieldUITestCase.swift (1)

223-256: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add the explicit deinit required by the project's SwiftLint config.

FieldUITestCase (Line 232) trips the required_deinit opt-in rule ("Classes should have an explicit deinit method"), which the project has evidently enabled. Aside from the lint gate, the rest of the setup/teardown refactor (unconditional seed in setUpWithError, gated clear in tearDownWithError, appearance reset) looks correct.

🛠️ Proposed fix
 `@MainActor`
 class FieldUITestCase: XCTestCase {
     var app: XCUIApplication!
     private(set) var seededSession = false
+
+    deinit {}
🤖 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/UITests/Support/FieldUITestCase.swift` around lines 223 - 256, Add an
explicit deinit to FieldUITestCase to satisfy the enabled SwiftLint
required_deinit rule, leaving the existing setup and teardown behavior
unchanged.

Source: Linters/SAST tools

ios/UITests/LoginValidationUITests.swift (1)

9-17: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicates FieldUITestCase.launchSignedOutApp/launchApp wiring.

This setup manually re-implements exactly what FieldUITestCase.launchSignedOutApp() (clear + LaunchLocale.arguments + MAINTENANCE_API_BASE_URL + launch) already does. LoginValidationUITests can't simply subclass FieldUITestCase because its setUpWithError unconditionally seeds a session, but the launch-environment wiring itself (env key, locale args) is now duplicated in three places across this cohort (here, and twice in PreflightUITests.swift), so a future change to the env var name or locale args risks silently diverging in one of them.

♻️ Suggested extraction
+extension XCUIApplication {
+    /// Shared launch wiring for the runner's isolated local backend.
+    static func mntFreshInstance(_ presentation: Presentation = .standard) throws -> XCUIApplication {
+        let app = XCUIApplication()
+        app.launchArguments += LaunchLocale.arguments
+        app.launchArguments += presentation.launchArguments
+        app.launchEnvironment["MAINTENANCE_API_BASE_URL"] = try RealBackendSession.baseURL()
+        return app
+    }
+}
+
 override func setUpWithError() throws {
     try super.setUpWithError()
     continueAfterFailure = false
     try RealSessionSeed.clear()
-    app = XCUIApplication()
-    app.launchArguments += LaunchLocale.arguments
-    app.launchEnvironment["MAINTENANCE_API_BASE_URL"] = try RealBackendSession.baseURL()
-    app.launch()
+    app = try XCUIApplication.mntFreshInstance()
+    app.launch()
 }

FieldUITestCase.launchApp would then delegate to the same helper.

🤖 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/UITests/LoginValidationUITests.swift` around lines 9 - 17, Extract the
shared app-launch wiring for locale arguments, MAINTENANCE_API_BASE_URL, and
launch into a reusable helper near FieldUITestCase, and have
FieldUITestCase.launchApp and launchSignedOutApp delegate to it. Update
LoginValidationUITests.setUpWithError and the duplicated PreflightUITests setup
paths to use that helper while preserving their distinct session-seeding
behavior.
🤖 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 `@backend/crates/workorder/rest/src/lib.rs`:
- Around line 5009-5113: Extract shared fixture builders for the duplicated
EquipmentSummary, NamedEntity, and AssignmentSummary literals used by
work_order_list_item_serializes_timestamps_as_rfc3339_strings and this timestamp
test. Update both tests to call the helpers, preserving their current fixture
values and allowing future nested-field changes to be maintained in one place.

---

Outside diff comments:
In `@ios/Sources/MaintenanceFieldCore/APIGateway.swift`:
- Around line 182-187: Update the .ok response handling in APIGateway to
validate that both tokens.accessToken and tokens.refreshToken are non-empty
before constructing AuthTokens; throw SessionRefreshError.invalidSession when
either is missing or empty, and add the corresponding behavior test case for an
empty access token.

In `@ios/UITests/LoginValidationUITests.swift`:
- Around line 9-17: Extract the shared app-launch wiring for locale arguments,
MAINTENANCE_API_BASE_URL, and launch into a reusable helper near
FieldUITestCase, and have FieldUITestCase.launchApp and launchSignedOutApp
delegate to it. Update LoginValidationUITests.setUpWithError and the duplicated
PreflightUITests setup paths to use that helper while preserving their distinct
session-seeding behavior.

In `@ios/UITests/Support/FieldUITestCase.swift`:
- Around line 223-256: Add an explicit deinit to FieldUITestCase to satisfy the
enabled SwiftLint required_deinit rule, leaving the existing setup and teardown
behavior unchanged.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 319c9853-a4ec-4bea-8b70-2561a93b498b

📥 Commits

Reviewing files that changed from the base of the PR and between ef61fad and 7f7c950.

⛔ Files ignored due to path filters (1)
  • backend/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (17)
  • .github/workflows/ios-ui-tests.yml
  • backend/crates/compliance/rest/Cargo.toml
  • backend/crates/compliance/rest/src/lib.rs
  • backend/crates/messenger/application/Cargo.toml
  • backend/crates/messenger/application/src/lib.rs
  • backend/crates/workorder/rest/src/lib.rs
  • ios/Sources/MaintenanceFieldApp/FieldAccessibilityID.swift
  • ios/Sources/MaintenanceFieldApp/FieldViews.swift
  • ios/Sources/MaintenanceFieldCore/APIGateway.swift
  • ios/Sources/MaintenanceFieldCoreBehaviorTests/main.swift
  • ios/UITests/FieldCriticalPathUITests.swift
  • ios/UITests/LoginValidationUITests.swift
  • ios/UITests/PreflightUITests.swift
  • ios/UITests/Support/FieldUITestCase.swift
  • ios/UITests/Support/RealSessionSeed.swift
  • scripts/check-ios-ui-test-fail-closed.mjs
  • scripts/check-ios-ui-test-fail-closed.test.mjs
💤 Files with no reviewable changes (2)
  • ios/Sources/MaintenanceFieldApp/FieldViews.swift
  • ios/Sources/MaintenanceFieldApp/FieldAccessibilityID.swift

Comment thread backend/crates/workorder/rest/src/lib.rs
@jason931225
jason931225 merged commit 3740621 into main Jul 23, 2026
18 of 19 checks passed
jason931225 pushed a commit that referenced this pull request Jul 23, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.2.1](v0.2.0...v0.2.1)
(2026-07-23)


### Bug Fixes

* **ci:** make iOS post-login E2E hermetic
([#486](#486))
([3740621](3740621))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
@jason931225
jason931225 deleted the codex/ios-ci-hermetic-selfhost branch August 3, 2026 13:45
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.

Provision MNT_UITEST_* secrets + dedicated UI-test backend for real iOS post-login suite

1 participant