> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tuturuuu.com/llms.txt
> Use this file to discover all available pages before exploring further.

# GitHub Actions Runbook

> How the repository’s automation is organized, gated, and operated.

GitHub Actions is the primary automation plane for Tuturuuu.

## Workflow Gatekeeping

Every major workflow can be disabled centrally through
`tuturuuu.ts`.

The reusable workflow
`ci-check.yml`
reads that config and emits `should_run`, which downstream jobs use before doing
real work.

For Vercel deployments, `ci-check.yml` also performs affected-app gating before
any install, build, or deploy step starts. It checks the current GitHub event's
changed files against Vercel app metadata and workspace dependency closures from
`tuturuuu.ts`.

The changed-file resolver must evaluate the full effective change range:

* Manual `workflow_dispatch` runs bypass affected-app gating when the workflow is
  enabled.
* Vercel production push workflows first look for the latest successful GitHub
  Deployment marker for the same workflow and branch, then diff that marker SHA
  to the current `GITHUB_SHA`.
* If no marker is available, push events use the GitHub event payload's complete
  commit file list instead of falling back to only the latest commit.
* Pull request events diff the PR base SHA to the PR head SHA.
* If the resolver cannot prove the full range, it leaves changed-file state
  unavailable so Vercel gating defaults open.

Affected Vercel rules:

* `apps/<app>/**` runs that app's preview or production Vercel workflow.
* `packages/<package>/**` runs every Vercel app whose transitive `workspace:*`
  dependency closure includes that package.
* `apps/*/package.json` and `packages/*/package.json` are dependency changes for
  their owning workspaces.
* `bun.lock`, root `package.json`, `turbo.json`, `tuturuuu.ts`, and
  `.github/workflows/ci-check.yml` run every Vercel app workflow.
* A Vercel workflow file change, such as
  `.github/workflows/vercel-preview-calendar.yaml`, runs that specific workflow.
* If changed files cannot be computed, the Vercel gate defaults open.

`bun.lock`-only changes intentionally run all Vercel app deploys because ownership
is ambiguous without a manifest or source path.

## Pull Request Close Cancellation

`cancel-pr-runs-on-close.yaml` runs on the `pull_request_target` `closed` event
and cancels active GitHub Actions runs that still belong to the closed pull
request. It exists to stop queued or long-running checks after a PR is closed
without adding `closed` triggers to every CI workflow.

Because `pull_request_target` runs with base-repository privileges, keep this
workflow narrow:

* Do not checkout or execute pull request head code.
* Checkout only the trusted default branch, with persisted credentials disabled.
* Pin GitHub-owned actions to full commit SHAs.
* Grant only `actions: write` and `contents: read` to the cancellation job.
* Do not pass repository secrets other than the short-lived `GITHUB_TOKEN`.

The cancellation script lists active runs by the closed PR head SHA. It cancels
matching PR workflow runs and same-repository branch push runs for the PR branch,
but never cancels `main` or `production` push runs. Runs in contributor forks are
outside the base repository token's authority and are not cancelled by this
workflow. The same trusted job paginates through Actions cache entries and
deletes only entries whose ref equals the closed pull request's merge ref
(`refs/pull/<number>/merge`). It never deletes default-branch cache entries.

## Main Workflow Groups

### Hosted web build and deployment checks

* `vercel-preview-platform.yaml`
* `vercel-preview-apps.yaml`
* `vercel-preview-calendar.yaml`
* `vercel-preview-chat.yaml`
* `vercel-preview-cms.yaml`
* `vercel-preview-drive.yaml`
* `vercel-preview-finance.yaml`
* `vercel-preview-inventory.yaml`
* `vercel-preview-infrastructure.yaml`
* `vercel-preview-learn.yaml`
* `vercel-preview-mail.yaml`
* `vercel-preview-meet.yaml`
* `vercel-preview-mind.yaml`
* `vercel-preview-nova.yaml`
* `vercel-preview-qr.yaml`
* `vercel-preview-rewise.yaml`
* `vercel-preview-shortener.yaml`
* `vercel-preview-storefront.yaml`
* `vercel-preview-tasks.yaml`
* `vercel-preview-teach.yaml`
* `vercel-preview-track.yaml`
* A matching `vercel-production-*.yaml` workflow exists for each app above

