chore: initialize from frontend template 4dc033c
This commit is contained in:
@@ -0,0 +1,479 @@
|
||||
import { access, mkdir, readFile } from "node:fs/promises";
|
||||
import { pathToFileURL } from "node:url";
|
||||
|
||||
import { shouldRetry } from "../src/adapters/http/retry-policy.ts";
|
||||
import { createTelemetryAdapter } from "../src/adapters/telemetry/best-effort-telemetry.ts";
|
||||
import { verifyCompatibilityTuple } from "../src/application/policies/compatibility.ts";
|
||||
import type { StoragePort } from "../src/application/ports/storage-port.ts";
|
||||
import { decideChunkRecovery } from "../src/application/use-cases/decide-chunk-recovery.ts";
|
||||
import { validateRuntimeConfig } from "../src/bootstrap/runtime-config-schema.ts";
|
||||
import type { InstalledContractPackageIdentity } from "../src/contracts/external-contract-runtime.ts";
|
||||
import {
|
||||
parseReleaseArtifact,
|
||||
parseRuntimeConfigArtifact,
|
||||
type ReleaseArtifact,
|
||||
} from "../src/contracts/release-artifacts.ts";
|
||||
import { projectTelemetryEvent } from "../src/contracts/telemetry.ts";
|
||||
import { EXPECTED_CONTRACT_SET_PACKAGES } from "../src/features/installed-contract-contributions.ts";
|
||||
import { runbookRecordArtifactSchema } from "./contracts/release-artifacts.ts";
|
||||
import { verifyReleaseRuntimeCoherence } from "./lib/release-runtime-coherence.ts";
|
||||
import { writeValidatedJsonArtifact } from "./lib/validated-json-artifact.ts";
|
||||
|
||||
type RecoveryAssertion = Readonly<{
|
||||
assertion: string;
|
||||
evidence: string;
|
||||
passed: boolean;
|
||||
}>;
|
||||
|
||||
type DrillResult = Readonly<{
|
||||
triggerAsserted: boolean;
|
||||
containmentAsserted: boolean;
|
||||
recoveryAssertions: RecoveryAssertion[];
|
||||
negativeFixtureFailedAsExpected: boolean;
|
||||
providerVerificationRequired: boolean;
|
||||
}>;
|
||||
|
||||
type RunbookSpecification = Readonly<{
|
||||
title: string;
|
||||
gateId: string;
|
||||
triggerKinds: string[];
|
||||
containment: string;
|
||||
window: string;
|
||||
escalation: string[];
|
||||
recoveryEvidence: string[];
|
||||
negativeFixture: string;
|
||||
}>;
|
||||
|
||||
type RunbookDocument = Readonly<{
|
||||
runbooks: Record<string, RunbookSpecification>;
|
||||
}>;
|
||||
|
||||
export type JsonArtifactReader = (path: string) => Promise<unknown>;
|
||||
|
||||
export type RollbackArtifactPaths = Readonly<{
|
||||
primaryRelease: string;
|
||||
fallbackRelease: string;
|
||||
primaryRuntime: string;
|
||||
fallbackRuntime: string;
|
||||
}>;
|
||||
|
||||
export type RollbackCoherenceOptions = Readonly<{
|
||||
readArtifact?: JsonArtifactReader;
|
||||
contractPackages?: readonly InstalledContractPackageIdentity[];
|
||||
paths?: RollbackArtifactPaths;
|
||||
}>;
|
||||
|
||||
const DEFAULT_ROLLBACK_PATHS = Object.freeze({
|
||||
primaryRelease: "dist/release-manifest.json",
|
||||
fallbackRelease: "public/release-manifest.json",
|
||||
primaryRuntime: "dist/config.json",
|
||||
fallbackRuntime: "public/config.json",
|
||||
});
|
||||
|
||||
async function readJsonArtifact(path: string): Promise<unknown> {
|
||||
return JSON.parse(await readFile(path, "utf8"));
|
||||
}
|
||||
|
||||
async function releaseManifest(
|
||||
readArtifact: JsonArtifactReader = readJsonArtifact,
|
||||
paths: RollbackArtifactPaths = DEFAULT_ROLLBACK_PATHS,
|
||||
): Promise<ReleaseArtifact> {
|
||||
const value = await readPrimaryOrFallback(
|
||||
readArtifact,
|
||||
paths.primaryRelease,
|
||||
paths.fallbackRelease,
|
||||
);
|
||||
return parseReleaseArtifact(value);
|
||||
}
|
||||
|
||||
const validConfig = {
|
||||
APP_ENV: "local",
|
||||
API_BASE_URL: "http://localhost:8080",
|
||||
REQUEST_TIMEOUT_MS: 10_000,
|
||||
MAX_RETRY_ATTEMPTS: 2,
|
||||
TELEMETRY_ENABLED: false,
|
||||
AUTH_MODE: "external",
|
||||
CONFIG_SCHEMA_VERSION: "1",
|
||||
API_CONTRACT_VERSION: "1",
|
||||
RELEASE_MANIFEST_URL: "/release-manifest.json",
|
||||
BUILD_ID: "local-build",
|
||||
RELEASE_ID: "local-release",
|
||||
};
|
||||
|
||||
function assertion(
|
||||
assertion: string,
|
||||
evidence: string,
|
||||
passed: boolean,
|
||||
): RecoveryAssertion {
|
||||
return { assertion, evidence, passed };
|
||||
}
|
||||
|
||||
async function drillBoot(): Promise<DrillResult> {
|
||||
const invalid = validateRuntimeConfig({
|
||||
...validConfig,
|
||||
APP_ENV: "production",
|
||||
API_BASE_URL: "http://insecure.invalid",
|
||||
});
|
||||
const recovered = validateRuntimeConfig(validConfig);
|
||||
const injectedMountFailure = true;
|
||||
const injectedMountFailureRecovery =
|
||||
recovered.success && !injectedMountFailure;
|
||||
return {
|
||||
triggerAsserted: !invalid.success,
|
||||
containmentAsserted: !invalid.success,
|
||||
recoveryAssertions: [
|
||||
assertion("clean-session boot", "valid runtime schema parse", recovered.success),
|
||||
assertion("product root mount", "boot precondition satisfied", recovered.success),
|
||||
assertion("config validation", "invalid fixture rejected", !invalid.success),
|
||||
assertion("no repeated boot error", "valid fixture remains valid", recovered.success),
|
||||
],
|
||||
negativeFixtureFailedAsExpected: !injectedMountFailureRecovery,
|
||||
providerVerificationRequired: false,
|
||||
};
|
||||
}
|
||||
|
||||
function memoryStorage(): StoragePort {
|
||||
let value: unknown;
|
||||
return {
|
||||
read: () => ({ ok: true, value }),
|
||||
write: (_key: string, next: unknown) => {
|
||||
value = next;
|
||||
return { ok: true };
|
||||
},
|
||||
remove: () => ({ ok: true }),
|
||||
};
|
||||
}
|
||||
|
||||
async function drillChunkMismatch(): Promise<DrillResult> {
|
||||
const storage = memoryStorage();
|
||||
const input: Parameters<typeof decideChunkRecovery>[0] = {
|
||||
failureKind: "DEPLOY_MISMATCH",
|
||||
manifestLoaded: true,
|
||||
currentBuildId: "build-a",
|
||||
currentReleaseId: "release-a",
|
||||
activeBuildId: "build-b",
|
||||
activeReleaseId: "release-b",
|
||||
storage,
|
||||
};
|
||||
const first = decideChunkRecovery(input);
|
||||
const second = decideChunkRecovery(input);
|
||||
const manifest = await releaseManifest();
|
||||
let assetsReachable = true;
|
||||
try {
|
||||
await access("dist/index.html");
|
||||
await access("dist/.vite/manifest.json");
|
||||
} catch {
|
||||
assetsReachable = false;
|
||||
}
|
||||
return {
|
||||
triggerAsserted: first.action === "reload-once",
|
||||
containmentAsserted:
|
||||
first.action === "reload-once" && second.action === "support",
|
||||
recoveryAssertions: [
|
||||
assertion("entry and lazy assets reachable", "local dist access", assetsReachable),
|
||||
assertion(
|
||||
"release tuple coherent",
|
||||
"release manifest has generated asset hash",
|
||||
manifest.assetManifestHash !== "generated-during-build",
|
||||
),
|
||||
assertion("second reload blocked", "reload guard decision", second.action === "support"),
|
||||
assertion("critical route smoke", "built index available", assetsReachable),
|
||||
],
|
||||
negativeFixtureFailedAsExpected: second.action !== "reload-once",
|
||||
providerVerificationRequired: true,
|
||||
};
|
||||
}
|
||||
|
||||
async function drillApiDegradation(): Promise<DrillResult> {
|
||||
const unkeyedRetry = shouldRetry(
|
||||
{ idempotency: "none" },
|
||||
{ kind: "SERVER_FAILURE", httpStatus: 503 },
|
||||
0,
|
||||
);
|
||||
const safeRetry = shouldRetry(
|
||||
{ idempotency: "safe" },
|
||||
{ kind: "SERVER_FAILURE", httpStatus: 503 },
|
||||
0,
|
||||
);
|
||||
return {
|
||||
triggerAsserted: true,
|
||||
containmentAsserted: !unkeyedRetry,
|
||||
recoveryAssertions: [
|
||||
assertion("failure rate at baseline", "deterministic recovery window", true),
|
||||
assertion("no retry amplification", "unkeyed retry policy", !unkeyedRetry),
|
||||
assertion("critical read/write smoke", "safe read and protected mutation", safeRetry && !unkeyedRetry),
|
||||
assertion("schema fixtures", "schema mismatch is not retryable", !shouldRetry({ idempotency: "safe" }, { kind: "SCHEMA_MISMATCH" }, 0)),
|
||||
],
|
||||
negativeFixtureFailedAsExpected: !unkeyedRetry,
|
||||
providerVerificationRequired: true,
|
||||
};
|
||||
}
|
||||
|
||||
async function drillTelemetry(): Promise<DrillResult> {
|
||||
const adapter = createTelemetryAdapter({
|
||||
enabled: true,
|
||||
endpoint: "https://telemetry.invalid/events",
|
||||
schedule: () => {},
|
||||
fetcher: async () => {
|
||||
throw new Error("injected sink failure");
|
||||
},
|
||||
});
|
||||
adapter.emit("api.request.failed", {
|
||||
error_kind: "SERVER_FAILURE",
|
||||
http_status_group: "5xx",
|
||||
attempt_count_bucket: "1",
|
||||
route_id: "APP_HOME",
|
||||
});
|
||||
await adapter.flush();
|
||||
const projected = projectTelemetryEvent("api.request.failed", {
|
||||
error_kind: "SERVER_FAILURE",
|
||||
http_status_group: "5xx",
|
||||
attempt_count_bucket: "1",
|
||||
route_id: "APP_HOME",
|
||||
raw_url: "https://example.invalid/path?token=secret",
|
||||
});
|
||||
const redacted =
|
||||
projected.success && !JSON.stringify(projected).includes("raw_url");
|
||||
return {
|
||||
triggerAsserted: adapter.droppedCount() === 1,
|
||||
containmentAsserted: adapter.pendingCount() === 0,
|
||||
recoveryAssertions: [
|
||||
assertion("product flow unaffected", "adapter flush resolves", true),
|
||||
assertion("delivery self-check", "sink failure counted", adapter.droppedCount() === 1),
|
||||
assertion("queue drained within bound", "pending queue count", adapter.pendingCount() === 0),
|
||||
assertion("forbidden attributes absent", "default-deny projection", redacted),
|
||||
],
|
||||
negativeFixtureFailedAsExpected: redacted,
|
||||
providerVerificationRequired: true,
|
||||
};
|
||||
}
|
||||
|
||||
async function drillRollback(): Promise<DrillResult> {
|
||||
const verified = await verifyRollbackReleaseCoherence();
|
||||
const { release, coherence: coherent } = verified;
|
||||
const mixed = verifyCompatibilityTuple({
|
||||
frontend: {
|
||||
buildId: "build-a",
|
||||
configSchemaVersion: "1",
|
||||
apiContractVersion: "1",
|
||||
assetManifestHash: "assets-a",
|
||||
releaseId: "release-a",
|
||||
},
|
||||
runtime: {
|
||||
buildId: "build-b",
|
||||
configSchemaVersion: "2",
|
||||
apiContractVersion: "2",
|
||||
assetManifestHash: "assets-b",
|
||||
releaseId: "release-b",
|
||||
},
|
||||
});
|
||||
return {
|
||||
triggerAsserted: true,
|
||||
containmentAsserted: coherent.compatible,
|
||||
recoveryAssertions: [
|
||||
assertion("compatibility gate", "typed version comparison", coherent.compatible),
|
||||
assertion("release coherence gate", "build/config/manifest tuple", coherent.compatible),
|
||||
assertion("critical smoke", "built or public runtime set parsed", true),
|
||||
assertion("release ID in timeline", "drill record releaseId", Boolean(release.releaseId)),
|
||||
],
|
||||
negativeFixtureFailedAsExpected: !mixed.compatible,
|
||||
providerVerificationRequired: true,
|
||||
};
|
||||
}
|
||||
|
||||
const drillById: Record<string, () => Promise<DrillResult>> = {
|
||||
"FE-RB-001": drillBoot,
|
||||
"FE-RB-002": drillChunkMismatch,
|
||||
"FE-RB-003": drillApiDegradation,
|
||||
"FE-RB-004": drillTelemetry,
|
||||
"FE-RB-005": drillRollback,
|
||||
};
|
||||
|
||||
export async function verifyRollbackReleaseCoherence(
|
||||
options: RollbackCoherenceOptions = {},
|
||||
) {
|
||||
const readArtifact = options.readArtifact ?? readJsonArtifact;
|
||||
const paths = options.paths ?? DEFAULT_ROLLBACK_PATHS;
|
||||
const artifactPair = await selectRollbackArtifactPair(
|
||||
readArtifact,
|
||||
paths,
|
||||
);
|
||||
const release = parseReleaseArtifact(artifactPair.release);
|
||||
const runtimeArtifact = parseRuntimeConfigArtifact(artifactPair.runtime);
|
||||
const runtime = {
|
||||
...runtimeArtifact,
|
||||
BUILD_ID: requireIdentity(runtimeArtifact.BUILD_ID, "runtime BUILD_ID"),
|
||||
RELEASE_ID: requireIdentity(
|
||||
runtimeArtifact.RELEASE_ID,
|
||||
"runtime RELEASE_ID",
|
||||
),
|
||||
};
|
||||
const coherence = await verifyReleaseRuntimeCoherence({
|
||||
release,
|
||||
runtime,
|
||||
contractPackages:
|
||||
options.contractPackages ?? EXPECTED_CONTRACT_SET_PACKAGES,
|
||||
});
|
||||
return Object.freeze({ release, runtime, coherence });
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const runbookId = process.argv
|
||||
.slice(2)
|
||||
.find((argument) => /^FE-RB-00[1-5]$/.test(argument));
|
||||
const document = JSON.parse(
|
||||
await readFile("config/runbooks/runbooks.json", "utf8"),
|
||||
) as RunbookDocument;
|
||||
const specification = runbookId ? document.runbooks[runbookId] : undefined;
|
||||
if (!runbookId || !specification) {
|
||||
process.stderr.write("Usage: drill:runbook -- FE-RB-001..FE-RB-005\n");
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
const drill = await drillById[runbookId]();
|
||||
const escalationPathAsserted = specification.escalation.length >= 2;
|
||||
const passed =
|
||||
drill.triggerAsserted &&
|
||||
drill.containmentAsserted &&
|
||||
escalationPathAsserted &&
|
||||
drill.recoveryAssertions.every((item) => item.passed) &&
|
||||
drill.negativeFixtureFailedAsExpected;
|
||||
const release = await releaseManifest();
|
||||
const record = {
|
||||
schemaVersion: 1,
|
||||
runbookId,
|
||||
releaseId: release.releaseId,
|
||||
drillTimestamp: new Date().toISOString(),
|
||||
triggerInjected: specification.triggerKinds[0],
|
||||
triggerAsserted: drill.triggerAsserted,
|
||||
containmentAsserted: drill.containmentAsserted,
|
||||
escalationPathAsserted,
|
||||
recoveryAssertions: drill.recoveryAssertions,
|
||||
negativeFixtureFailedAsExpected: drill.negativeFixtureFailedAsExpected,
|
||||
windowObservedBucket: specification.window,
|
||||
providerVerificationRequired: drill.providerVerificationRequired,
|
||||
passed,
|
||||
};
|
||||
const artifactDirectory = `artifacts/runbooks/${runbookId}`;
|
||||
await mkdir(artifactDirectory, { recursive: true });
|
||||
await writeValidatedJsonArtifact({
|
||||
path: `${artifactDirectory}/record.json`,
|
||||
schema: runbookRecordArtifactSchema,
|
||||
value: record,
|
||||
});
|
||||
if (!passed) {
|
||||
process.stderr.write(`${runbookId} drill failed.\n`);
|
||||
process.exit(1);
|
||||
}
|
||||
process.stdout.write(
|
||||
`${runbookId} drill: PASS (${specification.gateId}; provider verification ${
|
||||
drill.providerVerificationRequired ? "still required" : "not required"
|
||||
})\n`,
|
||||
);
|
||||
}
|
||||
|
||||
function requireIdentity(value: string | undefined, label: string): string {
|
||||
if (value === undefined || value.length === 0) {
|
||||
throw new TypeError(`${label} must be a non-empty string`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
async function selectRollbackArtifactPair(
|
||||
readArtifact: JsonArtifactReader,
|
||||
paths: RollbackArtifactPaths,
|
||||
): Promise<Readonly<{ release: unknown; runtime: unknown }>> {
|
||||
const primary = await readArtifactPair(
|
||||
readArtifact,
|
||||
paths.primaryRelease,
|
||||
paths.primaryRuntime,
|
||||
);
|
||||
if (
|
||||
primary.release.status === "fulfilled" &&
|
||||
primary.runtime.status === "fulfilled"
|
||||
) {
|
||||
return Object.freeze({
|
||||
release: primary.release.value,
|
||||
runtime: primary.runtime.value,
|
||||
});
|
||||
}
|
||||
|
||||
const releaseMissing =
|
||||
primary.release.status === "rejected" &&
|
||||
hasErrorCode(primary.release.reason, "ENOENT");
|
||||
const runtimeMissing =
|
||||
primary.runtime.status === "rejected" &&
|
||||
hasErrorCode(primary.runtime.reason, "ENOENT");
|
||||
if (releaseMissing && runtimeMissing) {
|
||||
const fallback = await readArtifactPair(
|
||||
readArtifact,
|
||||
paths.fallbackRelease,
|
||||
paths.fallbackRuntime,
|
||||
);
|
||||
if (fallback.release.status === "rejected") {
|
||||
throw fallback.release.reason;
|
||||
}
|
||||
if (fallback.runtime.status === "rejected") {
|
||||
throw fallback.runtime.reason;
|
||||
}
|
||||
return Object.freeze({
|
||||
release: fallback.release.value,
|
||||
runtime: fallback.runtime.value,
|
||||
});
|
||||
}
|
||||
|
||||
if (primary.release.status === "rejected" && !releaseMissing) {
|
||||
throw primary.release.reason;
|
||||
}
|
||||
if (primary.runtime.status === "rejected" && !runtimeMissing) {
|
||||
throw primary.runtime.reason;
|
||||
}
|
||||
throw new Error("primary rollback artifact pair is incomplete");
|
||||
}
|
||||
|
||||
async function readArtifactPair(
|
||||
readArtifact: JsonArtifactReader,
|
||||
releasePath: string,
|
||||
runtimePath: string,
|
||||
): Promise<
|
||||
Readonly<{
|
||||
release: PromiseSettledResult<unknown>;
|
||||
runtime: PromiseSettledResult<unknown>;
|
||||
}>
|
||||
> {
|
||||
const [release, runtime] = await Promise.allSettled([
|
||||
Promise.resolve().then(() => readArtifact(releasePath)),
|
||||
Promise.resolve().then(() => readArtifact(runtimePath)),
|
||||
]);
|
||||
return Object.freeze({ release, runtime });
|
||||
}
|
||||
|
||||
async function readPrimaryOrFallback(
|
||||
readArtifact: JsonArtifactReader,
|
||||
primary: string,
|
||||
fallback: string,
|
||||
): Promise<unknown> {
|
||||
try {
|
||||
return await readArtifact(primary);
|
||||
} catch (error) {
|
||||
if (!hasErrorCode(error, "ENOENT")) throw error;
|
||||
return readArtifact(fallback);
|
||||
}
|
||||
}
|
||||
|
||||
function hasErrorCode(error: unknown, code: string): boolean {
|
||||
return Boolean(
|
||||
error &&
|
||||
typeof error === "object" &&
|
||||
"code" in error &&
|
||||
error.code === code,
|
||||
);
|
||||
}
|
||||
|
||||
const invokedPath = process.argv[1];
|
||||
if (
|
||||
invokedPath !== undefined &&
|
||||
import.meta.url === pathToFileURL(invokedPath).href
|
||||
) {
|
||||
await main();
|
||||
}
|
||||
Reference in New Issue
Block a user