Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
3a57618
Guard poop-window to review navigation in native tests (#211)
patrickchin Nov 14, 2026
0e54a54
Fix disposable signing-proof SDK bootstrap and source pins (#213)
patrickchin Nov 14, 2026
16743df
Install emulator before the signing-proof executable guard (#214)
patrickchin Nov 14, 2026
dda39c8
Prepare strict app notice capture and packaged resource checks
patrickchin Nov 14, 2026
b67532b
Install emulator host runtime and diagnose loader dependencies (#215)
patrickchin Nov 14, 2026
ee31437
Admit compiled iOS hosts explicitly for isolated tests (#216)
patrickchin Nov 14, 2026
f812aa8
Project private sign-out readiness flags before confirmation (#217)
patrickchin Nov 14, 2026
8849c58
Bound Android root readback and owned emulator cleanup (#218)
patrickchin Nov 14, 2026
0597f4d
Close notice capture provenance and archive gaps
patrickchin Nov 14, 2026
2e2ebb1
Bind notice checks to clean source and configured CMake outputs
patrickchin Nov 14, 2026
0669507
Verify actual offline Android connectivity before signing (#219)
patrickchin Nov 14, 2026
911beff
Record sanitized Android signing exercise stages and failures (#221)
patrickchin Nov 14, 2026
24e81ac
Merge approved main into notice capture integration
patrickchin Nov 14, 2026
e0e2981
Wire explicit incomplete notice discovery and candidate gates
patrickchin Nov 14, 2026
dd4a94d
Diagnose native notice inputs and scope Gradle collection to main
patrickchin Nov 14, 2026
899e89b
Refine secret-safe source and setup notice failure receipts
patrickchin Nov 14, 2026
4fc7c77
Snapshot notice failure fields before finite projection
patrickchin Nov 14, 2026
fe91bcd
Bind observed Expo virtual source to its pinned provider
patrickchin Nov 14, 2026
8b4a86e
Retain finite Gradle collector failure boundaries
patrickchin Nov 14, 2026
28690aa
Cover required pinned Expo virtual providers explicitly
patrickchin Nov 14, 2026
77a2f9d
Bind CocoaPods linked framework inputs to their locked package
patrickchin Nov 14, 2026
84be432
Record finite bounded Gradle cause class fingerprints
patrickchin Nov 15, 2026
7994c3b
Bind generated pod inputs and finite artifact selection context
patrickchin Nov 15, 2026
3be89ec
Resolve only external archives while retaining local project inputs
patrickchin Nov 15, 2026
1a61c5a
Bind three selected Pods to their exact package license sources
patrickchin Nov 15, 2026
86dd277
Identify failed Gradle archive scans with verified hashes
patrickchin Nov 15, 2026
b37e6a3
Stream bounded native archive hashes with a separate size cap
patrickchin Nov 15, 2026
5464a4f
Recognize bounded JVM class structure independent of filename
patrickchin Nov 15, 2026
5d4cfa7
Bind 97 reviewed Pod own-package licence sources explicitly
patrickchin Nov 15, 2026
e0ad220
Merge reviewed JVM framing and explicit Pod own-licence bindings
patrickchin Nov 15, 2026
c8c03e4
Report CRC-verified empty notice inputs without attribution
patrickchin Nov 15, 2026
fc37718
Keep empty JSON licence descriptors as typed non-text evidence
patrickchin Nov 15, 2026
88fa985
Bind six npm own-attribution texts to exact source inputs
patrickchin Nov 15, 2026
82cede4
Bind three explicit Pod own-license attributions to live selectors
patrickchin Nov 15, 2026
36e06b8
Review five exact iOS npm notice attributions
patrickchin Nov 15, 2026
112686f
Review explicit iOS runtime and FileSystem notice records
patrickchin Nov 15, 2026
9cad245
Review three exact Expo own-product notice records
patrickchin Nov 15, 2026
e638ff7
Review exact Expo JavaScript notice records
patrickchin Nov 15, 2026
6c18974
Review exact React Native source-Pod notice records
patrickchin Nov 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 75 additions & 43 deletions .github/workflows/android-apk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
workflow_dispatch:
inputs:
version:
description: Artifact version (for example 1.12.0); leave blank for the next patch
description: Must match committed app.json; blank uses its version
required: false
type: string
publish:
Expand All @@ -26,6 +26,9 @@ concurrency:
jobs:
build:
runs-on: ubuntu-latest
env:
EXPO_PUBLIC_APP_POSTHOG_KEY: ${{ vars.EXPO_PUBLIC_APP_POSTHOG_KEY }}
EXPO_PUBLIC_APP_POSTHOG_HOST: ${{ vars.EXPO_PUBLIC_APP_POSTHOG_HOST || 'https://eu.i.posthog.com' }}
outputs:
tag: ${{ steps.release.outputs.tag }}
version: ${{ steps.release.outputs.version }}
Expand All @@ -37,52 +40,39 @@ jobs:
fetch-depth: 0
ref: ${{ github.sha }}
persist-credentials: false
- name: Choose release version
- uses: actions/setup-node@v6
with:
node-version: 22
cache: npm
- name: Validate committed candidate version
id: release
shell: bash
env:
PUPTIME_REQUESTED_VERSION: ${{ inputs.version }}
run: |
if [[ "$GITHUB_REF_TYPE" == "tag" ]]; then
tag="$GITHUB_REF_NAME"
base_tag=""
else
base_tag=$(git describe --tags --abbrev=0 --match 'v[0-9]*.[0-9]*.[0-9]*' HEAD)
if [[ ! "$base_tag" =~ ^v([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
echo "Unsupported release tag: $base_tag" >&2
exit 1
fi
if [[ -n "$PUPTIME_REQUESTED_VERSION" ]]; then
tag="v$PUPTIME_REQUESTED_VERSION"
else
tag="v${BASH_REMATCH[1]}.${BASH_REMATCH[2]}.$((BASH_REMATCH[3] + 1))"
fi
fi
if [[ ! "$tag" =~ ^v([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
echo "Unsupported release tag: $tag" >&2
exit 1
fi
{
echo "tag=$tag"
echo "version=${tag#v}"
echo "base_tag=$base_tag"
} >> "$GITHUB_OUTPUT"
- name: Set artifact version
env:
PUPTIME_VERSION: ${{ steps.release.outputs.version }}
run: |
node - <<'NODE'
const assert = require('node:assert/strict');
const fs = require('node:fs');
const config = require('./app.json');
const [major, minor, patch] = process.env.PUPTIME_VERSION.split('.').map(Number);
config.expo.version = process.env.PUPTIME_VERSION;
config.expo.android.versionCode = major * 1000000 + minor * 1000 + patch;
fs.writeFileSync('app.json', `${JSON.stringify(config, null, 2)}\n`);
const {execFileSync} = require('node:child_process');
const {expo} = JSON.parse(fs.readFileSync('app.json', 'utf8'));
const match = /^([0-9]+)\.([0-9]+)\.([0-9]+)$/.exec(expo.version);
assert.ok(match, 'Commit a valid candidate version before building.');
const [major, minor, patch] = match.slice(1).map(Number);
const code = major * 1000000 + minor * 1000 + patch;
assert.ok(Number.isSafeInteger(code) && code > 0 && expo.android.versionCode === code,
'Commit the matching Android versionCode before building.');
const requested = (process.env.PUPTIME_REQUESTED_VERSION || '').trim();
assert.ok(!requested || requested === expo.version, 'Requested version must equal committed app.json.');
const tag = 'v' + expo.version;
const isTag = process.env.GITHUB_REF_TYPE === 'tag';
assert.ok(!isTag || process.env.GITHUB_REF_NAME === tag, 'Tag must equal committed app.json.');
execFileSync('git', ['diff', '--quiet', 'HEAD', '--'], {stdio: 'pipe'});
const base = isTag ? '' : execFileSync('git',
['describe', '--tags', '--abbrev=0', '--match', 'v[0-9]*.[0-9]*.[0-9]*', 'HEAD'],
{encoding: 'utf8'}).trim();
assert.ok(!base || /^v[0-9]+\.[0-9]+\.[0-9]+$/.test(base), 'Invalid previous release tag.');
fs.appendFileSync(process.env.GITHUB_OUTPUT,
'tag=' + tag + '\nversion=' + expo.version + '\nbase_tag=' + base + '\n');
NODE
- uses: actions/setup-node@v6
with:
node-version: 22
cache: npm
- uses: actions/setup-java@v5
with:
distribution: temurin
Expand All @@ -91,16 +81,38 @@ jobs:
- run: npx expo prebuild --platform android --no-install
env:
CI: "1"
- name: Resolve notice inputs and seal committed candidate
run: |
umask 077
mkdir -p build/notices/android/private
(cd android && ./gradlew -I ../scripts/notices-runtime.init.gradle \
:app:writeNoticeRuntimeInputs --no-daemon \
-PnoticeMainProjectRoot="$PWD" \
-PnoticeInputsOutput="$PWD/../build/notices/android/private/runtime-inputs.json")
node scripts/capture-app-notices.mjs seal android \
--gradle-input build/notices/android/private/runtime-inputs.json
- run: ./gradlew assembleRelease bundleRelease --no-daemon
env:
EXPO_PUBLIC_APP_POSTHOG_KEY: ${{ vars.EXPO_PUBLIC_APP_POSTHOG_KEY }}
EXPO_PUBLIC_APP_POSTHOG_HOST: ${{ vars.EXPO_PUBLIC_APP_POSTHOG_HOST || 'https://eu.i.posthog.com' }}
working-directory: android
- name: Verify exact packaged notices and mapped build outputs
run: |
node scripts/capture-app-notices.mjs verify android \
--gradle-input build/notices/android/private/runtime-inputs.json \
--archive android/app/build/outputs/apk/release/app-release.apk \
--bundle android/app/build/generated/assets/react/release/index.android.bundle \
--source-map android/app/build/generated/sourcemaps/react/release/index.android.bundle.map
node scripts/capture-app-notices.mjs verify android \
--gradle-input build/notices/android/private/runtime-inputs.json \
--archive android/app/build/outputs/bundle/release/app-release.aab \
--bundle android/app/build/generated/assets/react/release/index.android.bundle \
--source-map android/app/build/generated/sourcemaps/react/release/index.android.bundle.map
- name: Verify artifacts and record qualification metadata
id: verify
shell: bash
run: |
mkdir qualification
cp build/notices/android/seal.json qualification/NOTICE-SEAL.json
cp build/notices/android/final-apk.json qualification/NOTICE-APK.json
cp build/notices/android/final-aab.json qualification/NOTICE-AAB.json
puptime_apksigner=$(find "$ANDROID_HOME/build-tools" -type f -name apksigner | sort -V | tail -n 1)
test -n "$puptime_apksigner"
"$puptime_apksigner" verify --verbose --print-certs android/app/build/outputs/apk/release/app-release.apk | tee qualification/APK-SIGNING.txt
Expand Down Expand Up @@ -147,6 +159,26 @@ jobs:
path: qualification/
if-no-files-found: error

- name: Upload unreviewed public notice inputs (INCOMPLETE)
if: always()
uses: actions/upload-artifact@v6
with:
name: unreviewed-oss-notice-inputs-android
path: build/notices/android/review/unreviewed-oss-notice-inputs.json
if-no-files-found: warn
retention-days: 3

- name: Upload discovery metadata (INCOMPLETE)
if: always()
uses: actions/upload-artifact@v6
with:
name: notice-discovery-metadata-android-INCOMPLETE
path: |
build/notices/android/discovery.json
build/notices/android/failure.json
if-no-files-found: warn
retention-days: 3

publish:
if: ${{ github.event_name == 'workflow_dispatch' && inputs.publish == true }}
needs: build
Expand Down
81 changes: 57 additions & 24 deletions .github/workflows/android-signing-proof.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ run-name: NOT FOR RELEASE - Android signing proof
on:
workflow_dispatch:
inputs:
expected_driver:
description: Exact reviewed 40-character SHA of this workflow and driver
type: string
required: true
expected_source:
description: Exact reviewed 40-character source SHA shared by this driver and both artifact-only runs
description: Exact reviewed 40-character app source SHA shared by both artifact-only runs
type: string
required: true
bridge_run_id:
Expand All @@ -26,6 +30,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 45
env:
PUPTIME_EXPECTED_DRIVER: ${{ inputs.expected_driver }}
PUPTIME_EXPECTED_SOURCE: ${{ inputs.expected_source }}
PUPTIME_BRIDGE_RUN: ${{ inputs.bridge_run_id }}
PUPTIME_LATER_RUN: ${{ inputs.later_run_id }}
Expand All @@ -48,17 +53,57 @@ jobs:
run: echo "PUPTIME_SIGNING_PRIVATE=$RUNNER_TEMP/puptime-signing-$GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT" >> "$GITHUB_ENV"
- name: Static source checks (no keys or device)
run: node scripts/verify-android-signing-upgrade.mjs --self-check
- run: npm ci
- name: Pin completed artifact-only runs and immutable APKs
env:
GH_TOKEN: ${{ github.token }}
run: node scripts/verify-android-signing-upgrade.mjs --preflight
- run: npm ci
- name: Prepare isolated Android tools
shell: bash
run: |
set -euo pipefail
umask 077
sdkmanager 'build-tools;36.0.0' 'system-images;android-36;google_apis;x86_64'
test -n "$ANDROID_HOME"
puptime_sdkmanager=$(find "$ANDROID_HOME/cmdline-tools" -type f -name sdkmanager | sort -V | tail -n 1)
test -n "$puptime_sdkmanager"
puptime_cmdline_bin=$(dirname "$puptime_sdkmanager")
for puptime_tool in "$puptime_cmdline_bin/sdkmanager" "$puptime_cmdline_bin/avdmanager" "$ANDROID_HOME/platform-tools/adb"; do
test -x "$puptime_tool"
printf 'Android executable: %s\n' "$puptime_tool"
done
export PATH="$puptime_cmdline_bin:$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator:$PATH"
printf '%s\n' "$puptime_cmdline_bin" "$ANDROID_HOME/platform-tools" "$ANDROID_HOME/emulator" >> "$GITHUB_PATH"
timeout 30 sdkmanager --version | sed -n '1,4p'
timeout 30 adb version | sed -n '1,4p'
sdkmanager 'emulator' 'build-tools;36.0.0' 'system-images;android-36;google_apis;x86_64'
sudo apt-get update
sudo apt-get install --yes --no-install-recommends libpulse0
dpkg-query -W libpulse0
for puptime_properties in "$ANDROID_HOME/emulator/source.properties" "$ANDROID_HOME/build-tools/36.0.0/source.properties" "$ANDROID_HOME/system-images/android-36/google_apis/x86_64/source.properties"; do
test -f "$puptime_properties"
printf 'Android package metadata: %s\n' "$puptime_properties"
awk '/^Pkg\.Revision[[:space:]]*=/ {revision=1} /^(Pkg\.(Desc|Revision|BuildId)|AndroidVersion\.ApiLevel)[[:space:]]*=/ && ++n<=4 {print} END {exit !revision}' "$puptime_properties"
done
test -x "$ANDROID_HOME/emulator/emulator"
printf 'Android executable: %s\n' "$ANDROID_HOME/emulator/emulator"
# The launcher may supply bundled SDK library paths; ldd is diagnostic only.
puptime_dependency_report="$PUPTIME_SIGNING_PRIVATE/loader-dependencies.txt"
: > "$puptime_dependency_report"
puptime_loader_failed=0
for puptime_binary in "$ANDROID_HOME/emulator/emulator" "$ANDROID_HOME/emulator/qemu/linux-x86_64/qemu-system-x86_64"; do
printf 'Checking host dependencies: %s\n' "$puptime_binary"
test -x "$puptime_binary"
timeout 30 ldd "$puptime_binary" >> "$puptime_dependency_report" 2>&1 || puptime_loader_failed=1
done
puptime_missing_libraries=$(awk '$2=="=>" && $3=="not" && $4=="found" {print $1}' "$puptime_dependency_report" | sort -u)
if [[ -n "$puptime_missing_libraries" || "$puptime_loader_failed" != 0 ]]; then
printf 'Unresolved emulator ELF libraries (diagnostic; launcher may supply SDK paths):\n%s\n' "$puptime_missing_libraries"
if [[ "$puptime_loader_failed" != 0 ]]; then
printf 'ELF inspection diagnostic; launcher and boot checks remain mandatory:\n'
sed -n '1,40p' "$puptime_dependency_report"
fi
fi
timeout 30 emulator -version | sed -n '1,4p'
export ANDROID_AVD_HOME="$PUPTIME_SIGNING_PRIVATE/avd"
mkdir -m 700 "$ANDROID_AVD_HOME"
echo "ANDROID_AVD_HOME=$ANDROID_AVD_HOME" >> "$GITHUB_ENV"
Expand All @@ -68,18 +113,12 @@ jobs:
printf 'no\n' | avdmanager create avd --name "Puptime_Signing_QA_$GITHUB_RUN_ID" --package 'system-images;android-36;google_apis;x86_64'
"$ANDROID_HOME/emulator/emulator" -avd "Puptime_Signing_QA_$GITHUB_RUN_ID" -port 5588 -no-window -no-audio -no-snapshot -gpu swiftshader_indirect > "$PUPTIME_SIGNING_PRIVATE/emulator.log" 2>&1 &
echo "$!" > "$PUPTIME_SIGNING_PRIVATE/emulator.pid"
timeout 180 adb -s emulator-5588 wait-for-device
for attempt in $(seq 1 90); do
if [[ "$(adb -s emulator-5588 shell getprop sys.boot_completed | tr -d '\r')" == 1 ]]; then break; fi
if [[ "$attempt" = 90 ]]; then exit 1; fi
sleep 2
done
test "$(adb -s emulator-5588 shell getprop sys.boot_completed | tr -d '\r')" = 1
adb -s emulator-5588 root
timeout 60 adb -s emulator-5588 wait-for-device
node scripts/verify-android-signing-upgrade.mjs --emulator-record
node scripts/verify-android-signing-upgrade.mjs --emulator-root
adb -s emulator-5588 shell svc wifi disable
adb -s emulator-5588 shell svc data disable
adb -s emulator-5588 shell cmd connectivity airplane-mode enable
node scripts/verify-android-signing-upgrade.mjs --emulator-offline
- name: Install the established Maestro version
env:
MAESTRO_VERSION: '2.11.0'
Expand All @@ -96,6 +135,12 @@ jobs:
find "$PUPTIME_SIGNING_PRIVATE" -type d -exec chmod 700 {} +
find "$PUPTIME_SIGNING_PRIVATE" -type f -exec chmod 600 {} +
fi
- name: Remove owned emulator and private QA material
if: always()
run: |
if [[ -f "$PUPTIME_SIGNING_PRIVATE/NOT-FOR-RELEASE" ]]; then
node scripts/verify-android-signing-upgrade.mjs --emulator-cleanup
fi
- name: Preserve only allowlisted non-secret comparison receipt
if: always()
uses: actions/upload-artifact@v6
Expand All @@ -104,15 +149,3 @@ jobs:
path: build/android-signing-safe/verification.json
if-no-files-found: ignore
retention-days: 7
- name: Remove owned emulator and private QA material
if: always()
shell: bash
run: |
set -euo pipefail
if [[ -f "$PUPTIME_SIGNING_PRIVATE/NOT-FOR-RELEASE" ]]; then
test "$(cat "$PUPTIME_SIGNING_PRIVATE/NOT-FOR-RELEASE")" = "$PUPTIME_EXPECTED_SOURCE"
if adb -s emulator-5588 emu avd name 2>/dev/null | grep -Fxq "Puptime_Signing_QA_$GITHUB_RUN_ID"; then
adb -s emulator-5588 emu kill
fi
rm -rf -- "$PUPTIME_SIGNING_PRIVATE"
fi
2 changes: 2 additions & 0 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ jobs:
cache: npm
- run: npm ci
- run: node --test scripts/native-task-cleanup.test.mjs
- run: node --test scripts/native-host-policy.test.mjs
- run: node --test scripts/app-notices.test.mjs scripts/native-notice-inputs.test.mjs
- run: npm run typecheck
- run: npm run typecheck:ios
- run: npm test
Expand Down
Loading
Loading