This list drifts as satellite apps are added; treat
`.github/workflows/vercel-preview-*.yaml` and
`.github/workflows/vercel-production-*.yaml` as the authoritative source.

CMS uses the same Vercel deployment pattern as the other satellite apps:

* preview workflow: `vercel-preview-cms.yaml`
* production workflow: `vercel-production-cms.yaml`
* project secret: `VERCEL_CMS_PROJECT_ID`
* environments: `vercel-preview-cms` and `vercel-production-cms`

The Apps gateway uses the same pattern:

* preview workflow: `vercel-preview-apps.yaml`
* production workflow: `vercel-production-apps.yaml`
* project secret: `VERCEL_APPS_PROJECT_ID`
* environments: `vercel-preview-apps` and `vercel-production-apps`

QR uses the same pattern:

* preview workflow: `vercel-preview-qr.yaml`
* production workflow: `vercel-production-qr.yaml`
* project secret: `VERCEL_QR_PROJECT_ID`
* environments: `vercel-preview-qr` and `vercel-production-qr`

Infrastructure uses the same deployment pattern:

* preview workflow: `vercel-preview-infrastructure.yaml`
* production workflow: `vercel-production-infrastructure.yaml`
* project secret: `VERCEL_INFRASTRUCTURE_PROJECT_ID`
* environments: `vercel-preview-infrastructure` and
  `vercel-production-infrastructure`

Mail uses the same deployment pattern:

* preview workflow: `vercel-preview-mail.yaml`
* production workflow: `vercel-production-mail.yaml`
* project secret: `VERCEL_MAIL_PROJECT_ID`
* environments: `vercel-preview-mail` and `vercel-production-mail`

These workflows:

* run with default `contents: read` permissions
* bind deploy jobs to `vercel-preview-<app>` or `vercel-production-<app>`
  GitHub Environments
* run preview workflows only through manual dispatch from `main`, with a
  required `preview_ref` input and an actor present in the
  `TRUSTED_PREVIEW_DEPLOY_ACTORS` repository variable; `vercel-preview-platform.yaml`
  is the exception and also runs on protected `main` pushes so Supabase staging
  migrations keep their same-SHA prerequisite signal
* reject production manual dispatches from non-production branches before
  install/build/deploy work starts
* install Bun with `.github/actions/setup-bun-with-retry`, pinned to the root
  `packageManager` Bun version and retried with exponential backoff
* install dependencies through `scripts/ci/run-with-backoff.sh` so transient Bun
  tarball or cache failures retry after cache cleanup
* build selected shared workspace dependencies before Vercel resolves package exports:
  `@tuturuuu/types`, `@tuturuuu/supabase`, and `@tuturuuu/internal-api`
* run `vercel pull`
* run `vercel build`
* deploy prebuilt artifacts for satellite apps and the hosted platform
  production workflow
* treat `vercel-preview-platform.yaml` as platform build validation only;
  self-hosted machines own their own Docker `apps/web` runtime deployment
* skip production push work when a newer commit already exists on the target
  branch
* record non-blocking GitHub Deployment markers after successful Vercel preview
  builds and production deploys so the next production push can evaluate every
  change since the last successful run

The platform preview workflow can cross-credit a successful same-SHA production
platform build marker. Production does not cross-credit preview markers because
it must build and deploy the prebuilt production artifacts. This only applies to
the platform workflows; satellite Vercel workflows still build and deploy
independently.

### Database automation

* `supabase-staging.yaml`
* `supabase-production.yaml`

These cover staging schema promotion and production schema promotion.

Database workflows that install the Supabase CLI use
`.github/actions/setup-supabase-cli-with-retry` after checkout. The local action
passes `github.token` through to `supabase/setup-cli@v2`, leaves `version` empty
so the repo-pinned Supabase CLI version is used instead of the anonymous
`latest` release lookup, and retries failed setup attempts with bounded
exponential backoff.

### Docker automation

* `docker-setup-check.yaml`
* `rust-backend.yml`

