diff --git a/docs/release-control/v6/internal/subsystems/deployment-installability.md b/docs/release-control/v6/internal/subsystems/deployment-installability.md index 7c40835be..311f1a871 100644 --- a/docs/release-control/v6/internal/subsystems/deployment-installability.md +++ b/docs/release-control/v6/internal/subsystems/deployment-installability.md @@ -984,6 +984,12 @@ upgrade, update, release, or artifact-selection behavior. effective on slow devices), while dynamic-import integrity remains enforced through the generated import map `integrity` block. Reintroducing whole-app preloading is a governed regression, not a tuning knob. + `frontend-modern/scripts/check-bundle-size.mjs` pins this posture against the + built output (modulepreload links limited to the entry's static imports, + import map `integrity` coverage of every built JS asset) and is the accepted + build-output verification proof for `frontend-modern/vite.config.ts` changes + under the `frontend-build-output` path policy, alongside the dev-runtime + orchestration proofs for dev-server-facing edits to the same file. Managed browser verification must also restart an existing hot-dev session when a verification lock is active or the runtime auth file no longer matches the deterministic dev user/hash. `tests/integration/scripts/run-playwright.mjs` diff --git a/docs/release-control/v6/internal/subsystems/registry.json b/docs/release-control/v6/internal/subsystems/registry.json index 2afeb05d3..ddb815343 100644 --- a/docs/release-control/v6/internal/subsystems/registry.json +++ b/docs/release-control/v6/internal/subsystems/registry.json @@ -4713,12 +4713,31 @@ "scripts/installtests/build_release_assets_test.go" ] }, + { + "id": "frontend-build-output", + "label": "frontend production build output proof", + "match_prefixes": [], + "match_files": [ + "frontend-modern/vite.config.ts" + ], + "allow_same_subsystem_tests": false, + "test_prefixes": [], + "exact_files": [ + "frontend-modern/scripts/check-bundle-size.mjs", + "scripts/release_control/ssh_host_key_policy_test.py", + "scripts/tests/test-hot-dev-auth.sh", + "scripts/tests/test-hot-dev-bg.sh", + "scripts/tests/test-hot-dev-runtime.sh", + "scripts/tests/test-toggle-mock.sh", + "tests/integration/scripts/managed-local-backend.test.mjs", + "tests/integration/tests/16-dev-runtime-recovery.spec.ts" + ] + }, { "id": "dev-runtime-orchestration", "label": "dev runtime orchestration proof", "match_prefixes": [], "match_files": [ - "frontend-modern/vite.config.ts", "go.mod", "go.sum", "Makefile", diff --git a/frontend-modern/scripts/check-bundle-size.mjs b/frontend-modern/scripts/check-bundle-size.mjs index bcad6f60f..50518763f 100644 --- a/frontend-modern/scripts/check-bundle-size.mjs +++ b/frontend-modern/scripts/check-bundle-size.mjs @@ -12,7 +12,10 @@ * 3. Groups files sharing the same logical name (e.g. two "Dashboard" chunks) * 4. Computes gzip size for each file using Node zlib (level 6, default) * 5. Compares per-chunk and total gzip sizes against .bundlesize.json thresholds - * 6. Exits 0 on pass, 1 on any violation + * 6. Asserts the built index.html preload posture: modulepreload links are + * limited to the entry's static imports (no lazy route chunks), and the + * import map integrity block covers every built JS asset + * 7. Exits 0 on pass, 1 on any violation */ import { readdirSync, readFileSync, writeFileSync } from 'node:fs'; @@ -90,6 +93,103 @@ function measureBuild() { return { groups, totalJsGzip, totalCssGzip }; } +// --------------------------------------------------------------------------- +// Built index.html preload posture +// --------------------------------------------------------------------------- + +/** + * Verify the deployment-installability preload invariant against the built + * index.html (see the subsystem contract clause on modulepreload posture): + * - the import map integrity block exists and covers every JS asset in + * dist/assets, so dynamic-import SRI stays enforced without preloading; + * - modulepreload links reference only the entry chunk's static imports — + * preloading lazy route chunks (preloadDynamicChunks: true) would fetch + * the whole app at cold start and defeat route-level code splitting. + * Returns a list of violation messages (empty on pass). + */ +function checkPreloadPosture() { + const errors = []; + let html; + try { + html = readFileSync(join(ROOT, 'dist', 'index.html'), 'utf8'); + } catch { + errors.push('dist/index.html not found. Run "npx vite build" first.'); + return errors; + } + + const importmapMatch = html.match(/