diff --git a/.github/workflows/mobile-ios.yml b/.github/workflows/mobile-ios.yml new file mode 100644 index 000000000..ce349fea5 --- /dev/null +++ b/.github/workflows/mobile-ios.yml @@ -0,0 +1,47 @@ +name: Mobile iOS SOURCE validation (no native build) + +on: + pull_request: + paths: + - '.github/workflows/mobile-ios.yml' + - 'packages/mobile-app/**' + - 'packages/ui/src/lib/i18n/messages/**/remoteAccess.ts' + - 'packages/ui/src/styles/tokens.css' + - 'packages/server/src/server/routes/auth-pages/login.html' + - 'package.json' + - 'package-lock.json' + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: mobile-ios-source-${{ github.ref }} + cancel-in-progress: true + +jobs: + source-validation: + name: SOURCE validation only — not an iOS app build + runs-on: ubuntu-24.04 + timeout-minutes: 20 + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: '24' + - name: Install locked source dependencies without desktop lifecycle scripts + run: npm ci --ignore-scripts --workspace @codenomad/mobile-app --include-workspace-root --no-audit --no-fund + - name: Check launcher and source security contracts (no native execution) + run: npm run typecheck --workspace @codenomad/mobile-app + - name: Run offline source regressions and real launcher import-graph check + run: npm run test --workspace @codenomad/mobile-app + - name: Build bundled web launcher only + run: npm run build --workspace @codenomad/mobile-app + - name: Verify vendored iOS ABI identities without Apple tooling + run: npm run ios:verify --workspace @codenomad/mobile-app + - name: Select Rust for pure policy tests only + run: rustup toolchain install 1.94.0 --profile minimal + - name: Test locked URL and authority policy without Tauri or GTK compilation + run: cargo +1.94.0 test --locked --manifest-path packages/mobile-app/src-tauri/policy/Cargo.toml diff --git a/packages/mobile-app/IOS_HANDOFF.md b/packages/mobile-app/IOS_HANDOFF.md new file mode 100644 index 000000000..b459ac61b --- /dev/null +++ b/packages/mobile-app/IOS_HANDOFF.md @@ -0,0 +1,123 @@ +# iOS source preparation — handoff to Shantur + +**Preparation only, not a delivered or device-qualified mobile application.** +Local iOS experimentation is stopped. This handoff does not authorize another +Mac/VM/SSH, runtime download, resolver, sandbox or diagnostic attempt. + +## What exists / what does not + +- The common Android/iOS companion is a bundled launcher plus one top-level + **HTTPS server-hosted UI**, not a mobile desktop backend. No Node/OpenCode/Git + runs on the phone, no remote native IPC capability is granted, and there is no + TLS bypass. Native endpoint/navigation/recovery authority must remain separate + from hosted DOM. Return is not logout: cookies/storage may persist; it does not + cancel server work or guarantee preservation of unsaved browser-only drafts. +- Source contracts, launcher typecheck/build and pure URL/authority tests exist. + Android debug artifacts remain **uninstalled/device-unvalidated** in this + handoff; a successful debug build is not mobile acceptance or store readiness. +- Historical SDK **26.2**, Swift language mode **5** compilation produced genuine + SwiftRs/Tauri/Recovery modules and objects for ARM64 iOS 15 device and Intel + iOS 15 simulator targets. Corrected and original-source LLVM IR controls were + checked. **Object compilation is not a linked application or executed FFI.** +- The separate mobile Cargo workspace vendors **Tauri 2.12.1** with a corrected + `Int32` request ID and `(Int32, Int32, CChar pointer)` callback ABI matching + Rust `i32`/C `int`. Preserve `ios:verify`, the vendored pre-link hash/version + guard, the app's owned `DEP_TAURI_IOS_LIBRARY_PATH` check, licenses, receipts, + exact Cargo lock and SwiftRs commit pin. See [IOS_ABI.md](IOS_ABI.md). +- FFI ID extremes, success/reject paths, async ownership/retain/Drop and + autorelease-pool lifetimes remain **unqualified**. SwiftRs Array escaped-pointer + warnings are real and unresolved; the command ABI fix does not fix them. +- No simulator runtime **26.2 / 23C54 Universal** is installed. No generated + Xcode app project, linked `.app`, IPA or native WKWebView execution is proven. + Complete locked native dependency resolution also remains outstanding. + +## Stopped attempts and evidence + +The two authorized Apple transport attempts were consumed; **no third +attempt** is authorized. An observed sandbox `SIGABRT` has an **unknown cause**; +do not infer a fix or broaden/repeat the profile. Required confinement of the +native Swift/linker cache has not been demonstrated. These are open gates, not +reasons to disable containment or use shared/global caches. + +Portable provenance is in `IOS_ABI.md` and the committed vendor receipts: +`tauri-2.12.1-upstream.json`, `tauri-2.12.1-local.json`, +`tauri-2.12.1-codenomad.patch` and `codenomad-ios-abi.sha256`. +The patch SHA256 is +`7416bbd978eb088706ad61439b5ac06a2632512c13aa7ebb4794c4138c9f300e`. +Historical successful component evidence is named `RESULT-r2.json` and +`SWIFTPM-PIN-r3.json`; private transport/crash/cache receipts remain with the +coordinator. They are not CI artifacts. Do not publish raw crashes, download +ledgers, credentials, machine paths or guest network identifiers. + +## Source-only CI and reproducible source checks + +[mobile-ios.yml](../../.github/workflows/mobile-ios.yml) runs on scoped pull +requests or manual dispatch on Linux: locked workspace npm installation with +scripts disabled, source tests/typecheck, bundled launcher build, ABI receipt +verification and locked pure Rust policy tests. It does not compile Tauri/GTK, +Swift or an iOS application, install Apple tooling, sign, export or upload a +binary. Actions are pinned to immutable commits and permissions are read-only; +no signing/account secrets are used. npm/Rust dependency acquisition in CI is +not an Apple download or a claim of a fully offline runner. + +From the repository root with existing dependencies/toolchain: + +```sh +npm run typecheck --workspace @codenomad/mobile-app +npm run test --workspace @codenomad/mobile-app +npm run build --workspace @codenomad/mobile-app +npm run ios:verify --workspace @codenomad/mobile-app +cargo test --offline --locked --manifest-path packages/mobile-app/src-tauri/policy/Cargo.toml +``` + +Preparation source checks passed locally: **19 JavaScript tests passed**, one +existing JDK-dependent Android XML test skipped at that stage. Latest-dev +Android integration subsequently passed **19/19 without a JDK skip**; the added +iOS workflow guard is checked separately during handoff integration. +Typecheck, launcher build and the verifier's **142 upstream files / 13 native guard inputs** passed. Pure Rust +policy tests passed **4/4**. The local explicit `+1.94.0` invocation unexpectedly +auto-installed that Rust toolchain: Cargo's `--offline` does **not** disable +rustup acquisition. Use an already installed toolchain for further local source +checks; do not treat these results as a fully offline toolchain setup. + +Local source checks and historical compiler receipts are separate from GitHub +execution. **This iOS workflow has not run on GitHub during preparation.** Its +static contract regression is `tests/ios-workflow.test.ts`. No native iOS local +test is requested by the commands above. A CI pass cannot clear native gates. + +## Shantur's next gates (in order, with fresh operator approval) + +1. Review source, security contracts, dependency receipts and CI results first. + Review [README.md](README.md), [IOS_ABI.md](IOS_ABI.md), + [DEPENDENCIES.md](DEPENDENCIES.md) and + [mobile delivery gates](../../dev-docs/MOBILE_DELIVERY.md). The server remains + **undeployed and not approved for public Internet exposure**. Preview + containment uses reply-from's locked Undici **5.29.0** with `pipelining: 0`; + that narrow mitigation is not general server security approval. The retained + workspace audit records **45 findings** (2 critical, 36 high, 4 moderate, + 3 low), not a fresh audit or a zero-vulnerability claim. Authentication, + origin/CSRF, rate limiting, proxy trust and preview isolation still need review. +2. Prefer a **supported, owned real Mac/device builder** with full Xcode, + CocoaPods, compatible Node/Rust and iOS targets, approved toolchain/license + provenance and adequate resources. Agree an **unsigned/non-distribution build + contract** and process-local toolchain/cache/output isolation before native + work; preserve ABI guards and fail closed if confinement cannot be met. + This is not permission to resume the exhausted experimental route. + **Do not run the current `ios:build` script as a default validation command:** + it requests `--export-method app-store-connect`. Agree a separate supported + unsigned build invocation first; no production export or signing by default. +3. Prove the exact locked native graph, generated-project wiring and app link; + then run real Rust→Tauri→Recovery FFI tests (ID extremes, success/reject, + async/pool-drain lifetimes and escaped pointers). Component compilation alone + cannot clear this gate. +4. On an authorized real device, qualify WKWebView login/cookie/SSE behavior, + trusted/invalid TLS, same-origin and off-origin navigation/redirects/popups, + remote IPC denial, document-start host flags, native return/recovery during + load/offline, keyboard/safe areas/accessibility/RTL, reload/background/resume, + renderer/process death and explicit reopening. Preserve drafts without + replaying failed mutations; return/disconnect must not masquerade as logout. +5. Treat signing, Apple accounts, provisioning, export and store submission as + **separate explicit operator authorizations**. Nothing here publishes to a + store. Assess App Review Guideline **4.2 (minimum functionality)**: a hosted + web wrapper may not provide sufficient utility for acceptance. Review policy + at submission time; do not promise approval. diff --git a/packages/mobile-app/README.md b/packages/mobile-app/README.md index 10f546fa5..2d0f892ab 100644 --- a/packages/mobile-app/README.md +++ b/packages/mobile-app/README.md @@ -112,6 +112,9 @@ References: [manual dispatch prerequisites](https://docs.github.com/en/actions/h ## iOS — native component checks and future app build +See [the stopped-experimentation state and Shantur handoff](IOS_HANDOFF.md). +The iOS PR workflow validates source only on Linux; it does not build an iOS app. + The app and Swift recovery package require **iOS 15.0 or newer**. Real SwiftRs/Tauri/Recovery modules and objects were compiled for Intel simulator and ARM64 device targets with SDK 26.2; this is **not an iOS application link or native FFI execution pass**. See [the source-owned Tauri C ABI correction, dependency pins and evidence](IOS_ABI.md). Local native iOS experimentation is stopped. The following commands describe future tooling, not an instruction to resume: agree a supported unsigned build contract first. The current `ios:build` requests distribution export and must not be used for default validation. A supported Mac with full Xcode (not just Command Line Tools), CocoaPods, Rust, Node/npm, and iOS Rust targets is required: ```text diff --git a/packages/mobile-app/tests/ios-workflow.test.ts b/packages/mobile-app/tests/ios-workflow.test.ts new file mode 100644 index 000000000..6b2b33446 --- /dev/null +++ b/packages/mobile-app/tests/ios-workflow.test.ts @@ -0,0 +1,38 @@ +import assert from "node:assert/strict" +import { readFileSync } from "node:fs" +import { test } from "node:test" + +test("iOS workflow stays read-only Linux SOURCE validation with no native/export steps", () => { + const workflow = readFileSync(new URL("../../../.github/workflows/mobile-ios.yml", import.meta.url), "utf8").replace(/\r\n/g, "\n") + assert.match(workflow, /^name: Mobile iOS SOURCE validation \(no native build\)$/m) + assert.match(workflow, /^ name: SOURCE validation only — not an iOS app build$/m) + assert.match(workflow, /^ runs-on: ubuntu-24\.04$/m) + assert.match(workflow, /^permissions:\n contents: read\n/m) + assert.equal([...workflow.matchAll(/^\s*permissions:/gm)].length, 1) + assert.equal([...workflow.matchAll(/^\s*runs-on:/gm)].length, 1) + assert.match(workflow, /^ persist-credentials: false$/m) + assert.equal(workflow.match(/\non:\n([\s\S]*?)\npermissions:/)?.[1], [ + " pull_request:", " paths:", + " - '.github/workflows/mobile-ios.yml'", + " - 'packages/mobile-app/**'", + " - 'packages/ui/src/lib/i18n/messages/**/remoteAccess.ts'", + " - 'packages/ui/src/styles/tokens.css'", + " - 'packages/server/src/server/routes/auth-pages/login.html'", + " - 'package.json'", " - 'package-lock.json'", + " workflow_dispatch:", "", + ].join("\n")) + assert.deepEqual([...workflow.matchAll(/^\s+- uses: (\S+)/gm)].map((match) => match[1]), [ + "actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683", + "actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020", + ]) + assert.deepEqual([...workflow.matchAll(/^\s+run: (.+)$/gm)].map((match) => match[1]), [ + "npm ci --ignore-scripts --workspace @codenomad/mobile-app --include-workspace-root --no-audit --no-fund", + "npm run typecheck --workspace @codenomad/mobile-app", + "npm run test --workspace @codenomad/mobile-app", + "npm run build --workspace @codenomad/mobile-app", + "npm run ios:verify --workspace @codenomad/mobile-app", + "rustup toolchain install 1.94.0 --profile minimal", + "cargo +1.94.0 test --locked --manifest-path packages/mobile-app/src-tauri/policy/Cargo.toml", + ]) + assert.doesNotMatch(workflow, /pull_request_target|secrets\.|write-all|contents: write|ios:(?:init|build)|app-store-connect|macos-|xcodebuild|sudo|ssh|sandbox-exec/) +})