`docker-setup-check.yaml` is the workflow to watch whenever Docker files,
compose files, or Docker helper scripts change. `rust-backend.yml` owns the
`apps/backend` Rust service checks and the migration Cloudflare checks:
formatting, locked dependency fetch, Clippy, tests, Cloudflare Worker target
validation, smoke reporter unit tests, native binary build, Docker image build,
`apps/tanstack-web` type-check/test validation, and the TanStack route-tree
generator/formatter unit test. Keep `scripts/generate-tanstack-route-tree.*`
in this workflow's path filters so route-tree generation changes run before
TanStack/Rust deployment handoff.

### TanStack/Rust Cloudflare deployment

`rust-backend.yml` is also the manually dispatched Cloudflare preview deployment
workflow for the migration pair:

* Rust backend Worker: `apps/backend/wrangler.jsonc`, Worker name
  `tuturuuu-backend`.
* TanStack Start Worker: `apps/tanstack-web/wrangler.jsonc`, Worker name
  `tuturuuu-tanstack-web`.

The workflow stays switchboard-controlled by the `rust-backend.yml` entry in
`tuturuuu.ts`. Pull request and push runs are validation-only and use
`contents: read`; they do not require Cloudflare secrets just to type-check,
test, lint config, build the Rust Worker bundle, or build the backend Docker
image.

Manual dispatch inputs:

* `deploy_target`: `none`, `backend`, `tanstack-web`, or `all`.
* `deployment_mode`: `dry-run` or `deploy`. Keep the default `dry-run` for the
  first run in a new account or environment.

Run `rust-backend.yml` from `main` for Cloudflare deployments. The deployment
preflight requires the dispatch ref to be `refs/heads/main` and `github.actor`
to be listed in the comma-delimited `TRUSTED_CLOUDFLARE_DEPLOY_ACTORS`
repository variable before it loads `CLOUDFLARE_API_TOKEN`. Keep that allowlist
limited to maintainers who can approve secret-backed Worker deployments.
Cloudflare deploy credentials must not be exposed to arbitrary branch code;
review or merge deployment changes before dispatching a secret-backed run.

The deploy jobs are bound to the `cloudflare-workers-preview` GitHub
Environment. Configure these GitHub values there before expecting deployment
jobs to run:

| Name                                      | GitHub storage       | Purpose                                                                                             |
| ----------------------------------------- | -------------------- | --------------------------------------------------------------------------------------------------- |
| `CLOUDFLARE_API_TOKEN`                    | Environment secret   | Cloudflare API token with Workers Scripts edit access for this account.                             |
| `CLOUDFLARE_ACCOUNT_ID`                   | Environment variable | Cloudflare account id used by Wrangler. A same-named secret is accepted only as a fallback.         |
| `BACKEND_WORKER_ORIGIN`                   | Environment variable | Deployed Rust backend Worker origin used by the post-deploy smoke gate.                             |
| `TANSTACK_WEB_WORKER_ORIGIN`              | Environment variable | Deployed TanStack Worker origin used by the post-deploy smoke gate.                                 |
| `CLOUDFLARE_SMOKE_BACKEND_INTERNAL_TOKEN` | Environment secret   | Dedicated smoke token mapped to `BACKEND_INTERNAL_TOKEN` only while running `bun smoke:cloudflare`. |

If either value is missing, the workflow emits a
`Cloudflare deployment skipped` warning, records setup guidance in the job
summary, and skips deploy jobs after CI validation has completed. The warning
names only the missing GitHub Environment keys, not their values. A non-empty
but invalid or under-scoped token normally surfaces as a Worker secret preflight
skip or a Wrangler failure before upload.

Worker runtime secrets live in Cloudflare, not GitHub. After the GitHub
credential preflight passes, a dedicated Worker secret preflight checks the
selected Workers with `wrangler secret list`.
It reads only secret names, never secret values. If Wrangler cannot list names,
or if any required name is
missing, the workflow emits a warning, writes setup commands to the job summary,
and skips the selected deploy job instead of failing later inside a deploy
step. Missing-secret warnings use the `Missing Worker secrets` wording and list
only secret names plus `bun wrangler secret put ...` commands; they never print
or infer secret values.

