feat(runtime): formalize 4-value runtime channel taxonomy#337
Conversation
Closes the brief "formalize runtime channels — local_dev,
operator_preview, staging, production. Render visibly in /api/health,
/status, /footer."
The ambiguity this fixes: Vercel's VERCEL_ENV is a 3-value union
(production / preview / development) that conflates two distinct
operational contexts:
- "operator preview" — ephemeral per-PR Vercel deploy
- "staging" — long-running stable pre-prod deploy
Both map to VERCEL_ENV=preview today. Operators can't tell which
they're looking at, which means audit findings can attach to the
wrong environment.
apps/web/lib/runtime/channel.ts:
- Closed RUNTIME_CHANNELS union: local_dev | operator_preview |
staging | production.
- Frozen label map (compound forms; never bare "Verified").
- resolveRuntimeChannel(env?) — deterministic resolver. Priority:
1. Explicit VITALCV_RUNTIME_CHANNEL override (wins).
2. VERCEL_ENV=production → production.
3. VERCEL_ENV=preview + VERCEL_URL matches staging pattern →
staging. Default pattern matches 'staging' or 'stage'
substring; overridable via VITALCV_STAGING_URL_PATTERN env.
4. VERCEL_ENV=preview otherwise → operator_preview.
5. VERCEL_ENV=development → local_dev.
6. NODE_ENV=production fallback → production (self-hosted).
7. Final fallback → local_dev.
- narrowRuntimeChannel(unknown) — returns null on unknown (no
silent default).
- isProductionChannel + isPreReleaseChannel — taxonomy helpers.
apps/web/app/api/health/route.ts — modified:
- Adds runtime_channel field to the response body.
- Preserves Wave-136 backward-compat config.clerk shape.
apps/web/components/runtime/RuntimeChannelFooter.tsx:
- Server component (reads process.env via resolveRuntimeChannel).
- Inline monochrome styles (no design-token dep — ships
standalone before #323 lands).
- Renders null in production by default (customer-facing pages
don't carry a channel banner).
- alwaysRender prop forces production chip (use on /status,
/truth-boundary, etc. where the channel attestation is part
of the audit surface).
- data-runtime-channel attribute matches channel id for
programmatic inspection.
46-test lockdown:
- Union has exactly 4 channels in canonical order.
- Every channel has a label; map is frozen; no bare "Verified".
- narrowRuntimeChannel returns null on every non-matching input.
- Resolver respects priority: override > VERCEL_ENV=production >
preview-staging-pattern > preview-default > VERCEL_ENV=dev >
NODE_ENV=production fallback > local_dev fallback.
- Custom VITALCV_STAGING_URL_PATTERN works.
- Resolver is deterministic: same env → same result every call.
- /api/health surfaces runtime_channel in the response body and
resolves correctly for production / preview / staging /
local_dev.
- /api/health preserves Wave-136 config.clerk shape.
- Footer renders null in production by default; alwaysRender
forces the chip.
- Every non-production channel renders distinct data-runtime-channel
attribute + visible label.
- Every chip carries data-testid="runtime-channel-footer".
- No vibrant red/green hex anywhere (monochrome doctrine).
- Banned-strings scan: clean (3 files).
What this PR does NOT do:
- Does NOT mount RuntimeChannelFooter into any specific page.
The component is ready; per-page wiring is a small follow-up
(one import + one render in app/layout.tsx).
- Does NOT modify /status page. That's an existing page;
if you want the chip mounted there too, it's a one-line
import on top of this PR's primitive.
- Does NOT remove the existing runtime_mode field (from
open PR #334). The two fields coexist:
runtime_mode — Vercel's 3-value
runtime_channel — VitalCV's 4-value
runtime_channel is the disambiguated taxonomy; runtime_mode
is preserved for backward-compat with monitoring that reads
Vercel's native value.
Standalone — branches off origin/main, no stacking.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c478b167f0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const pattern = typeof env.VITALCV_STAGING_URL_PATTERN === 'string' | ||
| ? new RegExp(env.VITALCV_STAGING_URL_PATTERN, 'i') | ||
| : DEFAULT_STAGING_PATTERN; |
There was a problem hiding this comment.
Guard custom staging regex parsing
Constructing new RegExp(env.VITALCV_STAGING_URL_PATTERN, 'i') without error handling can crash channel resolution when the env var contains an invalid pattern (for example a typo like '['), which turns /api/health into a 500 in VERCEL_ENV=preview and would also break any page that renders RuntimeChannelFooter. This regression is user-triggerable through configuration and should fail closed (e.g., catch SyntaxError and fall back to DEFAULT_STAGING_PATTERN or operator_preview).
Useful? React with 👍 / 👎.
Summary
Closes the brief "formalize runtime channels — render visibly in /api/health, /status, /footer."
The ambiguity this fixes: Vercel's `VERCEL_ENV` is 3 values (`production`/`preview`/`development`) that conflate operator-preview (ephemeral per-PR deploy) with staging (long-running stable pre-prod). Operators can't tell which one they're looking at; audit findings can attach to the wrong environment.
The 4 channels
Resolution rules (deterministic)
Files
Truth-contract invariants (46-test lockdown)
What this PR does NOT do
Validation