> ## 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.

# App coordination secret rollout

> Ordered, reversible procedure to stage, promote and retire the app coordination signing secret with an accept-only slot.

App coordination tokens (`ttr_app_...`), session-assurance transfers, signed
access tokens (`sat1.`), email unsubscribe tokens and inventory simulated-order
tokens are HMAC-signed with a server secret. Every runtime that verifies or
mints them must resolve the same secret chain, so a new secret is rolled out in
stages. The first value of the chain signs; every value is accepted.

## Env names

| Name | Role | Default |
| - | - | - |
| `TUTURUUU_APP_COORDINATION_SECRET`, `APP_COORDINATION_TOKEN_SECRET` | Current signer chain, in this order | Unchanged |
| `SUPABASE_SECRET_KEY`, `SUPABASE_SERVICE_ROLE_KEY`, `SUPABASE_SERVICE_KEY` | Fallbacks after the signer chain | Unchanged |
| `APP_COORDINATION_TOKEN_NEXT_SECRET` | Accept-only slot. Verifies, never signs while the chain has a value. At least 32 characters; a shorter value throws on every sign and verify | Unset |
| `APP_COORDINATION_TOKEN_SIGNER` | `next` moves the slot first (it signs; the chain still verifies). `current` or unset keeps it accept-only. Any other value, or `next` without the slot value, throws | `current` |
| `APP_COORDINATION_TOKEN_DISABLE_SUPABASE_FALLBACK` | `1` or `true` removes the three `SUPABASE_*` names from the list when `NODE_ENV=production`. An empty list throws; any other value throws | Unset |

With none of the three new names set, behavior is identical to before. The
purpose-bound tokens (`getServerTokenSecretCandidates`, email unsubscribe,
inventory simulated order) resolve their own dedicated names first, then follow
the same three rules, through `resolveServerTokenSecrets` in
`packages/utils/src/app-coordination-token.ts`.

Mint the next value as 32 or more random bytes (for example 64 hex
characters) in the secret manager. Never paste it into a commit, PR or chat.
`SUPABASE_SECRET_KEY` itself stays on every Worker: the database client still
needs it. Only its use as a token-signing fallback is retired.

## Order

Each step is complete only when every runtime in the table below has the new
deployment and a smoke passes. Run steps in order; wait the stated time before
the next.

1. **Deploy the code everywhere** with no new env set. Verify that sign and
   verify behave as before (cross-app sign-in, app-token exchange).
2. **Add `APP_COORDINATION_TOKEN_NEXT_SECRET`** (same value) to Web and every
   Worker below, as a secret, then redeploy or restart each. Tokens still sign
   with the current signer. Smoke: sign in from Web into two satellites.
   An invalid or short value surfaces immediately as a thrown error, not as a
   silent downgrade.
3. **Promote**: set `APP_COORDINATION_TOKEN_SIGNER=next` on every runtime.
   Roll Web last. Until Web flips, satellites already sign with `next`, and
   Web still verifies them because it holds the slot. Tokens issued before the
   flip keep verifying through the chain.
4. **Wait for the longest token lifetime to elapse.** App coordination
   tokens default to 8 hours (`DEFAULT_EXPIRES_IN_SECONDS` in
   `packages/utils/src/app-coordination-token.ts`; callers may set less),
   signed access tokens take a TTL per call, and simulated-order tokens last 24
   hours. Email unsubscribe tokens carry no expiry: links already sent stay
   valid only while the secret that signed them remains in the chain.
5. **Retire the fallbacks**: set
   `APP_COORDINATION_TOKEN_DISABLE_SUPABASE_FALLBACK=1` everywhere. Make sure
   `TUTURUUU_APP_COORDINATION_SECRET` (or the slot) is present first, because
   an empty list fails closed. If that secret equals the old Supabase signer
   value, existing unsubscribe links keep verifying.
6. **Hold**: leave `APP_COORDINATION_TOKEN_NEXT_SECRET` and
   `APP_COORDINATION_TOKEN_SIGNER=next` set. Moving the promoted value into
   `TUTURUUU_APP_COORDINATION_SECRET` and dropping the old one is a separate,
   later change that retires every token the old value signed, including
   unsent-yet-valid unsubscribe links.

## Rollback