| Worker                  | Required Cloudflare Worker secrets                                                                                                                                                                    |
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `tuturuuu-backend`      | `BACKEND_INTERNAL_TOKEN`, `TUTURUUU_APP_COORDINATION_SECRET`, `SUPABASE_URL`, `SUPABASE_SERVICE_ROLE_KEY`, `CRON_SECRET`, `DISCORD_APP_DEPLOYMENT_URL`, `AURORA_EXTERNAL_URL`, `AURORA_EXTERNAL_WSID` |
| `tuturuuu-tanstack-web` | `BACKEND_PUBLIC_ORIGIN`, `BACKEND_INTERNAL_TOKEN`                                                                                                                                                     |

Bootstrap missing Worker secrets from a trusted local shell with Wrangler:

```sh theme={null}
bun wrangler secret put BACKEND_INTERNAL_TOKEN --config apps/backend/wrangler.jsonc
bun wrangler secret put TUTURUUU_APP_COORDINATION_SECRET --config apps/backend/wrangler.jsonc
bun wrangler secret put SUPABASE_URL --config apps/backend/wrangler.jsonc
bun wrangler secret put SUPABASE_SERVICE_ROLE_KEY --config apps/backend/wrangler.jsonc
bun wrangler secret put CRON_SECRET --config apps/backend/wrangler.jsonc
bun wrangler secret put DISCORD_APP_DEPLOYMENT_URL --config apps/backend/wrangler.jsonc
bun wrangler secret put AURORA_EXTERNAL_URL --config apps/backend/wrangler.jsonc
bun wrangler secret put AURORA_EXTERNAL_WSID --config apps/backend/wrangler.jsonc
bun wrangler secret put BACKEND_PUBLIC_ORIGIN --config apps/tanstack-web/wrangler.jsonc
bun wrangler secret put BACKEND_INTERNAL_TOKEN --config apps/tanstack-web/wrangler.jsonc
```

Deploy the backend before the TanStack Worker when `deploy_target` is not `all`.
The TanStack Worker has a `BACKEND` service binding to `tuturuuu-backend`, so a
frontend-only deployment assumes the backend Worker and its secrets already
exist. When `deploy_target=all`, a skipped backend deployment also keeps the
TanStack deployment skipped, even if the TanStack Worker secrets are present.

`deployment_mode=dry-run` compiles and runs Wrangler checks without uploading,
but it still uses the same Cloudflare account, token, Worker name, and secret
name preflights as `deployment_mode=deploy`. Treat a dry-run skip as a setup
gap, not as a deploy failure. `deployment_mode=deploy` uploads the selected
Worker version only after those preflights pass.

When `deploy_target=all` and `deployment_mode=deploy`, the
`post-deploy-smoke` job runs only after both Worker deploy jobs succeed. It
requires `BACKEND_WORKER_ORIGIN`, `TANSTACK_WEB_WORKER_ORIGIN`, and
`CLOUDFLARE_SMOKE_BACKEND_INTERNAL_TOKEN`; missing values fail the full deploy
workflow with a warning, an error, and setup guidance in the step summary
instead of silently skipping runtime verification. The warning title is
`Cloudflare smoke inputs missing`, and the error title is
`Cloudflare smoke verification blocked`. The job runs:

```sh theme={null}
bun smoke:cloudflare --output "$CLOUDFLARE_SMOKE_REPORT_PATH"
```

The JSON report is written under
`tmp/benchmarks/web-migration/<run-id>-<attempt>/cloudflare-smoke.json`,
uploaded as the `cloudflare-smoke-<run-id>-<attempt>` workflow artifact, and
summarized in the GitHub step summary. The report must come from the deployed
backend and TanStack Worker origins; do not point these variables at local
Wrangler or Docker origins in the `cloudflare-workers-preview` Environment.

### Quality and security

* `type-check.yaml`
* `turbo-unit-tests.yaml`
* `biome-check.yaml`
* `codeql.yml`
* `codecov.yaml`
* `i18n-check.yaml`
* `check-migrations.yml`
* `check-migration-timestamps.yml`
* `branch-name-check.yaml`

