Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
1f0da68
ci: sentry sourcemaps upload + android internal track workflow
claude Apr 22, 2026
c06a00b
chore(legal): consolidate legal entity to "TheFireDev LLC" across leg…
claude Apr 22, 2026
1659dba
chore(e2e): add high-priority testIDs for Maestro coverage
claude Apr 22, 2026
79ea971
fix(phi): wire redaction into sentry, pino, query_analytics_log
claude Apr 22, 2026
49efff2
fix(disclaimer): implement version propagation + re-acknowledgment fl…
claude Apr 22, 2026
ff6a487
feat(infra): CI probes, E2E Maestro baseline, drug-safety plan, cf-wo…
claude Apr 22, 2026
5334682
feat(formulary): cert-scoped drug annotation module (scaffold + tests)
claude Apr 22, 2026
f935325
feat(paywall): scaffold Free→Pro upgrade flow (UI + tests, wiring plan)
claude Apr 22, 2026
4e4f799
docs(plans): TX + FL ingestion blueprints (4 agencies)
claude Apr 22, 2026
0f13e64
feat(eval): 200-case retrieval harness with MRR@10 tracking
claude Apr 22, 2026
09f5da2
docs(business): clinical validation + agency outreach templates + CRM…
claude Apr 22, 2026
bfff4a5
docs(plan): cf-worker parity port plan (7 divergences)
claude Apr 22, 2026
0b365c6
docs(sre): load test scaffold + chaos + DR plans
claude Apr 22, 2026
57e677d
feat(disclaimer): client-side blocking ack modal triggered by version…
claude Apr 22, 2026
5fbdd28
feat(paywall): wire into search/agency/voice/offline flows behind FEA…
claude Apr 22, 2026
cfdac79
feat(formulary): wire cert-scoped annotation into search behind FEATU…
claude Apr 22, 2026
480945b
feat(safety): Phase 1 extract — parallel module with risk-class submo…
claude Apr 22, 2026
0f90299
docs(pr): merge-ready PR description with commit list, flags, test su…
claude Apr 22, 2026
394870b
feat(agent/peds-weight): Broselow zones + APLS age formula weight est…
claude Apr 22, 2026
1354339
feat(agent/handoff): SBAR/MIST structured handoff generator (tRPC + U…
claude Apr 22, 2026
e5177cd
feat(agent/differential): ranked differential diagnosis with agency-s…
claude Apr 22, 2026
3b5844f
feat(agent/walker): step-by-step protocol walker with dose calc + cer…
claude Apr 22, 2026
5d34afe
feat(tools): surface 4 new AI agents in Tools tab (handoff, different…
claude Apr 22, 2026
544a24b
feat(agent/screener): stroke (CPSS+LAMS) + sepsis (qSOFA+SIRS) + trau…
claude Apr 22, 2026
cc386a8
feat(agent/field): airway+RSI checklist, radio report generator, MCI …
claude Apr 22, 2026
7d76dd5
feat(agent/field): burn assessment + toxidrome recognition + medicati…
claude Apr 22, 2026
cfdde28
feat(agent/field): OB maternity (delivery/PPH/NRP/APGAR) + behavioral…
claude Apr 23, 2026
b46f99c
chore(asc): update store.config.json metadata per ASO plan + document…
claude Apr 23, 2026
cd158d9
feat(landing): conversion-optimized hero + features + pricing + FAQ +…
claude Apr 23, 2026
40101a9
feat(admin/onboarding): dashboard widgets, onboarding cert+agency cap…
claude Apr 23, 2026
c0c969d
feat(agent/epcr): NEMSIS ePCR draft generator (ImageTrend/ESO/emsChar…
claude Apr 23, 2026
1b08657
feat(ingestion): multi-state ingestion runner + validator + manual GH…
claude Apr 23, 2026
786fdc0
docs(content): publishable long-form SEO articles (Ref 814, EMT/Param…
claude Apr 23, 2026
919f7fe
feat(drugs): drug reference expansion seed — 60 drugs across 9 EMS-cr…
claude Apr 23, 2026
a7aa37b
feat(agent/field): GCS+neuro assessment, respiratory distress scoring…
claude Apr 23, 2026
570016a
feat(full-call): wire contract types for future integrated call workf…
claude Apr 23, 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
208 changes: 208 additions & 0 deletions .github/workflows/android-internal.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
name: Android Internal Track

# Internal-track automated submission for Google Play.
# Uses eas.json "preview" submit profile (track: internal) by default so that
# Google Play internal testers are the first to receive builds from CI.
# Production track is gated behind the "production" input below to avoid
# accidental production submissions.
#
# Trigger paths:
# 1. workflow_dispatch - manual run from Actions UI
# 2. Tag push v*-android - lightweight tag convention for Android-only cuts
#
# Required GitHub Actions secrets:
# - EXPO_TOKEN EAS API auth (expo.dev -> access tokens)
# - ANDROID_GOOGLE_SERVICE_ACCOUNT_KEY Google Play service account JSON
# (contents of google-service-account.json)
# - SENTRY_AUTH_TOKEN (optional) Sourcemap upload on successful build
#
# All workflow_dispatch inputs are channeled through env: blocks before use in
# any shell command to avoid workflow command-injection (see GitHub security
# hardening guidance).

on:
workflow_dispatch:
inputs:
profile:
description: 'EAS build profile'
type: choice
required: true
default: 'production'
options:
- production
- preview
submit_track:
description: 'Google Play track to submit to'
type: choice
required: true
default: 'internal'
options:
- internal
- beta
- production
skip_submit:
description: 'Build only, skip Play Store submit'
type: boolean
required: false
default: false
push:
tags:
- 'v*-android'

# Prevent parallel Android release builds stepping on each other's binary numbers
concurrency:
group: android-internal-${{ github.ref }}
cancel-in-progress: false

permissions:
contents: read

env:
NODE_VERSION: '22'

jobs:
build-and-submit:
name: EAS build + Play Store submit (Android)
runs-on: ubuntu-latest
timeout-minutes: 60

steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Install pnpm
uses: pnpm/action-setup@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Setup EAS
uses: expo/expo-github-action@v8
with:
eas-version: latest
token: ${{ secrets.EXPO_TOKEN }}

- name: Verify EAS auth
run: eas whoami

- name: Resolve build profile
id: profile
env:
EVENT_NAME: ${{ github.event_name }}
INPUT_PROFILE: ${{ github.event.inputs.profile }}
INPUT_SUBMIT_TRACK: ${{ github.event.inputs.submit_track }}
INPUT_SKIP_SUBMIT: ${{ github.event.inputs.skip_submit }}
run: |
set -euo pipefail
if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
# workflow_dispatch inputs are validated against choice enums above,
# but we still only echo them to GITHUB_OUTPUT via env, never
# splicing the raw template expression into the script body.
echo "profile=$INPUT_PROFILE" >> "$GITHUB_OUTPUT"
echo "submit_track=$INPUT_SUBMIT_TRACK" >> "$GITHUB_OUTPUT"
echo "skip_submit=$INPUT_SKIP_SUBMIT" >> "$GITHUB_OUTPUT"
else
# Tag push defaults to production build -> internal track
echo "profile=production" >> "$GITHUB_OUTPUT"
echo "submit_track=internal" >> "$GITHUB_OUTPUT"
echo "skip_submit=false" >> "$GITHUB_OUTPUT"
fi

- name: Materialize Google Play service account key
env:
GSA_KEY: ${{ secrets.ANDROID_GOOGLE_SERVICE_ACCOUNT_KEY }}
run: |
set -euo pipefail
if [ -z "${GSA_KEY:-}" ]; then
echo "::error::ANDROID_GOOGLE_SERVICE_ACCOUNT_KEY secret is not set"
exit 1
fi
printf '%s' "$GSA_KEY" > google-service-account.json
# Basic JSON sanity check so we fail here, not deep in eas submit
node -e "JSON.parse(require('fs').readFileSync('google-service-account.json','utf8'))"
echo "Service account key materialized"

- name: EAS build (Android)
id: build
env:
EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
PROFILE: ${{ steps.profile.outputs.profile }}
run: |
set -euo pipefail
echo "Running: eas build --platform android --profile $PROFILE --non-interactive --wait"
eas build \
--platform android \
--profile "$PROFILE" \
--non-interactive \
--wait \
--json 2>&1 | tee build-output.json
# Extract the build id of the first (and only) build for follow-up submit
BUILD_ID=$(node -e "const d=JSON.parse(require('fs').readFileSync('build-output.json','utf8'));console.log(Array.isArray(d)?d[0].id:d.id)")
echo "build_id=$BUILD_ID" >> "$GITHUB_OUTPUT"
echo "Build id: $BUILD_ID"

- name: EAS submit (Google Play)
if: steps.profile.outputs.skip_submit != 'true'
env:
EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
TRACK: ${{ steps.profile.outputs.submit_track }}
BUILD_ID: ${{ steps.build.outputs.build_id }}
run: |
set -euo pipefail
# Match track to an eas.json submit profile that already sets it
case "$TRACK" in
internal) SUBMIT_PROFILE=preview ;;
beta) SUBMIT_PROFILE=beta ;;
production) SUBMIT_PROFILE=production ;;
*) echo "::error::Unknown track $TRACK"; exit 1 ;;
esac
echo "Submitting build $BUILD_ID to $TRACK via profile $SUBMIT_PROFILE"
eas submit \
--platform android \
--profile "$SUBMIT_PROFILE" \
--id "$BUILD_ID" \
--non-interactive

