Compare commits

...
Author SHA1 Message Date
donghyeon-ka 6b4b956d51 fix: authenticate field performance evidence context 2026-07-25 22:30:14 +09:00
donghyeon-ka a2a97ebcc7 fix: budget transitive initial JavaScript chunks 2026-07-25 21:46:07 +09:00
donghyeon-ka b1625252d5 feat: enforce web vitals performance budgets 2026-07-25 21:26:51 +09:00
donghyeon-ka 9d40724ab2 Merge branch 'feature-frontend-release-cache-rollback-contract' into develop 2026-07-25 21:22:35 +09:00
donghyeon-ka eb37cbe8be feat: enforce coherent release and rollback contract 2026-07-25 21:22:35 +09:00
donghyeon-ka b82bc73c6c Merge branch 'feature-frontend-contract-compatibility-governance' into develop 2026-07-25 21:19:20 +09:00
donghyeon-ka 89f3c69413 feat: govern frontend contract compatibility 2026-07-25 21:19:20 +09:00
donghyeon-ka 5ad6fb032e Merge branch 'feature-frontend-contract-registry-governance' into develop 2026-07-25 21:16:46 +09:00
donghyeon-ka 52f4896b63 feat: govern contract registries and snapshots 2026-07-25 21:16:46 +09:00
donghyeon-ka 3c347d40d8 Merge branch 'feature-frontend-browser-security-boundary-contract' into develop 2026-07-25 21:14:32 +09:00
donghyeon-ka 6f88915c7a feat: enforce browser security boundaries 2026-07-25 21:14:32 +09:00
donghyeon-ka 675603c3a2 Merge branch 'feature-frontend-build-bundle-supply-chain-contract' into develop 2026-07-25 21:13:13 +09:00
donghyeon-ka 4a3110974b feat: generate build and supply-chain evidence 2026-07-25 21:13:13 +09:00
donghyeon-ka caf09ecd56 Merge branch 'feature-accessibility-baseline-contract' into develop 2026-07-25 21:11:23 +09:00
49 changed files with 2535 additions and 2 deletions
+63
View File
@@ -0,0 +1,63 @@
{
"schemaVersion": 1,
"families": {
"api": {
"additive": {
"before": { "required": ["id"], "properties": { "id": {} } },
"after": {
"required": ["id"],
"properties": { "id": {}, "displayName": {} }
}
},
"breaking": {
"before": { "required": ["id"], "properties": { "id": {} } },
"after": {
"required": ["id", "name"],
"properties": { "id": {}, "name": {} }
}
}
},
"config": {
"additive": {
"before": { "required": ["APP_ENV"], "properties": { "APP_ENV": {} } },
"after": {
"required": ["APP_ENV"],
"properties": { "APP_ENV": {}, "OPTIONAL_FLAG": {} }
}
},
"breaking": {
"before": { "required": ["APP_ENV"], "properties": { "APP_ENV": {} } },
"after": {
"required": ["APP_ENV", "NEW_REQUIRED"],
"properties": { "APP_ENV": {}, "NEW_REQUIRED": {} }
}
}
},
"storage": {
"additive": {
"before": { "properties": { "theme": {} } },
"after": { "properties": { "theme": {}, "contrast": {} } }
},
"breaking": {
"before": { "properties": { "theme": {} } },
"after": { "properties": {} }
}
},
"release": {
"additive": {
"before": { "required": ["buildId"], "properties": { "buildId": {} } },
"after": {
"required": ["buildId"],
"properties": { "buildId": {}, "builtAt": {} }
}
},
"breaking": {
"before": { "required": ["buildId"], "properties": { "buildId": {} } },
"after": {
"required": ["buildId", "assetManifestHash"],
"properties": { "buildId": {}, "assetManifestHash": {} }
}
}
}
}
}
+116
View File
@@ -0,0 +1,116 @@
{
"schemaVersion": 1,
"registries": [
{
"registryId": "FE-REG-ROUTE",
"path": "src/contracts/routes.js",
"exportName": "ROUTE_REGISTRY",
"owner": "feature-routing-navigation-guard-contract",
"requiredFields": [
"routeId",
"path",
"paramsSchema",
"searchSchema",
"access",
"loadingSurface",
"errorSurface",
"chunkId"
]
},
{
"registryId": "FE-REG-API",
"path": "src/contracts/api-operations.js",
"exportName": "API_OPERATIONS",
"owner": "feature-api-client-response-envelope-contract",
"requiredFields": [
"method",
"path",
"operationId",
"auth",
"timeoutMs",
"idempotency",
"requestSchema",
"responseSchema",
"owner"
]
},
{
"registryId": "FE-REG-ENV",
"path": "src/contracts/env.js",
"exportName": "ENV_REGISTRY",
"owner": "feature-frontend-env-runtime-config-contract",
"requiredFields": ["phase", "classification", "required", "defaultValue"]
},
{
"registryId": "FE-REG-STORAGE",
"path": "src/contracts/storage-keys.js",
"exportName": "STORAGE_REGISTRY",
"owner": "feature-frontend-storage-registry-contract",
"requiredFields": [
"logicalName",
"physicalKey",
"backend",
"classification",
"schemaVersion",
"ttl",
"migration",
"quotaFallback"
]
},
{
"registryId": "FE-REG-ERROR",
"path": "src/contracts/errors.js",
"exportName": "ERROR_REGISTRY",
"owner": "feature-frontend-error-classification-boundary-contract",
"requiredFields": [
"kind",
"defaultRetryable",
"severity",
"userMessageKey",
"action",
"telemetryEvent",
"redaction"
]
},
{
"registryId": "FE-REG-QUERY",
"path": "src/contracts/query-keys.js",
"exportName": "QUERY_REGISTRY",
"owner": "feature-server-state-caching-contract",
"requiredFields": [
"namespace",
"serialization",
"identity",
"invalidation",
"version",
"persistence"
]
},
{
"registryId": "FE-REG-TELEMETRY",
"path": "src/contracts/telemetry.js",
"exportName": "TELEMETRY_REGISTRY",
"owner": "feature-frontend-observability-logging-trace-contract",
"requiredFields": [
"eventName",
"trigger",
"requiredAttributes",
"optionalAttributes",
"forbiddenAttributes",
"sampling",
"delivery"
]
},
{
"registryId": "FE-REG-RELEASE",
"path": "src/contracts/release-tokens.js",
"exportName": "RELEASE_TOKEN_REGISTRY",
"owner": "feature-frontend-release-cache-rollback-contract",
"requiredFields": ["token", "source", "compatibilityRole"]
}
],
"compatibilityImpact": {
"allowed": ["none", "additive", "behavior-change", "breaking"],
"current": "additive"
}
}
+31
View File
@@ -0,0 +1,31 @@
{
"schemaVersion": 1,
"surfaces": {
"index": {
"path": "/",
"cacheControl": "no-cache",
"securityHeaders": true
},
"runtimeConfig": {
"path": "/config.json",
"cacheControl": "no-store",
"securityHeaders": true
},
"releaseManifest": {
"path": "/release-manifest.json",
"cacheControl": "no-store",
"securityHeaders": true
},
"hashedAsset": {
"pathPattern": "/assets/*",
"cacheControl": "public, max-age=31536000, immutable",
"securityHeaders": false
},
"sourceMap": {
"public": false
},
"serviceWorker": {
"enabled": false
}
}
}
@@ -0,0 +1,35 @@
{
"schemaVersion": 1,
"responses": {
"index": {
"cache-control": "no-cache",
"content-security-policy": "default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'; form-action 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; connect-src 'self' https:; font-src 'self'; upgrade-insecure-requests",
"strict-transport-security": "max-age=31536000; includeSubDomains",
"x-frame-options": "DENY",
"referrer-policy": "strict-origin-when-cross-origin",
"x-content-type-options": "nosniff",
"permissions-policy": "camera=(), microphone=(), geolocation=()"
},
"runtimeConfig": {
"cache-control": "no-store",
"content-security-policy": "default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'; form-action 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; connect-src 'self' https:; font-src 'self'; upgrade-insecure-requests",
"strict-transport-security": "max-age=31536000; includeSubDomains",
"x-frame-options": "DENY",
"referrer-policy": "strict-origin-when-cross-origin",
"x-content-type-options": "nosniff",
"permissions-policy": "camera=(), microphone=(), geolocation=()"
},
"releaseManifest": {
"cache-control": "no-store",
"content-security-policy": "default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'; form-action 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; connect-src 'self' https:; font-src 'self'; upgrade-insecure-requests",
"strict-transport-security": "max-age=31536000; includeSubDomains",
"x-frame-options": "DENY",
"referrer-policy": "strict-origin-when-cross-origin",
"x-content-type-options": "nosniff",
"permissions-policy": "camera=(), microphone=(), geolocation=()"
},
"hashedAsset": {
"cache-control": "public, max-age=31536000, immutable"
}
}
}
+11
View File
@@ -0,0 +1,11 @@
{
"schemaVersion": 1,
"headers": {
"Content-Security-Policy": "default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'; form-action 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; connect-src 'self' https:; font-src 'self'; upgrade-insecure-requests",
"Strict-Transport-Security": "max-age=31536000; includeSubDomains",
"X-Frame-Options": "DENY",
"Referrer-Policy": "strict-origin-when-cross-origin",
"X-Content-Type-Options": "nosniff",
"Permissions-Policy": "camera=(), microphone=(), geolocation=()"
}
}
+18
View File
@@ -0,0 +1,18 @@
{
"schemaVersion": 1,
"bundle": {
"initialJsGzipBytes": 204800,
"lazyChunkGzipBytes": 122880
},
"lab": {
"lcpMs": 2500,
"cls": 0.1,
"namedInteractionMs": 200
},
"field": {
"p75LcpMs": 2500,
"p75Cls": 0.1,
"p75InpMs": 200,
"minimumEligibleSamples": null
}
}
@@ -0,0 +1,25 @@
{
"schemaVersion": 1,
"releaseId": "local-release",
"environment": "replace-with-production",
"source": {
"system": "",
"exportId": ""
},
"privacy": {
"approved": false,
"approvalRef": ""
},
"window": {
"start": "2026-06-01T00:00:00Z",
"end": "2026-06-29T00:00:00Z"
},
"thresholdDecision": {
"status": "pending",
"minimumEligibleSamples": null,
"owner": "",
"reviewedAt": "",
"evidenceRef": ""
},
"samples": []
}
+77
View File
@@ -0,0 +1,77 @@
{
"schemaVersion": 1,
"fixtures": [
{
"name": "coherent-release",
"expectedCompatible": true,
"frontend": {
"buildId": "build-a",
"configSchemaVersion": "1.0",
"apiContractVersion": "1.0",
"assetManifestHash": "assets-a",
"releaseId": "release-a"
},
"runtime": {
"buildId": "build-a",
"configSchemaVersion": "1.1",
"apiContractVersion": "1.2",
"assetManifestHash": "assets-a",
"releaseId": "release-a"
}
},
{
"name": "mixed-html-and-assets",
"expectedCompatible": false,
"frontend": {
"buildId": "build-a",
"configSchemaVersion": "1.0",
"apiContractVersion": "1.0",
"assetManifestHash": "assets-a",
"releaseId": "release-a"
},
"runtime": {
"buildId": "build-b",
"configSchemaVersion": "1.0",
"apiContractVersion": "1.0",
"assetManifestHash": "assets-b",
"releaseId": "release-b"
}
},
{
"name": "incompatible-runtime-config",
"expectedCompatible": false,
"frontend": {
"buildId": "build-a",
"configSchemaVersion": "1.0",
"apiContractVersion": "1.0",
"assetManifestHash": "assets-a",
"releaseId": "release-a"
},
"runtime": {
"buildId": "build-a",
"configSchemaVersion": "2.0",
"apiContractVersion": "1.0",
"assetManifestHash": "assets-a",
"releaseId": "release-a"
}
},
{
"name": "incompatible-api-contract",
"expectedCompatible": false,
"frontend": {
"buildId": "build-a",
"configSchemaVersion": "1.0",
"apiContractVersion": "1.0",
"assetManifestHash": "assets-a",
"releaseId": "release-a"
},
"runtime": {
"buildId": "build-a",
"configSchemaVersion": "1.0",
"apiContractVersion": "2.0",
"assetManifestHash": "assets-a",
"releaseId": "release-a"
}
}
]
}
@@ -0,0 +1,78 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "ART-FE-FIELD-WEB-VITALS@1",
"type": "object",
"required": [
"schemaVersion",
"generatedAt",
"window",
"context",
"metrics",
"thresholds",
"eligibility",
"status",
"passed"
],
"properties": {
"schemaVersion": { "const": 1 },
"generatedAt": { "type": "string", "format": "date-time" },
"window": { "type": "object", "required": ["days", "start", "end"] },
"context": {
"type": "object",
"required": [
"source",
"sourceSystem",
"exportId",
"network",
"routeAggregation",
"releaseId",
"privacyApprovalRef",
"thresholdDecisionRef",
"validationFailures"
],
"properties": {
"source": { "type": "string" },
"sourceSystem": { "type": ["string", "null"] },
"exportId": { "type": ["string", "null"] },
"network": { "const": "production-real-user" },
"routeAggregation": { "const": "route-id-only" },
"releaseId": { "type": ["string", "null"] },
"privacyApprovalRef": { "type": ["string", "null"] },
"thresholdDecisionRef": { "type": ["string", "null"] },
"validationFailures": {
"type": "array",
"items": { "type": "string" }
}
},
"additionalProperties": false
},
"thresholds": {
"type": "object",
"required": [
"p75LcpMs",
"p75Cls",
"p75InpMs",
"minimumEligibleSamples"
]
},
"metrics": {
"type": "object",
"required": ["p75LcpMs", "p75Cls", "p75InpMs"]
},
"eligibility": {
"type": "object",
"required": [
"consentRequired",
"totalSamples",
"eligibleSamples",
"minimumEligibleSamples",
"routeSamples"
]
},
"status": {
"enum": ["PASS", "FAIL_THRESHOLD", "FAIL_UNVERIFIED"]
},
"passed": { "type": "boolean" }
},
"additionalProperties": false
}
@@ -0,0 +1,30 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "ART-FE-LAB@1",
"type": "object",
"required": [
"schemaVersion",
"generatedAt",
"context",
"metrics",
"thresholds",
"fixtures",
"passed"
],
"properties": {
"schemaVersion": { "const": 1 },
"generatedAt": { "type": "string", "format": "date-time" },
"context": {
"type": "object",
"required": ["runner", "browser", "viewport", "network", "cpu", "cache", "build"]
},
"metrics": {
"type": "object",
"required": ["lcpMs", "cls", "namedInteractionMs"]
},
"thresholds": { "type": "object" },
"fixtures": { "type": "array", "minItems": 2 },
"passed": { "type": "boolean" }
},
"additionalProperties": false
}
@@ -0,0 +1,17 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "ART-FE-003@1",
"type": "object",
"required": ["schemaVersion", "generatedAt", "artifact", "fixtures", "passed"],
"properties": {
"schemaVersion": { "const": 1 },
"generatedAt": { "type": "string", "format": "date-time" },
"artifact": {
"type": "object",
"required": ["checked", "compatible", "mismatches"]
},
"fixtures": { "type": "array", "minItems": 2 },
"passed": { "type": "boolean" }
},
"additionalProperties": false
}
+11
View File
@@ -0,0 +1,11 @@
# Contract compatibility and rollback rules
The blocking tuple is `(buildId, configSchemaVersion, apiContractVersion,
assetManifestHash, releaseId)`. Versions are parsed numerically.
1. additive changes preserve current required fields
2. breaking changes require a major version bump
3. persisted cache is discarded unless an explicit tested migration exists
4. an incompatible config or API contract blocks product mount
5. rollback restores HTML, assets, runtime config, API compatibility, and
release manifest as one coherent set
+22
View File
@@ -0,0 +1,22 @@
# Performance evidence contract
Performance evidence is deliberately split by measurement context:
- `bundle.json` records production build output and enforces initial JavaScript
at 200 KiB gzip and every lazy chunk at 120 KiB gzip.
- `lab.json` records Chromium/runner/viewport/network/CPU/cache/build context and
enforces LCP 2.5 s, CLS 0.10, and the named route interaction at 200 ms.
- `field-web-vitals.json` records consent-filtered, route-ID aggregated,
release-specific production samples over 28 days and evaluates p75 LCP, CLS,
and INP against 2.5 s, 0.10, and 200 ms.
The field minimum eligible-sample threshold is intentionally unresolved until
a privacy-approved telemetry baseline exists. Therefore the field command
fails closed with `FAIL_UNVERIFIED` when run against the example input. Provide
`FIELD_WEB_VITALS_INPUT` and `MIN_ELIGIBLE_SAMPLES` only after that decision is
recorded. The external input must identify a production release and an exact
28-day export window, name the source/export, carry privacy-approval and
threshold-decision references, and contain only non-negative route-ID samples.
The environment threshold must be a positive integer equal to the approved
decision embedded in the input. Invalid metadata fails as `FAIL_UNVERIFIED`;
the example can never serve as production evidence.
+25
View File
@@ -0,0 +1,25 @@
# Release, cache, and rollback contract
Each deployment is an immutable `releases/<releaseId>/` artifact set. The
provider adapter must upload assets, release manifest, runtime config, and
verify asset reachability before atomically switching the active HTML pointer.
The post-switch boot, route, API, telemetry, and reload-loop smoke checks close
the deployment.
Rollback selects a prior release tuple, confirms its assets and runtime/API
compatibility, atomically switches the complete set, performs the provider
cache action, and repeats the smoke checks. Rebuilding an old commit, replacing
HTML alone, or declaring recovery from cache-purge completion is prohibited.
Recovery is established by old/new reachability probes.
The provider-independent cache defaults are:
- hashed assets: `public, max-age=31536000, immutable`
- HTML: `no-cache`
- runtime config and release manifest: `no-store`
- public source maps: disabled
- service worker/offline cache: disabled
`corepack pnpm verify:hosting-headers` uses a deterministic fixture locally.
Set `HOSTING_BASE_URL` to probe deployed responses; production promotion
requires the artifact to report `mode: "live"`.
+13
View File
@@ -0,0 +1,13 @@
# Browser security boundary
The browser bundle is public. Secrets, token lifecycle, raw HTML injection,
dynamic code execution, untrusted script URLs, and public production source
maps are prohibited defaults.
`config/hosting/security-headers.json` is the declared header set. Hosting
verification compares that declaration with live responses. CSP deliberately
omits `unsafe-inline` and `unsafe-eval`; production code and built assets must
remain compatible with that baseline.
Route guards are UX hints and client validation does not replace backend
authorization or validation.
+18
View File
@@ -0,0 +1,18 @@
# Build and supply-chain gate
Merge and release controls:
- frozen `pnpm-lock.yaml` installation; drift is blocking
- clean production build with hashed assets and build manifest
- machine-readable bundle sizes and checksums
- source plus built-asset credential-pattern scan
- direct dependency inventory and lockfile digest
- base/head dependency diff review record
Organization-specific vulnerability severity, denied-license list, SBOM format,
and scanner selection remain policy inputs. An approved suppression must record
reason, owner, expiry, affected package, and compensating control. Expired
suppressions are blocking.
`artifacts/security/dependency-diff.json` is a local baseline. CI replaces it
with the actual base/head direct and transitive lockfile diff before release.
+21
View File
@@ -35,6 +35,7 @@ export default [
"artifacts/**",
"tests/fixtures/typecheck/**",
"tests/fixtures/architecture/forbidden/**",
"tests/fixtures/security/forbidden/**",
],
},
eslint.configs.recommended,
@@ -98,4 +99,24 @@ export default [
]),
},
},
{
files: ["**/*.{js,jsx}"],
rules: {
"no-eval": "error",
"no-new-func": "error",
"no-script-url": "error",
"no-restricted-syntax": [
"error",
{
selector: "JSXAttribute[name.name='dangerouslySetInnerHTML']",
message: "Raw HTML injection is prohibited by FE-OC-019.",
},
{
selector:
"CallExpression[callee.object.name='document'][callee.property.name='createElement'][arguments.0.value='script']",
message: "Runtime script construction is prohibited by FE-OC-019.",
},
],
},
},
];
+13 -1
View File
@@ -11,6 +11,7 @@
"scripts": {
"dev": "vite",
"build": "vite build && node scripts/generate-build-manifest.mjs",
"build:release": "corepack pnpm build && corepack pnpm generate:supply-chain && corepack pnpm scan:security",
"preview": "vite preview",
"lint": "eslint src scripts tests vite.config.js vitest.config.js playwright.config.js --max-warnings=0",
"check:architecture": "node scripts/check-architecture.mjs",
@@ -24,7 +25,18 @@
"test:a11y": "playwright test --grep @a11y && node scripts/write-a11y-report.mjs",
"review:a11y-manual": "node scripts/verify-a11y-manual.mjs",
"test:sample-removal": "node scripts/test-sample-removal.mjs",
"test:all": "pnpm test:runtime-schema && pnpm test:unit && pnpm test:component && pnpm test:integration"
"test:all": "corepack pnpm test:runtime-schema && corepack pnpm test:unit && corepack pnpm test:component && corepack pnpm test:integration",
"verify:lockfile": "corepack pnpm install --frozen-lockfile",
"generate:supply-chain": "node scripts/generate-supply-chain.mjs",
"scan:security": "node scripts/security-scan.mjs",
"check:browser-security": "node scripts/check-browser-security.mjs",
"check:registries": "node scripts/check-registries.mjs",
"verify:compatibility": "node scripts/check-compatibility.mjs",
"verify:release": "node scripts/verify-release.mjs",
"verify:hosting-headers": "node scripts/verify-hosting-headers.mjs",
"check:bundle": "node scripts/generate-supply-chain.mjs && node scripts/check-bundle.mjs",
"test:performance": "node scripts/test-performance.mjs",
"collect:web-vitals-evidence": "node scripts/collect-web-vitals-evidence.mjs"
},
"dependencies": {
"@tanstack/react-query": "5.101.4",
+11
View File
@@ -0,0 +1,11 @@
{
"schemaVersion": 1,
"appVersion": "0.1.0",
"buildId": "local-build",
"commitSha": "local",
"configSchemaVersion": "1",
"apiContractVersion": "1",
"assetManifestHash": "generated-during-build",
"releaseId": "local-release",
"builtAt": "1970-01-01T00:00:00.000Z"
}
@@ -0,0 +1,39 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "build-manifest.schema.json",
"type": "object",
"required": [
"schemaVersion",
"buildId",
"commitSha",
"generatedAt",
"buildContext",
"outputs"
],
"properties": {
"schemaVersion": { "const": 1 },
"buildId": { "type": "string", "minLength": 1 },
"commitSha": { "type": "string", "minLength": 1 },
"generatedAt": { "type": "string", "format": "date-time" },
"buildContext": {
"type": "object",
"required": ["nodeVersion", "packageManagerVersion", "runnerImage"],
"properties": {
"nodeVersion": { "type": "string" },
"packageManagerVersion": { "type": "string" },
"runnerImage": { "type": "string" }
},
"additionalProperties": false
},
"outputs": {
"type": "object",
"required": ["directory", "viteManifest"],
"properties": {
"directory": { "type": "string" },
"viteManifest": { "type": "string" }
},
"additionalProperties": false
}
},
"additionalProperties": false
}
@@ -0,0 +1,29 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": [
"schemaVersion",
"generatedAt",
"compatibilityImpact",
"failures",
"registries"
],
"properties": {
"schemaVersion": { "const": 1 },
"generatedAt": { "type": "string", "format": "date-time" },
"compatibilityImpact": {
"enum": ["none", "additive", "behavior-change", "breaking"]
},
"failures": { "type": "array", "maxItems": 0 },
"registries": {
"type": "array",
"minItems": 8,
"maxItems": 8,
"items": {
"type": "object",
"required": ["registryId", "owner", "source", "rowCount", "rows"]
}
}
},
"additionalProperties": false
}
+42
View File
@@ -0,0 +1,42 @@
import { readdir } from "node:fs/promises";
import { spawnSync } from "node:child_process";
const pnpmCli = /** @type {string} */ (process.env.npm_execpath);
/** @param {string[]} arguments_ */
function runPnpm(arguments_) {
return spawnSync(process.execPath, [pnpmCli, ...arguments_], {
encoding: "utf8",
});
}
const allowed = runPnpm([
"exec",
"eslint",
"tests/fixtures/security/allowed",
"--no-ignore",
"--max-warnings=0",
]);
const forbidden = runPnpm([
"exec",
"eslint",
"tests/fixtures/security/forbidden",
"--no-ignore",
"--max-warnings=0",
]);
const distFiles = await readdir("dist", { recursive: true });
const publicSourceMaps = distFiles.filter((file) => String(file).endsWith(".map"));
if (allowed.status !== 0 || forbidden.status === 0 || publicSourceMaps.length > 0) {
process.stderr.write(allowed.stderr || allowed.stdout);
process.stderr.write(forbidden.stderr || forbidden.stdout);
if (publicSourceMaps.length > 0) {
process.stderr.write(`Public source maps found: ${publicSourceMaps.join(", ")}\n`);
}
process.exit(1);
}
process.stdout.write(
"Browser security fixtures: injection rejected, public source maps absent\n",
);
+100
View File
@@ -0,0 +1,100 @@
import { readFile, writeFile } from "node:fs/promises";
import { evaluateBundleBudget } from "../src/application/policies/performance-budgets.js";
import { classifyViteJavascript } from "./lib/classify-vite-bundle.mjs";
const report =
/** @type {{
* outputs: Array<{ path: string, gzipBytes: number }>,
* [key: string]: unknown
* }} */ (
JSON.parse(await readFile("artifacts/performance/bundle.json", "utf8"))
);
const viteManifest =
/** @type {Record<string, { file: string, isEntry?: boolean, imports?: string[] }>} */ (
JSON.parse(await readFile("dist/.vite/manifest.json", "utf8"))
);
const budgets =
/** @type {{ initialJsGzipBytes: number, lazyChunkGzipBytes: number }} */ (
JSON.parse(await readFile("config/performance/budgets.json", "utf8")).bundle
);
const outputByPath = new Map(
report.outputs.map((output) => [output.path.replace(/^dist\//, ""), output]),
);
const classification = classifyViteJavascript(viteManifest);
const initialJsGzipBytes = classification.initialFiles.reduce(
(total, file) => total + (outputByPath.get(file)?.gzipBytes ?? 0),
0,
);
const lazyChunks = classification.lazyFiles.map((file) => ({
path: file,
gzipBytes: outputByPath.get(file)?.gzipBytes ?? 0,
}));
const missingOutputs = [
...classification.initialFiles,
...classification.lazyFiles,
].filter((file) => !outputByPath.has(file));
const measurements = { initialJsGzipBytes, lazyChunks };
const result = evaluateBundleBudget(measurements, budgets);
const fixtures = [
{
name: "initial-js-over-budget",
passed:
!evaluateBundleBudget(
{
initialJsGzipBytes: budgets.initialJsGzipBytes + 1,
lazyChunks: [],
},
budgets,
).passed,
},
{
name: "lazy-chunk-over-budget",
passed:
!evaluateBundleBudget(
{
initialJsGzipBytes: 0,
lazyChunks: [
{
path: "fixture.js",
gzipBytes: budgets.lazyChunkGzipBytes + 1,
},
],
},
budgets,
).passed,
},
];
const passed =
result.passed &&
fixtures.every((fixture) => fixture.passed) &&
classification.missingImports.length === 0 &&
missingOutputs.length === 0;
const completedReport = {
...report,
measurements,
classification,
missingOutputs,
thresholds: budgets,
results: result,
fixtures,
passed,
};
await writeFile(
"artifacts/performance/bundle.json",
`${JSON.stringify(completedReport, null, 2)}\n`,
);
if (!passed) {
process.stderr.write(
`Bundle budget or manifest integrity failed: ${[
...classification.missingImports,
...missingOutputs,
].join(", ")}\n`,
);
process.exit(1);
}
process.stdout.write(
`Bundle budget: PASS (initial JS ${initialJsGzipBytes} / ${budgets.initialJsGzipBytes} gzip bytes)\n`,
);
+43
View File
@@ -0,0 +1,43 @@
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { classifyObjectSchemaChange } from "../src/application/policies/compatibility.js";
const fixtures = JSON.parse(
await readFile("config/compatibility/fixtures.json", "utf8"),
);
const results = [];
for (const [family, cases] of Object.entries(fixtures.families)) {
for (const expected of ["additive", "breaking"]) {
const fixture = cases[expected];
const actual = classifyObjectSchemaChange(fixture.before, fixture.after);
results.push({ family, expected, actual, passed: actual === expected });
}
}
await mkdir("artifacts/release", { recursive: true });
await writeFile(
"artifacts/release/compatibility.json",
`${JSON.stringify(
{
schemaVersion: 1,
generatedAt: new Date().toISOString(),
rules: [
"additive changes preserve required fields",
"breaking changes require version bump and migration, discard, fallback, or rollback",
"config and API major versions must match",
"incompatible persisted cache is discarded by default",
"rollback uses a coherent compatibility tuple",
],
results,
},
null,
2,
)}\n`,
);
if (results.some((result) => !result.passed)) {
process.stderr.write("Compatibility fixture classification failed.\n");
process.exit(1);
}
process.stdout.write("Compatibility fixtures: PASS\n");
+112
View File
@@ -0,0 +1,112 @@
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import { pathToFileURL } from "node:url";
const governance = JSON.parse(
await readFile("config/contracts/registry-governance.json", "utf8"),
);
const failures = [];
const owners = new Map();
const snapshots = [];
for (const specification of governance.registries) {
if (owners.has(specification.registryId)) {
failures.push(`duplicate owner for ${specification.registryId}`);
}
owners.set(specification.registryId, specification.owner);
let rows = specification.declaredRows;
try {
await access(specification.path);
const module = await import(
`${pathToFileURL(path.resolve(specification.path)).href}?registry-check=${Date.now()}`
);
rows = module[specification.exportName];
} catch {
if (!rows) failures.push(`missing registry source ${specification.path}`);
}
if (!rows || typeof rows !== "object" || Array.isArray(rows)) {
failures.push(`${specification.registryId} is not an object registry`);
continue;
}
for (const [rowName, row] of Object.entries(rows)) {
if (!row || typeof row !== "object" || Array.isArray(row)) {
failures.push(`${specification.registryId}.${rowName} is not an object`);
continue;
}
for (const field of specification.requiredFields) {
if (!(field in row)) {
failures.push(`${specification.registryId}.${rowName} missing ${field}`);
}
}
}
snapshots.push({
registryId: specification.registryId,
owner: specification.owner,
source: specification.path,
rowCount: Object.keys(rows).length,
rows,
});
}
const sourceFiles = [
"src/application",
"src/presentation",
"src/domain",
];
const adHocPatterns = [
{ name: "direct fetch", expression: /\bfetch\s*\(/ },
{ name: "direct localStorage", expression: /\blocalStorage\.(?:get|set|remove)Item/ },
{ name: "direct import.meta.env", expression: /\bimport\.meta\.env\./ },
{ name: "raw API path", expression: /["']\/api\// },
];
/** @param {string} directory */
async function scanDirectory(directory) {
const entries = await import("node:fs/promises").then(({ readdir }) =>
readdir(directory, { withFileTypes: true }),
);
for (const entry of entries) {
const target = path.join(directory, entry.name);
if (entry.isDirectory()) {
await scanDirectory(target);
continue;
}
if (!/\.(js|jsx|mjs)$/.test(entry.name)) continue;
const content = await readFile(target, "utf8");
for (const pattern of adHocPatterns) {
if (pattern.expression.test(content)) {
failures.push(`ad-hoc ${pattern.name} in ${target}`);
}
}
}
}
for (const sourceDirectory of sourceFiles) {
await scanDirectory(sourceDirectory);
}
await mkdir("artifacts/quality", { recursive: true });
await writeFile(
"artifacts/quality/registries.json",
`${JSON.stringify(
{
schemaVersion: 1,
generatedAt: new Date().toISOString(),
compatibilityImpact: governance.compatibilityImpact.current,
failures,
registries: snapshots,
},
null,
2,
)}\n`,
);
if (failures.length > 0) {
process.stderr.write(`Registry governance failed:\n${failures.join("\n")}\n`);
process.exit(1);
}
process.stdout.write(`Registry governance: ${snapshots.length} registries PASS\n`);
+106
View File
@@ -0,0 +1,106 @@
import { mkdir, readFile, writeFile } from "node:fs/promises";
import {
evaluateFieldBudget,
percentile75,
} from "../src/application/policies/performance-budgets.js";
import { validateFieldEvidenceInput } from "./lib/field-vitals-evidence.mjs";
const inputPath =
process.env.FIELD_WEB_VITALS_INPUT ??
"config/performance/field-input.example.json";
const rawInput = JSON.parse(await readFile(inputPath, "utf8"));
const now = new Date();
const validation = validateFieldEvidenceInput(
rawInput,
process.env.MIN_ELIGIBLE_SAMPLES,
now,
);
const input = validation.data;
const configured =
/** @type {{
* p75LcpMs: number,
* p75Cls: number,
* p75InpMs: number,
* minimumEligibleSamples: number | null
* }} */ (
JSON.parse(await readFile("config/performance/budgets.json", "utf8")).field
);
const minimumEligibleSamples = validation.minimumEligibleSamples;
const fallbackEnd = now;
const fallbackStart = new Date(fallbackEnd);
fallbackStart.setUTCDate(fallbackStart.getUTCDate() - 28);
const start = input ? new Date(input.window.start) : fallbackStart;
const end = input ? new Date(input.window.end) : fallbackEnd;
const eligible = (input?.samples ?? []).filter((sample) => {
const timestamp = new Date(sample.timestamp);
return (
sample.consent === true &&
sample.releaseId === input?.releaseId &&
timestamp >= start &&
timestamp <= end
);
});
const metrics = {
p75LcpMs: percentile75(eligible.map((sample) => sample.lcpMs)),
p75Cls: percentile75(eligible.map((sample) => sample.cls)),
p75InpMs: percentile75(eligible.map((sample) => sample.inpMs)),
};
const thresholds = { ...configured, minimumEligibleSamples };
const result = evaluateFieldBudget(
{ metrics, eligibleSamples: eligible.length },
thresholds,
);
const passed = validation.passed && result.passed;
const status = validation.passed ? result.status : "FAIL_UNVERIFIED";
const routeSamples = Object.fromEntries(
Object.entries(
eligible.reduce(
(counts, sample) => {
counts[sample.routeId] = (counts[sample.routeId] ?? 0) + 1;
return counts;
},
/** @type {Record<string, number>} */ ({}),
),
).sort(([left], [right]) => left.localeCompare(right)),
);
const report = {
schemaVersion: 1,
generatedAt: now.toISOString(),
window: { days: 28, start: start.toISOString(), end: end.toISOString() },
context: {
source: inputPath,
sourceSystem: input?.source.system ?? null,
exportId: input?.source.exportId ?? null,
network: "production-real-user",
routeAggregation: "route-id-only",
releaseId: input?.releaseId ?? null,
privacyApprovalRef: input?.privacy.approvalRef ?? null,
thresholdDecisionRef: input?.thresholdDecision.evidenceRef ?? null,
validationFailures: validation.failures,
},
metrics,
thresholds,
eligibility: {
consentRequired: true,
totalSamples: input?.samples.length ?? 0,
eligibleSamples: eligible.length,
minimumEligibleSamples,
routeSamples,
},
status,
passed,
};
await mkdir("artifacts/performance", { recursive: true });
await writeFile(
"artifacts/performance/field-web-vitals.json",
`${JSON.stringify(report, null, 2)}\n`,
);
if (!passed) {
process.stderr.write(
`Field Web Vitals: ${status} (approved threshold decision and valid 28-day production evidence are required)\n`,
);
process.exit(1);
}
process.stdout.write("Field Web Vitals: PASS\n");
+29 -1
View File
@@ -1,3 +1,4 @@
import { createHash } from "node:crypto";
import { mkdir, readFile, writeFile } from "node:fs/promises";
import process from "node:process";
@@ -5,13 +6,23 @@ const packageJson = JSON.parse(await readFile("package.json", "utf8"));
const packageManagerVersion = packageJson.packageManager.split("@").at(-1);
const buildId = process.env.VITE_BUILD_ID ?? "local-build";
const commitSha = process.env.VITE_COMMIT_SHA ?? "local";
const releaseId = process.env.RELEASE_ID ?? "local-release";
const runnerImage = process.env.CI_RUNNER_IMAGE ?? `${process.platform}-${process.arch}`;
const builtAt = new Date().toISOString();
const viteManifest = await readFile("dist/.vite/manifest.json");
const assetManifestHash = createHash("sha256")
.update(viteManifest)
.digest("hex");
const runtimeConfig = JSON.parse(await readFile("dist/config.json", "utf8"));
runtimeConfig.BUILD_ID = buildId;
runtimeConfig.RELEASE_ID = releaseId;
const manifest = {
schemaVersion: 1,
buildId,
commitSha,
generatedAt: new Date().toISOString(),
generatedAt: builtAt,
buildContext: {
nodeVersion: process.version,
packageManagerVersion,
@@ -23,7 +34,24 @@ const manifest = {
},
};
const releaseManifest = {
schemaVersion: 1,
appVersion: packageJson.version,
buildId,
commitSha,
configSchemaVersion: runtimeConfig.CONFIG_SCHEMA_VERSION,
apiContractVersion: runtimeConfig.API_CONTRACT_VERSION,
assetManifestHash,
releaseId,
builtAt,
};
await mkdir("artifacts/release", { recursive: true });
await writeFile("dist/config.json", `${JSON.stringify(runtimeConfig, null, 2)}\n`);
await writeFile(
"dist/release-manifest.json",
`${JSON.stringify(releaseManifest, null, 2)}\n`,
);
await writeFile(
"artifacts/release/build-manifest.json",
`${JSON.stringify(manifest, null, 2)}\n`,
+102
View File
@@ -0,0 +1,102 @@
import { createHash } from "node:crypto";
import { gzipSync } from "node:zlib";
import {
mkdir,
readFile,
readdir,
stat,
writeFile,
} from "node:fs/promises";
import path from "node:path";
/** @param {string} directory @returns {Promise<string[]>} */
async function filesWithin(directory) {
const entries = await readdir(directory, { withFileTypes: true });
const nested = /** @type {string[][]} */ (await Promise.all(
entries.map((entry) => {
const target = path.join(directory, entry.name);
return entry.isDirectory() ? filesWithin(target) : [target];
}),
));
return nested.flat().sort();
}
const packageJson = JSON.parse(await readFile("package.json", "utf8"));
const lockfile = await readFile("pnpm-lock.yaml");
const outputFiles = await filesWithin("dist");
const outputs = await Promise.all(
outputFiles.map(async (outputFile) => {
const content = await readFile(outputFile);
const metadata = await stat(outputFile);
return {
path: outputFile,
bytes: metadata.size,
gzipBytes: gzipSync(content).byteLength,
sha256: createHash("sha256").update(content).digest("hex"),
};
}),
);
const dependencies = {
...packageJson.dependencies,
...packageJson.devDependencies,
};
const inventory = Object.entries(dependencies)
.sort(([left], [right]) => left.localeCompare(right))
.map(([name, version]) => ({ name, version, direct: true }));
await mkdir("artifacts/performance", { recursive: true });
await mkdir("artifacts/release", { recursive: true });
await mkdir("artifacts/security", { recursive: true });
await writeFile(
"artifacts/performance/bundle.json",
`${JSON.stringify(
{
schemaVersion: 1,
generatedAt: new Date().toISOString(),
context: {
nodeVersion: process.version,
packageManager: packageJson.packageManager,
runnerImage: process.env.CI_RUNNER_IMAGE ?? `${process.platform}-${process.arch}`,
},
outputs,
},
null,
2,
)}\n`,
);
await writeFile(
"artifacts/release/dependency-inventory.json",
`${JSON.stringify(
{
schemaVersion: 1,
lockfileSha256: createHash("sha256").update(lockfile).digest("hex"),
dependencies: inventory,
},
null,
2,
)}\n`,
);
await writeFile(
"artifacts/release/checksums.txt",
`${outputs.map((output) => `${output.sha256} ${output.path}`).join("\n")}\n`,
);
await writeFile(
"artifacts/security/dependency-diff.json",
`${JSON.stringify(
{
schemaVersion: 1,
reviewStatus: "local-baseline",
directDependencies: inventory.length,
highRiskUnreviewed: [],
lockfileSha256: createHash("sha256").update(lockfile).digest("hex"),
},
null,
2,
)}\n`,
);
+49
View File
@@ -0,0 +1,49 @@
/**
* @typedef {{
* file: string,
* isEntry?: boolean,
* imports?: string[]
* }} ViteManifestEntry
*/
/**
* Static imports of an entry are part of initial JavaScript. Every remaining
* JavaScript output is governed by the lazy-chunk budget.
*
* @param {Record<string, ViteManifestEntry>} manifest
*/
export function classifyViteJavascript(manifest) {
const initialFiles = new Set();
const visitedKeys = new Set();
const pendingKeys = Object.entries(manifest)
.filter(([, entry]) => entry.isEntry)
.map(([key]) => key);
const missingImports = [];
while (pendingKeys.length > 0) {
const key = /** @type {string} */ (pendingKeys.pop());
if (visitedKeys.has(key)) continue;
visitedKeys.add(key);
const entry = manifest[key];
if (!entry) {
missingImports.push(key);
continue;
}
if (entry.file.endsWith(".js")) initialFiles.add(entry.file);
pendingKeys.push(...(entry.imports ?? []));
}
const allJavaScript = new Set(
Object.values(manifest)
.map((entry) => entry.file)
.filter((file) => file.endsWith(".js")),
);
const lazyFiles = [...allJavaScript].filter(
(file) => !initialFiles.has(file),
);
return Object.freeze({
initialFiles: Object.freeze([...initialFiles].sort()),
lazyFiles: Object.freeze(lazyFiles.sort()),
missingImports: Object.freeze(missingImports.sort()),
});
}
+122
View File
@@ -0,0 +1,122 @@
import { z } from "zod";
const WINDOW_MILLISECONDS = 28 * 24 * 60 * 60 * 1000;
const nonEmptyString = z.string().trim().min(1);
const timestamp = nonEmptyString.refine(
(value) => Number.isFinite(Date.parse(value)),
"must be an RFC 3339 timestamp",
);
const sampleSchema = z
.object({
timestamp,
consent: z.boolean(),
releaseId: nonEmptyString,
routeId: nonEmptyString.regex(/^[A-Z][A-Z0-9_]*$/),
lcpMs: z.number().finite().nonnegative(),
cls: z.number().finite().nonnegative(),
inpMs: z.number().finite().nonnegative(),
})
.strict();
const fieldEvidenceInputSchema = z
.object({
schemaVersion: z.literal(1),
environment: z.literal("production"),
releaseId: nonEmptyString.refine(
(value) => value !== "local-release",
"must identify an immutable production release",
),
source: z
.object({
system: nonEmptyString,
exportId: nonEmptyString,
})
.strict(),
privacy: z
.object({
approved: z.literal(true),
approvalRef: nonEmptyString,
})
.strict(),
window: z
.object({
start: timestamp,
end: timestamp,
})
.strict(),
thresholdDecision: z
.object({
status: z.literal("approved"),
minimumEligibleSamples: z.number().int().positive(),
owner: nonEmptyString,
reviewedAt: timestamp,
evidenceRef: nonEmptyString,
})
.strict(),
samples: z.array(sampleSchema),
})
.strict()
.superRefine((input, context) => {
const start = Date.parse(input.window.start);
const end = Date.parse(input.window.end);
if (end - start !== WINDOW_MILLISECONDS) {
context.addIssue({
code: "custom",
path: ["window"],
message: "must cover exactly 28 days",
});
}
});
/**
* @param {unknown} input
* @param {string | undefined} configuredMinimum
* @param {Date} [now]
*/
export function validateFieldEvidenceInput(
input,
configuredMinimum,
now = new Date(),
) {
const parsed = fieldEvidenceInputSchema.safeParse(input);
const failures = parsed.success
? []
: parsed.error.issues.map(
(issue) => `${issue.path.join(".") || "input"}: ${issue.message}`,
);
const minimumEligibleSamples = Number(configuredMinimum);
if (
configuredMinimum === undefined ||
!Number.isInteger(minimumEligibleSamples) ||
minimumEligibleSamples <= 0
) {
failures.push("MIN_ELIGIBLE_SAMPLES: must be a positive integer");
}
if (parsed.success) {
if (
parsed.data.thresholdDecision.minimumEligibleSamples !==
minimumEligibleSamples
) {
failures.push(
"MIN_ELIGIBLE_SAMPLES: does not match the approved threshold decision",
);
}
if (Date.parse(parsed.data.window.end) > now.getTime()) {
failures.push("window.end: must not be in the future");
}
if (Date.parse(parsed.data.thresholdDecision.reviewedAt) > now.getTime()) {
failures.push("thresholdDecision.reviewedAt: must not be in the future");
}
}
return Object.freeze({
data: parsed.success ? parsed.data : null,
failures: Object.freeze(failures),
minimumEligibleSamples:
Number.isInteger(minimumEligibleSamples) && minimumEligibleSamples > 0
? minimumEligibleSamples
: null,
passed: parsed.success && failures.length === 0,
});
}
+82
View File
@@ -0,0 +1,82 @@
import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
import path from "node:path";
const scanRoots = ["src", "dist"];
const findings = /** @type {Array<{ruleId: string, file: string}>} */ ([]);
const patterns = [
{ id: "private-key", expression: /-----BEGIN (?:RSA |EC )?PRIVATE KEY-----/g },
{ id: "aws-access-key", expression: /\bAKIA[0-9A-Z]{16}\b/g },
{ id: "github-token", expression: /\bgh[pousr]_[A-Za-z0-9_]{30,}\b/g },
{
id: "assigned-secret",
expression:
/\b(?:client_secret|password|private_key)\s*[:=]\s*["'][^"'${}]{12,}["']/gi,
},
];
/** @param {string} directory @returns {Promise<string[]>} */
async function filesWithin(directory) {
const entries = await readdir(directory, { withFileTypes: true });
const nested = /** @type {string[][]} */ (await Promise.all(
entries.map((entry) => {
const target = path.join(directory, entry.name);
return entry.isDirectory() ? filesWithin(target) : [target];
}),
));
return nested.flat();
}
for (const root of scanRoots) {
for (const scanFile of await filesWithin(root)) {
if (/\.(png|jpg|jpeg|gif|woff2?|zip)$/i.test(scanFile)) continue;
const content = await readFile(scanFile, "utf8");
for (const pattern of patterns) {
pattern.expression.lastIndex = 0;
if (pattern.expression.test(content)) {
findings.push({ ruleId: pattern.id, file: scanFile });
}
}
}
}
const sarif = {
version: "2.1.0",
$schema:
"https://json.schemastore.org/sarif-2.1.0.json",
runs: [
{
tool: {
driver: {
name: "ca-frontend-secret-scan",
rules: patterns.map((pattern) => ({
id: pattern.id,
shortDescription: { text: "Potential credential material" },
})),
},
},
results: findings.map((finding) => ({
ruleId: finding.ruleId,
message: { text: "Potential secret material must be removed." },
locations: [
{
physicalLocation: {
artifactLocation: { uri: finding.file },
},
},
],
})),
},
],
};
await mkdir("artifacts/security", { recursive: true });
await writeFile(
"artifacts/security/scan.sarif",
`${JSON.stringify(sarif, null, 2)}\n`,
);
if (findings.length > 0) {
process.stderr.write(`Security scan found ${findings.length} blocking result(s).\n`);
process.exit(1);
}
process.stdout.write("Source and built-asset secret scan: PASS\n");
+148
View File
@@ -0,0 +1,148 @@
import { spawn } from "node:child_process";
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { performance } from "node:perf_hooks";
import process from "node:process";
import { chromium } from "@playwright/test";
import { evaluateLabBudget } from "../src/application/policies/performance-budgets.js";
const server = spawn(
"corepack",
["pnpm", "preview", "--host", "127.0.0.1", "--port", "4173"],
{ stdio: "ignore" },
);
const baseUrl = "http://127.0.0.1:4173";
async function waitForServer() {
for (let attempt = 0; attempt < 50; attempt += 1) {
try {
const response = await fetch(baseUrl);
if (response.ok) return;
} catch {
// The bounded retry loop handles startup races.
}
await new Promise((resolve) => setTimeout(resolve, 100));
}
throw new Error("Preview server did not become ready.");
}
try {
await waitForServer();
const release = JSON.parse(
await readFile("dist/release-manifest.json", "utf8"),
);
const thresholds = JSON.parse(
await readFile("config/performance/budgets.json", "utf8"),
).lab;
const browser = await chromium.launch();
try {
const context = await browser.newContext({
viewport: { width: 1280, height: 720 },
});
const page = await context.newPage();
const cdp = await context.newCDPSession(page);
await cdp.send("Network.enable");
await cdp.send("Network.emulateNetworkConditions", {
offline: false,
latency: 40,
downloadThroughput: 200_000,
uploadThroughput: 93_750,
connectionType: "cellular4g",
});
await cdp.send("Emulation.setCPUThrottlingRate", { rate: 4 });
await page.addInitScript(() => {
const evidence = { lcpMs: 0, cls: 0 };
/** @type {any} */ (window).__contractPerformance = evidence;
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) evidence.lcpMs = entry.startTime;
}).observe({ type: "largest-contentful-paint", buffered: true });
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!(/** @type {any} */ (entry)).hadRecentInput) {
evidence.cls += /** @type {any} */ (entry).value;
}
}
}).observe({ type: "layout-shift", buffered: true });
});
await page.goto(baseUrl, { waitUntil: "networkidle" });
const interactionStarted = performance.now();
await page.getByRole("link", { name: "샘플 리소스" }).click();
await page.getByRole("heading", { name: "세션이 필요합니다." }).waitFor();
const namedInteractionMs = performance.now() - interactionStarted;
const paint = await page.evaluate(
() => /** @type {any} */ (window).__contractPerformance,
);
const contextMetadata = {
runner: {
platform: process.platform,
architecture: process.arch,
nodeVersion: process.version,
},
browser: { name: "chromium", version: await browser.version() },
viewport: { width: 1280, height: 720 },
network: {
profile: "contract-fast-4g",
latencyMs: 40,
downloadBytesPerSecond: 200_000,
uploadBytesPerSecond: 93_750,
},
cpu: { throttlingRate: 4 },
cache: { state: "cold", isolation: "new-browser-context" },
build: { buildId: release.buildId, releaseId: release.releaseId },
};
const metrics = {
lcpMs: Math.round(paint.lcpMs),
cls: Number(paint.cls.toFixed(4)),
namedInteractionMs: Math.round(namedInteractionMs),
};
const result = evaluateLabBudget(
{ context: contextMetadata, metrics },
thresholds,
);
const fixtures = [
{
name: "missing-context",
passed: !evaluateLabBudget({ metrics }, thresholds).passed,
},
{
name: "lcp-over-threshold",
passed: !evaluateLabBudget(
{
context: contextMetadata,
metrics: { ...metrics, lcpMs: thresholds.lcpMs + 1 },
},
thresholds,
).passed,
},
];
const passed = result.passed && fixtures.every((fixture) => fixture.passed);
await mkdir("artifacts/performance", { recursive: true });
await writeFile(
"artifacts/performance/lab.json",
`${JSON.stringify(
{
schemaVersion: 1,
generatedAt: new Date().toISOString(),
context: contextMetadata,
metrics,
thresholds,
fixtures,
passed,
},
null,
2,
)}\n`,
);
if (!passed) {
throw new Error(`Lab performance failed: ${JSON.stringify(metrics)}`);
}
process.stdout.write(
`Lab performance: PASS (LCP ${metrics.lcpMs}ms, CLS ${metrics.cls}, interaction ${metrics.namedInteractionMs}ms)\n`,
);
} finally {
await browser.close();
}
} finally {
server.kill("SIGTERM");
}
+110
View File
@@ -0,0 +1,110 @@
import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
const cachePolicy = JSON.parse(
await readFile("config/hosting/cache-policy.json", "utf8"),
);
const securityPolicy = JSON.parse(
await readFile("config/hosting/security-headers.json", "utf8"),
);
const baseUrl = process.env.HOSTING_BASE_URL;
/** @type {Record<string, Record<string, string>>} */
let responses;
let mode;
if (baseUrl) {
mode = "live";
const assets = await readdir("dist/assets");
const hashedAsset = assets.find((file) => !file.endsWith(".map"));
if (!hashedAsset) throw new Error("No built hashed asset found.");
const paths = {
index: "/",
runtimeConfig: "/config.json",
releaseManifest: "/release-manifest.json",
hashedAsset: `/assets/${hashedAsset}`,
};
responses = {};
for (const [surface, pathname] of Object.entries(paths)) {
const response = await fetch(new URL(pathname, baseUrl));
responses[surface] = Object.fromEntries(
[...response.headers.entries()].map(([name, value]) => [
name.toLowerCase(),
value,
]),
);
}
} else {
mode = "fixture";
responses = JSON.parse(
await readFile("config/hosting/response-headers.fixture.json", "utf8"),
).responses;
}
const results = [];
for (const [surface, policy] of Object.entries(cachePolicy.surfaces)) {
if (!("cacheControl" in policy)) continue;
const observed = responses[surface]?.["cache-control"];
results.push({
surface,
header: "cache-control",
expected: policy.cacheControl,
observed,
passed: observed === policy.cacheControl,
});
if (policy.securityHeaders) {
for (const [header, expected] of Object.entries(securityPolicy.headers)) {
const observedSecurity = responses[surface]?.[header.toLowerCase()];
results.push({
surface,
header: header.toLowerCase(),
expected,
observed: observedSecurity,
passed: observedSecurity === expected,
});
}
}
}
results.push({
surface: "sourceMap",
header: "public",
expected: false,
observed: cachePolicy.surfaces.sourceMap.public,
passed: cachePolicy.surfaces.sourceMap.public === false,
});
results.push({
surface: "serviceWorker",
header: "enabled",
expected: false,
observed: cachePolicy.surfaces.serviceWorker.enabled,
passed: cachePolicy.surfaces.serviceWorker.enabled === false,
});
const passed = results.every((result) => result.passed);
await mkdir("artifacts/release", { recursive: true });
await writeFile(
"artifacts/release/hosting-headers.json",
`${JSON.stringify(
{
schemaVersion: 1,
generatedAt: new Date().toISOString(),
mode,
baseUrl: baseUrl ?? null,
providerVerificationRequired: mode !== "live",
results,
passed,
},
null,
2,
)}\n`,
);
if (!passed) {
process.stderr.write("Hosting cache/security header verification failed.\n");
process.exit(1);
}
process.stdout.write(
`Hosting header contract: PASS (${mode}; live verification ${
mode === "live" ? "complete" : "required before promotion"
})\n`,
);
+87
View File
@@ -0,0 +1,87 @@
import { createHash } from "node:crypto";
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { verifyCompatibilityTuple } from "../src/application/policies/compatibility.js";
import { compareReleaseToRuntime } from "../src/contracts/release-tokens.js";
const fixturesDocument =
/** @type {{
* fixtures: Array<{
* name: string,
* expectedCompatible: boolean,
* frontend: {
* buildId: string,
* configSchemaVersion: string,
* apiContractVersion: string,
* assetManifestHash: string,
* releaseId: string
* },
* runtime: {
* buildId: string,
* configSchemaVersion: string,
* apiContractVersion: string,
* assetManifestHash: string,
* releaseId: string
* }
* }>
* }} */ (
JSON.parse(
await readFile("config/release/coherence-fixtures.json", "utf8"),
)
);
const release = JSON.parse(await readFile("dist/release-manifest.json", "utf8"));
const runtimeConfig = JSON.parse(await readFile("dist/config.json", "utf8"));
const viteManifest = await readFile("dist/.vite/manifest.json");
const actualAssetManifestHash = createHash("sha256")
.update(viteManifest)
.digest("hex");
const artifactComparison = compareReleaseToRuntime(release, runtimeConfig);
const artifactMismatches = [...artifactComparison.mismatches];
if (release.assetManifestHash !== actualAssetManifestHash) {
artifactMismatches.push("assetManifestContent");
}
const fixtures = fixturesDocument.fixtures.map((fixture) => {
const result = verifyCompatibilityTuple({
frontend: fixture.frontend,
runtime: fixture.runtime,
});
return {
name: fixture.name,
expectedCompatible: fixture.expectedCompatible,
actualCompatible: result.compatible,
mismatches: result.mismatches,
passed: result.compatible === fixture.expectedCompatible,
};
});
const artifact = {
checked: true,
compatible: artifactComparison.compatible && artifactMismatches.length === 0,
mismatches: artifactMismatches,
releaseId: release.releaseId,
};
const passed = artifact.compatible && fixtures.every((fixture) => fixture.passed);
const report = {
schemaVersion: 1,
generatedAt: new Date().toISOString(),
artifact,
fixtures,
passed,
};
await mkdir("artifacts/release", { recursive: true });
await writeFile(
"artifacts/release/verification.json",
`${JSON.stringify(report, null, 2)}\n`,
);
if (!passed) {
process.stderr.write(
`Release coherence failed: ${artifactMismatches.join(", ") || "fixture"}\n`,
);
process.exit(1);
}
process.stdout.write(
`Release coherence: PASS (${fixtures.length - 1} mixed fixtures rejected)\n`,
);
+102
View File
@@ -0,0 +1,102 @@
export const COMPATIBILITY_TUPLE_FIELDS = Object.freeze([
"buildId",
"configSchemaVersion",
"apiContractVersion",
"assetManifestHash",
"releaseId",
]);
/** @param {string} version */
export function parseNumericVersion(version) {
const match = /^(\d+)(?:\.(\d+))?(?:\.(\d+))?$/.exec(version);
if (!match) return null;
return {
major: Number(match[1]),
minor: Number(match[2] ?? 0),
patch: Number(match[3] ?? 0),
};
}
/** @param {string} supported @param {string} actual */
export function isVersionCompatible(supported, actual) {
const expected = parseNumericVersion(supported);
const candidate = parseNumericVersion(actual);
if (!expected || !candidate) return false;
return (
expected.major === candidate.major &&
candidate.minor >= expected.minor
);
}
/**
* @param {{
* frontend: {
* buildId: string,
* configSchemaVersion: string,
* apiContractVersion: string,
* assetManifestHash: string,
* releaseId: string
* },
* runtime: {
* buildId: string,
* configSchemaVersion: string,
* apiContractVersion: string,
* assetManifestHash: string,
* releaseId: string
* }
* }} input
*/
export function verifyCompatibilityTuple(input) {
const mismatches = [];
if (input.frontend.buildId !== input.runtime.buildId) mismatches.push("buildId");
if (
!isVersionCompatible(
input.frontend.configSchemaVersion,
input.runtime.configSchemaVersion,
)
) {
mismatches.push("configSchemaVersion");
}
if (
!isVersionCompatible(
input.frontend.apiContractVersion,
input.runtime.apiContractVersion,
)
) {
mismatches.push("apiContractVersion");
}
if (input.frontend.assetManifestHash !== input.runtime.assetManifestHash) {
mismatches.push("assetManifestHash");
}
const releaseWarning =
input.frontend.releaseId === input.runtime.releaseId
? null
: "releaseId";
return Object.freeze({
compatible: mismatches.length === 0,
mismatches: Object.freeze(mismatches),
warnings: Object.freeze(releaseWarning ? [releaseWarning] : []),
});
}
/**
* @param {{ required?: string[], properties?: Record<string, unknown> }} before
* @param {{ required?: string[], properties?: Record<string, unknown> }} after
*/
export function classifyObjectSchemaChange(before, after) {
const beforeRequired = new Set(before.required ?? []);
const afterRequired = new Set(after.required ?? []);
const removedProperties = Object.keys(before.properties ?? {}).filter(
(key) => !(key in (after.properties ?? {})),
);
const addedRequired = [...afterRequired].filter(
(key) => !beforeRequired.has(key),
);
if (removedProperties.length > 0 || addedRequired.length > 0) return "breaking";
const addedProperties = Object.keys(after.properties ?? {}).filter(
(key) => !(key in (before.properties ?? {})),
);
return addedProperties.length > 0 ? "additive" : "none";
}
@@ -0,0 +1,96 @@
/**
* @param {{
* initialJsGzipBytes: number,
* lazyChunks: Array<{ path: string, gzipBytes: number }>
* }} measurements
* @param {{ initialJsGzipBytes: number, lazyChunkGzipBytes: number }} thresholds
*/
export function evaluateBundleBudget(measurements, thresholds) {
const initialPassed =
measurements.initialJsGzipBytes <= thresholds.initialJsGzipBytes;
const lazyResults = measurements.lazyChunks.map((chunk) => ({
...chunk,
threshold: thresholds.lazyChunkGzipBytes,
passed: chunk.gzipBytes <= thresholds.lazyChunkGzipBytes,
}));
return Object.freeze({
initialPassed,
lazyResults: Object.freeze(lazyResults),
passed: initialPassed && lazyResults.every((chunk) => chunk.passed),
});
}
/**
* @param {{
* context?: Record<string, unknown>,
* metrics: { lcpMs: number, cls: number, namedInteractionMs: number }
* }} report
* @param {{ lcpMs: number, cls: number, namedInteractionMs: number }} thresholds
*/
export function evaluateLabBudget(report, thresholds) {
const requiredContext = [
"runner",
"browser",
"viewport",
"network",
"cpu",
"cache",
"build",
];
const missingContext = requiredContext.filter(
(field) => report.context?.[field] === undefined,
);
const results = {
lcp: report.metrics.lcpMs <= thresholds.lcpMs,
cls: report.metrics.cls <= thresholds.cls,
namedInteraction:
report.metrics.namedInteractionMs <= thresholds.namedInteractionMs,
};
return Object.freeze({
missingContext: Object.freeze(missingContext),
results: Object.freeze(results),
passed: missingContext.length === 0 && Object.values(results).every(Boolean),
});
}
/** @param {number[]} values */
export function percentile75(values) {
if (values.length === 0) return null;
const sorted = [...values].sort((left, right) => left - right);
return sorted[Math.ceil(sorted.length * 0.75) - 1];
}
/**
* @param {{
* metrics: { p75LcpMs: number | null, p75Cls: number | null, p75InpMs: number | null },
* eligibleSamples: number
* }} report
* @param {{
* p75LcpMs: number,
* p75Cls: number,
* p75InpMs: number,
* minimumEligibleSamples: number | null
* }} thresholds
*/
export function evaluateFieldBudget(report, thresholds) {
if (
thresholds.minimumEligibleSamples === null ||
report.eligibleSamples < thresholds.minimumEligibleSamples ||
Object.values(report.metrics).some((value) => value === null)
) {
return Object.freeze({
status: /** @type {const} */ ("FAIL_UNVERIFIED"),
passed: false,
});
}
const passed =
/** @type {number} */ (report.metrics.p75LcpMs) <= thresholds.p75LcpMs &&
/** @type {number} */ (report.metrics.p75Cls) <= thresholds.p75Cls &&
/** @type {number} */ (report.metrics.p75InpMs) <= thresholds.p75InpMs;
return Object.freeze({
status: passed
? /** @type {const} */ ("PASS")
: /** @type {const} */ ("FAIL_THRESHOLD"),
passed,
});
}
+65
View File
@@ -0,0 +1,65 @@
import { verifyCompatibilityTuple } from "../application/policies/compatibility.js";
export const RELEASE_TOKEN_REGISTRY = Object.freeze({
appVersion: token("appVersion", "manifest", "human release label"),
buildId: token("buildId", "CI build", "asset and HTML coherence"),
commitSha: token("commitSha", "VCS", "source traceability"),
configSchemaVersion: token(
"configSchemaVersion",
"runtime config schema",
"boot compatibility",
),
apiContractVersion: token(
"apiContractVersion",
"frontend/backend agreement",
"schema compatibility",
),
assetManifestHash: token(
"assetManifestHash",
"build output",
"chunk integrity and mismatch detection",
),
releaseId: token("releaseId", "deploy system", "rollback target"),
builtAt: token("builtAt", "CI", "diagnostics only; never cache identity"),
});
/**
* @param {string} name
* @param {string} source
* @param {string} compatibilityRole
*/
function token(name, source, compatibilityRole) {
return Object.freeze({ token: name, source, compatibilityRole });
}
/**
* Compare a release manifest and runtime configuration structurally. Version
* fields are delegated to the numeric compatibility policy, never compared
* lexically.
*
* @param {{
* buildId: string,
* configSchemaVersion: string,
* apiContractVersion: string,
* assetManifestHash: string,
* releaseId: string
* }} release
* @param {{
* BUILD_ID: string,
* CONFIG_SCHEMA_VERSION: string,
* API_CONTRACT_VERSION: string,
* RELEASE_ID: string
* }} runtimeConfig
*/
export function compareReleaseToRuntime(release, runtimeConfig) {
return verifyCompatibilityTuple({
frontend: release,
runtime: {
buildId: runtimeConfig.BUILD_ID,
configSchemaVersion: runtimeConfig.CONFIG_SCHEMA_VERSION,
apiContractVersion: runtimeConfig.API_CONTRACT_VERSION,
assetManifestHash: release.assetManifestHash,
releaseId: runtimeConfig.RELEASE_ID,
},
});
}
+9
View File
@@ -0,0 +1,9 @@
/**
* Untrusted content is rendered as a React text node. HTML interpretation is
* intentionally not offered by this template.
*
* @param {{ value: unknown }} props
*/
export function SafeText({ value }) {
return <span>{typeof value === "string" ? value : String(value ?? "")}</span>;
}
+53
View File
@@ -0,0 +1,53 @@
// @vitest-environment jsdom
import { render, screen } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import { SafeText } from "../../src/presentation/security/safe-text.jsx";
import { assertSafeConfigNames } from "../../src/contracts/env.js";
import { defineStorageKey } from "../../src/contracts/storage-keys.js";
import { projectTelemetryEvent } from "../../src/contracts/telemetry.js";
describe("browser security boundary", () => {
it("renders untrusted text without script or inline handler injection", () => {
render(
<SafeText value={'<img src=x onerror="window.compromised=true"><script>x</script>'} />,
);
expect(screen.getByText(/<img/)).toBeVisible();
expect(document.querySelector("script")).toBeNull();
expect(document.querySelector("[onerror]")).toBeNull();
});
it("rejects secret-like client configuration names", () => {
expect(() => assertSafeConfigNames({ PRIVATE_KEY: "not-public" })).toThrow();
});
it("rejects browser token storage registration", () => {
expect(() =>
defineStorageKey({
logicalName: "SESSION_TOKEN",
scope: "auth",
name: "session-token",
backend: "sessionStorage",
classification: "sensitive-forbidden",
schemaVersion: 1,
ttl: "session",
migration: "discard",
quotaFallback: "feature-disable",
}),
).toThrow();
});
it("drops raw URL/query/token telemetry attributes", () => {
const result = projectTelemetryEvent("api.request.failed", {
error_kind: "SERVER_FAILURE",
http_status_group: "5xx",
attempt_count_bucket: "1",
route_id: "APP_HOME",
raw_url: "https://api.test?token=private",
query_string: "token=private",
});
expect(result.success).toBe(true);
expect(JSON.stringify(result)).not.toMatch(/raw_url|query_string|private/);
});
});
+3
View File
@@ -0,0 +1,3 @@
export function Fixture({ value }) {
return <span>{value}</span>;
}
+5
View File
@@ -0,0 +1,5 @@
export function attachScript(source) {
const script = document.createElement("script");
script.src = source;
document.head.append(script);
}
+1
View File
@@ -0,0 +1 @@
export const execute = (source) => eval(source);
+3
View File
@@ -0,0 +1,3 @@
export function RawHtml({ value }) {
return <div dangerouslySetInnerHTML={{ __html: value }} />;
}
+40
View File
@@ -0,0 +1,40 @@
import { describe, expect, it } from "vitest";
import { classifyViteJavascript } from "../../scripts/lib/classify-vite-bundle.mjs";
describe("Vite bundle classification", () => {
it("counts transitive static imports as initial and keeps dynamic chunks lazy", () => {
expect(
classifyViteJavascript({
"index.html": {
file: "assets/entry.js",
isEntry: true,
imports: ["_shared.js"],
},
"_shared.js": { file: "assets/shared.js", imports: ["_runtime.js"] },
"_runtime.js": { file: "assets/runtime.js" },
"src/lazy.js": { file: "assets/lazy.js" },
}),
).toEqual({
initialFiles: [
"assets/entry.js",
"assets/runtime.js",
"assets/shared.js",
],
lazyFiles: ["assets/lazy.js"],
missingImports: [],
});
});
it("reports a manifest import that cannot be resolved", () => {
expect(
classifyViteJavascript({
"index.html": {
file: "assets/entry.js",
isEntry: true,
imports: ["_missing.js"],
},
}).missingImports,
).toEqual(["_missing.js"]);
});
});
+77
View File
@@ -0,0 +1,77 @@
import { describe, expect, it } from "vitest";
import {
classifyObjectSchemaChange,
isVersionCompatible,
parseNumericVersion,
verifyCompatibilityTuple,
} from "../../src/application/policies/compatibility.js";
describe("contract compatibility", () => {
it("uses numeric version parsing rather than lexical comparison", () => {
expect(parseNumericVersion("1.10.0")).toEqual({ major: 1, minor: 10, patch: 0 });
expect(isVersionCompatible("1.9", "1.10")).toBe(true);
expect(isVersionCompatible("1.9", "2.0")).toBe(false);
expect(isVersionCompatible("next", "1.0")).toBe(false);
});
it("distinguishes additive and breaking object changes", () => {
const base = { required: ["id"], properties: { id: {} } };
expect(
classifyObjectSchemaChange(base, {
required: ["id"],
properties: { id: {}, name: {} },
}),
).toBe("additive");
expect(
classifyObjectSchemaChange(base, {
required: ["id", "name"],
properties: { id: {}, name: {} },
}),
).toBe("breaking");
});
it("treats release ID mismatch as a warning when the blocking tuple is coherent", () => {
const frontend = {
buildId: "build-a",
configSchemaVersion: "1.0",
apiContractVersion: "1.0",
assetManifestHash: "hash-a",
releaseId: "release-a",
};
expect(
verifyCompatibilityTuple({
frontend,
runtime: { ...frontend, releaseId: "release-b" },
}),
).toEqual({
compatible: true,
mismatches: [],
warnings: ["releaseId"],
});
});
it("blocks mixed build, config, API, or asset tuples", () => {
const frontend = {
buildId: "build-a",
configSchemaVersion: "1.0",
apiContractVersion: "1.0",
assetManifestHash: "hash-a",
releaseId: "release-a",
};
expect(
verifyCompatibilityTuple({
frontend,
runtime: {
...frontend,
buildId: "build-b",
configSchemaVersion: "2.0",
assetManifestHash: "hash-b",
},
}),
).toMatchObject({
compatible: false,
mismatches: ["buildId", "configSchemaVersion", "assetManifestHash"],
});
});
});
+94
View File
@@ -0,0 +1,94 @@
import { describe, expect, it } from "vitest";
import { validateFieldEvidenceInput } from "../../scripts/lib/field-vitals-evidence.mjs";
const input = {
schemaVersion: 1,
environment: "production",
releaseId: "release-2026-06-29",
source: {
system: "privacy-approved-rum-export",
exportId: "export-2026-06-29",
},
privacy: {
approved: true,
approvalRef: "PRIVACY-42",
},
window: {
start: "2026-06-01T00:00:00Z",
end: "2026-06-29T00:00:00Z",
},
thresholdDecision: {
status: "approved",
minimumEligibleSamples: 25,
owner: "performance-owner",
reviewedAt: "2026-06-30T00:00:00Z",
evidenceRef: "PERF-BASELINE-7",
},
samples: [
{
timestamp: "2026-06-20T00:00:00Z",
consent: true,
releaseId: "release-2026-06-29",
routeId: "APP_HOME",
lcpMs: 1200,
cls: 0.01,
inpMs: 80,
},
],
};
describe("field Web Vitals evidence input", () => {
it("accepts reviewed, coherent 28-day production metadata", () => {
expect(
validateFieldEvidenceInput(
input,
"25",
new Date("2026-07-01T00:00:00Z"),
),
).toMatchObject({
failures: [],
minimumEligibleSamples: 25,
passed: true,
});
});
it("rejects a threshold that does not match the owner decision", () => {
expect(
validateFieldEvidenceInput(
input,
"10",
new Date("2026-07-01T00:00:00Z"),
),
).toMatchObject({
failures: [
"MIN_ELIGIBLE_SAMPLES: does not match the approved threshold decision",
],
passed: false,
});
});
it("rejects local, unapproved, malformed, or impossible measurements", () => {
const invalid = {
...input,
environment: "local",
releaseId: "local-release",
privacy: { approved: false, approvalRef: "" },
window: { ...input.window, end: "2026-06-28T00:00:00Z" },
samples: [{ ...input.samples[0], lcpMs: -1 }],
};
const validation = validateFieldEvidenceInput(
invalid,
"-1",
new Date("2026-07-01T00:00:00Z"),
);
expect(validation.passed).toBe(false);
expect(validation.failures.join("\n")).toContain("environment");
expect(validation.failures.join("\n")).toContain("releaseId");
expect(validation.failures.join("\n")).toContain("privacy");
expect(validation.failures.join("\n")).toContain("lcpMs");
expect(validation.failures.join("\n")).toContain(
"MIN_ELIGIBLE_SAMPLES: must be a positive integer",
);
});
});
+77
View File
@@ -0,0 +1,77 @@
import { describe, expect, it } from "vitest";
import {
evaluateBundleBudget,
evaluateFieldBudget,
evaluateLabBudget,
percentile75,
} from "../../src/application/policies/performance-budgets.js";
describe("performance budgets", () => {
it("rejects initial and lazy JavaScript above their named limits", () => {
const thresholds = {
initialJsGzipBytes: 200,
lazyChunkGzipBytes: 120,
};
expect(
evaluateBundleBudget(
{ initialJsGzipBytes: 201, lazyChunks: [] },
thresholds,
).passed,
).toBe(false);
expect(
evaluateBundleBudget(
{
initialJsGzipBytes: 100,
lazyChunks: [{ path: "lazy.js", gzipBytes: 121 }],
},
thresholds,
).passed,
).toBe(false);
});
it("fails lab evidence when context is absent or a metric is over budget", () => {
const thresholds = { lcpMs: 2500, cls: 0.1, namedInteractionMs: 200 };
expect(
evaluateLabBudget(
{ metrics: { lcpMs: 1000, cls: 0, namedInteractionMs: 50 } },
thresholds,
),
).toMatchObject({ passed: false });
expect(
evaluateLabBudget(
{
context: {
runner: {},
browser: {},
viewport: {},
network: {},
cpu: {},
cache: {},
build: {},
},
metrics: { lcpMs: 2501, cls: 0, namedInteractionMs: 50 },
},
thresholds,
).passed,
).toBe(false);
});
it("uses the nearest-rank p75 and fails closed while sample minimum is deferred", () => {
expect(percentile75([4, 1, 3, 2])).toBe(3);
expect(
evaluateFieldBudget(
{
metrics: { p75LcpMs: 1000, p75Cls: 0.01, p75InpMs: 50 },
eligibleSamples: 100,
},
{
p75LcpMs: 2500,
p75Cls: 0.1,
p75InpMs: 200,
minimumEligibleSamples: null,
},
),
).toEqual({ status: "FAIL_UNVERIFIED", passed: false });
});
});
+21
View File
@@ -0,0 +1,21 @@
import { readFile } from "node:fs/promises";
import { describe, expect, it } from "vitest";
describe("registry governance manifest", () => {
it("declares exactly eight single-owner registries and impact labels", async () => {
const governance = JSON.parse(
await readFile("config/contracts/registry-governance.json", "utf8"),
);
expect(governance.registries).toHaveLength(8);
expect(new Set(governance.registries.map((entry) => entry.registryId)).size).toBe(
8,
);
expect(governance.registries.every((entry) => entry.owner)).toBe(true);
expect(governance.compatibilityImpact.allowed).toEqual([
"none",
"additive",
"behavior-change",
"breaking",
]);
});
});
+54
View File
@@ -0,0 +1,54 @@
import { describe, expect, it } from "vitest";
import {
compareReleaseToRuntime,
RELEASE_TOKEN_REGISTRY,
} from "../../src/contracts/release-tokens.js";
describe("release coherence", () => {
it("owns all eight release tokens and keeps builtAt diagnostic-only", () => {
expect(Object.keys(RELEASE_TOKEN_REGISTRY)).toHaveLength(8);
expect(RELEASE_TOKEN_REGISTRY.builtAt.compatibilityRole).toContain(
"never cache identity",
);
});
it("compares the runtime config to the release structurally", () => {
const release = {
buildId: "build-a",
configSchemaVersion: "1.0",
apiContractVersion: "1.0",
assetManifestHash: "assets-a",
releaseId: "release-a",
};
expect(
compareReleaseToRuntime(release, {
BUILD_ID: "build-a",
CONFIG_SCHEMA_VERSION: "1.2",
API_CONTRACT_VERSION: "1.1",
RELEASE_ID: "release-a",
}),
).toMatchObject({ compatible: true, mismatches: [] });
});
it("rejects HTML-only rollback against a newer runtime config", () => {
const oldRelease = {
buildId: "build-old",
configSchemaVersion: "1.0",
apiContractVersion: "1.0",
assetManifestHash: "assets-old",
releaseId: "release-old",
};
expect(
compareReleaseToRuntime(oldRelease, {
BUILD_ID: "build-new",
CONFIG_SCHEMA_VERSION: "2.0",
API_CONTRACT_VERSION: "2.0",
RELEASE_ID: "release-new",
}),
).toMatchObject({
compatible: false,
mismatches: ["buildId", "configSchemaVersion", "apiContractVersion"],
});
});
});