The test workflows (`turbo-unit-tests.yaml` and `codecov.yaml`) run `bun setup`
through `scripts/ci/run-with-backoff.sh` before executing tests so dependency
installation and workspace package builds match the local setup path while
surviving transient tarball extraction failures. The Codecov coverage test run
also uses the helper with a two-attempt cap so transient runner interruptions
such as exit code `130` retry once without masking deterministic test failures.
Vercel workflows should invoke Turborepo through `bun turbo:local ...` after the
retried `bun install` so CI uses the pinned repo dependency instead of resolving
a global or downloaded Turbo binary. Cacheable builds, type checks, and tests
must be executed through `.github/actions/run-with-turbo-remote-cache`; trusted
jobs pass the dedicated repository `TURBO_TOKEN` and `TURBO_TEAM` variable,
while pull-request and Dependabot jobs leave both inputs empty and use the
task-family local fallback cache.

Vercel deploy credentials must stay environment-scoped. Do not put
`${{ secrets.* }}` values in a Vercel workflow-level `env:` block, and do not
export production Supabase, encryption, or provider secrets from GitHub
Actions. Store app runtime and build-time configuration in the Vercel project
environment. Remote-cache identity is the narrow exception: it is passed only
to the wrapped `vercel build` step and never written to `GITHUB_ENV` or a
workflow/job environment. The regression test
`bun test scripts/ci/release-workflows.test.js` enforces this for every
`vercel-preview-*.yaml` and `vercel-production-*.yaml` workflow.

### Cache and artifact resources

`actions-storage-report.yaml` runs weekly and can be dispatched manually. It
is read-only: it queries the repository's live Actions cache policy and current
cache/artifact inventories, groups cache bytes by key prefix, and summarizes
artifact count, size, age, and largest workflow families. Cache status is
informational at 80% of the configured limit, warning at 90%, and critical at
100%. The report does not assume a fixed artifact byte entitlement and does not
require an organization billing token.

The July 10, 2026 audit found a 10 GB cache maximum and 7-day retention. Target
steady-state usage below 9 GB so GitHub does not continuously evict and recreate
high-reuse entries. Prefer Bun downloads, package-manager data, task-family
Turbo state, native dependencies, shared Rust state, and service-scoped
BuildKit layers over final application binaries. Leave managed uv and CodeQL
caching to their official actions.

All `upload-artifact` steps must set `retention-days` and
`if-no-files-found`. Optional failure diagnostics may warn; release and deploy
handoffs must fail when absent. E2E diagnostics are failure-only and retained 7
days, package tarballs are retained 1 day, development mobile deliverables 7
days, and production store deliverables 14 days.

Preview deploy credentials must not be exposed to arbitrary branch push code.
Most preview Vercel workflows are manual-only: run the workflow from `main`,
provide the branch, tag, or SHA in `preview_ref`, and keep
`TRUSTED_PREVIEW_DEPLOY_ACTORS` limited to maintainers who can approve
secret-backed preview builds. The workflow still checks out and builds the
requested `preview_ref`, so reviewers should treat that ref as code that can
execute during install/build.

Cloudflare Worker manual deployments follow the same trust boundary: run
`rust-backend.yml` from `main`, keep `TRUSTED_CLOUDFLARE_DEPLOY_ACTORS` limited
to trusted maintainers, and avoid exposing `CLOUDFLARE_API_TOKEN` to unreviewed
branch code. The workflow checks the protected dispatch ref and actor allowlist
before loading Cloudflare credentials or running Worker deploy steps.

`vercel-preview-platform.yaml` also runs on protected `main` pushes. Keep that
exception narrow: `supabase-staging.yaml` is triggered by the platform preview
build workflow and production migration requires a successful `main` staging
migration for the same SHA before it can run `supabase db push --include-all`.

### Other delivery surfaces

* `discord-modal-deploy.yml`
* `mobile-build-android.yaml`
* `mobile-build-ios.yaml`
* `mobile-build-macos.yaml`
* `mobile-build-windows.yaml`
* `mobile-deploy-stores.yaml`
* `release-*.yaml` package publishing workflows

## Operational Rules

### Manual dispatch

Use `workflow_dispatch` when:

* you need to rerun a deployment intentionally
* you need to promote a migration outside the normal trigger timing
* you need to recover from a failed but otherwise understood automation path

Manual dispatch bypasses affected-app gating when the workflow is enabled in
`tuturuuu.ts`. Disabled `ci` entries still skip, even for manual dispatch.

For preview Vercel deployments, dispatch the workflow from `main`, set
`preview_ref` to the branch, tag, or SHA to deploy, and confirm the actor is in
`TRUSTED_PREVIEW_DEPLOY_ACTORS`. Do not reintroduce preview `push` triggers for
secret-backed Vercel workflows.