- name: Upload Android sourcemaps to Sentry
if: steps.profile.outputs.profile == 'production'
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: apex-u9
SENTRY_PROJECT: protocol-guide
EAS_BUILD_GIT_COMMIT_HASH: ${{ github.sha }}
run: |
set -euo pipefail
if [ -z "${SENTRY_AUTH_TOKEN:-}" ]; then
echo "::warning::SENTRY_AUTH_TOKEN not set - skipping Android sourcemap upload"
exit 0
fi
chmod +x eas-hooks/post-build-sentry.sh
eas-hooks/post-build-sentry.sh

- name: Cleanup service account key
if: always()
run: rm -f google-service-account.json

- name: Summary
if: always()
env:
PROFILE: ${{ steps.profile.outputs.profile }}
SUBMIT_TRACK: ${{ steps.profile.outputs.submit_track }}
SKIP_SUBMIT: ${{ steps.profile.outputs.skip_submit }}
BUILD_ID: ${{ steps.build.outputs.build_id }}
run: |
{
echo "### Android Internal Track run"
echo "- Profile: $PROFILE"
echo "- Submit track: $SUBMIT_TRACK"
echo "- Skipped submit: $SKIP_SUBMIT"
echo "- Build id: $BUILD_ID"
} >> "$GITHUB_STEP_SUMMARY"
Loading
Loading