Carries eight template commits: the provider sandbox actually running, release
admission to a named environment, the product feature manifest with its runtime
kill switch, architecture and documentation rules that match what is enforced,
the removability fixtures, and the browser, visual and performance evidence.
Product identity is unchanged. `package.json` keeps `tech-log-frontend` and the
catalog keeps the Tech Log naming; the home page was not in the delta. The
visual baselines are this product's own — the template's were excluded from the
transplant and these were regenerated here, where the only difference is the
platform overview's new product-feature section.
What this repository gains operationally: `config/runtime/{local,development,
staging,production}.json` with `FE-GATE-027` refusing an artifact whose runtime
document does not match the environment it is being admitted to, and
`FEATURE_OVERRIDES` for taking an installed feature out of service without a
rebuild.
Verified here: eight gates green, build green, visual 5/5, and 1,858 of 1,859
tests in the suites that do not need a sandbox — the one failure passes in
isolation and is a jsdom lazy-chunk timeout under parallel load. The provider
suites cannot run on this machine at all: `kernel.apparmor_restrict_unprivileged
_userns=1` makes `bwrap --unshare-net` fail, reproducible without any code from
either repository.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
161 lines
5.0 KiB
TypeScript
161 lines
5.0 KiB
TypeScript
import type { RuntimeConfigArtifact } from "./release-artifacts.ts";
|
|
|
|
/**
|
|
* §6.4. Which environment an artifact is allowed to be deployed to.
|
|
*
|
|
* Release coherence answers "do these artifacts describe each other?". It does
|
|
* not answer "is this the artifact production should receive?", and the two are
|
|
* not the same question: a build whose runtime document says `APP_ENV: local`,
|
|
* `AUTH_MODE: demo` and `API_BASE_URL: http://localhost:8080/` is perfectly
|
|
* coherent with itself. Without an admission step such a build is a valid
|
|
* release candidate, and the only thing standing between it and production is
|
|
* that nobody happened to promote it.
|
|
*
|
|
* Admission is therefore a separate, declared decision: a caller states the
|
|
* target it intends, and this module says whether the artifact may go there.
|
|
* Every rule below is a refusal, so an unrecognised target or an unreadable
|
|
* field fails closed rather than passing by omission.
|
|
*/
|
|
|
|
export const DEPLOYMENT_TARGETS = Object.freeze([
|
|
"local",
|
|
"development",
|
|
"staging",
|
|
"production",
|
|
] as const);
|
|
|
|
export type DeploymentTarget = (typeof DEPLOYMENT_TARGETS)[number];
|
|
|
|
/**
|
|
* Targets that serve real users over the public internet. They carry the full
|
|
* rule set; `local` and `development` only have to be honest about what they
|
|
* are.
|
|
*/
|
|
const PUBLIC_TARGETS: ReadonlySet<DeploymentTarget> = new Set([
|
|
"staging",
|
|
"production",
|
|
]);
|
|
|
|
/** Placeholder identifiers a developer build emits when nothing supplied one. */
|
|
const PLACEHOLDER_IDENTIFIERS: ReadonlySet<string> = new Set([
|
|
"local-build",
|
|
"local-release",
|
|
"local",
|
|
"dev",
|
|
"unknown",
|
|
]);
|
|
|
|
export type AdmissionViolation = Readonly<{ field: string; reason: string }>;
|
|
|
|
export type AdmissionInput = RuntimeConfigArtifact &
|
|
Readonly<{ BUILD_ID?: string; RELEASE_ID?: string }>;
|
|
|
|
export function isDeploymentTarget(value: unknown): value is DeploymentTarget {
|
|
return (
|
|
typeof value === "string" &&
|
|
(DEPLOYMENT_TARGETS as readonly string[]).includes(value)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Every reason this artifact may not be deployed to `target`. An empty list is
|
|
* the only admission.
|
|
*/
|
|
export function findAdmissionViolations(
|
|
target: DeploymentTarget,
|
|
config: AdmissionInput,
|
|
): readonly AdmissionViolation[] {
|
|
const violations: AdmissionViolation[] = [];
|
|
if (config.APP_ENV !== target) {
|
|
violations.push({
|
|
field: "APP_ENV",
|
|
reason: `artifact declares ${config.APP_ENV} but is being admitted to ${target}`,
|
|
});
|
|
}
|
|
if (!PUBLIC_TARGETS.has(target)) return Object.freeze(violations);
|
|
|
|
if (config.AUTH_MODE !== "external") {
|
|
violations.push({
|
|
field: "AUTH_MODE",
|
|
reason: `${target} requires an external identity provider, not ${config.AUTH_MODE}`,
|
|
});
|
|
}
|
|
violations.push(...publicEndpointViolations("API_BASE_URL", config.API_BASE_URL));
|
|
if (config.TELEMETRY_ENABLED && !config.TELEMETRY_ENDPOINT) {
|
|
violations.push({
|
|
field: "TELEMETRY_ENDPOINT",
|
|
reason: "telemetry is enabled without an endpoint",
|
|
});
|
|
}
|
|
if (config.TELEMETRY_ENDPOINT) {
|
|
violations.push(
|
|
...publicEndpointViolations("TELEMETRY_ENDPOINT", config.TELEMETRY_ENDPOINT),
|
|
);
|
|
}
|
|
for (const field of ["BUILD_ID", "RELEASE_ID"] as const) {
|
|
const value = config[field];
|
|
if (typeof value !== "string" || value.length === 0) {
|
|
violations.push({ field, reason: `${target} requires a build identity` });
|
|
continue;
|
|
}
|
|
if (PLACEHOLDER_IDENTIFIERS.has(value.toLowerCase())) {
|
|
violations.push({
|
|
field,
|
|
reason: `${value} is a developer placeholder, not a released identity`,
|
|
});
|
|
}
|
|
}
|
|
return Object.freeze(violations);
|
|
}
|
|
|
|
function publicEndpointViolations(
|
|
field: string,
|
|
value: string,
|
|
): readonly AdmissionViolation[] {
|
|
let url: URL;
|
|
try {
|
|
url = new URL(value);
|
|
} catch {
|
|
return [{ field, reason: "is not an absolute URL" }];
|
|
}
|
|
const violations: AdmissionViolation[] = [];
|
|
if (url.protocol !== "https:") {
|
|
violations.push({ field, reason: `${url.protocol} is not permitted; use https` });
|
|
}
|
|
if (isNonPublicHost(url.hostname)) {
|
|
violations.push({
|
|
field,
|
|
reason: `${url.hostname} is not reachable from a user's browser`,
|
|
});
|
|
}
|
|
return violations;
|
|
}
|
|
|
|
/**
|
|
* Hosts that only resolve inside the machine or network that built the
|
|
* artifact. A deployment pointing at one of these is a developer configuration
|
|
* that escaped, not a production endpoint.
|
|
*/
|
|
function isNonPublicHost(hostname: string): boolean {
|
|
const host = hostname.toLowerCase().replace(/^\[|\]$/gu, "");
|
|
if (
|
|
host === "localhost" ||
|
|
host.endsWith(".localhost") ||
|
|
host === "::1" ||
|
|
host === "0.0.0.0" ||
|
|
host === "::"
|
|
) {
|
|
return true;
|
|
}
|
|
const octets = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/u.exec(host);
|
|
if (!octets) return false;
|
|
const [first, second] = [Number(octets[1]), Number(octets[2])];
|
|
return (
|
|
first === 127 ||
|
|
first === 10 ||
|
|
(first === 192 && second === 168) ||
|
|
(first === 172 && second >= 16 && second <= 31) ||
|
|
(first === 169 && second === 254)
|
|
);
|
|
}
|