Package release workflows that expose production secrets or trusted publishing
authority must add their own ref guard before dependency installation or publish
jobs. Release Please is the only workflow that generates monorepo version and
changelog PRs. It runs from `production`, uses
`secrets.RELEASE_PLEASE_TOKEN` so generated PRs and releases can trigger
downstream workflows, and falls back to `github.token` only to keep the job from
failing when the bot token is not provisioned. The `github.token` fallback does
not trigger downstream workflow runs from generated release PRs. Release Please
rejects non-production manual dispatches before the write-capable release job.
Package publish workflows consume release-please version bumps on `production`;
they must not recreate checksum or PR-title version bump automation. Package
release workflows use npm trusted publishing: build and artifact-pack work runs
before any OIDC permission is granted, then the `publish-npm` job downloads one
tarball, verifies its package name and version, and runs `npm publish` without
`NPM_TOKEN`. Each publish job is bound to its package release environment, and
the matching npm trusted publisher must use this repository, the workflow
filename, and that environment. Manual dispatch is only valid with the branch
selector set to `production`; non-production refs are rejected before the package
or publish jobs can start. Every workflow-published package manifest must also
declare `repository.type: "git"`,
`repository.url: "https://github.com/tutur3u/platform"`, and
`repository.directory` matching the package path. npm validates those fields
against GitHub Actions provenance and rejects publishes with `E422` when the
packed manifest has an empty or mismatched repository URL. The tarball handoff
uses required-file behavior, one-day retention, and compression level 0 because
`.tgz` is already compressed. Preparation runs any package lifecycle build
through root Turbo first, then calls `npm pack --ignore-scripts` so `prepack`
cannot bypass the shared cache wrapper.

Release Please can move an oversized generated pull request body into
`release-notes.md` on a companion
`release-please--branches--<branch>--release-notes` branch. The workflow runs
`node scripts/ci/release-please-overflow-recovery.js --target-branch production`
before `googleapis/release-please-action@v5`; when a merged pending release PR
still points at a missing overflow file, the script recreates the branch and
file from the merged manifest bump and current `production` changelogs, then the
normal Release Please action can create the releases. If the script reports
`skipped`, inspect the pending labels and merged release PR body before retrying.

Keep internal Tuturuuu dependencies as `workspace:*` in source package
manifests so local builds always use the checked-out workspace. Package release
workflows must first run
`node scripts/ci/package-release-readiness.js gate-package-release packages/<name>`
in a short pre-build gate. The gate checks the package version and publishable
Tuturuuu workspace dependencies once, dispatches missing dependency workflows,
and exits green without build, pack, or publish work while dependencies are
pending. If a dependency workflow for the same production SHA already failed,
completed successfully without npm visibility, or cannot be inspected, the gate
fails immediately. When the gate outputs `should_publish == true` and
`dependencies_ready == true`, the workflow can build and rewrite the checked-out
manifest immediately before `npm pack` with
`node scripts/ci/prepare-npm-package-manifest.js packages/<name>`. That
temporary rewrite replaces `workspace:` protocol ranges with the current
workspace package versions so npm consumers can install the tarball. It must
preserve package-included `file:` tarball dependencies, such as `@tuturuuu/ui`'s
vendored SheetJS tarball, instead of rewriting them to mutable external tarball
URLs. After
`npm publish`, the publish job polls `npm view` for the exact published version
before it reports success, then a separate non-OIDC job dispatches direct
dependent package workflows without checking out the repo or carrying publish
authority. The internal
`node scripts/ci/package-release-readiness.js dispatch-dependent-workflows packages/<name>`
command is available for direct/manual dispatches that can read the checkout.
If npm returns first-publish or permission errors, fix the npm package access and
trusted publisher setup instead of skipping the package. If a published package
depends on another release-please-managed Tuturuuu package, that dependency also
needs its own
`release-*-package.yaml` workflow and matching `tuturuuu.ts` entry.

