From 4040d68b80d2cec8227f6f1a615141f92bba2f36 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 14 Sep 2026 04:57:06 +0000 Subject: [PATCH 1/2] feat(verification): invoke the Android Main Gate from Actions Change, Candidate, and Public Release already have workflows. Main Gate stayed local-only. Add a dispatch/schedule workflow that evaluates `--gate main` on ubuntu-latest without hosting KVM or an emulator, then fail-closes on a failed verdict. --- .github/workflows/android-main.yml | 42 +++++++++++++++++++++++++ scripts/android-gates-workflow.test.mjs | 13 ++++++-- 2 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/android-main.yml diff --git a/.github/workflows/android-main.yml b/.github/workflows/android-main.yml new file mode 100644 index 00000000..d3fb2d0d --- /dev/null +++ b/.github/workflows/android-main.yml @@ -0,0 +1,42 @@ +name: Android main verification + +on: + schedule: + - cron: "17 4 * * 1" + workflow_dispatch: + +permissions: + contents: read + +jobs: + evaluate-main: + name: Evaluate main gate + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: "22" + package-manager-cache: false + + - run: npm install --global npm@11.19.0 + - run: npm ci --ignore-scripts + + - id: main + name: Evaluate main gate + continue-on-error: true + run: npm run verify:android-gates -- --gate main --run-id "${{ github.run_id }}-main" + + - name: Upload Main Gate evidence + if: ${{ always() }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: android-main-evidence + path: artifacts/mobile-evidence/android-gates + if-no-files-found: error + retention-days: 30 + + - name: Enforce main verdict + if: ${{ steps.main.outcome == 'failure' }} + run: exit 1 diff --git a/scripts/android-gates-workflow.test.mjs b/scripts/android-gates-workflow.test.mjs index 791bf09a..e873612f 100644 --- a/scripts/android-gates-workflow.test.mjs +++ b/scripts/android-gates-workflow.test.mjs @@ -19,8 +19,9 @@ async function workflowFiles() { } test("workflows invoke every Android gate without a release promotion", async () => { - const [build, candidate, publicRelease, release] = await Promise.all([ + const [build, main, candidate, publicRelease, release] = await Promise.all([ workflow("build.yml"), + workflow("android-main.yml"), workflow("android-candidate.yml"), workflow("android-public-release.yml"), workflow("release.yml"), @@ -29,10 +30,18 @@ test("workflows invoke every Android gate without a release promotion", async () assert.match(build.source, /--gate change/); assert.match(build.source, /ANDROID_GATE_FRAGMENT: "1"/); assert.doesNotMatch(build.source, /--gate main/); + assert.match(main.source, /--gate main/); + assert.equal(main.value.jobs["main-gate"], undefined); + assert.ok(main.value.jobs["evaluate-main"]); assert.match(candidate.source, /--gate candidate/); assert.match(publicRelease.source, /--gate public-release/); assert.doesNotMatch(release.source, /verify:android-gates|android-public-release/i); - for (const source of [build.source, candidate.source, publicRelease.source]) { + for (const source of [ + build.source, + main.source, + candidate.source, + publicRelease.source, + ]) { assert.doesNotMatch(source, /firebase\s+test\s+lab|test-lab/i); assert.doesNotMatch(source, HOSTED_EMU_FORBIDDEN); } From 1b663ba33c5f53d301d2e8ef5b010398afda7c58 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 14 Sep 2026 03:24:59 +0000 Subject: [PATCH 2/2] fix(mobile): drop expo-network from Expo plugins Listing expo-network in app.json plugins makes expo config and export load build/Network.js, which Node 22 cannot type-strip under node_modules. Keep the dependency for autolinking; permissions come from the native module. --- apps/mobile/app.json | 3 +-- apps/mobile/tests/scaffold.test.mjs | 4 ++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/mobile/app.json b/apps/mobile/app.json index 829062f5..594b0081 100644 --- a/apps/mobile/app.json +++ b/apps/mobile/app.json @@ -49,8 +49,7 @@ "minSdkVersion": 30 } } - ], - "expo-network" + ] ], "experiments": { "typedRoutes": true diff --git a/apps/mobile/tests/scaffold.test.mjs b/apps/mobile/tests/scaffold.test.mjs index 58d043e9..7dfef646 100644 --- a/apps/mobile/tests/scaffold.test.mjs +++ b/apps/mobile/tests/scaffold.test.mjs @@ -68,6 +68,10 @@ test("the Android build enables SQLCipher and excludes all app data from backup" assert.equal(packageManifest.dependencies["expo-secure-store"], "57.0.2"); assert.equal(packageManifest.dependencies["expo-sqlite"], "57.0.2"); assert.equal(packageManifest.dependencies["expo-network"], "57.0.1"); + assert.ok( + !appManifest.expo.plugins.flat().includes("expo-network"), + "expo-network has no app.plugin.js; listing it in plugins loads build/Network.js under Node and fails type stripping", + ); }); test("SQLCipher is proven in memory before a persistent database is opened", () => {