Skip to main content
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, the production planner and platform preview perform affected-app gating before any install, build, or deploy step starts. They check the current GitHub event’s changed files against Vercel app metadata and workspace dependency closures from tuturuuu.ts. Platform preview performs the check inside its deploy job so protected main pushes create one runner, not a reusable gate runner followed by a deployment runner. 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.
  • Storefront UI modules under packages/ui/src/components/ui/storefront/** are scoped to their verified consumers, Inventory and Storefront, instead of rebuilding every app that imports another part of @tuturuuu/ui.
  • 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.
Production pushes run .github/workflows/vercel-production.yaml once. That planner evaluates all targets from their individual deployment markers and calls only the affected vercel-production-<app>.yaml workflows as reusable jobs. This keeps every automatic deployment attached to the original push SHA instead of creating workflow_dispatch runs. Do not restore per-app push triggers or check-ci jobs; doing so recreates the queued preflight fan-out the planner replaces. Each reusable production workflow uses a static per-app concurrency prefix plus the Git ref. Do not derive this key from reusable-workflow caller context: that can make sibling app jobs share one group, cancel one another, and surface red X statuses where unselected jobs should be skipped. Enable cancel-in-progress only when the inherited event is a push to refs/heads/production; main commits and manual recovery dispatches must not cancel the active deployment. 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
  • call production workflows from the production push planner through workflow_call; retain workflow_dispatch only for deliberate operator reruns
  • 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
  • cancel superseded production runs through per-workflow, per-branch concurrency only for newer production pushes, so main commits and manual recovery dispatches cannot interrupt the active deployment
  • 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. The TanStack/Rust migration is currently paused. Keep rust-backend.yml and vercel-production-tanstack-web.yaml set to false in tuturuuu.ts; the shared ci-check.yml switchboard will leave the expensive Rust/TanStack jobs skipped. Re-enable them only when active maintenance resumes and the full workflow is again an intentional delivery gate. The shared e2e-tests.yaml workflow also keeps migration-e2e disabled and publishes only the next image bundle. Maintained Next.js Playwright coverage continues to run; the retained TanStack dual-stack and comparison matrix is a manual source reference until migration work resumes.

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, which is currently disabled while the migration is paused. 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: 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. Bootstrap missing Worker secrets from a trusted local shell with Wrangler:
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:
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. Automatic CodeQL uses GitHub’s organization-managed dynamic/github-code-scanning/codeql workflow for JavaScript/TypeScript and Python. codeql.yml is a manual-only fallback so the repository retains an explicit workflow file without duplicating managed push or pull-request scans. Do not add automatic or cron triggers to the fallback; bun git-sync mirrors the already-scanned main commit to production. e2e-tests.yaml uses native push paths for E2E specs, Playwright and Docker configuration, database fixtures, dependency manifests, lockfiles, and its own runner scripts. Normal application source-only commits do not create the image bundle and six downstream consumers. E2E has no cron trigger: automatic runs are limited to matching commits, and maintainers can still use manual dispatch for an intentional full run. Supabase migration workflows still consume the required platform workflow-run signals, but compare the target SHA with the last successful migration marker. Only database and migration-control changes proceed to Supabase CLI setup and db push; an unavailable marker fails open. Staging and production each use a single serialized evaluate-and-migrate job, and production preserves the same-SHA platform deployment plus staging-success prerequisites. 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. Generic switchboard-controlled workflows still honor disabled ci entries, while trusted manual satellite preview workflows launch their guarded deploy job directly and do not keep an unused tuturuuu.ts toggle. 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, and do not add a standalone check-ci job to a manual-only preview: the dispatch already selected the build and the extra job only consumes another runner. Preview concurrency uses the workflow plus preview_ref, so rerunning the same target cancels stale work without canceling a different preview target. 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. The same workflow then runs node scripts/ci/release-please-auto-approve.js --target-branch production, which approves the generated release PR while it is still nothing but generated output. The Protected branches ruleset requires an approving review, so an untouched release PR otherwise sits at REVIEW_REQUIRED indefinitely. Every one of these must hold or the script skips and leaves the PR for a human: the head branch is a release-please--branches-- branch against the target branch, the PR was opened by a release automation identity, every commit is a chore(release): release ... commit by that same account, and every changed file is one release-please itself rewrites. That last allowlist is derived from release-please-config.json — each package’s changelog, its version file for its release type, and its extra-files — so adding a package or changing its release type cannot silently widen what auto-approval accepts. Nothing needs to un-approve: the ruleset sets dismiss_stale_reviews_on_push, so any later push to the release branch drops the approval, including one this workflow never observes. The step deliberately uses github.token rather than RELEASE_PLEASE_TOKEN, because GitHub refuses a review from the account that opened the pull request — release-please and the approval have to be different identities. While RELEASE_PLEASE_TOKEN is unset both are github-actions[bot], and the script reports blocked and leaves the run green rather than failing. Use --dry-run to see the decision without posting a review. release-please-auto-merge.yaml closes the loop on the generated release PR. It runs daily at 00:00 UTC (cron: "0 0 * * *"), which is 07:00 in Vietnam (UTC+7), and on manual dispatch. It does not reimplement the merge in YAML: it runs bun git-release-please and then bun git-sync, the same two commands the release flow uses locally, so the scheduled path and the manual path cannot drift. Those scripts own the merge rules — platform version sync, conflict detection, bun check, and fast-forward safety — and are covered by scripts/git-release-please.test.js and scripts/git-sync.test.js. The workflow resolves what to do before it installs anything. It prefers origin/release-please--branches--production, ignores the companion --release-notes overflow branch, and skips the merge entirely when the release branch is already an ancestor of origin/main — so a run with nothing to merge is a clean no-op rather than a red run. It separately checks whether origin/production has drifted from origin/main and syncs even when there was no merge, which is what makes the schedule self-healing. Every run that pushes ends by asserting the two refs resolve to the same commit and fails loudly if they do not. An empty daily run remains harmless because the workflow exits cleanly when there is no generated release branch to merge and the protected branches are already aligned. The job checks out main with fetch-depth: 0 and secrets.RELEASE_PLEASE_TOKEN, falling back to github.token. The fallback cannot complete a release: the Protected branches ruleset covers main and production and its only bypass actor is OrganizationAdmin, so a run on github.token merges cleanly and then dies on GH013: Changes must be made through a pull request. RELEASE_PLEASE_TOKEN must therefore hold a personal access token (repo + workflow scopes) owned by an organization admin. Granting the Actions app a ruleset bypass is not an alternative: pushes made with GITHUB_TOKEN do not trigger push workflows, so the production push would silently skip Vercel Production Deployment Planner and the next Release Please run and stall the release chain. A run that would need to push checks for the secret up front and fails with an actionable error, rather than discovering it after bun check. The job also installs Flutter (3.47.x, the same pin as mobile.yaml) and runs flutter pub get in apps/mobile before the merge. Release Please bumps apps/mobile/pubspec.yaml on every release, so touchesMobile() in scripts/git-release-please.js is always true and bun check is always followed by bun check:mobile; without a toolchain dart-format, flutter-analyze and flutter-test all exit 127. Keep every mobile build/deploy workflow on that same Flutter minor. The mobile lockfile is resolved with Dart 3.13; Flutter 3.44.9 bundles Dart 3.12.2 and downgrades 16 locked packages during flutter pub get, which dirties the checkout before bun git-release-please can begin. Top-level permissions is {} and contents: write is granted on the job alone, and concurrency is grouped with cancel-in-progress: false because cancelling a half-finished release merge would leave main and production split. Use the dry_run dispatch input to exercise the merge and run bun git-sync --no-push without publishing anything. 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. Database paths therefore select the platform deployment in the production planner, and supabase-production.yaml requires both the successful planner run and the vercel-production-platform deployment marker for the same SHA before it can run production migrations. The gate queries the planner run because reusable app workflows do not create standalone Actions runs. 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.

Superseded-run guards

Production Vercel workflows use a static per-app concurrency prefix and Git ref with a cancel-in-progress predicate restricted to push events on refs/heads/production. A newer production push cancels only the older run for that same app instead of cancelling sibling app deployments or allowing an obsolete run to finish green. A main commit or manual recovery dispatch does not cancel the running production deploy. The newest production run stays queued or active and deploys the cumulative branch state. Deployment markers still let affected-app gating compare every change since the last successful deployment, including changes accumulated across canceled intermediate runs. If the marker is missing or cannot be trusted, Vercel gating defaults open so a later unrelated commit cannot hide an earlier app change.

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 the production deployment planner and also re-evaluates after a matching main staging migration succeeds. It still checks the latest planner result, a successful production platform 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. The iOS/macOS build workflows currently resolve macos-latest to GitHub’s macOS 26 ARM64 image with Xcode 26+. The former Xcode 16.4 compatibility pins for connectivity_plus and device_info_plus are no longer required. Before adding a future Apple-specific dependency pin, verify the active runner image from a build log and reproduce the native build failure on that toolchain.

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:
iOS IPA verification requires full Xcode, not only Command Line Tools:
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 canceled because a newer run superseded it, then follow the newest run in the same workflow-and-branch concurrency group.
  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.