| Step | Rollback | Effect |
| - | - | - |
| 6 | Nothing to undo; if the old value was dropped later, restore it to `TUTURUUU_APP_COORDINATION_SECRET` | Tokens it signed verify again |
| 5 | Unset the disable flag everywhere | The `SUPABASE_*` fallbacks verify and sign again |
| 3 | Unset `APP_COORDINATION_TOKEN_SIGNER` (or set `current`) | The old signer signs again; tokens signed with `next` still verify because the slot stays set. Do not remove the slot until those tokens expire |
| 2 | Unset `APP_COORDINATION_TOKEN_NEXT_SECRET` | Only valid before step 3 has run anywhere. After promotion, tokens signed with `next` stop verifying |
| 1 | Redeploy the previous version | The new env names are ignored |

If a deployment is partial after step 3 (some runtimes without the slot), flip
the signer back to `current` first and redeploy the stragglers before trying
again.

## Runtimes that read the secret

Web (`apps/web`, Next.js) and every satellite that verifies tokens through
`@tuturuuu/auth`, `@tuturuuu/utils` or `@tuturuuu/inventory-core`.

Native Start Workers whose packet or startup check names the secret (see each
packet for its Worker name, vars and secrets):
[ai](https://docs.tuturuuu.com/build/devops/ai-start-void.md),
[chat](https://docs.tuturuuu.com/build/devops/chat-start-void.md),
[cms](https://docs.tuturuuu.com/build/devops/cms-start-void.md),
[drive](https://docs.tuturuuu.com/build/devops/drive-start-void.md),
[external](https://docs.tuturuuu.com/build/devops/external-start-void.md),
[finance](https://docs.tuturuuu.com/build/devops/finance-start-void.md),
[forms](https://docs.tuturuuu.com/build/devops/forms-start-void.md),
[git](https://docs.tuturuuu.com/build/devops/git-start-void.md),
[hive](https://docs.tuturuuu.com/build/devops/hive-start-void.md),
[infrastructure](https://docs.tuturuuu.com/build/devops/infrastructure-start-void.md),
[inventory](https://docs.tuturuuu.com/build/devops/inventory-start-void.md),
[learn](https://docs.tuturuuu.com/build/devops/learn-start-void.md),
[mind](https://docs.tuturuuu.com/build/devops/mind-start-void.md),
[nova](https://docs.tuturuuu.com/build/devops/nova-start-void.md),
[pay](https://docs.tuturuuu.com/build/devops/pay-start-void.md),
[rewise](https://docs.tuturuuu.com/build/devops/rewise-start-void.md),
[shortener](https://docs.tuturuuu.com/build/devops/shortener-start-void.md),
[storefront](https://docs.tuturuuu.com/build/devops/storefront-start-void.md),
[tasks](https://docs.tuturuuu.com/build/devops/tasks-start-void.md),
[teach](https://docs.tuturuuu.com/build/devops/teach-start-void.md),
[track](https://docs.tuturuuu.com/build/devops/track-start-void.md),
[workspace](https://docs.tuturuuu.com/build/devops/workspace-start-void.md).

Other packets that name it: [finance-pay](https://docs.tuturuuu.com/build/devops/finance-pay-start.md),
[mastermind](https://docs.tuturuuu.com/build/devops/mastermind-web.md),
[playground](https://docs.tuturuuu.com/build/devops/playground-cutover-packet.md),
[tools](https://docs.tuturuuu.com/build/devops/tools-cutover-packet.md),
[wave 1](https://docs.tuturuuu.com/build/devops/wave1-cutover-packets.md),
[web Docker](https://docs.tuturuuu.com/build/devops/web-docker-deployment.md) and
[TanStack/Rust local deploy](https://docs.tuturuuu.com/build/devops/tanstack-rust-local-deploy.md)
(inactive runtimes).

Apps with `env.example` entries for the secret: `calendar`, `cms`, `drive`,
`finance`, `forms`, `hive`, `inventory`, `learn`, `mind`, `nova`, `pay`,
`rewise`, `shortener`, `tasks`, `teach`, `track`, `workspace`, plus `web`.

Worker startup checks require `TUTURUUU_APP_COORDINATION_SECRET` (tasks,
tools, forms, playground, storefront and others, see their `cloudflare/`
folder). Those checks keep requiring it; the new names are optional and are
read directly from the runtime environment.

The paused Rust backend (`apps/backend`) verifies app tokens with its own
implementation and does not read the new names. It must adopt the same rules
before it is resumed; do not roll out step 3 while it serves live traffic.

## Verification

* Unit: `packages/utils/src/app-coordination-token.test.ts` covers default
  behavior, accept-only, a verifier holding only the slot, promotion with old
  tokens, fallback removal, the empty-list failure and the short secret.
* Per runtime, before moving on: one cross-app sign-in, one app-token
  exchange, one signed-access-token flow, and confirm there are no
  `must be at least 32 characters` or `requires` errors in Worker logs.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.