The platform production Vercel workflow also runs
`node scripts/ci/package-release-readiness.js gate-changed-package-versions`
before installing dependencies. Release-please package bumps therefore publish
and become visible on npm before the normal production platform deployment
continues. The helper intentionally checks only the checked-out latest commit,
not the whole push event payload, so package changes from earlier commits in a
multi-commit push are not bound to an unrelated newer SHA. If the latest commit
does change package manifests, the deploy job grants `actions: write` because
the helper may dispatch missing package release workflows for that same
production SHA. When related package releases are queued or running, the helper
sets `packages_ready=false`; the platform build skips successfully instead of
polling npm until the runner times out. npm publish authority remains isolated to
package `publish-npm` jobs. A skipped package gate must not count as production
deployment for database migrations: `supabase-production.yaml` requires the
successful `vercel-production-platform` deployment marker for the same SHA
before it can run production migrations.

Filtered production Docker installs only include the selected app and the
dependencies that app needs. Workspace packages with direct `tsc` build scripts
must declare `typescript` in their own `devDependencies`; do not
rely on the root devDependency for production Docker builds. Programmatic
compiler API consumers must stay on the active TypeScript 7 toolchain instead
of carrying legacy compiler compatibility packages.

Next.js production apps also declare `@typescript/native-preview` in their own
`devDependencies` while the repo uses the TypeScript 7 native compiler package.
The TS7 `typescript` package does not ship the legacy
`typescript/lib/typescript.js` API file that Next checks during build-time
TypeScript setup; the native-preview marker makes Next use its supported TS7
native compiler path instead of trying to auto-install classic TypeScript.

### Newer-commit guards

Production Vercel push workflows intentionally skip if the branch already has a
newer commit than the current run. A skipped deployment may be correct rather
than broken.

The deployment marker closes the gap created by these stale-run skips. When a
later push reaches the latest commit, affected-app gating compares against the
last successful marker rather than only the newest push payload, so app changes
from skipped intermediate runs still queue the correct Vercel workflow.

### Prerequisite guards

* `supabase-staging.yaml` requires a successful `main` platform preview build
  trigger unless manually dispatched.
* Its deploy step links the staging project and runs
  `supabase db push --include-all`.
* `supabase-production.yaml` runs after production platform deployment and also
  re-evaluates after a matching `main` staging migration succeeds. It still
  checks both the latest production platform workflow result, a successful
  production deployment marker, and a completed successful staging migration for
  the same commit before running
  `supabase db push --include-all`.

### Mobile iOS native assets

Mobile workflows cache the Flutter SDK/pub downloads through the Flutter setup
action. Android workflows also use the toolchain-aware Gradle cache, and iOS or
macOS workflows cache CocoaPods downloads plus the trunk repository. They do
not cache `apps/mobile/build`, final APK/AAB/IPA/app bundles, `Pods`, or
`apps/mobile/.dart_tool`. Flutter native asset hook outputs can become stale
across simulator/device builds and SDK updates; a stale `.dart_tool` cache can
leave `NativeAssetsManifest.json` referencing `objective_c` while
`build/native_assets/ios/` is missing the generated framework.

Every workflow still runs `flutter pub get` and its platform build on every run.
Development deliverables are retained for 7 days and production store
deliverables for 14 days. Archive directory-based app bundles before upload,
disable redundant artifact compression for already-compressed archives and
mobile packages, and fail if a required deliverable is missing.

The workflow explicitly runs `flutter config --no-enable-swift-package-manager`
after Flutter setup. Keep the iOS CI path on CocoaPods while `image_cropper` and
`dkimagepickercontroller` resolve incompatible `TOCropViewController` Swift
package ranges; otherwise Flutter 3.44+ can fail before the simulator build
with an Xcode package dependency resolution error.

Root `bun check` runs `scripts/check-mobile-dependencies.js` to catch mobile
dependency versions known to fail Apple CI before the native build starts. While
the iOS/macOS build workflows use Xcode 16.4, keep `connectivity_plus` pinned to
`7.0.0` because `7.1.1` references `NWPath.isUltraConstrained`, and keep
`device_info_plus` pinned to `12.3.0` because `12.4.0` references
`NSProcessInfo.isiOSAppOnVision`; those symbols are not available in that CI SDK.

### Mobile store beta deployment

