# Release and rollout safety (T01) This repository uses Gitea Actions for CI. The workflow in `.gitea/workflows/ci.yml` installs the frozen lockfile, runs local tests/check/build, compiles the checked-in synthetic reference fixture, runs an isolated demo web smoke, and prepares a separate Docker/Compose artifact job. It does not receive deployment credentials, trigger a remote deployment, or write a persistent Convex backend. ## Server-side rollout gate `APP_CAPABILITY_ALLOWLIST` is a comma-separated deployment setting parsed by `src/lib/server/release-gate.ts`. An omitted setting preserves the existing private asset/sign-in baseline (`asset_search`, `asset_valuation`, `asset_sync`, `assistant_dispatch`, `session_token`, `identity`, and `subscriptions`). An explicitly empty setting disables all capabilities. Unknown names fail configuration rather than becoming an accidental opt-in; known capabilities that are not in the baseline (for example `wallet_balance`, `inventory_export`, and `manufacturing_opportunities`) are default-deny. The web server checks the allowlist at the relevant boundary: - `/api/chat` checks `assistant_dispatch` before invoking manual or external intent routing. - `/assets` and assistant execution check the selected asset capability plus `asset_valuation`, because the current shared report intentionally returns a reference valuation even for browse results. - `/api/sync` and the application synchronizer check `asset_sync`. - `/api/session/token` checks `session_token`. - Character management and OAuth entry points check `identity`. - The Convex browser status subscription checks `subscriptions`; sync bridge mutations check `asset_sync` in the backend authorization plane too. The allowlist is copied into Convex by `bun run convex:configure`; that setting is required there rather than inferred from the web container. This is an admission control mechanism, not a UI feature flag. Disabled operations return an explicit unavailable/503 response and do not invoke a provider. The current character status subscription is gated in Convex; no durable user-job service exists in this baseline, so future job kinds remain closed until they have reviewed enqueue and worker entry points. ## Immutable release metadata `APP_RELEASE_ID` is carried into the runtime image and returned as the non-secret `x-application-release` response header. Local/fixture runs use `local` or `fixture`. Staging and production must provide a non-placeholder identifier (normally the source commit or an operator-approved image digest). `bun run release:check` requires an explicit `RELEASE_ENV` for live runs and rejects placeholder staging/production identifiers, unpinned staging/production backend/dashboard images or Dockerfile bases, demo mode in staging/production, invalid capability names, tracked secret-like files, and managed secret names in built browser artifacts. The Dockerfile uses digest-pinned Bun bases and accepts `--build-arg APP_RELEASE_ID=...`; Compose passes the runtime value explicitly. ## Isolated local validation Use a separate worktree-local output directory and a unique Compose project name for backend experiments. Never point these commands at a persistent backend or copy a live `.env`/volume: ```sh APP_MODE=demo APP_RELEASE_ID=fixture bun run build APP_MODE=demo APP_RELEASE_ID=fixture bun run release:check APP_MODE=demo APP_RELEASE_ID=fixture bun run test:fixture ``` `test:fixture` compiles the checked-in reference sources into a temporary folder, starts the built server on loopback with synthetic data, checks the release header and matching `/assets`/chat totals, then kills the server and removes the temporary artifact directory. For an isolated Compose rehearsal, set `COMPOSE_PROJECT_NAME` to a unique throwaway value, keep all binds on loopback, use a fixture/demo environment, and remove the project only after recording the result. The release gate does not itself perform migrations, backups, restores, or cleanup of an operator's persistent deployment. ## Evidence boundaries Read-only `tea` inspection on 2026-09-19 reported no Gitea workflows or runs for `jb/eden`; runner labels, Docker availability, and action availability are therefore not validated. The workflow is prepared locally but has not run remotely. The source job runs a strict staging release check after building the client, and the separate container job validates Compose expansion and builds the digest-pinned web image. No browser automation, OAuth callback failure injection, Convex migration rollback, instance-credential backup/restore, key rotation, or live revocation/transfer evidence is claimed by this lane. Those remain explicit operator/integration gates and must use synthetic or separately consented isolated environments.