124 lines
6.5 KiB
Markdown
124 lines
6.5 KiB
Markdown
# 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.
|
|
|
|
### Policy authority and update order
|
|
|
|
The web and Convex processes receive the same value, but parse it in separate
|
|
processes. The web parser preserves the private baseline when its setting is
|
|
omitted for development compatibility; the Convex/backend parser requires an
|
|
explicit, valid setting and fails closed when propagation is missing, empty,
|
|
or malformed. Convex is authoritative for persisted subscriptions and bridge
|
|
mutations; the web gate is an earlier ingress/provider guard, not a replacement
|
|
for backend authorization.
|
|
|
|
Update the backend setting first, validate a status query and representative
|
|
bridge mutation in an isolated deployment, then update the web setting and
|
|
verify the HTTP gate. A web-only narrowing does **not** revoke existing
|
|
Convex subscriptions, sessions, or grants, and this lane makes no such claim.
|
|
A backend-only narrowing may cause already-running web requests to receive
|
|
backend denials until the web setting converges. Record both settings and the
|
|
validation timestamps in the release evidence; rollback must restore the
|
|
backend policy before reopening web 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.
|
|
|
|
## Restore and migration evidence
|
|
|
|
The synthetic restore/migration rehearsal is specified in
|
|
[`docs/restore-rehearsal.md`](restore-rehearsal.md). It includes Convex data
|
|
and instance-credential backup/restore, application rollback, migration
|
|
rollback prerequisites, session/grant invalidation decisions, resource
|
|
budgets, and a redacted evidence template. It has not been executed in this
|
|
lane.
|
|
|
|
## Evidence boundaries
|
|
|
|
Read-only `tea` inspection on 2026-09-19 reported no Gitea workflows or runs
|
|
for `jb/eden`; runner labels, Docker availability, protected-ref enforcement,
|
|
and action availability were 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. The separate container job is currently
|
|
**disabled** (`if: ${{ false }}`), not merely push-filtered: its rootless
|
|
runner and protected-ref policy require operator verification and approval
|
|
before enablement. Its draft commands use per-run image, volume, and container
|
|
names and clean up only those names, but are not release evidence while the job
|
|
is disabled. 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.
|