`mobile-deploy-stores.yaml` runs automatically on `production` pushes that touch
`apps/mobile/**`, `scripts/mobile-deployment/**`, the workflow file,
`ci-check.yml`, or `tuturuuu.ts`. It is a beta-store workflow only:

* Android builds the production flavor AAB from `apps/mobile/.env.github` and
  publishes it to the Google Play `internal` track.
* iOS builds the production flavor IPA from `apps/mobile/.env.github` and
  uploads it to TestFlight.
* CI requests a signed GitHub OIDC token for the
  `tuturuuu-mobile-deployment` audience, then fetches an Android or iOS bundle
  from `https://tuturuuu.com/api/v1/mobile-deployment/bundle` with the issued
  `MOBILE_DEPLOYMENT_CI_TOKEN`.
* If `MOBILE_DEPLOYMENT_CI_TOKEN` is not configured in the `mobile-store-beta`
  GitHub Environment yet, the credentials preflight emits a notice and skips the
  Android and iOS publish jobs instead of failing the workflow. A non-empty but
  invalid token still fails during the bundle fetch.
* The apps/web mobile deployment vault verifies the CI token, GitHub OIDC
  issuer, audience, repository, `production` ref, workflow file, and
  `mobile-store-beta` environment before returning any plaintext.
* CI hydrates ignored Firebase, signing, store, and `.env.github` files into
  fixed paths under `apps/mobile` and `$RUNNER_TEMP` with `umask 077`, validates
  plaintext SHA-256 hashes, masks secret values, and deletes generated files in
  `always()` cleanup steps.
* The workflow rejects any Google Play track other than `internal`, uploads only
  the AAB/IPA build artifacts, and uses TestFlight-only iOS upload.
* Release Please owns mobile version and build-number bumps. Duplicate store
  build numbers should fail in CI instead of being bumped by this workflow.

The workflow is bound to the `mobile-store-beta` GitHub Environment. Keep that
environment restricted to the `production` branch and store only
`MOBILE_DEPLOYMENT_CI_TOKEN` there. Mobile signing, Firebase, store API, and
build-time secrets live in the root workspace mobile deployment vault at
`/internal/mobile-deployment` on the infra app and require
`manage_mobile_deployment_vault`. Manage non-file values in the vault's
**Secrets** panel, then use the vault's Verify action before activating the draft. Legacy
base64 file payload env keys are intentionally blocked; Firebase, signing, and
store credential payloads must be uploaded as file resources.

Local release-build verification should run before relying on the workflow:

```sh theme={null}
bun test scripts/ci/check-workflow-config.test.js
ruby -e "require 'yaml'; YAML.load_file('.github/workflows/mobile-deploy-stores.yaml')"
git diff --check
bun check:mobile
(cd apps/mobile && flutter pub get)
(cd apps/mobile && flutter build appbundle --release --flavor production --target lib/main_production.dart --dart-define-from-file=.env.github)
```

iOS IPA verification requires full Xcode, not only Command Line Tools:

```sh theme={null}
xcodebuild -version
(cd apps/mobile && flutter config --no-enable-swift-package-manager)
(cd apps/mobile && flutter build ipa --release --flavor production --target lib/main_production.dart --dart-define-from-file=.env.github)
```

If `xcodebuild -version` reports that Command Line Tools are selected, switch to
full Xcode before claiming the iOS archive has been verified locally.

## Fast Triage Checklist

1. Check whether `ci-check.yml` disabled the workflow through `tuturuuu.ts`.
2. For Vercel workflows, inspect the `ci-check.yml` decision reason and matched paths.
3. Inspect the changed-file source, base SHA, head SHA, and path count emitted by
   `resolve-changed-files.ts`.
4. Check whether the workflow was skipped because a newer commit exists.
5. Check whether the prior successful GitHub Deployment marker exists for that
   Vercel workflow and branch.
6. Check path filters to confirm GitHub should have started the lightweight workflow.
7. For database workflows, inspect the prerequisite evaluation job before the deploy job.
8. For Docker changes, verify `docker-setup-check.yaml` specifically.

## When You Add New Automation

1. Add the workflow file under `.github/workflows/`.
2. Add its key to `tuturuuu.ts` unless you intentionally want it always on by default.
3. Document the workflow in `apps/docs`.
4. If it changes a docs-visible page, add that page to `apps/docs/docs.json`.
