Workflow Gatekeeping
Every major workflow can be disabled centrally throughtuturuuu.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_dispatchruns 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.
apps/<app>/**runs that app’s preview or production Vercel workflow.packages/<package>/**runs every Vercel app whose transitiveworkspace:*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.jsonandpackages/*/package.jsonare dependency changes for their owning workspaces.bun.lock, rootpackage.json,turbo.json,tuturuuu.ts, and.github/workflows/ci-check.ymlrun 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.
.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: writeandcontents: readto the cancellation job. - Do not pass repository secrets other than the short-lived
GITHUB_TOKEN.
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.yamlvercel-preview-apps.yamlvercel-preview-calendar.yamlvercel-preview-chat.yamlvercel-preview-cms.yamlvercel-preview-drive.yamlvercel-preview-finance.yamlvercel-preview-inventory.yamlvercel-preview-infrastructure.yamlvercel-preview-learn.yamlvercel-preview-mail.yamlvercel-preview-meet.yamlvercel-preview-mind.yamlvercel-preview-nova.yamlvercel-preview-qr.yamlvercel-preview-rewise.yamlvercel-preview-shortener.yamlvercel-preview-storefront.yamlvercel-preview-tasks.yamlvercel-preview-teach.yamlvercel-preview-track.yaml- A matching
vercel-production-*.yamlworkflow exists for each app above
.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-cmsandvercel-production-cms
- preview workflow:
vercel-preview-apps.yaml - production workflow:
vercel-production-apps.yaml - project secret:
VERCEL_APPS_PROJECT_ID - environments:
vercel-preview-appsandvercel-production-apps
- preview workflow:
vercel-preview-qr.yaml - production workflow:
vercel-production-qr.yaml - project secret:
VERCEL_QR_PROJECT_ID - environments:
vercel-preview-qrandvercel-production-qr
- preview workflow:
vercel-preview-infrastructure.yaml - production workflow:
vercel-production-infrastructure.yaml - project secret:
VERCEL_INFRASTRUCTURE_PROJECT_ID - environments:
vercel-preview-infrastructureandvercel-production-infrastructure
- preview workflow:
vercel-preview-mail.yaml - production workflow:
vercel-production-mail.yaml - project secret:
VERCEL_MAIL_PROJECT_ID - environments:
vercel-preview-mailandvercel-production-mail
- run with default
contents: readpermissions - bind deploy jobs to
vercel-preview-<app>orvercel-production-<app>GitHub Environments - run preview workflows only through manual dispatch from
main, with a requiredpreview_refinput and an actor present in theTRUSTED_PREVIEW_DEPLOY_ACTORSrepository variable;vercel-preview-platform.yamlis the exception and also runs on protectedmainpushes 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; retainworkflow_dispatchonly for deliberate operator reruns - install Bun with
.github/actions/setup-bun-with-retry, pinned to the rootpackageManagerBun version and retried with exponential backoff - install dependencies through
scripts/ci/run-with-backoff.shso 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.yamlas platform build validation only; self-hosted machines own their own Dockerapps/webruntime deployment - cancel superseded production runs through per-workflow, per-branch
concurrency only for newer
productionpushes, somaincommits 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
Database automation
supabase-staging.yamlsupabase-production.yaml
.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.yamlrust-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 nametuturuuu-backend. - TanStack Start Worker:
apps/tanstack-web/wrangler.jsonc, Worker nametuturuuu-tanstack-web.
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, orall.deployment_mode:dry-runordeploy. Keep the defaultdry-runfor the first run in a new account or environment.
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_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:
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.yamlturbo-unit-tests.yamlbiome-check.yamlcodeql.ymlcodecov.yamli18n-check.yamlcheck-migrations.ymlcheck-migration-timestamps.ymlbranch-name-check.yaml
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.ymlmobile-build-android.yamlmobile-build-ios.yamlmobile-build-macos.yamlmobile-build-windows.yamlmobile-deploy-stores.yamlrelease-*.yamlpackage publishing workflows
Operational Rules
Manual dispatch
Useworkflow_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
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 acancel-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.yamlrequires a successfulmainplatform preview build trigger unless manually dispatched.- Its deploy step links the staging project and runs
supabase db push --include-all. supabase-production.yamlruns after the production deployment planner and also re-evaluates after a matchingmainstaging 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 runningsupabase 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 cacheapps/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.githuband publishes it to the Google Playinternaltrack. - iOS builds the production flavor IPA from
apps/mobile/.env.githuband uploads it to TestFlight. - CI requests a signed GitHub OIDC token for the
tuturuuu-mobile-deploymentaudience, then fetches an Android or iOS bundle fromhttps://tuturuuu.com/api/v1/mobile-deployment/bundlewith the issuedMOBILE_DEPLOYMENT_CI_TOKEN. - If
MOBILE_DEPLOYMENT_CI_TOKENis not configured in themobile-store-betaGitHub 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,
productionref, workflow file, andmobile-store-betaenvironment before returning any plaintext. - CI hydrates ignored Firebase, signing, store, and
.env.githubfiles into fixed paths underapps/mobileand$RUNNER_TEMPwithumask 077, validates plaintext SHA-256 hashes, masks secret values, and deletes generated files inalways()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.
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:
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
- Check whether
ci-check.ymldisabled the workflow throughtuturuuu.ts. - For Vercel workflows, inspect the
ci-check.ymldecision reason and matched paths. - Inspect the changed-file source, base SHA, head SHA, and path count emitted by
resolve-changed-files.ts. - 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.
- Check whether the prior successful GitHub Deployment marker exists for that Vercel workflow and branch.
- Check path filters to confirm GitHub should have started the lightweight workflow.
- For database workflows, inspect the prerequisite evaluation job before the deploy job.
- For Docker changes, verify
docker-setup-check.yamlspecifically.
When You Add New Automation
- Add the workflow file under
.github/workflows/. - Add its key to
tuturuuu.tsunless you intentionally want it always on by default. - Document the workflow in
apps/docs. - If it changes a docs-visible page, add that page to
apps/docs/docs.json.