@empire-builder-kit/nx 1.0.0-beta.9 → 1.0.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE +6 -0
- package/README.md +113 -3
- package/executors.json +30 -0
- package/package.json +84 -6
- package/src/api-contract/baseline-update.d.ts +82 -0
- package/src/api-contract/baseline-update.js +225 -0
- package/src/api-contract/baseline-update.js.map +1 -0
- package/src/api-contract/compatibility.d.ts +30 -0
- package/src/api-contract/compatibility.js +643 -0
- package/src/api-contract/compatibility.js.map +1 -0
- package/src/api-contract/contract-graph.d.ts +35 -0
- package/src/api-contract/contract-graph.js +141 -0
- package/src/api-contract/contract-graph.js.map +1 -0
- package/src/api-contract/evidence.d.ts +23 -0
- package/src/api-contract/evidence.js +87 -0
- package/src/api-contract/evidence.js.map +1 -0
- package/src/api-contract/index.d.ts +6 -0
- package/src/api-contract/index.js +10 -0
- package/src/api-contract/index.js.map +1 -0
- package/src/api-contract/model.d.ts +144 -0
- package/src/api-contract/model.fixtures.d.ts +2 -0
- package/src/api-contract/model.fixtures.js +81 -0
- package/src/api-contract/model.fixtures.js.map +1 -0
- package/src/api-contract/model.js +993 -0
- package/src/api-contract/model.js.map +1 -0
- package/src/api-contract/rollout.d.ts +32 -0
- package/src/api-contract/rollout.js +88 -0
- package/src/api-contract/rollout.js.map +1 -0
- package/src/authorization/compatibility.d.ts +62 -0
- package/src/authorization/compatibility.js +175 -0
- package/src/authorization/compatibility.js.map +1 -0
- package/src/authorization/index.d.ts +1 -0
- package/src/authorization/index.js +5 -0
- package/src/authorization/index.js.map +1 -0
- package/src/blueprints/execution.d.ts +25 -1
- package/src/blueprints/execution.js +246 -83
- package/src/blueprints/execution.js.map +1 -1
- package/src/blueprints/external-sandbox.d.ts +53 -0
- package/src/blueprints/external-sandbox.js +1182 -0
- package/src/blueprints/external-sandbox.js.map +1 -0
- package/src/blueprints/manifest.d.ts +42 -2
- package/src/blueprints/manifest.js +82 -27
- package/src/blueprints/manifest.js.map +1 -1
- package/src/blueprints/resolver.d.ts +14 -5
- package/src/blueprints/resolver.js +228 -68
- package/src/blueprints/resolver.js.map +1 -1
- package/src/cli/agent-command.d.ts +30 -0
- package/src/cli/agent-command.js +204 -0
- package/src/cli/agent-command.js.map +1 -0
- package/src/cli/agent-runtime.d.ts +25 -0
- package/src/cli/agent-runtime.js +488 -0
- package/src/cli/agent-runtime.js.map +1 -0
- package/src/cli/blueprint-authority.d.ts +9 -0
- package/src/cli/blueprint-authority.js +43 -0
- package/src/cli/blueprint-authority.js.map +1 -0
- package/src/cli/blueprint-command.d.ts +24 -0
- package/src/cli/blueprint-command.js +63 -0
- package/src/cli/blueprint-command.js.map +1 -0
- package/src/cli/bootstrap.d.ts +31 -0
- package/src/cli/bootstrap.js +165 -0
- package/src/cli/bootstrap.js.map +1 -0
- package/src/cli/dev-command.d.ts +92 -0
- package/src/cli/dev-command.js +561 -0
- package/src/cli/dev-command.js.map +1 -0
- package/src/cli/main.d.ts +131 -0
- package/src/cli/main.js +1199 -0
- package/src/cli/main.js.map +1 -0
- package/src/cli/output.d.ts +19 -0
- package/src/cli/output.js +51 -0
- package/src/cli/output.js.map +1 -0
- package/src/cli/personal-secret-runtime.d.ts +81 -0
- package/src/cli/personal-secret-runtime.js +567 -0
- package/src/cli/personal-secret-runtime.js.map +1 -0
- package/src/cli/secret-terminal.d.ts +12 -0
- package/src/cli/secret-terminal.js +128 -0
- package/src/cli/secret-terminal.js.map +1 -0
- package/src/container-images.d.ts +1 -0
- package/src/container-images.js +5 -0
- package/src/container-images.js.map +1 -0
- package/src/data-lifecycle/index.d.ts +3 -0
- package/src/data-lifecycle/index.js +7 -0
- package/src/data-lifecycle/index.js.map +1 -0
- package/src/data-lifecycle/model.d.ts +172 -0
- package/src/data-lifecycle/model.fixtures.d.ts +2 -0
- package/src/data-lifecycle/model.fixtures.js +244 -0
- package/src/data-lifecycle/model.fixtures.js.map +1 -0
- package/src/data-lifecycle/model.js +95 -0
- package/src/data-lifecycle/model.js.map +1 -0
- package/src/data-lifecycle/sensitive-artifacts.d.ts +28 -0
- package/src/data-lifecycle/sensitive-artifacts.js +146 -0
- package/src/data-lifecycle/sensitive-artifacts.js.map +1 -0
- package/src/data-lifecycle/validation.d.ts +32 -0
- package/src/data-lifecycle/validation.js +837 -0
- package/src/data-lifecycle/validation.js.map +1 -0
- package/src/database-retirement/contract.d.ts +68 -0
- package/src/database-retirement/contract.js +166 -0
- package/src/database-retirement/contract.js.map +1 -0
- package/src/database-retirement/index.d.ts +1 -0
- package/src/database-retirement/index.js +5 -0
- package/src/database-retirement/index.js.map +1 -0
- package/src/deterministic-order.d.ts +2 -0
- package/src/deterministic-order.js +7 -0
- package/src/deterministic-order.js.map +1 -0
- package/src/developer-agent/context.d.ts +27 -0
- package/src/developer-agent/context.js +497 -0
- package/src/developer-agent/context.js.map +1 -0
- package/src/developer-agent/generator-planning.d.ts +34 -0
- package/src/developer-agent/generator-planning.js +645 -0
- package/src/developer-agent/generator-planning.js.map +1 -0
- package/src/developer-agent/generator-preview.d.ts +8 -0
- package/src/developer-agent/generator-preview.js +650 -0
- package/src/developer-agent/generator-preview.js.map +1 -0
- package/src/developer-agent/git-inspection.d.ts +108 -0
- package/src/developer-agent/git-inspection.js +815 -0
- package/src/developer-agent/git-inspection.js.map +1 -0
- package/src/developer-agent/handoff.d.ts +43 -0
- package/src/developer-agent/handoff.js +698 -0
- package/src/developer-agent/handoff.js.map +1 -0
- package/src/developer-agent/index.d.ts +10 -0
- package/src/developer-agent/index.js +14 -0
- package/src/developer-agent/index.js.map +1 -0
- package/src/developer-agent/model.d.ts +481 -0
- package/src/developer-agent/model.js +143 -0
- package/src/developer-agent/model.js.map +1 -0
- package/src/developer-agent/operation-registry.d.ts +45 -0
- package/src/developer-agent/operation-registry.js +1255 -0
- package/src/developer-agent/operation-registry.js.map +1 -0
- package/src/developer-agent/ordering.d.ts +9 -0
- package/src/developer-agent/ordering.js +15 -0
- package/src/developer-agent/ordering.js.map +1 -0
- package/src/developer-agent/planning.d.ts +18 -0
- package/src/developer-agent/planning.js +447 -0
- package/src/developer-agent/planning.js.map +1 -0
- package/src/developer-agent/preview-changes.d.ts +26 -0
- package/src/developer-agent/preview-changes.js +203 -0
- package/src/developer-agent/preview-changes.js.map +1 -0
- package/src/developer-agent/preview-source.d.ts +2 -0
- package/src/developer-agent/preview-source.js +16 -0
- package/src/developer-agent/preview-source.js.map +1 -0
- package/src/developer-agent/validation.d.ts +18 -0
- package/src/developer-agent/validation.js +2617 -0
- package/src/developer-agent/validation.js.map +1 -0
- package/src/developer-agent/workspace-inspection.d.ts +20 -0
- package/src/developer-agent/workspace-inspection.js +1134 -0
- package/src/developer-agent/workspace-inspection.js.map +1 -0
- package/src/developer-agent/workspace-path.d.ts +6 -0
- package/src/developer-agent/workspace-path.js +22 -0
- package/src/developer-agent/workspace-path.js.map +1 -0
- package/src/executors/blueprint-trust/blueprint-trust.d.ts +29 -0
- package/src/executors/blueprint-trust/blueprint-trust.js +115 -0
- package/src/executors/blueprint-trust/blueprint-trust.js.map +1 -0
- package/src/executors/blueprint-trust/schema.json +27 -0
- package/src/executors/blueprint-verification/blueprint-verification.d.ts +13 -0
- package/src/executors/blueprint-verification/blueprint-verification.js +110 -0
- package/src/executors/blueprint-verification/blueprint-verification.js.map +1 -0
- package/src/executors/blueprint-verification/schema.json +44 -0
- package/src/executors/deployment-plan/deployment-plan.js +1 -0
- package/src/executors/deployment-plan/deployment-plan.js.map +1 -1
- package/src/executors/dev-doctor/dev-doctor.d.ts +19 -5
- package/src/executors/dev-doctor/dev-doctor.js +186 -32
- package/src/executors/dev-doctor/dev-doctor.js.map +1 -1
- package/src/executors/dev-doctor/schema.json +1 -6
- package/src/executors/dev-setup/dev-setup.d.ts +7 -1
- package/src/executors/dev-setup/dev-setup.js +14 -3
- package/src/executors/dev-setup/dev-setup.js.map +1 -1
- package/src/executors/developer-context/developer-context.d.ts +38 -0
- package/src/executors/developer-context/developer-context.js +78 -0
- package/src/executors/developer-context/developer-context.js.map +1 -0
- package/src/executors/developer-context/schema.json +87 -0
- package/src/executors/fleet/fleet.d.ts +12 -43
- package/src/executors/fleet/fleet.js +120 -500
- package/src/executors/fleet/fleet.js.map +1 -1
- package/src/executors/fleet/schema.json +64 -14
- package/src/executors/github-bootstrap/github-bootstrap.d.ts +1 -0
- package/src/executors/github-bootstrap/github-bootstrap.js +1 -0
- package/src/executors/github-bootstrap/github-bootstrap.js.map +1 -1
- package/src/executors/github-bootstrap/schema.json +5 -0
- package/src/executors/infrastructure-plan/infrastructure-plan.d.ts +144 -0
- package/src/executors/infrastructure-plan/infrastructure-plan.js +3320 -0
- package/src/executors/infrastructure-plan/infrastructure-plan.js.map +1 -0
- package/src/executors/infrastructure-plan/schema.json +78 -0
- package/src/executors/secret-guardrails/schema.json +6 -0
- package/src/executors/secret-guardrails/secret-guardrails.d.ts +3 -0
- package/src/executors/secret-guardrails/secret-guardrails.js +44 -0
- package/src/executors/secret-guardrails/secret-guardrails.js.map +1 -0
- package/src/executors/secret-metadata/schema.json +31 -0
- package/src/executors/secret-metadata/secret-metadata.d.ts +32 -0
- package/src/executors/secret-metadata/secret-metadata.js +131 -0
- package/src/executors/secret-metadata/secret-metadata.js.map +1 -0
- package/src/executors/sst-lifecycle/logging-preflight.d.ts +17 -0
- package/src/executors/sst-lifecycle/logging-preflight.js +196 -0
- package/src/executors/sst-lifecycle/logging-preflight.js.map +1 -0
- package/src/executors/sst-lifecycle/run-sst-command.d.ts +23 -3
- package/src/executors/sst-lifecycle/run-sst-command.js +113 -10
- package/src/executors/sst-lifecycle/run-sst-command.js.map +1 -1
- package/src/executors/sst-lifecycle/schema.json +9 -5
- package/src/executors/sst-lifecycle/secret-preflight.d.ts +25 -0
- package/src/executors/sst-lifecycle/secret-preflight.js +119 -0
- package/src/executors/sst-lifecycle/secret-preflight.js.map +1 -0
- package/src/foundation/discovery.d.ts +17 -1
- package/src/foundation/discovery.js +16 -1
- package/src/foundation/discovery.js.map +1 -1
- package/src/foundation/index.d.ts +3 -0
- package/src/foundation/index.js +3 -0
- package/src/foundation/index.js.map +1 -1
- package/src/foundation/local-database-contract.d.ts +1 -0
- package/src/foundation/local-database-contract.js +3 -0
- package/src/foundation/local-database-contract.js.map +1 -1
- package/src/foundation/logging-preflight.d.ts +30 -0
- package/src/foundation/logging-preflight.js +138 -0
- package/src/foundation/logging-preflight.js.map +1 -0
- package/src/foundation/logging.d.ts +70 -0
- package/src/foundation/logging.js +133 -0
- package/src/foundation/logging.js.map +1 -0
- package/src/foundation/preflight.d.ts +9 -0
- package/src/foundation/preflight.js +58 -2
- package/src/foundation/preflight.js.map +1 -1
- package/src/foundation/provision-database-bindings.d.ts +9 -1
- package/src/foundation/provision-database-bindings.js +25 -5
- package/src/foundation/provision-database-bindings.js.map +1 -1
- package/src/foundation/provision-logical-database.d.ts +10 -2
- package/src/foundation/provision-logical-database.js +68 -113
- package/src/foundation/provision-logical-database.js.map +1 -1
- package/src/foundation/router-origin-proof.d.ts +2 -0
- package/src/foundation/router-origin-proof.js +25 -0
- package/src/foundation/router-origin-proof.js.map +1 -0
- package/src/foundation/stages.d.ts +1 -0
- package/src/foundation/stages.js +9 -1
- package/src/foundation/stages.js.map +1 -1
- package/src/frontend-security/index.d.ts +1 -0
- package/src/frontend-security/index.js +5 -0
- package/src/frontend-security/index.js.map +1 -0
- package/src/frontend-security/security-headers.d.ts +50 -0
- package/src/frontend-security/security-headers.js +418 -0
- package/src/frontend-security/security-headers.js.map +1 -0
- package/src/generators/feature-registry.d.ts +13 -0
- package/src/generators/feature-registry.js +73 -0
- package/src/generators/feature-registry.js.map +1 -0
- package/src/generators/function/function.d.ts +1 -1
- package/src/generators/function/function.js +51 -6
- package/src/generators/function/function.js.map +1 -1
- package/src/generators/function/schema.d.ts +1 -0
- package/src/generators/function/schema.json +6 -1
- package/src/generators/logging-registry.d.ts +13 -0
- package/src/generators/logging-registry.js +62 -0
- package/src/generators/logging-registry.js.map +1 -0
- package/src/generators/post-generation-install.d.ts +67 -0
- package/src/generators/post-generation-install.js +3308 -0
- package/src/generators/post-generation-install.js.map +1 -0
- package/src/generators/preset/generator.js +3 -0
- package/src/generators/preset/generator.js.map +1 -1
- package/src/generators/preset/tool-versions.d.ts +18 -0
- package/src/generators/preset/tool-versions.js +22 -0
- package/src/generators/preset/tool-versions.js.map +1 -0
- package/src/generators/preset/workspace-files-developer-agent.d.ts +14 -0
- package/src/generators/preset/workspace-files-developer-agent.js +511 -0
- package/src/generators/preset/workspace-files-developer-agent.js.map +1 -0
- package/src/generators/preset/workspace-files-foundation-workload-roles.d.ts +6 -0
- package/src/generators/preset/workspace-files-foundation-workload-roles.js +406 -0
- package/src/generators/preset/workspace-files-foundation-workload-roles.js.map +1 -0
- package/src/generators/preset/workspace-files-performance-cost.d.ts +66 -0
- package/src/generators/preset/workspace-files-performance-cost.js +866 -0
- package/src/generators/preset/workspace-files-performance-cost.js.map +1 -0
- package/src/generators/preset/workspace-files-performance-load.d.ts +73 -0
- package/src/generators/preset/workspace-files-performance-load.js +801 -0
- package/src/generators/preset/workspace-files-performance-load.js.map +1 -0
- package/src/generators/preset/workspace-files-workload-role-baseline.d.ts +9 -0
- package/src/generators/preset/workspace-files-workload-role-baseline.js +1102 -0
- package/src/generators/preset/workspace-files-workload-role-baseline.js.map +1 -0
- package/src/generators/preset/workspace-files.d.ts +14 -1
- package/src/generators/preset/workspace-files.js +8189 -245
- package/src/generators/preset/workspace-files.js.map +1 -1
- package/src/generators/repository-policy/repository-policy.js +3 -47
- package/src/generators/repository-policy/repository-policy.js.map +1 -1
- package/src/generators/slice/built-in-plan-api-contract-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-api-contract-files.js +2656 -0
- package/src/generators/slice/built-in-plan-api-contract-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-api-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-api-files.js +1186 -0
- package/src/generators/slice/built-in-plan-api-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-async-work-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-async-work-files.js +7903 -0
- package/src/generators/slice/built-in-plan-async-work-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-authorization-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-authorization-files.js +2314 -0
- package/src/generators/slice/built-in-plan-authorization-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-authorization-infrastructure-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-authorization-infrastructure-files.js +559 -0
- package/src/generators/slice/built-in-plan-authorization-infrastructure-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-data-deletion-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-data-deletion-files.js +1101 -0
- package/src/generators/slice/built-in-plan-data-deletion-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-data-export-infrastructure-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-data-export-infrastructure-files.js +249 -0
- package/src/generators/slice/built-in-plan-data-export-infrastructure-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-data-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-data-files.js +1530 -0
- package/src/generators/slice/built-in-plan-data-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-data-lifecycle-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-data-lifecycle-files.js +750 -0
- package/src/generators/slice/built-in-plan-data-lifecycle-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-data-lifecycle-runtime-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-data-lifecycle-runtime-files.js +828 -0
- package/src/generators/slice/built-in-plan-data-lifecycle-runtime-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-data-retention-job-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-data-retention-job-files.js +2604 -0
- package/src/generators/slice/built-in-plan-data-retention-job-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-data-retention-launcher-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-data-retention-launcher-files.js +144 -0
- package/src/generators/slice/built-in-plan-data-retention-launcher-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-data-rights-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-data-rights-files.js +4628 -0
- package/src/generators/slice/built-in-plan-data-rights-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-database-lifecycle-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-database-lifecycle-files.js +1901 -0
- package/src/generators/slice/built-in-plan-database-lifecycle-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-database-retirement-rehearsal-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-database-retirement-rehearsal-files.js +485 -0
- package/src/generators/slice/built-in-plan-database-retirement-rehearsal-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-foundation-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-foundation-files.js +184 -0
- package/src/generators/slice/built-in-plan-foundation-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-frontend-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-frontend-files.js +2233 -0
- package/src/generators/slice/built-in-plan-frontend-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-infrastructure-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-infrastructure-files.js +3038 -0
- package/src/generators/slice/built-in-plan-infrastructure-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-infrastructure-safety-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-infrastructure-safety-files.js +312 -0
- package/src/generators/slice/built-in-plan-infrastructure-safety-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-metadata-files.d.ts +3 -0
- package/src/generators/slice/built-in-plan-metadata-files.js +219 -0
- package/src/generators/slice/built-in-plan-metadata-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-operations-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-operations-files.js +630 -0
- package/src/generators/slice/built-in-plan-operations-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-performance-files.d.ts +3 -0
- package/src/generators/slice/built-in-plan-performance-files.js +3293 -0
- package/src/generators/slice/built-in-plan-performance-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-project-files.d.ts +3 -0
- package/src/generators/slice/built-in-plan-project-files.js +979 -0
- package/src/generators/slice/built-in-plan-project-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-release-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-release-files.js +6387 -0
- package/src/generators/slice/built-in-plan-release-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-reliability-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-reliability-files.js +4499 -0
- package/src/generators/slice/built-in-plan-reliability-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-secret-runtime-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-secret-runtime-files.js +1004 -0
- package/src/generators/slice/built-in-plan-secret-runtime-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-shared.d.ts +53 -0
- package/src/generators/slice/built-in-plan-shared.js +113 -0
- package/src/generators/slice/built-in-plan-shared.js.map +1 -0
- package/src/generators/slice/built-in-plan-testing-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-testing-files.js +2356 -0
- package/src/generators/slice/built-in-plan-testing-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-verification-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-verification-files.js +358 -0
- package/src/generators/slice/built-in-plan-verification-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-web-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-web-files.js +430 -0
- package/src/generators/slice/built-in-plan-web-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-workload-role-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-workload-role-files.js +2356 -0
- package/src/generators/slice/built-in-plan-workload-role-files.js.map +1 -0
- package/src/generators/slice/built-in-plan.d.ts +8 -0
- package/src/generators/slice/built-in-plan.js +264 -0
- package/src/generators/slice/built-in-plan.js.map +1 -0
- package/src/generators/slice/schema.json +1 -1
- package/src/generators/slice/slice.d.ts +1 -22
- package/src/generators/slice/slice.js +185 -1510
- package/src/generators/slice/slice.js.map +1 -1
- package/src/generators/upgrade/schema.d.ts +2 -0
- package/src/generators/upgrade/schema.json +11 -1
- package/src/generators/upgrade/upgrade.d.ts +51 -1
- package/src/generators/upgrade/upgrade.js +820 -82
- package/src/generators/upgrade/upgrade.js.map +1 -1
- package/src/generators/workload/frozen-container.d.ts +8 -0
- package/src/generators/workload/frozen-container.js +36 -0
- package/src/generators/workload/frozen-container.js.map +1 -0
- package/src/generators/workload/performance-registration.d.ts +32 -0
- package/src/generators/workload/performance-registration.js +226 -0
- package/src/generators/workload/performance-registration.js.map +1 -0
- package/src/generators/workload/reliability-registration.d.ts +16 -0
- package/src/generators/workload/reliability-registration.js +150 -0
- package/src/generators/workload/reliability-registration.js.map +1 -0
- package/src/generators/workload/schema.d.ts +1 -0
- package/src/generators/workload/schema.json +6 -1
- package/src/generators/workload/service-performance-files.d.ts +4 -0
- package/src/generators/workload/service-performance-files.js +1846 -0
- package/src/generators/workload/service-performance-files.js.map +1 -0
- package/src/generators/workload/workload.d.ts +3 -2
- package/src/generators/workload/workload.js +1083 -18
- package/src/generators/workload/workload.js.map +1 -1
- package/src/git-policy/action-pins.d.ts +10 -0
- package/src/git-policy/action-pins.js +10 -0
- package/src/git-policy/action-pins.js.map +1 -1
- package/src/git-policy/github-bootstrap.d.ts +14 -1
- package/src/git-policy/github-bootstrap.js +274 -46
- package/src/git-policy/github-bootstrap.js.map +1 -1
- package/src/index.d.ts +2 -0
- package/src/index.js +2 -0
- package/src/index.js.map +1 -1
- package/src/infrastructure-change/convergence.d.ts +13 -0
- package/src/infrastructure-change/convergence.js +85 -0
- package/src/infrastructure-change/convergence.js.map +1 -0
- package/src/infrastructure-change/drift.d.ts +13 -0
- package/src/infrastructure-change/drift.js +112 -0
- package/src/infrastructure-change/drift.js.map +1 -0
- package/src/infrastructure-change/index.d.ts +7 -0
- package/src/infrastructure-change/index.js +11 -0
- package/src/infrastructure-change/index.js.map +1 -0
- package/src/infrastructure-change/model.d.ts +253 -0
- package/src/infrastructure-change/model.js +28 -0
- package/src/infrastructure-change/model.js.map +1 -0
- package/src/infrastructure-change/normalize.d.ts +12 -0
- package/src/infrastructure-change/normalize.js +245 -0
- package/src/infrastructure-change/normalize.js.map +1 -0
- package/src/infrastructure-change/plan.d.ts +40 -0
- package/src/infrastructure-change/plan.js +361 -0
- package/src/infrastructure-change/plan.js.map +1 -0
- package/src/infrastructure-change/risk.d.ts +7 -0
- package/src/infrastructure-change/risk.js +269 -0
- package/src/infrastructure-change/risk.js.map +1 -0
- package/src/infrastructure-change/summary.d.ts +20 -0
- package/src/infrastructure-change/summary.js +130 -0
- package/src/infrastructure-change/summary.js.map +1 -0
- package/src/local-dev/context-discovery.d.ts +2 -0
- package/src/local-dev/context-discovery.js +59 -0
- package/src/local-dev/context-discovery.js.map +1 -0
- package/src/local-dev/contract-validation.d.ts +15 -0
- package/src/local-dev/contract-validation.js +84 -0
- package/src/local-dev/contract-validation.js.map +1 -0
- package/src/local-dev/contracts.d.ts +10 -1
- package/src/local-dev/contracts.js +57 -5
- package/src/local-dev/contracts.js.map +1 -1
- package/src/local-dev/control-contract.d.ts +67 -0
- package/src/local-dev/control-contract.js +180 -0
- package/src/local-dev/control-contract.js.map +1 -0
- package/src/local-dev/dev-command-core.d.ts +60 -0
- package/src/local-dev/dev-command-core.js +89 -0
- package/src/local-dev/dev-command-core.js.map +1 -0
- package/src/local-dev/effects.d.ts +16 -0
- package/src/local-dev/effects.js +63 -0
- package/src/local-dev/effects.js.map +1 -0
- package/src/local-dev/environment-inspector.d.ts +125 -0
- package/src/local-dev/environment-inspector.js +687 -0
- package/src/local-dev/environment-inspector.js.map +1 -0
- package/src/local-dev/environment-state.d.ts +113 -0
- package/src/local-dev/environment-state.js +463 -0
- package/src/local-dev/environment-state.js.map +1 -0
- package/src/local-dev/errors.d.ts +6 -0
- package/src/local-dev/errors.js +41 -0
- package/src/local-dev/errors.js.map +1 -0
- package/src/local-dev/fleet-client.d.ts +33 -0
- package/src/local-dev/fleet-client.js +156 -0
- package/src/local-dev/fleet-client.js.map +1 -0
- package/src/local-dev/fleet-configuration.d.ts +13 -0
- package/src/local-dev/fleet-configuration.js +71 -0
- package/src/local-dev/fleet-configuration.js.map +1 -0
- package/src/local-dev/fleet-inventory.d.ts +56 -0
- package/src/local-dev/fleet-inventory.js +469 -0
- package/src/local-dev/fleet-inventory.js.map +1 -0
- package/src/local-dev/fleet-ipc.d.ts +28 -0
- package/src/local-dev/fleet-ipc.js +267 -0
- package/src/local-dev/fleet-ipc.js.map +1 -0
- package/src/local-dev/fleet-launcher.d.ts +53 -0
- package/src/local-dev/fleet-launcher.js +379 -0
- package/src/local-dev/fleet-launcher.js.map +1 -0
- package/src/local-dev/fleet-lock.d.ts +41 -0
- package/src/local-dev/fleet-lock.js +135 -0
- package/src/local-dev/fleet-lock.js.map +1 -0
- package/src/local-dev/fleet-logs.d.ts +66 -0
- package/src/local-dev/fleet-logs.js +542 -0
- package/src/local-dev/fleet-logs.js.map +1 -0
- package/src/local-dev/fleet-process.d.ts +50 -0
- package/src/local-dev/fleet-process.js +289 -0
- package/src/local-dev/fleet-process.js.map +1 -0
- package/src/local-dev/fleet-runtime.d.ts +22 -0
- package/src/local-dev/fleet-runtime.js +368 -0
- package/src/local-dev/fleet-runtime.js.map +1 -0
- package/src/local-dev/fleet-session.d.ts +70 -0
- package/src/local-dev/fleet-session.js +301 -0
- package/src/local-dev/fleet-session.js.map +1 -0
- package/src/local-dev/fleet-slice-runner-daemon.d.ts +1 -0
- package/src/local-dev/fleet-slice-runner-daemon.js +21 -0
- package/src/local-dev/fleet-slice-runner-daemon.js.map +1 -0
- package/src/local-dev/fleet-startup.d.ts +22 -0
- package/src/local-dev/fleet-startup.js +114 -0
- package/src/local-dev/fleet-startup.js.map +1 -0
- package/src/local-dev/fleet-state.d.ts +62 -0
- package/src/local-dev/fleet-state.js +297 -0
- package/src/local-dev/fleet-state.js.map +1 -0
- package/src/local-dev/fleet-supervisor-daemon.d.ts +1 -0
- package/src/local-dev/fleet-supervisor-daemon.js +41 -0
- package/src/local-dev/fleet-supervisor-daemon.js.map +1 -0
- package/src/local-dev/fleet-supervisor.d.ts +52 -0
- package/src/local-dev/fleet-supervisor.js +384 -0
- package/src/local-dev/fleet-supervisor.js.map +1 -0
- package/src/local-dev/gateway.js +1 -1
- package/src/local-dev/gateway.js.map +1 -1
- package/src/local-dev/identity.js +4 -6
- package/src/local-dev/identity.js.map +1 -1
- package/src/local-dev/index.d.ts +1 -0
- package/src/local-dev/index.js +1 -0
- package/src/local-dev/index.js.map +1 -1
- package/src/local-dev/local-context.d.ts +62 -0
- package/src/local-dev/local-context.js +299 -0
- package/src/local-dev/local-context.js.map +1 -0
- package/src/local-dev/nx-workspace.d.ts +45 -0
- package/src/local-dev/nx-workspace.js +228 -0
- package/src/local-dev/nx-workspace.js.map +1 -0
- package/src/local-dev/personal-secret-ledger.d.ts +75 -0
- package/src/local-dev/personal-secret-ledger.js +391 -0
- package/src/local-dev/personal-secret-ledger.js.map +1 -0
- package/src/local-dev/personal-stage.d.ts +18 -0
- package/src/local-dev/personal-stage.js +129 -0
- package/src/local-dev/personal-stage.js.map +1 -0
- package/src/local-dev/private-state.d.ts +17 -0
- package/src/local-dev/private-state.js +254 -0
- package/src/local-dev/private-state.js.map +1 -0
- package/src/local-dev/process-identity.d.ts +9 -0
- package/src/local-dev/process-identity.js +79 -0
- package/src/local-dev/process-identity.js.map +1 -0
- package/src/local-dev/runtime-paths.d.ts +17 -0
- package/src/local-dev/runtime-paths.js +33 -0
- package/src/local-dev/runtime-paths.js.map +1 -0
- package/src/local-dev/secret-slice-claim.d.ts +17 -0
- package/src/local-dev/secret-slice-claim.js +75 -0
- package/src/local-dev/secret-slice-claim.js.map +1 -0
- package/src/local-dev/slice-claim.d.ts +36 -0
- package/src/local-dev/slice-claim.js +199 -0
- package/src/local-dev/slice-claim.js.map +1 -0
- package/src/local-dev/slice-runner.d.ts +121 -0
- package/src/local-dev/slice-runner.js +715 -0
- package/src/local-dev/slice-runner.js.map +1 -0
- package/src/local-dev/tls.js +51 -17
- package/src/local-dev/tls.js.map +1 -1
- package/src/ownership/composition.d.ts +5 -2
- package/src/ownership/composition.js +26 -3
- package/src/ownership/composition.js.map +1 -1
- package/src/ownership/hash.d.ts +1 -1
- package/src/ownership/hash.js +3 -1
- package/src/ownership/hash.js.map +1 -1
- package/src/ownership/record.d.ts +32 -1
- package/src/ownership/record.js +139 -2
- package/src/ownership/record.js.map +1 -1
- package/src/performance/cost-collection.d.ts +225 -0
- package/src/performance/cost-collection.js +825 -0
- package/src/performance/cost-collection.js.map +1 -0
- package/src/performance/cost-controls.d.ts +131 -0
- package/src/performance/cost-controls.js +336 -0
- package/src/performance/cost-controls.js.map +1 -0
- package/src/performance/evidence.d.ts +140 -0
- package/src/performance/evidence.js +599 -0
- package/src/performance/evidence.js.map +1 -0
- package/src/performance/index.d.ts +10 -0
- package/src/performance/index.js +14 -0
- package/src/performance/index.js.map +1 -0
- package/src/performance/load-safety.d.ts +84 -0
- package/src/performance/load-safety.js +346 -0
- package/src/performance/load-safety.js.map +1 -0
- package/src/performance/model.d.ts +129 -0
- package/src/performance/model.js +452 -0
- package/src/performance/model.js.map +1 -0
- package/src/performance/pr-artifact-budget.d.ts +42 -0
- package/src/performance/pr-artifact-budget.js +151 -0
- package/src/performance/pr-artifact-budget.js.map +1 -0
- package/src/performance/runner.d.ts +75 -0
- package/src/performance/runner.js +227 -0
- package/src/performance/runner.js.map +1 -0
- package/src/performance/scenario-endpoint.d.ts +1 -0
- package/src/performance/scenario-endpoint.js +5 -0
- package/src/performance/scenario-endpoint.js.map +1 -0
- package/src/performance/scenario.d.ts +193 -0
- package/src/performance/scenario.js +736 -0
- package/src/performance/scenario.js.map +1 -0
- package/src/performance/test-fixtures.d.ts +7 -0
- package/src/performance/test-fixtures.js +169 -0
- package/src/performance/test-fixtures.js.map +1 -0
- package/src/performance/unit-cost.d.ts +105 -0
- package/src/performance/unit-cost.js +364 -0
- package/src/performance/unit-cost.js.map +1 -0
- package/src/release/index.d.ts +4 -0
- package/src/release/index.js +8 -0
- package/src/release/index.js.map +1 -0
- package/src/release/lifecycle.d.ts +132 -0
- package/src/release/lifecycle.js +411 -0
- package/src/release/lifecycle.js.map +1 -0
- package/src/release/manifest.d.ts +148 -0
- package/src/release/manifest.js +503 -0
- package/src/release/manifest.js.map +1 -0
- package/src/release/rollout.d.ts +170 -0
- package/src/release/rollout.js +578 -0
- package/src/release/rollout.js.map +1 -0
- package/src/release/source-authority.d.ts +19 -0
- package/src/release/source-authority.js +195 -0
- package/src/release/source-authority.js.map +1 -0
- package/src/repository-policy/deployment-plan.d.ts +2 -1
- package/src/repository-policy/deployment-plan.js +2 -0
- package/src/repository-policy/deployment-plan.js.map +1 -1
- package/src/repository-policy/policy.d.ts +1 -1
- package/src/repository-policy/policy.js +29 -25
- package/src/repository-policy/policy.js.map +1 -1
- package/src/schemas/aggregate-count-evidence-v1.json +135 -0
- package/src/schemas/api-contract-baseline-v1.json +674 -0
- package/src/schemas/api-contract-compatibility-report-v1.json +126 -0
- package/src/schemas/aws-billing-actual-evidence-v1.json +407 -0
- package/src/schemas/blueprint-v1.json +18 -1
- package/src/schemas/data-lifecycle-policy-v1.json +517 -0
- package/src/schemas/generated-ownership-v2.json +22 -0
- package/src/schemas/performance-policy-v1.json +335 -0
- package/src/schemas/repository-policy-v1.json +1 -0
- package/src/schemas/testing-evidence-quarantine-v1.json +168 -0
- package/src/schemas/testing-evidence-result-v1.json +319 -0
- package/src/secrets/adapters.d.ts +49 -0
- package/src/secrets/adapters.js +184 -0
- package/src/secrets/adapters.js.map +1 -0
- package/src/secrets/aws-control-plane.d.ts +20 -0
- package/src/secrets/aws-control-plane.js +255 -0
- package/src/secrets/aws-control-plane.js.map +1 -0
- package/src/secrets/declaration-verification.d.ts +11 -0
- package/src/secrets/declaration-verification.js +68 -0
- package/src/secrets/declaration-verification.js.map +1 -0
- package/src/secrets/guardrails.d.ts +26 -0
- package/src/secrets/guardrails.js +328 -0
- package/src/secrets/guardrails.js.map +1 -0
- package/src/secrets/index.d.ts +12 -0
- package/src/secrets/index.js +16 -0
- package/src/secrets/index.js.map +1 -0
- package/src/secrets/metadata.d.ts +48 -0
- package/src/secrets/metadata.js +171 -0
- package/src/secrets/metadata.js.map +1 -0
- package/src/secrets/model.d.ts +49 -0
- package/src/secrets/model.js +125 -0
- package/src/secrets/model.js.map +1 -0
- package/src/secrets/operator.d.ts +101 -0
- package/src/secrets/operator.js +229 -0
- package/src/secrets/operator.js.map +1 -0
- package/src/secrets/permissions.d.ts +34 -0
- package/src/secrets/permissions.js +86 -0
- package/src/secrets/permissions.js.map +1 -0
- package/src/secrets/personal-command.d.ts +83 -0
- package/src/secrets/personal-command.js +197 -0
- package/src/secrets/personal-command.js.map +1 -0
- package/src/secrets/personal-ledger.d.ts +21 -0
- package/src/secrets/personal-ledger.js +135 -0
- package/src/secrets/personal-ledger.js.map +1 -0
- package/src/secrets/registry.d.ts +9 -0
- package/src/secrets/registry.js +86 -0
- package/src/secrets/registry.js.map +1 -0
- package/src/secrets/verification.d.ts +18 -0
- package/src/secrets/verification.js +139 -0
- package/src/secrets/verification.js.map +1 -0
- package/src/secrets/workload-access.d.ts +46 -0
- package/src/secrets/workload-access.js +163 -0
- package/src/secrets/workload-access.js.map +1 -0
- package/src/supply-chain/action-pins.d.ts +26 -0
- package/src/supply-chain/action-pins.js +208 -0
- package/src/supply-chain/action-pins.js.map +1 -0
- package/src/supply-chain/blueprint-trust-git.d.ts +130 -0
- package/src/supply-chain/blueprint-trust-git.js +1581 -0
- package/src/supply-chain/blueprint-trust-git.js.map +1 -0
- package/src/supply-chain/blueprint-trust-process.d.ts +4 -0
- package/src/supply-chain/blueprint-trust-process.js +88 -0
- package/src/supply-chain/blueprint-trust-process.js.map +1 -0
- package/src/supply-chain/blueprint-trust.d.ts +354 -0
- package/src/supply-chain/blueprint-trust.js +1558 -0
- package/src/supply-chain/blueprint-trust.js.map +1 -0
- package/src/supply-chain/blueprint-verification-authority.d.ts +62 -0
- package/src/supply-chain/blueprint-verification-authority.js +588 -0
- package/src/supply-chain/blueprint-verification-authority.js.map +1 -0
- package/src/supply-chain/blueprint-verification-execution.d.ts +68 -0
- package/src/supply-chain/blueprint-verification-execution.js +624 -0
- package/src/supply-chain/blueprint-verification-execution.js.map +1 -0
- package/src/supply-chain/build-output.d.ts +35 -0
- package/src/supply-chain/build-output.js +398 -0
- package/src/supply-chain/build-output.js.map +1 -0
- package/src/supply-chain/common.d.ts +21 -0
- package/src/supply-chain/common.js +107 -0
- package/src/supply-chain/common.js.map +1 -0
- package/src/supply-chain/container-artifact-contract.d.ts +23 -0
- package/src/supply-chain/container-artifact-contract.js +208 -0
- package/src/supply-chain/container-artifact-contract.js.map +1 -0
- package/src/supply-chain/container-release.d.ts +119 -0
- package/src/supply-chain/container-release.js +496 -0
- package/src/supply-chain/container-release.js.map +1 -0
- package/src/supply-chain/ecr-applicability.d.ts +19 -0
- package/src/supply-chain/ecr-applicability.js +103 -0
- package/src/supply-chain/ecr-applicability.js.map +1 -0
- package/src/supply-chain/ecr-scanning.d.ts +83 -0
- package/src/supply-chain/ecr-scanning.js +321 -0
- package/src/supply-chain/ecr-scanning.js.map +1 -0
- package/src/supply-chain/ecs-deployed-image.d.ts +85 -0
- package/src/supply-chain/ecs-deployed-image.js +300 -0
- package/src/supply-chain/ecs-deployed-image.js.map +1 -0
- package/src/supply-chain/errors.d.ts +6 -0
- package/src/supply-chain/errors.js +15 -0
- package/src/supply-chain/errors.js.map +1 -0
- package/src/supply-chain/generated-blueprint-trust-command.d.ts +4 -0
- package/src/supply-chain/generated-blueprint-trust-command.js +1362 -0
- package/src/supply-chain/generated-blueprint-trust-command.js.map +1 -0
- package/src/supply-chain/generated-command.d.ts +1 -0
- package/src/supply-chain/generated-command.js +687 -0
- package/src/supply-chain/generated-command.js.map +1 -0
- package/src/supply-chain/generated-container-release-command.d.ts +16 -0
- package/src/supply-chain/generated-container-release-command.js +2087 -0
- package/src/supply-chain/generated-container-release-command.js.map +1 -0
- package/src/supply-chain/generated-ecr-collector-command.d.ts +21 -0
- package/src/supply-chain/generated-ecr-collector-command.js +1338 -0
- package/src/supply-chain/generated-ecr-collector-command.js.map +1 -0
- package/src/supply-chain/generated-files.d.ts +13 -0
- package/src/supply-chain/generated-files.js +401 -0
- package/src/supply-chain/generated-files.js.map +1 -0
- package/src/supply-chain/generated-scanner-command.d.ts +1 -0
- package/src/supply-chain/generated-scanner-command.js +718 -0
- package/src/supply-chain/generated-scanner-command.js.map +1 -0
- package/src/supply-chain/index.d.ts +28 -0
- package/src/supply-chain/index.js +35 -0
- package/src/supply-chain/index.js.map +1 -0
- package/src/supply-chain/inventory.d.ts +79 -0
- package/src/supply-chain/inventory.js +717 -0
- package/src/supply-chain/inventory.js.map +1 -0
- package/src/supply-chain/license-expression.d.ts +7 -0
- package/src/supply-chain/license-expression.js +128 -0
- package/src/supply-chain/license-expression.js.map +1 -0
- package/src/supply-chain/pnpm-toolchain-authority.d.ts +31 -0
- package/src/supply-chain/pnpm-toolchain-authority.js +316 -0
- package/src/supply-chain/pnpm-toolchain-authority.js.map +1 -0
- package/src/supply-chain/policy.d.ts +105 -0
- package/src/supply-chain/policy.js +301 -0
- package/src/supply-chain/policy.js.map +1 -0
- package/src/supply-chain/publication.d.ts +48 -0
- package/src/supply-chain/publication.js +85 -0
- package/src/supply-chain/publication.js.map +1 -0
- package/src/supply-chain/sbom.d.ts +228 -0
- package/src/supply-chain/sbom.js +826 -0
- package/src/supply-chain/sbom.js.map +1 -0
- package/src/supply-chain/scanner.d.ts +77 -0
- package/src/supply-chain/scanner.js +502 -0
- package/src/supply-chain/scanner.js.map +1 -0
- package/src/supply-chain/vendored-source.d.ts +95 -0
- package/src/supply-chain/vendored-source.js +315 -0
- package/src/supply-chain/vendored-source.js.map +1 -0
- package/src/testing-evidence/fixture-scope.d.ts +52 -0
- package/src/testing-evidence/fixture-scope.js +140 -0
- package/src/testing-evidence/fixture-scope.js.map +1 -0
- package/src/testing-evidence/index.d.ts +5 -0
- package/src/testing-evidence/index.js +9 -0
- package/src/testing-evidence/index.js.map +1 -0
- package/src/testing-evidence/quarantine.d.ts +48 -0
- package/src/testing-evidence/quarantine.js +253 -0
- package/src/testing-evidence/quarantine.js.map +1 -0
- package/src/testing-evidence/result.d.ts +108 -0
- package/src/testing-evidence/result.js +571 -0
- package/src/testing-evidence/result.js.map +1 -0
- package/src/testing-evidence/runner-adapter.d.ts +51 -0
- package/src/testing-evidence/runner-adapter.js +288 -0
- package/src/testing-evidence/runner-adapter.js.map +1 -0
- package/src/testing-evidence/sensitive-artifacts.d.ts +13 -0
- package/src/testing-evidence/sensitive-artifacts.js +93 -0
- package/src/testing-evidence/sensitive-artifacts.js.map +1 -0
- package/src/work-markers/work-markers.js +4 -3
- package/src/work-markers/work-markers.js.map +1 -1
- package/src/workload-roles/account-baseline.d.ts +132 -0
- package/src/workload-roles/account-baseline.js +743 -0
- package/src/workload-roles/account-baseline.js.map +1 -0
- package/src/workload-roles/authority.d.ts +38 -0
- package/src/workload-roles/authority.js +156 -0
- package/src/workload-roles/authority.js.map +1 -0
- package/src/workload-roles/aws-adapter.d.ts +99 -0
- package/src/workload-roles/aws-adapter.js +416 -0
- package/src/workload-roles/aws-adapter.js.map +1 -0
- package/src/workload-roles/aws-internal-handler.d.ts +27 -0
- package/src/workload-roles/aws-internal-handler.js +58 -0
- package/src/workload-roles/aws-internal-handler.js.map +1 -0
- package/src/workload-roles/aws-s3-state.d.ts +52 -0
- package/src/workload-roles/aws-s3-state.js +164 -0
- package/src/workload-roles/aws-s3-state.js.map +1 -0
- package/src/workload-roles/aws-sdk-control-plane.d.ts +12 -0
- package/src/workload-roles/aws-sdk-control-plane.js +291 -0
- package/src/workload-roles/aws-sdk-control-plane.js.map +1 -0
- package/src/workload-roles/boundary.d.ts +33 -0
- package/src/workload-roles/boundary.js +130 -0
- package/src/workload-roles/boundary.js.map +1 -0
- package/src/workload-roles/convergence.d.ts +66 -0
- package/src/workload-roles/convergence.js +186 -0
- package/src/workload-roles/convergence.js.map +1 -0
- package/src/workload-roles/effective-policy.d.ts +51 -0
- package/src/workload-roles/effective-policy.js +183 -0
- package/src/workload-roles/effective-policy.js.map +1 -0
- package/src/workload-roles/handler.d.ts +43 -0
- package/src/workload-roles/handler.js +130 -0
- package/src/workload-roles/handler.js.map +1 -0
- package/src/workload-roles/history.d.ts +47 -0
- package/src/workload-roles/history.js +324 -0
- package/src/workload-roles/history.js.map +1 -0
- package/src/workload-roles/index.d.ts +17 -0
- package/src/workload-roles/index.js +21 -0
- package/src/workload-roles/index.js.map +1 -0
- package/src/workload-roles/injection.d.ts +119 -0
- package/src/workload-roles/injection.js +495 -0
- package/src/workload-roles/injection.js.map +1 -0
- package/src/workload-roles/manifest.d.ts +54 -0
- package/src/workload-roles/manifest.js +343 -0
- package/src/workload-roles/manifest.js.map +1 -0
- package/src/workload-roles/provisioner.d.ts +70 -0
- package/src/workload-roles/provisioner.js +231 -0
- package/src/workload-roles/provisioner.js.map +1 -0
- package/src/workload-roles/registration.d.ts +101 -0
- package/src/workload-roles/registration.js +457 -0
- package/src/workload-roles/registration.js.map +1 -0
- package/src/workload-roles/stage-boundary.d.ts +31 -0
- package/src/workload-roles/stage-boundary.js +208 -0
- package/src/workload-roles/stage-boundary.js.map +1 -0
- package/src/workload-roles/standard-slice.d.ts +54 -0
- package/src/workload-roles/standard-slice.js +252 -0
- package/src/workload-roles/standard-slice.js.map +1 -0
- package/migrations/standard-pre-v1-to-v1.md +0 -11
- package/src/foundation/aurora-logical-database.d.ts +0 -76
- package/src/foundation/aurora-logical-database.js +0 -419
- package/src/foundation/aurora-logical-database.js.map +0 -1
|
@@ -0,0 +1,2233 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.renderProductFrontendFiles = renderProductFrontendFiles;
|
|
4
|
+
const built_in_plan_shared_js_1 = require("./built-in-plan-shared.js");
|
|
5
|
+
const index_js_1 = require("../../frontend-security/index.js");
|
|
6
|
+
/** @internal */
|
|
7
|
+
function renderProductFrontendFiles(name, pascalName) {
|
|
8
|
+
const states = [
|
|
9
|
+
'loading',
|
|
10
|
+
'empty',
|
|
11
|
+
'success',
|
|
12
|
+
'validation',
|
|
13
|
+
'partial',
|
|
14
|
+
'forbidden',
|
|
15
|
+
'not-found',
|
|
16
|
+
'failure',
|
|
17
|
+
'stale',
|
|
18
|
+
'offline',
|
|
19
|
+
];
|
|
20
|
+
return {
|
|
21
|
+
'frontend/contract.json': (0, built_in_plan_shared_js_1.json)({
|
|
22
|
+
accessibility: {
|
|
23
|
+
automatedScan: 'axe-rendered-state',
|
|
24
|
+
conformanceTarget: 'WCAG-2.2-AA',
|
|
25
|
+
manualEvidence: [
|
|
26
|
+
'keyboard',
|
|
27
|
+
'focus',
|
|
28
|
+
'announcements',
|
|
29
|
+
'zoom-and-reflow',
|
|
30
|
+
'reduced-motion',
|
|
31
|
+
'assistive-technology',
|
|
32
|
+
],
|
|
33
|
+
nativeBeforeAria: true,
|
|
34
|
+
zeroViolationsIsConformanceClaim: false,
|
|
35
|
+
},
|
|
36
|
+
browserSecurity: {
|
|
37
|
+
browserStorage: 'no-sensitive-data',
|
|
38
|
+
clientGraphResolution: 'typescript-tsconfig-local-module-closure',
|
|
39
|
+
clientPublicConfiguration: ['NEXT_PUBLIC_API_URL'],
|
|
40
|
+
effectiveResponseValidation: true,
|
|
41
|
+
foundationBaselineMayNotBeWeakened: true,
|
|
42
|
+
headerApplicationPoint: 'next-proxy-request-nonce',
|
|
43
|
+
headerProfiles: ['local-development', 'deployed-https'],
|
|
44
|
+
productionInlineAuthority: 'per-request-cryptographic-nonce',
|
|
45
|
+
sliceHeaderAuthority: 'frontend/security-headers.json#csp-only',
|
|
46
|
+
serverOnlyMarker: 'server-only',
|
|
47
|
+
},
|
|
48
|
+
deploymentProof: {
|
|
49
|
+
generatedStandardProfile: 'server-rendered-opennext',
|
|
50
|
+
privateStaticFirstProfile: 'gupta-leary-site',
|
|
51
|
+
staticExportSliceGeneration: 'post-1.0',
|
|
52
|
+
},
|
|
53
|
+
evidence: {
|
|
54
|
+
baselineProfile: 'browser-and-operating-system',
|
|
55
|
+
screenshotsContainOnlyNeutralSyntheticContent: true,
|
|
56
|
+
visualBaseline: 'frontend/visual-baselines.json',
|
|
57
|
+
visualObservations: '.ebk/evidence/frontend/frontend-visual-observations.json',
|
|
58
|
+
},
|
|
59
|
+
ownership: {
|
|
60
|
+
adopterOwned: ['brand', 'content', 'product-interaction', 'theme'],
|
|
61
|
+
managed: [
|
|
62
|
+
'validators',
|
|
63
|
+
'neutral-proof-fixture',
|
|
64
|
+
'default-test-targets',
|
|
65
|
+
],
|
|
66
|
+
seeded: [
|
|
67
|
+
'frontend-contract',
|
|
68
|
+
'route-state-inventory',
|
|
69
|
+
'security-header-policy',
|
|
70
|
+
'support-policy',
|
|
71
|
+
'visual-baseline',
|
|
72
|
+
'accessibility-runbook',
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
postV1Exclusions: [
|
|
76
|
+
'organization-design-system-generation',
|
|
77
|
+
'non-slice-recipe-distribution',
|
|
78
|
+
'static-export-slice-generation',
|
|
79
|
+
'operations-console-ui',
|
|
80
|
+
'hosted-component-marketplace',
|
|
81
|
+
],
|
|
82
|
+
publicConfiguration: {
|
|
83
|
+
allowlist: ['NEXT_PUBLIC_API_URL'],
|
|
84
|
+
dynamicEnvironmentLookup: 'forbidden-in-client-graph',
|
|
85
|
+
},
|
|
86
|
+
schemaVersion: 1,
|
|
87
|
+
semanticQueries: ['role', 'name', 'description', 'state'],
|
|
88
|
+
slice: name,
|
|
89
|
+
stateFixture: `/${name}/_ebk/frontend-states`,
|
|
90
|
+
states,
|
|
91
|
+
}),
|
|
92
|
+
'frontend/support-policy.json': (0, built_in_plan_shared_js_1.json)({
|
|
93
|
+
assistiveTechnology: [
|
|
94
|
+
{
|
|
95
|
+
browser: 'adopter-selected',
|
|
96
|
+
screenReader: 'adopter-selected',
|
|
97
|
+
status: 'manual-proof-required',
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
browserFamilies: [
|
|
101
|
+
{
|
|
102
|
+
family: 'chromium',
|
|
103
|
+
policy: 'current-stable',
|
|
104
|
+
status: 'framework-default-executed',
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
family: 'firefox',
|
|
108
|
+
policy: 'current-stable',
|
|
109
|
+
status: 'unevaluated-adopter-owned',
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
family: 'webkit',
|
|
113
|
+
policy: 'current-stable',
|
|
114
|
+
status: 'unevaluated-adopter-owned',
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
inputModalities: ['keyboard', 'pointer', 'touch'],
|
|
118
|
+
languageDirections: ['ltr', 'rtl-review-when-applicable'],
|
|
119
|
+
note: 'The framework default executes Chromium only. Firefox, WebKit, and assistive-technology status remain unevaluated until adopter-owned evidence exists; upgrades do not silently change this seeded policy.',
|
|
120
|
+
schemaVersion: 1,
|
|
121
|
+
slice: name,
|
|
122
|
+
viewportClasses: ['narrow', 'medium', 'wide'],
|
|
123
|
+
}),
|
|
124
|
+
'frontend/routes.json': (0, built_in_plan_shared_js_1.json)({
|
|
125
|
+
routes: [
|
|
126
|
+
{
|
|
127
|
+
actor: 'public',
|
|
128
|
+
feature: 'product-entry',
|
|
129
|
+
path: `/${name}`,
|
|
130
|
+
readAuthorization: 'public-content-only',
|
|
131
|
+
states: ['success', 'failure'],
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
actor: 'unauthenticated-user',
|
|
135
|
+
feature: 'sign-in',
|
|
136
|
+
mutationAuthorization: 'server-authentication-and-session-cookie-plan',
|
|
137
|
+
path: `/${name}/sign-in`,
|
|
138
|
+
states: [
|
|
139
|
+
'initial',
|
|
140
|
+
'loading',
|
|
141
|
+
'validation',
|
|
142
|
+
'success',
|
|
143
|
+
'failure',
|
|
144
|
+
'offline',
|
|
145
|
+
],
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
actor: 'unauthenticated-user',
|
|
149
|
+
feature: 'password-reset',
|
|
150
|
+
mutationAuthorization: 'server-normalized-non-enumerating-reset-request',
|
|
151
|
+
path: `/${name}/password-reset`,
|
|
152
|
+
states: [
|
|
153
|
+
'initial',
|
|
154
|
+
'loading',
|
|
155
|
+
'validation',
|
|
156
|
+
'success',
|
|
157
|
+
'failure',
|
|
158
|
+
'offline',
|
|
159
|
+
],
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
actor: 'synthetic-proof-only',
|
|
163
|
+
feature: 'neutral-frontend-states',
|
|
164
|
+
path: `/${name}/_ebk/frontend-states`,
|
|
165
|
+
readAuthorization: 'public-neutral-content-only',
|
|
166
|
+
states,
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
schemaVersion: 1,
|
|
170
|
+
slice: name,
|
|
171
|
+
}),
|
|
172
|
+
'frontend/security-headers.json': (0, built_in_plan_shared_js_1.json)((0, index_js_1.sliceFrontendSecurityHeaderPolicy)(name)),
|
|
173
|
+
'frontend/visual-baselines.json': (0, built_in_plan_shared_js_1.json)({
|
|
174
|
+
records: [],
|
|
175
|
+
schemaVersion: 1,
|
|
176
|
+
slice: name,
|
|
177
|
+
}),
|
|
178
|
+
'docs/frontend-accessibility.md': `# ${pascalName} frontend and accessibility
|
|
179
|
+
|
|
180
|
+
The Standard Slice uses a server-first Next.js App Router boundary. Routes and
|
|
181
|
+
layouts own URL structure, metadata, composition, and coarse loading or error
|
|
182
|
+
states. Server Components may resolve identity, authorization, configuration,
|
|
183
|
+
secrets, and protected data. Client Components are the smallest possible
|
|
184
|
+
interaction boundary and may receive only intentional serializable view models.
|
|
185
|
+
Application policy, validation, and mutations remain server-authoritative.
|
|
186
|
+
|
|
187
|
+
The generated Standard profile proves server-rendered OpenNext behavior. The
|
|
188
|
+
required static-first proof belongs to the private \`gupta-leary-site\`
|
|
189
|
+
application. Static-export Slice generation and an organization design-system
|
|
190
|
+
generator are post-1.0 work; do not imitate either contract inside this Slice.
|
|
191
|
+
|
|
192
|
+
## Ownership
|
|
193
|
+
|
|
194
|
+
\`web/src/app/globals.css\`, product content, page composition, brand, and theme
|
|
195
|
+
remain seeded Slice-owned material. Framework regeneration must preserve them.
|
|
196
|
+
The \`/_ebk/frontend-states\` route is a managed neutral proof fixture, not a
|
|
197
|
+
product component catalog and not an import surface for product code.
|
|
198
|
+
|
|
199
|
+
The seeded files in \`frontend/\` declare this Slice's route/state, support, and
|
|
200
|
+
visual-evidence decisions. Review them when browser support or a journey
|
|
201
|
+
changes; framework upgrades must not silently rewrite adopter commitments.
|
|
202
|
+
|
|
203
|
+
## Server and client boundaries
|
|
204
|
+
|
|
205
|
+
- Mark every secret or protected-data module with \`import 'server-only'\`.
|
|
206
|
+
- Client code may read only the literal public keys in
|
|
207
|
+
\`frontend/contract.json\`. A \`NEXT_PUBLIC_\` prefix means public, not safe.
|
|
208
|
+
- Never pass ORM records, credentials, authorization policy, or protected
|
|
209
|
+
identifiers into a Client Component.
|
|
210
|
+
- Do not put protected values in URLs, browser storage, analytics, screenshots,
|
|
211
|
+
logs, or error copy.
|
|
212
|
+
- Raw HTML, dynamic environment access, and a client-to-server-only import fail
|
|
213
|
+
\`frontend-boundaries\`.
|
|
214
|
+
|
|
215
|
+
## Response-header ownership
|
|
216
|
+
|
|
217
|
+
The Foundation owns HTTPS, deployed HSTS, and the minimum Permissions-Policy,
|
|
218
|
+
Referrer-Policy, and X-Content-Type-Options values in
|
|
219
|
+
\`packages/foundation/contracts/frontend-security-headers.json\`. This Slice
|
|
220
|
+
owns only its enforced CSP template in \`frontend/security-headers.json\`. The
|
|
221
|
+
generated Next 16 \`proxy.ts\` creates a cryptographically random nonce for
|
|
222
|
+
each deployed request, composes those disjoint sources, places the materialized
|
|
223
|
+
CSP on the request so Next can nonce its framework output, and applies the same
|
|
224
|
+
headers to the response. Do not add the same headers to Next configuration,
|
|
225
|
+
route handlers, OpenNext, or the Foundation Router. Case-insensitive duplicates
|
|
226
|
+
and cross-owner declarations fail before the response is rendered.
|
|
227
|
+
|
|
228
|
+
The deployed profile intentionally forces dynamic rendering: static generation,
|
|
229
|
+
ISR, PPR, and shared CDN caching cannot safely reuse a request nonce. The
|
|
230
|
+
standard SST/OpenNext server keeps Proxy inside the server runtime. A future
|
|
231
|
+
external Proxy placement must preserve the request-header handoff and the exact
|
|
232
|
+
response policy before it can replace this authority.
|
|
233
|
+
|
|
234
|
+
The managed \`/_ebk/frontend-csp\` response is the one reviewed raw-HTML proof
|
|
235
|
+
adapter. It contains neutral synthetic parser-inserted script and style pairs so
|
|
236
|
+
the deployed browser check can prove that matching nonces execute and missing
|
|
237
|
+
nonces are refused. Product routes must not import or imitate that adapter.
|
|
238
|
+
|
|
239
|
+
The local-development profile omits HSTS over loopback HTTP and permits only the
|
|
240
|
+
development connections needed by Next. Staging and production are locked to
|
|
241
|
+
the deployed-https profile. Local packed proof can exercise the deployed
|
|
242
|
+
response composition over loopback, but only a deployed HTTPS observation is
|
|
243
|
+
hosted transport evidence. Header observation aborts after ten seconds;
|
|
244
|
+
\`EBK_FRONTEND_HEADER_FETCH_TIMEOUT_MS\` may lower that bound for a constrained
|
|
245
|
+
runner but cannot increase it.
|
|
246
|
+
|
|
247
|
+
## Route and state review
|
|
248
|
+
|
|
249
|
+
For every product route, record the actor, read authorization, mutation
|
|
250
|
+
authorization, view model, freshness, applicable states, and recovery behavior.
|
|
251
|
+
The neutral fixture demonstrates loading, empty, success, validation, partial,
|
|
252
|
+
forbidden, not-found, failure, stale, and offline states. Product fixtures
|
|
253
|
+
should reproduce equivalent states with synthetic inputs rather than production
|
|
254
|
+
data.
|
|
255
|
+
|
|
256
|
+
Every mutation authenticates, authorizes, validates, normalizes, and applies
|
|
257
|
+
idempotency or concurrency behavior on the server. A hidden or disabled control
|
|
258
|
+
is never authorization. Validation errors remain associated with their fields
|
|
259
|
+
and are summarized or announced when useful.
|
|
260
|
+
|
|
261
|
+
## Required checks
|
|
262
|
+
|
|
263
|
+
Run the Slice targets for:
|
|
264
|
+
|
|
265
|
+
- static accessibility lint and client/server boundary validation;
|
|
266
|
+
- rendered-state accessibility scans;
|
|
267
|
+
- keyboard, focus restoration, and announcement behavior;
|
|
268
|
+
- narrow viewport reflow and reduced motion;
|
|
269
|
+
- effective local and deployed response headers; and
|
|
270
|
+
- stable neutral screenshots and the visual report.
|
|
271
|
+
|
|
272
|
+
Automated scans identify only a subset of accessibility barriers and are not a
|
|
273
|
+
WCAG conformance claim. Critical journeys additionally require a documented
|
|
274
|
+
keyboard, zoom/reflow, and assistive-technology pass. Record the browser,
|
|
275
|
+
assistive technology, versions, journey, findings, repairs, and reviewer
|
|
276
|
+
without copying user content.
|
|
277
|
+
|
|
278
|
+
An accessibility quarantine uses the shared \`testing/quarantine.json\` record
|
|
279
|
+
and adds an \`accessibility\` object containing the affected WCAG criteria,
|
|
280
|
+
journey, and an approver distinct from the owner. The exception must remain
|
|
281
|
+
bounded, supplemental, and linked to remediation. It cannot waive a security
|
|
282
|
+
header failure, leaked server module, unresolved \`EBK-SECURITY\` or
|
|
283
|
+
\`EBK-BLOCKER\` marker, or the only proof of a protected journey.
|
|
284
|
+
|
|
285
|
+
## Visual reports
|
|
286
|
+
|
|
287
|
+
Browser proof writes synthetic screenshots and observations under
|
|
288
|
+
\`.ebk/evidence/frontend/\`. The committed
|
|
289
|
+
\`frontend/visual-baselines.json\` contains digests and no image data. Updating
|
|
290
|
+
it is an intentional local development action with a work reference; CI and
|
|
291
|
+
protected stages refuse updates. Baselines are browser-and-operating-system
|
|
292
|
+
specific so one reviewed profile cannot silently replace another. Review the
|
|
293
|
+
retained screenshots, semantic tests, accessibility results, browser console,
|
|
294
|
+
and network results together.
|
|
295
|
+
|
|
296
|
+
## Larger design systems and external starters
|
|
297
|
+
|
|
298
|
+
V1 manages validators, test adapters, and this neutral proof fixture. It does
|
|
299
|
+
not publish product-facing components from the framework. Product components
|
|
300
|
+
remain Slice-owned; future reusable wrappers belong to an organization-owned
|
|
301
|
+
design-system project only after that separate post-1.0 generator contract is
|
|
302
|
+
implemented. Until then, keep brand and theme decisions in the Slice and do not
|
|
303
|
+
create a second framework token authority.
|
|
304
|
+
|
|
305
|
+
An external non-Slice starter needs an accepted recipe, ownership, upgrade, and
|
|
306
|
+
verification contract. The v1 Slice Blueprint must not be stretched to
|
|
307
|
+
distribute one implicitly. This boundary is deliberate even when an example
|
|
308
|
+
looks reusable.
|
|
309
|
+
|
|
310
|
+
## Manual review
|
|
311
|
+
|
|
312
|
+
Confirm, at minimum:
|
|
313
|
+
|
|
314
|
+
1. landmark and heading order, names, roles, descriptions, and state;
|
|
315
|
+
2. complete keyboard operation, visible focus, logical order, dialog
|
|
316
|
+
containment, and focus restoration;
|
|
317
|
+
3. validation, status, loading, and recovery announcements without excessive
|
|
318
|
+
live-region noise;
|
|
319
|
+
4. text and non-text contrast, non-color cues, target size, narrow reflow, 200%
|
|
320
|
+
text sizing, browser zoom, and long-content expansion;
|
|
321
|
+
5. reduced-motion behavior and absence of dangerous flashing;
|
|
322
|
+
6. useful behavior when images, fonts, analytics, or optional enhancement fail;
|
|
323
|
+
7. effective HTTPS headers and enforced CSP rather than report-only alone; and
|
|
324
|
+
8. no credentials, personal data, protected identifiers, or raw form values in
|
|
325
|
+
retained evidence.
|
|
326
|
+
`,
|
|
327
|
+
'web/src/lib/public-configuration.ts': `export const PUBLIC_WEB_CONFIGURATION_KEYS = [
|
|
328
|
+
'NEXT_PUBLIC_API_URL',
|
|
329
|
+
] as const;
|
|
330
|
+
|
|
331
|
+
export interface PublicWebConfiguration {
|
|
332
|
+
apiUrl?: string;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function publicUrl(value: string | undefined): string | undefined {
|
|
336
|
+
const trimmed = value?.trim();
|
|
337
|
+
if (!trimmed) return undefined;
|
|
338
|
+
const parsed = new URL(trimmed);
|
|
339
|
+
const local =
|
|
340
|
+
parsed.hostname === '127.0.0.1' ||
|
|
341
|
+
parsed.hostname === 'localhost' ||
|
|
342
|
+
parsed.hostname === '::1';
|
|
343
|
+
if (
|
|
344
|
+
(parsed.protocol !== 'https:' && !(local && parsed.protocol === 'http:')) ||
|
|
345
|
+
parsed.username ||
|
|
346
|
+
parsed.password ||
|
|
347
|
+
parsed.search ||
|
|
348
|
+
parsed.hash
|
|
349
|
+
) {
|
|
350
|
+
throw new Error('NEXT_PUBLIC_API_URL must be an HTTPS URL without credentials, query, or fragment.');
|
|
351
|
+
}
|
|
352
|
+
return parsed.href.replace(/\\/$/, '');
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export function readPublicWebConfiguration(
|
|
356
|
+
environment: Readonly<{ NEXT_PUBLIC_API_URL?: string }> = {
|
|
357
|
+
NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL,
|
|
358
|
+
},
|
|
359
|
+
): PublicWebConfiguration {
|
|
360
|
+
const apiUrl = publicUrl(environment.NEXT_PUBLIC_API_URL);
|
|
361
|
+
return apiUrl ? { apiUrl } : {};
|
|
362
|
+
}
|
|
363
|
+
`,
|
|
364
|
+
'web/src/lib/server-configuration.ts': `import 'server-only';
|
|
365
|
+
|
|
366
|
+
export type ServerWebConfigurationKey =
|
|
367
|
+
| 'AUTH_CLIENT_ID'
|
|
368
|
+
| 'EBK_SOURCE_COMMIT'
|
|
369
|
+
| 'EBK_STAGE';
|
|
370
|
+
|
|
371
|
+
export function requireServerWebConfiguration(
|
|
372
|
+
key: ServerWebConfigurationKey,
|
|
373
|
+
environment: Readonly<Record<string, string | undefined>> = process.env,
|
|
374
|
+
): string {
|
|
375
|
+
const value = environment[key]?.trim();
|
|
376
|
+
if (!value) {
|
|
377
|
+
throw new Error(key + ' is required by the ${name} server runtime.');
|
|
378
|
+
}
|
|
379
|
+
return value;
|
|
380
|
+
}
|
|
381
|
+
`,
|
|
382
|
+
'web/src/app/%5Febk/frontend-states/fixture.module.css': `.fixture {
|
|
383
|
+
background: #ffffff;
|
|
384
|
+
color: #172033;
|
|
385
|
+
font: 1rem/1.5 system-ui, sans-serif;
|
|
386
|
+
margin: 0 auto;
|
|
387
|
+
max-width: 72rem;
|
|
388
|
+
overflow-wrap: anywhere;
|
|
389
|
+
padding: 1.5rem;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
.fixture a {
|
|
393
|
+
color: #0645ad;
|
|
394
|
+
text-decoration-thickness: .12em;
|
|
395
|
+
text-underline-offset: .16em;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
.fixture :where(a, button, input):focus-visible {
|
|
399
|
+
outline: .2rem solid #b94a00;
|
|
400
|
+
outline-offset: .2rem;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
.fixture :where(button, input) {
|
|
404
|
+
font: inherit;
|
|
405
|
+
min-height: 2.75rem;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
.fixture button {
|
|
409
|
+
background: #153f78;
|
|
410
|
+
border: .125rem solid #153f78;
|
|
411
|
+
border-radius: .25rem;
|
|
412
|
+
color: #ffffff;
|
|
413
|
+
cursor: pointer;
|
|
414
|
+
padding: .55rem 1rem;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.fixture button:hover {
|
|
418
|
+
background: #0b2d5b;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
.fixture nav ul,
|
|
422
|
+
.stateNavigation ul,
|
|
423
|
+
.pagination {
|
|
424
|
+
display: flex;
|
|
425
|
+
flex-wrap: wrap;
|
|
426
|
+
gap: .75rem;
|
|
427
|
+
list-style: none;
|
|
428
|
+
padding: 0;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
.stateNavigation a {
|
|
432
|
+
align-items: center;
|
|
433
|
+
display: inline-flex;
|
|
434
|
+
min-height: 2.75rem;
|
|
435
|
+
padding-inline: .5rem;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
.panel {
|
|
439
|
+
border: .125rem solid #52657d;
|
|
440
|
+
border-radius: .5rem;
|
|
441
|
+
margin-block: 1rem;
|
|
442
|
+
padding: 1rem;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
.error {
|
|
446
|
+
border-color: #8b1d2c;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
.field {
|
|
450
|
+
display: grid;
|
|
451
|
+
gap: .35rem;
|
|
452
|
+
margin-block: 1rem;
|
|
453
|
+
max-width: 32rem;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
.field input {
|
|
457
|
+
border: .125rem solid #52657d;
|
|
458
|
+
border-radius: .25rem;
|
|
459
|
+
padding: .5rem;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
.field input[aria-invalid='true'] {
|
|
463
|
+
border-color: #8b1d2c;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
.tableWrapper {
|
|
467
|
+
max-width: 100%;
|
|
468
|
+
overflow-x: auto;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
.table {
|
|
472
|
+
border-collapse: collapse;
|
|
473
|
+
inline-size: 100%;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
.table :where(th, td) {
|
|
477
|
+
border: .0625rem solid #52657d;
|
|
478
|
+
padding: .5rem;
|
|
479
|
+
text-align: start;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
.dialog {
|
|
483
|
+
background: #ffffff;
|
|
484
|
+
border: .125rem solid #172033;
|
|
485
|
+
border-radius: .5rem;
|
|
486
|
+
color: #172033;
|
|
487
|
+
max-inline-size: min(32rem, calc(100% - 2rem));
|
|
488
|
+
padding: 1.25rem;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
.dialog::backdrop {
|
|
492
|
+
background: rgb(12 20 34 / 70%);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
.motionSample {
|
|
496
|
+
transition: transform 180ms ease;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
.motionSample:hover {
|
|
500
|
+
transform: translateY(-.125rem);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
@media (prefers-reduced-motion: reduce) {
|
|
504
|
+
.fixture *,
|
|
505
|
+
.fixture *::before,
|
|
506
|
+
.fixture *::after {
|
|
507
|
+
animation-duration: .01ms !important;
|
|
508
|
+
animation-iteration-count: 1 !important;
|
|
509
|
+
scroll-behavior: auto !important;
|
|
510
|
+
transition-duration: .01ms !important;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
@media (max-width: 30rem) {
|
|
515
|
+
.fixture {
|
|
516
|
+
padding: .75rem;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
`,
|
|
520
|
+
'web/src/app/%5Febk/frontend-states/states.ts': `export const FRONTEND_FIXTURE_STATES = ${JSON.stringify(states)} as const;
|
|
521
|
+
export type FrontendFixtureState = (typeof FRONTEND_FIXTURE_STATES)[number];
|
|
522
|
+
`,
|
|
523
|
+
'web/src/app/%5Febk/frontend-states/fixture.tsx': `'use client';
|
|
524
|
+
|
|
525
|
+
import { useRef, useState } from 'react';
|
|
526
|
+
import styles from './fixture.module.css';
|
|
527
|
+
import type { FrontendFixtureState } from './states';
|
|
528
|
+
|
|
529
|
+
function StatePanel({ state }: Readonly<{ state: FrontendFixtureState }>) {
|
|
530
|
+
if (state === 'loading') {
|
|
531
|
+
return <section aria-labelledby="state-heading" className={styles.panel}>
|
|
532
|
+
<h2 id="state-heading">Loading records</h2>
|
|
533
|
+
<p aria-live="polite" role="status">Loading the current records.</p>
|
|
534
|
+
</section>;
|
|
535
|
+
}
|
|
536
|
+
if (state === 'empty') {
|
|
537
|
+
return <section aria-labelledby="state-heading" className={styles.panel}>
|
|
538
|
+
<h2 id="state-heading">No records yet</h2>
|
|
539
|
+
<p>Create the first record when the required details are available.</p>
|
|
540
|
+
<button type="button">Create a record</button>
|
|
541
|
+
</section>;
|
|
542
|
+
}
|
|
543
|
+
if (state === 'validation') {
|
|
544
|
+
return <section aria-labelledby="state-heading" className={styles.panel}>
|
|
545
|
+
<h2 id="state-heading">Review the form</h2>
|
|
546
|
+
<div className={styles.error} role="alert">
|
|
547
|
+
<p>There is one problem. Correct it and submit again.</p>
|
|
548
|
+
<a href="#fixture-name">Enter a record name</a>
|
|
549
|
+
</div>
|
|
550
|
+
<div className={styles.field}>
|
|
551
|
+
<label htmlFor="fixture-name">Record name</label>
|
|
552
|
+
<input
|
|
553
|
+
aria-describedby="fixture-name-error"
|
|
554
|
+
aria-invalid="true"
|
|
555
|
+
id="fixture-name"
|
|
556
|
+
name="name"
|
|
557
|
+
required
|
|
558
|
+
/>
|
|
559
|
+
<p id="fixture-name-error">Enter a record name.</p>
|
|
560
|
+
</div>
|
|
561
|
+
<button type="button">Save record</button>
|
|
562
|
+
</section>;
|
|
563
|
+
}
|
|
564
|
+
if (state === 'partial') {
|
|
565
|
+
return <section aria-labelledby="state-heading" className={styles.panel}>
|
|
566
|
+
<h2 id="state-heading">Records partially available</h2>
|
|
567
|
+
<p role="status">Recent records are available. Archived records could not be loaded.</p>
|
|
568
|
+
<button type="button">Retry archived records</button>
|
|
569
|
+
</section>;
|
|
570
|
+
}
|
|
571
|
+
if (state === 'forbidden') {
|
|
572
|
+
return <section aria-labelledby="state-heading" className={styles.panel}>
|
|
573
|
+
<h2 id="state-heading">Access not permitted</h2>
|
|
574
|
+
<p>Your signed-in account cannot view these records.</p>
|
|
575
|
+
<a href="/${name}">Return to ${pascalName}</a>
|
|
576
|
+
</section>;
|
|
577
|
+
}
|
|
578
|
+
if (state === 'not-found') {
|
|
579
|
+
return <section aria-labelledby="state-heading" className={styles.panel}>
|
|
580
|
+
<h2 id="state-heading">Record not found</h2>
|
|
581
|
+
<p>It may have been removed or the link may be incorrect.</p>
|
|
582
|
+
<a href="/${name}">Return to ${pascalName}</a>
|
|
583
|
+
</section>;
|
|
584
|
+
}
|
|
585
|
+
if (state === 'failure') {
|
|
586
|
+
return <section aria-labelledby="state-heading" className={styles.panel}>
|
|
587
|
+
<h2 id="state-heading">Records unavailable</h2>
|
|
588
|
+
<p role="alert">The request failed. Support reference: ui-safe-0001.</p>
|
|
589
|
+
<button type="button">Try again</button>
|
|
590
|
+
</section>;
|
|
591
|
+
}
|
|
592
|
+
if (state === 'stale') {
|
|
593
|
+
return <section aria-labelledby="state-heading" className={styles.panel}>
|
|
594
|
+
<h2 id="state-heading">Newer changes are available</h2>
|
|
595
|
+
<p role="status">This view is out of date. Review changes before saving.</p>
|
|
596
|
+
<button type="button">Review newer changes</button>
|
|
597
|
+
</section>;
|
|
598
|
+
}
|
|
599
|
+
if (state === 'offline') {
|
|
600
|
+
return <section aria-labelledby="state-heading" className={styles.panel}>
|
|
601
|
+
<h2 id="state-heading">Connection interrupted</h2>
|
|
602
|
+
<p role="status">Your change was not sent. Reconnect and try again.</p>
|
|
603
|
+
<button type="button">Retry connection</button>
|
|
604
|
+
</section>;
|
|
605
|
+
}
|
|
606
|
+
return <section aria-labelledby="state-heading" className={styles.panel}>
|
|
607
|
+
<h2 id="state-heading">Record saved</h2>
|
|
608
|
+
<p role="status">The synthetic record was saved.</p>
|
|
609
|
+
</section>;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
export function NeutralFrontendStateFixture({
|
|
613
|
+
state,
|
|
614
|
+
}: Readonly<{ state: FrontendFixtureState }>) {
|
|
615
|
+
const dialog = useRef<HTMLDialogElement>(null);
|
|
616
|
+
const dialogClose = useRef<HTMLButtonElement>(null);
|
|
617
|
+
const dialogTrigger = useRef<HTMLButtonElement>(null);
|
|
618
|
+
const [dialogOpen, setDialogOpen] = useState(false);
|
|
619
|
+
|
|
620
|
+
function openDialog() {
|
|
621
|
+
setDialogOpen(true);
|
|
622
|
+
dialog.current?.showModal();
|
|
623
|
+
queueMicrotask(() => dialogClose.current?.focus());
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function restoreDialogFocus() {
|
|
627
|
+
setDialogOpen(false);
|
|
628
|
+
dialogTrigger.current?.focus();
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
return <div className={styles.fixture}>
|
|
632
|
+
<StatePanel state={state} />
|
|
633
|
+
|
|
634
|
+
<section aria-labelledby="actions-heading" className={styles.panel}>
|
|
635
|
+
<h2 id="actions-heading">Semantic actions</h2>
|
|
636
|
+
<p>Use a link to navigate and a button to change the current interface.</p>
|
|
637
|
+
<a href="/${name}">Return to ${pascalName}</a>
|
|
638
|
+
{' '}
|
|
639
|
+
<button
|
|
640
|
+
aria-expanded={dialogOpen}
|
|
641
|
+
aria-haspopup="dialog"
|
|
642
|
+
className={styles.motionSample}
|
|
643
|
+
onClick={openDialog}
|
|
644
|
+
ref={dialogTrigger}
|
|
645
|
+
type="button"
|
|
646
|
+
>
|
|
647
|
+
Open details dialog
|
|
648
|
+
</button>
|
|
649
|
+
</section>
|
|
650
|
+
|
|
651
|
+
<section aria-labelledby="table-heading" className={styles.panel}>
|
|
652
|
+
<h2 id="table-heading">Synthetic records</h2>
|
|
653
|
+
<div className={styles.tableWrapper}>
|
|
654
|
+
<table className={styles.table}>
|
|
655
|
+
<caption>Neutral fixture record status</caption>
|
|
656
|
+
<thead><tr><th scope="col">Record</th><th scope="col">Status</th></tr></thead>
|
|
657
|
+
<tbody><tr><th scope="row">Example A</th><td>Ready</td></tr></tbody>
|
|
658
|
+
</table>
|
|
659
|
+
</div>
|
|
660
|
+
<nav aria-label="Record pages">
|
|
661
|
+
<ul className={styles.pagination}>
|
|
662
|
+
<li><a aria-current="page" href="?state=success">Page 1</a></li>
|
|
663
|
+
<li><a href="?state=empty">Page 2</a></li>
|
|
664
|
+
</ul>
|
|
665
|
+
</nav>
|
|
666
|
+
</section>
|
|
667
|
+
|
|
668
|
+
<dialog
|
|
669
|
+
aria-labelledby="details-dialog-title"
|
|
670
|
+
className={styles.dialog}
|
|
671
|
+
onCancel={restoreDialogFocus}
|
|
672
|
+
onClose={restoreDialogFocus}
|
|
673
|
+
ref={dialog}
|
|
674
|
+
>
|
|
675
|
+
<h2 id="details-dialog-title">Synthetic record details</h2>
|
|
676
|
+
<p>This dialog contains neutral proof content only.</p>
|
|
677
|
+
<button onClick={() => dialog.current?.close()} ref={dialogClose} type="button">
|
|
678
|
+
Close details
|
|
679
|
+
</button>
|
|
680
|
+
</dialog>
|
|
681
|
+
</div>;
|
|
682
|
+
}
|
|
683
|
+
`,
|
|
684
|
+
'web/src/app/%5Febk/frontend-states/page.tsx': `import type { Metadata } from 'next';
|
|
685
|
+
import { NeutralFrontendStateFixture } from './fixture';
|
|
686
|
+
import styles from './fixture.module.css';
|
|
687
|
+
import {
|
|
688
|
+
FRONTEND_FIXTURE_STATES,
|
|
689
|
+
type FrontendFixtureState,
|
|
690
|
+
} from './states';
|
|
691
|
+
|
|
692
|
+
export const metadata: Metadata = {
|
|
693
|
+
robots: { follow: false, index: false },
|
|
694
|
+
title: '${pascalName} neutral frontend states',
|
|
695
|
+
};
|
|
696
|
+
|
|
697
|
+
function fixtureState(value: string | undefined): FrontendFixtureState {
|
|
698
|
+
return FRONTEND_FIXTURE_STATES.find((state) => state === value) ?? 'success';
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
export default async function FrontendStates({
|
|
702
|
+
searchParams,
|
|
703
|
+
}: Readonly<{ searchParams: Promise<{ state?: string }> }>) {
|
|
704
|
+
const state = fixtureState((await searchParams).state);
|
|
705
|
+
return <main>
|
|
706
|
+
<h1>${pascalName} neutral frontend states</h1>
|
|
707
|
+
<p>
|
|
708
|
+
This managed route contains synthetic accessibility evidence, not product
|
|
709
|
+
content or a shared design system.
|
|
710
|
+
</p>
|
|
711
|
+
<nav aria-label="Frontend fixture states" className={styles.stateNavigation}>
|
|
712
|
+
<ul>
|
|
713
|
+
{FRONTEND_FIXTURE_STATES.map((candidate) => <li key={candidate}>
|
|
714
|
+
<a
|
|
715
|
+
aria-current={candidate === state ? 'page' : undefined}
|
|
716
|
+
href={'/${name}/_ebk/frontend-states?state=' + candidate}
|
|
717
|
+
>
|
|
718
|
+
{candidate}
|
|
719
|
+
</a>
|
|
720
|
+
</li>)}
|
|
721
|
+
</ul>
|
|
722
|
+
</nav>
|
|
723
|
+
<NeutralFrontendStateFixture state={state} />
|
|
724
|
+
</main>;
|
|
725
|
+
}
|
|
726
|
+
`,
|
|
727
|
+
'web/src/app/%5Febk/frontend-states/page.spec.tsx': `import { renderToStaticMarkup } from 'react-dom/server';
|
|
728
|
+
import { describe, expect, it } from 'vitest';
|
|
729
|
+
import { NeutralFrontendStateFixture } from './fixture';
|
|
730
|
+
import { FRONTEND_FIXTURE_STATES } from './states';
|
|
731
|
+
|
|
732
|
+
describe('neutral frontend state fixture', () => {
|
|
733
|
+
it.each(FRONTEND_FIXTURE_STATES)('renders the %s state with one named region', (state) => {
|
|
734
|
+
const html = renderToStaticMarkup(<NeutralFrontendStateFixture state={state} />);
|
|
735
|
+
expect(html).toContain('id="state-heading"');
|
|
736
|
+
expect(html).not.toContain('dangerouslySetInnerHTML');
|
|
737
|
+
expect(html).not.toContain('placeholder');
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
it('keeps the dialog, table, pagination, field, and status semantics inspectable', () => {
|
|
741
|
+
const html = renderToStaticMarkup(<NeutralFrontendStateFixture state="validation" />);
|
|
742
|
+
expect(html).toContain('<dialog');
|
|
743
|
+
expect(html).toContain('aria-labelledby="details-dialog-title"');
|
|
744
|
+
expect(html).toContain('<caption>');
|
|
745
|
+
expect(html).toContain('aria-label="Record pages"');
|
|
746
|
+
expect(html).toContain('aria-invalid="true"');
|
|
747
|
+
expect(html).toContain('role="alert"');
|
|
748
|
+
});
|
|
749
|
+
});
|
|
750
|
+
`,
|
|
751
|
+
'web/src/app/%5Febk/frontend-csp/route.ts': `import 'server-only';
|
|
752
|
+
import { headers } from 'next/headers';
|
|
753
|
+
|
|
754
|
+
const noncePattern = /^[A-Za-z0-9+/_-]{16,128}={0,2}$/;
|
|
755
|
+
|
|
756
|
+
export async function GET(): Promise<Response> {
|
|
757
|
+
const nonce = (await headers()).get('x-nonce') ?? '';
|
|
758
|
+
if (!noncePattern.test(nonce)) {
|
|
759
|
+
return new Response('Strict CSP proof requires the request nonce.', {
|
|
760
|
+
status: 500,
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
return new Response(
|
|
764
|
+
'<!doctype html><html><head>' +
|
|
765
|
+
'<style nonce="' + nonce + '">#approved-style{color:rgb(1,2,3)}</style>' +
|
|
766
|
+
'<style>#unapproved-style{color:rgb(4,5,6)}</style>' +
|
|
767
|
+
'</head><body>' +
|
|
768
|
+
'<h1>${pascalName} strict CSP proof</h1>' +
|
|
769
|
+
'<p id="approved-style">Approved style</p>' +
|
|
770
|
+
'<p id="unapproved-style">Unapproved style</p>' +
|
|
771
|
+
'<script nonce="' + nonce + '">' +
|
|
772
|
+
"document.documentElement.dataset.ebkApprovedInline='executed'" +
|
|
773
|
+
'</script>' +
|
|
774
|
+
'<script>' +
|
|
775
|
+
"document.documentElement.dataset.ebkUnapprovedInline='executed'" +
|
|
776
|
+
'</script>' +
|
|
777
|
+
'</body></html>',
|
|
778
|
+
{ headers: { 'Content-Type': 'text/html; charset=utf-8' } },
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
`,
|
|
782
|
+
'scripts/frontend-boundaries.mjs': `import {
|
|
783
|
+
lstatSync,
|
|
784
|
+
mkdirSync,
|
|
785
|
+
opendirSync,
|
|
786
|
+
readFileSync,
|
|
787
|
+
writeFileSync,
|
|
788
|
+
} from 'node:fs';
|
|
789
|
+
import { dirname, extname, join, relative, resolve, sep } from 'node:path';
|
|
790
|
+
import { fileURLToPath } from 'node:url';
|
|
791
|
+
import ts from 'typescript';
|
|
792
|
+
|
|
793
|
+
const projectRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
794
|
+
const sourceRoot = join(projectRoot, 'web', 'src');
|
|
795
|
+
const contractPath = join(projectRoot, 'frontend', 'contract.json');
|
|
796
|
+
const tsconfigPath = join(projectRoot, 'web', 'tsconfig.json');
|
|
797
|
+
const SOURCE_EXTENSIONS = new Set(['.cjs', '.js', '.jsx', '.mjs', '.ts', '.tsx']);
|
|
798
|
+
const CLIENT_ASSET_EXTENSIONS = new Set(['.css']);
|
|
799
|
+
const MAX_SOURCE_DIRECTORIES = 512;
|
|
800
|
+
const MAX_SOURCE_FILES = 1_024;
|
|
801
|
+
const MAX_SOURCE_FILE_BYTES = 1024 * 1024;
|
|
802
|
+
const MAX_SOURCE_BYTES = 16 * 1024 * 1024;
|
|
803
|
+
const MAX_TRAVERSAL_ENTRIES = 20_000;
|
|
804
|
+
|
|
805
|
+
function portable(path) {
|
|
806
|
+
return relative(projectRoot, path).split(sep).join('/');
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
function lineOf(sourceFile, node) {
|
|
810
|
+
return sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
function sourcePaths(root) {
|
|
814
|
+
const paths = [];
|
|
815
|
+
const findings = [];
|
|
816
|
+
const pending = [root];
|
|
817
|
+
let directories = 0;
|
|
818
|
+
let entries = 0;
|
|
819
|
+
let totalBytes = 0;
|
|
820
|
+
let traversalAborted = false;
|
|
821
|
+
while (pending.length > 0 && !traversalAborted) {
|
|
822
|
+
const directory = pending.pop();
|
|
823
|
+
directories += 1;
|
|
824
|
+
if (directories > MAX_SOURCE_DIRECTORIES) {
|
|
825
|
+
findings.push({
|
|
826
|
+
classification: 'source-directory-limit-exceeded',
|
|
827
|
+
limit: MAX_SOURCE_DIRECTORIES,
|
|
828
|
+
path: portable(directory),
|
|
829
|
+
});
|
|
830
|
+
traversalAborted = true;
|
|
831
|
+
break;
|
|
832
|
+
}
|
|
833
|
+
const handle = opendirSync(directory);
|
|
834
|
+
try {
|
|
835
|
+
let entry;
|
|
836
|
+
while ((entry = handle.readSync()) !== null) {
|
|
837
|
+
entries += 1;
|
|
838
|
+
if (entries > MAX_TRAVERSAL_ENTRIES) {
|
|
839
|
+
findings.push({
|
|
840
|
+
classification: 'source-entry-limit-exceeded',
|
|
841
|
+
limit: MAX_TRAVERSAL_ENTRIES,
|
|
842
|
+
path: portable(directory),
|
|
843
|
+
});
|
|
844
|
+
traversalAborted = true;
|
|
845
|
+
break;
|
|
846
|
+
}
|
|
847
|
+
const path = join(directory, entry.name);
|
|
848
|
+
if (entry.isSymbolicLink()) {
|
|
849
|
+
findings.push({
|
|
850
|
+
classification: 'source-symlink-forbidden',
|
|
851
|
+
path: portable(path),
|
|
852
|
+
});
|
|
853
|
+
} else if (entry.isDirectory()) {
|
|
854
|
+
pending.push(path);
|
|
855
|
+
} else if (entry.isFile() && SOURCE_EXTENSIONS.has(extname(entry.name))) {
|
|
856
|
+
const size = lstatSync(path).size;
|
|
857
|
+
if (size > MAX_SOURCE_FILE_BYTES) {
|
|
858
|
+
findings.push({
|
|
859
|
+
bytes: size,
|
|
860
|
+
classification: 'source-file-byte-limit-exceeded',
|
|
861
|
+
limit: MAX_SOURCE_FILE_BYTES,
|
|
862
|
+
path: portable(path),
|
|
863
|
+
});
|
|
864
|
+
continue;
|
|
865
|
+
}
|
|
866
|
+
if (paths.length >= MAX_SOURCE_FILES) {
|
|
867
|
+
findings.push({
|
|
868
|
+
classification: 'source-file-limit-exceeded',
|
|
869
|
+
limit: MAX_SOURCE_FILES,
|
|
870
|
+
path: portable(path),
|
|
871
|
+
});
|
|
872
|
+
traversalAborted = true;
|
|
873
|
+
break;
|
|
874
|
+
}
|
|
875
|
+
if (totalBytes + size > MAX_SOURCE_BYTES) {
|
|
876
|
+
findings.push({
|
|
877
|
+
classification: 'source-total-byte-limit-exceeded',
|
|
878
|
+
limit: MAX_SOURCE_BYTES,
|
|
879
|
+
path: portable(path),
|
|
880
|
+
});
|
|
881
|
+
traversalAborted = true;
|
|
882
|
+
break;
|
|
883
|
+
}
|
|
884
|
+
totalBytes += size;
|
|
885
|
+
paths.push(path);
|
|
886
|
+
} else if (
|
|
887
|
+
!entry.isFile() &&
|
|
888
|
+
!entry.isDirectory() &&
|
|
889
|
+
!entry.isSymbolicLink()
|
|
890
|
+
) {
|
|
891
|
+
findings.push({
|
|
892
|
+
classification: 'source-special-file-forbidden',
|
|
893
|
+
path: portable(path),
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
} finally {
|
|
898
|
+
handle.closeSync();
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
return {
|
|
902
|
+
findings,
|
|
903
|
+
paths: traversalAborted ? [] : paths,
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
function outsideSourceRoot(target) {
|
|
908
|
+
const fromSourceRoot = relative(sourceRoot, target);
|
|
909
|
+
return (
|
|
910
|
+
fromSourceRoot === '..' ||
|
|
911
|
+
fromSourceRoot.startsWith('..' + sep) ||
|
|
912
|
+
resolve(sourceRoot, fromSourceRoot) !== target
|
|
913
|
+
);
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
function relativeImport(importer, specifier, existing, compilerOptions) {
|
|
917
|
+
const requestedTarget = resolve(dirname(importer), specifier);
|
|
918
|
+
if (outsideSourceRoot(requestedTarget)) {
|
|
919
|
+
return {
|
|
920
|
+
failure: 'outside-source-root',
|
|
921
|
+
kind: 'relative',
|
|
922
|
+
specifier,
|
|
923
|
+
target: requestedTarget,
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
const resolvedModule = ts.resolveModuleName(
|
|
927
|
+
specifier,
|
|
928
|
+
importer,
|
|
929
|
+
compilerOptions,
|
|
930
|
+
ts.sys,
|
|
931
|
+
).resolvedModule;
|
|
932
|
+
if (!resolvedModule) {
|
|
933
|
+
if (
|
|
934
|
+
ts.sys.fileExists(requestedTarget) &&
|
|
935
|
+
CLIENT_ASSET_EXTENSIONS.has(extname(requestedTarget))
|
|
936
|
+
) {
|
|
937
|
+
return { kind: 'asset', specifier, target: requestedTarget };
|
|
938
|
+
}
|
|
939
|
+
return {
|
|
940
|
+
failure: ts.sys.fileExists(requestedTarget)
|
|
941
|
+
? 'unsupported-target'
|
|
942
|
+
: 'unresolved',
|
|
943
|
+
kind: 'relative',
|
|
944
|
+
specifier,
|
|
945
|
+
target: ts.sys.fileExists(requestedTarget) ? requestedTarget : undefined,
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
const target = resolve(resolvedModule.resolvedFileName);
|
|
949
|
+
if (outsideSourceRoot(target)) {
|
|
950
|
+
return {
|
|
951
|
+
failure: 'outside-source-root',
|
|
952
|
+
kind: 'relative',
|
|
953
|
+
specifier,
|
|
954
|
+
target,
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
if (!existing.has(target)) {
|
|
958
|
+
if (CLIENT_ASSET_EXTENSIONS.has(extname(target))) {
|
|
959
|
+
return { kind: 'asset', specifier, target };
|
|
960
|
+
}
|
|
961
|
+
return {
|
|
962
|
+
failure: 'unsupported-target',
|
|
963
|
+
kind: 'relative',
|
|
964
|
+
specifier,
|
|
965
|
+
target,
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
return { kind: 'relative', specifier, target };
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
function compilerConfiguration() {
|
|
972
|
+
const loaded = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
|
|
973
|
+
if (loaded.error) {
|
|
974
|
+
throw new Error(
|
|
975
|
+
'web/tsconfig.json is invalid: ' +
|
|
976
|
+
ts.flattenDiagnosticMessageText(loaded.error.messageText, ' '),
|
|
977
|
+
);
|
|
978
|
+
}
|
|
979
|
+
const parsed = ts.parseJsonConfigFileContent(
|
|
980
|
+
loaded.config,
|
|
981
|
+
ts.sys,
|
|
982
|
+
dirname(tsconfigPath),
|
|
983
|
+
undefined,
|
|
984
|
+
tsconfigPath,
|
|
985
|
+
);
|
|
986
|
+
if (parsed.errors.length > 0) {
|
|
987
|
+
throw new Error(
|
|
988
|
+
'web/tsconfig.json is invalid: ' +
|
|
989
|
+
parsed.errors
|
|
990
|
+
.map((diagnostic) =>
|
|
991
|
+
ts.flattenDiagnosticMessageText(diagnostic.messageText, ' '),
|
|
992
|
+
)
|
|
993
|
+
.join(' '),
|
|
994
|
+
);
|
|
995
|
+
}
|
|
996
|
+
return {
|
|
997
|
+
aliasPatterns: Object.keys(parsed.options.paths ?? {}).sort(),
|
|
998
|
+
options: parsed.options,
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
function aliasMatches(pattern, specifier) {
|
|
1003
|
+
const wildcard = pattern.indexOf('*');
|
|
1004
|
+
if (wildcard === -1) return pattern === specifier;
|
|
1005
|
+
if (pattern.indexOf('*', wildcard + 1) !== -1) return false;
|
|
1006
|
+
const prefix = pattern.slice(0, wildcard);
|
|
1007
|
+
const suffix = pattern.slice(wildcard + 1);
|
|
1008
|
+
return (
|
|
1009
|
+
specifier.length >= prefix.length + suffix.length &&
|
|
1010
|
+
specifier.startsWith(prefix) &&
|
|
1011
|
+
specifier.endsWith(suffix)
|
|
1012
|
+
);
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
function moduleImport(
|
|
1016
|
+
importer,
|
|
1017
|
+
specifier,
|
|
1018
|
+
existing,
|
|
1019
|
+
compilerOptions,
|
|
1020
|
+
aliasPatterns,
|
|
1021
|
+
) {
|
|
1022
|
+
if (specifier.startsWith('.')) {
|
|
1023
|
+
return relativeImport(importer, specifier, existing, compilerOptions);
|
|
1024
|
+
}
|
|
1025
|
+
const alias = aliasPatterns.find((pattern) =>
|
|
1026
|
+
aliasMatches(pattern, specifier),
|
|
1027
|
+
);
|
|
1028
|
+
const resolvedModule = ts.resolveModuleName(
|
|
1029
|
+
specifier,
|
|
1030
|
+
importer,
|
|
1031
|
+
compilerOptions,
|
|
1032
|
+
ts.sys,
|
|
1033
|
+
).resolvedModule;
|
|
1034
|
+
if (!resolvedModule) {
|
|
1035
|
+
return alias
|
|
1036
|
+
? { alias, failure: 'unresolved', kind: 'alias', specifier }
|
|
1037
|
+
: undefined;
|
|
1038
|
+
}
|
|
1039
|
+
if (!alias && resolvedModule.isExternalLibraryImport) return undefined;
|
|
1040
|
+
const kind = alias ? 'alias' : 'base-url';
|
|
1041
|
+
const target = resolve(resolvedModule.resolvedFileName);
|
|
1042
|
+
if (outsideSourceRoot(target)) {
|
|
1043
|
+
return {
|
|
1044
|
+
alias,
|
|
1045
|
+
failure: 'outside-source-root',
|
|
1046
|
+
kind,
|
|
1047
|
+
specifier,
|
|
1048
|
+
target,
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
if (!existing.has(target)) {
|
|
1052
|
+
return {
|
|
1053
|
+
alias,
|
|
1054
|
+
failure: 'unsupported-target',
|
|
1055
|
+
kind,
|
|
1056
|
+
specifier,
|
|
1057
|
+
target,
|
|
1058
|
+
};
|
|
1059
|
+
}
|
|
1060
|
+
return { alias, kind, specifier, target };
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
function scriptKind(path) {
|
|
1064
|
+
switch (extname(path)) {
|
|
1065
|
+
case '.js':
|
|
1066
|
+
case '.mjs':
|
|
1067
|
+
case '.cjs':
|
|
1068
|
+
return ts.ScriptKind.JS;
|
|
1069
|
+
case '.jsx':
|
|
1070
|
+
return ts.ScriptKind.JSX;
|
|
1071
|
+
case '.tsx':
|
|
1072
|
+
return ts.ScriptKind.TSX;
|
|
1073
|
+
default:
|
|
1074
|
+
return ts.ScriptKind.TS;
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
function calledModule(node) {
|
|
1079
|
+
if (
|
|
1080
|
+
ts.isImportDeclaration(node) ||
|
|
1081
|
+
ts.isExportDeclaration(node)
|
|
1082
|
+
) {
|
|
1083
|
+
return node.moduleSpecifier && ts.isStringLiteralLike(node.moduleSpecifier)
|
|
1084
|
+
? { kind: 'static', specifier: node.moduleSpecifier.text }
|
|
1085
|
+
: undefined;
|
|
1086
|
+
}
|
|
1087
|
+
if (!ts.isCallExpression(node)) return undefined;
|
|
1088
|
+
const kind =
|
|
1089
|
+
node.expression.kind === ts.SyntaxKind.ImportKeyword
|
|
1090
|
+
? 'dynamic-import'
|
|
1091
|
+
: ts.isIdentifier(node.expression) && node.expression.text === 'require'
|
|
1092
|
+
? 'require'
|
|
1093
|
+
: undefined;
|
|
1094
|
+
if (!kind) return undefined;
|
|
1095
|
+
const argument = node.arguments[0];
|
|
1096
|
+
return node.arguments.length === 1 && argument && ts.isStringLiteralLike(argument)
|
|
1097
|
+
? { kind, specifier: argument.text }
|
|
1098
|
+
: { kind, specifier: null };
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
function environmentRead(node) {
|
|
1102
|
+
if (
|
|
1103
|
+
ts.isPropertyAccessExpression(node) &&
|
|
1104
|
+
isProcessEnvironmentObject(node.expression)
|
|
1105
|
+
) {
|
|
1106
|
+
return node.name.text;
|
|
1107
|
+
}
|
|
1108
|
+
if (
|
|
1109
|
+
ts.isElementAccessExpression(node) &&
|
|
1110
|
+
isProcessEnvironmentObject(node.expression)
|
|
1111
|
+
) {
|
|
1112
|
+
return ts.isStringLiteralLike(node.argumentExpression)
|
|
1113
|
+
? node.argumentExpression.text
|
|
1114
|
+
: null;
|
|
1115
|
+
}
|
|
1116
|
+
return undefined;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
function isProcessEnvironmentObject(node) {
|
|
1120
|
+
return (
|
|
1121
|
+
(ts.isPropertyAccessExpression(node) &&
|
|
1122
|
+
ts.isIdentifier(node.expression) &&
|
|
1123
|
+
node.expression.text === 'process' &&
|
|
1124
|
+
node.name.text === 'env') ||
|
|
1125
|
+
(ts.isElementAccessExpression(node) &&
|
|
1126
|
+
ts.isIdentifier(node.expression) &&
|
|
1127
|
+
node.expression.text === 'process' &&
|
|
1128
|
+
ts.isStringLiteralLike(node.argumentExpression) &&
|
|
1129
|
+
node.argumentExpression.text === 'env')
|
|
1130
|
+
);
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
function serverPackage(specifier) {
|
|
1134
|
+
return (
|
|
1135
|
+
specifier === 'next/headers' ||
|
|
1136
|
+
specifier === 'next/server' ||
|
|
1137
|
+
specifier === 'server-only' ||
|
|
1138
|
+
specifier.startsWith('@aws-sdk/') ||
|
|
1139
|
+
specifier === '@empire-builder-kit/runtime/auth-backend' ||
|
|
1140
|
+
specifier === '@empire-builder-kit/runtime/database' ||
|
|
1141
|
+
specifier === '@empire-builder-kit/runtime/secrets'
|
|
1142
|
+
);
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
export function inspectFrontendBoundaries() {
|
|
1146
|
+
const contract = JSON.parse(readFileSync(contractPath, 'utf8'));
|
|
1147
|
+
if (
|
|
1148
|
+
contract?.schemaVersion !== 1 ||
|
|
1149
|
+
contract?.slice !== '${name}' ||
|
|
1150
|
+
!Array.isArray(contract?.publicConfiguration?.allowlist)
|
|
1151
|
+
) {
|
|
1152
|
+
throw new Error('frontend/contract.json is missing the managed public configuration contract.');
|
|
1153
|
+
}
|
|
1154
|
+
const allowedPublic = new Set(contract.publicConfiguration.allowlist);
|
|
1155
|
+
const discovered = sourcePaths(sourceRoot);
|
|
1156
|
+
const findings = [...discovered.findings];
|
|
1157
|
+
const paths = discovered.paths;
|
|
1158
|
+
const existing = new Set(paths);
|
|
1159
|
+
const modules = new Map();
|
|
1160
|
+
const { aliasPatterns, options: compilerOptions } = compilerConfiguration();
|
|
1161
|
+
|
|
1162
|
+
for (const path of paths) {
|
|
1163
|
+
const source = readFileSync(path, 'utf8');
|
|
1164
|
+
const sourceFile = ts.createSourceFile(
|
|
1165
|
+
path,
|
|
1166
|
+
source,
|
|
1167
|
+
ts.ScriptTarget.Latest,
|
|
1168
|
+
true,
|
|
1169
|
+
scriptKind(path),
|
|
1170
|
+
);
|
|
1171
|
+
const imports = [];
|
|
1172
|
+
const dynamicModuleSpecifiers = [];
|
|
1173
|
+
const serverPackages = [];
|
|
1174
|
+
let serverOnly = false;
|
|
1175
|
+
const inspectModules = (node) => {
|
|
1176
|
+
const called = calledModule(node);
|
|
1177
|
+
if (called) {
|
|
1178
|
+
const moduleSpecifier = called.specifier;
|
|
1179
|
+
if (moduleSpecifier === null) {
|
|
1180
|
+
dynamicModuleSpecifiers.push({
|
|
1181
|
+
kind: called.kind,
|
|
1182
|
+
line: lineOf(sourceFile, node),
|
|
1183
|
+
});
|
|
1184
|
+
} else {
|
|
1185
|
+
if (moduleSpecifier === 'server-only') serverOnly = true;
|
|
1186
|
+
if (serverPackage(moduleSpecifier)) serverPackages.push(moduleSpecifier);
|
|
1187
|
+
const target = moduleImport(
|
|
1188
|
+
path,
|
|
1189
|
+
moduleSpecifier,
|
|
1190
|
+
existing,
|
|
1191
|
+
compilerOptions,
|
|
1192
|
+
aliasPatterns,
|
|
1193
|
+
);
|
|
1194
|
+
if (target) imports.push(target);
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
ts.forEachChild(node, inspectModules);
|
|
1198
|
+
};
|
|
1199
|
+
inspectModules(sourceFile);
|
|
1200
|
+
let client = false;
|
|
1201
|
+
for (const statement of sourceFile.statements) {
|
|
1202
|
+
if (
|
|
1203
|
+
!ts.isExpressionStatement(statement) ||
|
|
1204
|
+
!ts.isStringLiteral(statement.expression)
|
|
1205
|
+
) {
|
|
1206
|
+
break;
|
|
1207
|
+
}
|
|
1208
|
+
if (statement.expression.text === 'use client') client = true;
|
|
1209
|
+
}
|
|
1210
|
+
modules.set(path, {
|
|
1211
|
+
client,
|
|
1212
|
+
dynamicModuleSpecifiers,
|
|
1213
|
+
imports: imports.filter(
|
|
1214
|
+
(candidate, index) =>
|
|
1215
|
+
imports.findIndex(
|
|
1216
|
+
(entry) => JSON.stringify(entry) === JSON.stringify(candidate),
|
|
1217
|
+
) === index,
|
|
1218
|
+
),
|
|
1219
|
+
serverOnly,
|
|
1220
|
+
serverPackages: [...new Set(serverPackages)],
|
|
1221
|
+
sourceFile,
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
const clientReachable = new Map();
|
|
1226
|
+
for (const [path, module] of modules) {
|
|
1227
|
+
if (!module.client) continue;
|
|
1228
|
+
const pending = [
|
|
1229
|
+
{
|
|
1230
|
+
ancestry: [],
|
|
1231
|
+
edgeKinds: [],
|
|
1232
|
+
path,
|
|
1233
|
+
root: path,
|
|
1234
|
+
},
|
|
1235
|
+
];
|
|
1236
|
+
const visited = new Set();
|
|
1237
|
+
while (pending.length > 0) {
|
|
1238
|
+
const current = pending.pop();
|
|
1239
|
+
if (!current) continue;
|
|
1240
|
+
if (current.ancestry.includes(current.path)) {
|
|
1241
|
+
const cycleStart = current.ancestry.indexOf(current.path);
|
|
1242
|
+
const cycleEdgeKinds = [
|
|
1243
|
+
...new Set(current.edgeKinds.slice(cycleStart)),
|
|
1244
|
+
].sort();
|
|
1245
|
+
const cyclePaths = current.ancestry
|
|
1246
|
+
.slice(cycleStart)
|
|
1247
|
+
.map((cyclePath) => portable(cyclePath))
|
|
1248
|
+
.sort();
|
|
1249
|
+
const reachedThroughAlias = cycleEdgeKinds.includes('alias');
|
|
1250
|
+
const reachedThroughBaseUrl = cycleEdgeKinds.includes('base-url');
|
|
1251
|
+
if (
|
|
1252
|
+
cycleEdgeKinds.length > 1 &&
|
|
1253
|
+
(reachedThroughAlias || reachedThroughBaseUrl)
|
|
1254
|
+
) {
|
|
1255
|
+
findings.push({
|
|
1256
|
+
classification: 'client-mixed-local-module-cycle',
|
|
1257
|
+
client: portable(current.root),
|
|
1258
|
+
cycleEdgeKinds,
|
|
1259
|
+
path: cyclePaths[0],
|
|
1260
|
+
});
|
|
1261
|
+
} else if (reachedThroughBaseUrl) {
|
|
1262
|
+
findings.push({
|
|
1263
|
+
classification: 'client-base-url-cycle',
|
|
1264
|
+
client: portable(current.root),
|
|
1265
|
+
cycleEdgeKinds,
|
|
1266
|
+
path: cyclePaths[0],
|
|
1267
|
+
});
|
|
1268
|
+
} else if (reachedThroughAlias) {
|
|
1269
|
+
findings.push({
|
|
1270
|
+
classification: 'client-path-alias-cycle',
|
|
1271
|
+
client: portable(current.root),
|
|
1272
|
+
cycleEdgeKinds,
|
|
1273
|
+
path: cyclePaths[0],
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
1276
|
+
continue;
|
|
1277
|
+
}
|
|
1278
|
+
if (visited.has(current.path)) continue;
|
|
1279
|
+
visited.add(current.path);
|
|
1280
|
+
const currentModule = modules.get(current.path);
|
|
1281
|
+
if (!currentModule) continue;
|
|
1282
|
+
if (!clientReachable.has(current.path)) {
|
|
1283
|
+
clientReachable.set(current.path, new Set());
|
|
1284
|
+
}
|
|
1285
|
+
clientReachable.get(current.path).add(current.root);
|
|
1286
|
+
if (currentModule.serverOnly) {
|
|
1287
|
+
findings.push({
|
|
1288
|
+
classification: 'client-imports-server-only',
|
|
1289
|
+
client: portable(current.root),
|
|
1290
|
+
path: portable(current.path),
|
|
1291
|
+
});
|
|
1292
|
+
}
|
|
1293
|
+
for (const dynamic of currentModule.dynamicModuleSpecifiers) {
|
|
1294
|
+
findings.push({
|
|
1295
|
+
classification: 'client-dynamic-module-specifier',
|
|
1296
|
+
client: portable(current.root),
|
|
1297
|
+
kind: dynamic.kind,
|
|
1298
|
+
line: dynamic.line,
|
|
1299
|
+
path: portable(current.path),
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1302
|
+
for (const packageName of currentModule.serverPackages) {
|
|
1303
|
+
findings.push({
|
|
1304
|
+
classification: 'client-imports-server-package',
|
|
1305
|
+
client: portable(current.root),
|
|
1306
|
+
package: packageName,
|
|
1307
|
+
path: portable(current.path),
|
|
1308
|
+
});
|
|
1309
|
+
}
|
|
1310
|
+
const orderedImports = [...currentModule.imports].sort((left, right) =>
|
|
1311
|
+
JSON.stringify(right) < JSON.stringify(left)
|
|
1312
|
+
? -1
|
|
1313
|
+
: JSON.stringify(right) > JSON.stringify(left)
|
|
1314
|
+
? 1
|
|
1315
|
+
: 0,
|
|
1316
|
+
);
|
|
1317
|
+
for (const imported of orderedImports) {
|
|
1318
|
+
if (imported.failure) {
|
|
1319
|
+
const importKind =
|
|
1320
|
+
imported.kind === 'relative'
|
|
1321
|
+
? 'relative-import'
|
|
1322
|
+
: imported.kind === 'alias'
|
|
1323
|
+
? 'path-alias'
|
|
1324
|
+
: 'base-url';
|
|
1325
|
+
findings.push({
|
|
1326
|
+
alias: imported.alias,
|
|
1327
|
+
classification:
|
|
1328
|
+
imported.failure === 'outside-source-root'
|
|
1329
|
+
? 'client-' + importKind + '-outside-source-root'
|
|
1330
|
+
: imported.failure === 'unsupported-target'
|
|
1331
|
+
? 'client-' + importKind + '-target-unsupported'
|
|
1332
|
+
: 'client-' + importKind + '-unresolved',
|
|
1333
|
+
client: portable(current.root),
|
|
1334
|
+
path: portable(current.path),
|
|
1335
|
+
specifier: imported.specifier,
|
|
1336
|
+
target: imported.target ? portable(imported.target) : undefined,
|
|
1337
|
+
});
|
|
1338
|
+
continue;
|
|
1339
|
+
}
|
|
1340
|
+
pending.push({
|
|
1341
|
+
ancestry: [...current.ancestry, current.path],
|
|
1342
|
+
edgeKinds: [...current.edgeKinds, imported.kind],
|
|
1343
|
+
path: imported.target,
|
|
1344
|
+
root: current.root,
|
|
1345
|
+
});
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
for (const [path, module] of modules) {
|
|
1351
|
+
const inClientGraph = clientReachable.has(path);
|
|
1352
|
+
const visit = (node) => {
|
|
1353
|
+
const key = environmentRead(node);
|
|
1354
|
+
const parent = node.parent;
|
|
1355
|
+
const parentConsumesEnvironment =
|
|
1356
|
+
(parent &&
|
|
1357
|
+
ts.isPropertyAccessExpression(parent) &&
|
|
1358
|
+
parent.expression === node) ||
|
|
1359
|
+
(parent &&
|
|
1360
|
+
ts.isElementAccessExpression(parent) &&
|
|
1361
|
+
parent.expression === node);
|
|
1362
|
+
if (inClientGraph && key === null) {
|
|
1363
|
+
findings.push({
|
|
1364
|
+
classification: 'client-dynamic-environment-access',
|
|
1365
|
+
line: lineOf(module.sourceFile, node),
|
|
1366
|
+
path: portable(path),
|
|
1367
|
+
});
|
|
1368
|
+
} else if (
|
|
1369
|
+
inClientGraph &&
|
|
1370
|
+
isProcessEnvironmentObject(node) &&
|
|
1371
|
+
!parentConsumesEnvironment
|
|
1372
|
+
) {
|
|
1373
|
+
findings.push({
|
|
1374
|
+
classification: 'client-dynamic-environment-access',
|
|
1375
|
+
line: lineOf(module.sourceFile, node),
|
|
1376
|
+
path: portable(path),
|
|
1377
|
+
});
|
|
1378
|
+
} else if (inClientGraph && typeof key === 'string') {
|
|
1379
|
+
findings.push({
|
|
1380
|
+
classification:
|
|
1381
|
+
key.startsWith('NEXT_PUBLIC_') && allowedPublic.has(key)
|
|
1382
|
+
? undefined
|
|
1383
|
+
: key.startsWith('NEXT_PUBLIC_')
|
|
1384
|
+
? 'undeclared-public-configuration'
|
|
1385
|
+
: 'client-reads-server-environment',
|
|
1386
|
+
key:
|
|
1387
|
+
key.startsWith('NEXT_PUBLIC_') && !allowedPublic.has(key)
|
|
1388
|
+
? key
|
|
1389
|
+
: undefined,
|
|
1390
|
+
line: lineOf(module.sourceFile, node),
|
|
1391
|
+
path: portable(path),
|
|
1392
|
+
});
|
|
1393
|
+
}
|
|
1394
|
+
if (
|
|
1395
|
+
(ts.isIdentifier(node) && node.text === 'dangerouslySetInnerHTML') ||
|
|
1396
|
+
(ts.isPropertyAccessExpression(node) && node.name.text === 'innerHTML')
|
|
1397
|
+
) {
|
|
1398
|
+
findings.push({
|
|
1399
|
+
classification: 'raw-html-sink-requires-reviewed-adapter',
|
|
1400
|
+
line: lineOf(module.sourceFile, node),
|
|
1401
|
+
path: portable(path),
|
|
1402
|
+
});
|
|
1403
|
+
}
|
|
1404
|
+
ts.forEachChild(node, visit);
|
|
1405
|
+
};
|
|
1406
|
+
visit(module.sourceFile);
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
const normalized = findings
|
|
1410
|
+
.filter((finding) => finding.classification)
|
|
1411
|
+
.map((finding) =>
|
|
1412
|
+
Object.fromEntries(
|
|
1413
|
+
Object.entries(finding).filter(([, value]) => value !== undefined),
|
|
1414
|
+
),
|
|
1415
|
+
)
|
|
1416
|
+
.sort((left, right) =>
|
|
1417
|
+
JSON.stringify(left) < JSON.stringify(right)
|
|
1418
|
+
? -1
|
|
1419
|
+
: JSON.stringify(left) > JSON.stringify(right)
|
|
1420
|
+
? 1
|
|
1421
|
+
: 0,
|
|
1422
|
+
);
|
|
1423
|
+
return {
|
|
1424
|
+
counts: { checked: modules.size, findings: normalized.length },
|
|
1425
|
+
findings: normalized,
|
|
1426
|
+
kind: 'ebk-frontend-boundary-result',
|
|
1427
|
+
outcome: normalized.length === 0 ? 'passed' : 'failed',
|
|
1428
|
+
schemaVersion: 1,
|
|
1429
|
+
slice: '${name}',
|
|
1430
|
+
};
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
function main() {
|
|
1434
|
+
const result = inspectFrontendBoundaries();
|
|
1435
|
+
const outputArgument = process.argv.find((value) => value.startsWith('--output='));
|
|
1436
|
+
if (outputArgument) {
|
|
1437
|
+
const output = resolve(projectRoot, outputArgument.slice('--output='.length));
|
|
1438
|
+
if (
|
|
1439
|
+
relative(projectRoot, output).startsWith('..' + sep) ||
|
|
1440
|
+
relative(projectRoot, output) === '..'
|
|
1441
|
+
) {
|
|
1442
|
+
throw new Error('Frontend boundary evidence must remain inside the Product Slice.');
|
|
1443
|
+
}
|
|
1444
|
+
mkdirSync(dirname(output), { recursive: true, mode: 0o700 });
|
|
1445
|
+
writeFileSync(output, JSON.stringify(result, null, 2) + '\\n', { mode: 0o600 });
|
|
1446
|
+
}
|
|
1447
|
+
process.stdout.write(JSON.stringify(result) + '\\n');
|
|
1448
|
+
if (result.outcome !== 'passed') process.exitCode = 1;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
main();
|
|
1452
|
+
`,
|
|
1453
|
+
'scripts/frontend-security-headers.mjs': `import { createHash } from 'node:crypto';
|
|
1454
|
+
import { mkdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';
|
|
1455
|
+
import { dirname, relative, resolve, sep } from 'node:path';
|
|
1456
|
+
import { fileURLToPath } from 'node:url';
|
|
1457
|
+
import {
|
|
1458
|
+
inspectFrontendSecurityHeaders as inspectOwnedFrontendSecurityHeaders,
|
|
1459
|
+
resolveFrontendSecurityHeaderProfile,
|
|
1460
|
+
} from '@empire-builder-kit/nx/frontend-security';
|
|
1461
|
+
|
|
1462
|
+
const projectRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
1463
|
+
const foundationPolicyPath = resolve(
|
|
1464
|
+
projectRoot,
|
|
1465
|
+
'..',
|
|
1466
|
+
'foundation',
|
|
1467
|
+
'contracts',
|
|
1468
|
+
'frontend-security-headers.json',
|
|
1469
|
+
);
|
|
1470
|
+
const slicePolicyPath = resolve(
|
|
1471
|
+
projectRoot,
|
|
1472
|
+
'frontend',
|
|
1473
|
+
'security-headers.json',
|
|
1474
|
+
);
|
|
1475
|
+
const readPolicy = (path) => {
|
|
1476
|
+
const contents = readFileSync(path);
|
|
1477
|
+
return {
|
|
1478
|
+
digest: 'sha256:' + createHash('sha256').update(contents).digest('hex'),
|
|
1479
|
+
value: JSON.parse(contents),
|
|
1480
|
+
};
|
|
1481
|
+
};
|
|
1482
|
+
const foundationPolicy = readPolicy(foundationPolicyPath);
|
|
1483
|
+
const slicePolicy = readPolicy(slicePolicyPath);
|
|
1484
|
+
const gitObjectIdPattern = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/;
|
|
1485
|
+
const MAX_FRONTEND_HEADER_FETCH_TIMEOUT_MS = 10_000;
|
|
1486
|
+
|
|
1487
|
+
function loopback(hostname) {
|
|
1488
|
+
return ['127.0.0.1', 'localhost', '::1', '[::1]'].includes(hostname);
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
function safeTarget(value, profile, allowLoopbackTransportProof) {
|
|
1492
|
+
const target = new URL(value);
|
|
1493
|
+
if (
|
|
1494
|
+
(target.protocol !== 'https:' &&
|
|
1495
|
+
!(
|
|
1496
|
+
loopback(target.hostname) &&
|
|
1497
|
+
target.protocol === 'http:' &&
|
|
1498
|
+
(profile === 'local-development' || allowLoopbackTransportProof)
|
|
1499
|
+
)) ||
|
|
1500
|
+
target.username ||
|
|
1501
|
+
target.password ||
|
|
1502
|
+
target.search ||
|
|
1503
|
+
target.hash
|
|
1504
|
+
) {
|
|
1505
|
+
throw new Error('Frontend header target must be HTTPS without credentials, query, or fragment.');
|
|
1506
|
+
}
|
|
1507
|
+
return target;
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
export function inspectFrontendSecurityHeaders({
|
|
1511
|
+
allowLoopbackTransportProof = false,
|
|
1512
|
+
headers,
|
|
1513
|
+
https,
|
|
1514
|
+
profile = 'deployed-https',
|
|
1515
|
+
}) {
|
|
1516
|
+
return inspectOwnedFrontendSecurityHeaders({
|
|
1517
|
+
actual: headers,
|
|
1518
|
+
allowLoopbackTransportProof,
|
|
1519
|
+
foundation: foundationPolicy.value,
|
|
1520
|
+
https,
|
|
1521
|
+
profile,
|
|
1522
|
+
slice: slicePolicy.value,
|
|
1523
|
+
sliceName: '${name}',
|
|
1524
|
+
});
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
function responseNonce(headers) {
|
|
1528
|
+
const policy = new Headers(headers).get('content-security-policy') ?? '';
|
|
1529
|
+
const nonces = [
|
|
1530
|
+
...policy.matchAll(/'nonce-([A-Za-z0-9+/_-]{16,128}={0,2})'/g),
|
|
1531
|
+
].map((match) => match[1]);
|
|
1532
|
+
const unique = [...new Set(nonces)];
|
|
1533
|
+
return unique.length === 1 && nonces.length >= 2 ? unique[0] : undefined;
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
export function inspectFrontendNonceFreshness(firstHeaders, secondHeaders) {
|
|
1537
|
+
const first = responseNonce(firstHeaders);
|
|
1538
|
+
const second = responseNonce(secondHeaders);
|
|
1539
|
+
return {
|
|
1540
|
+
firstNoncePresent: first !== undefined,
|
|
1541
|
+
fresh: first !== undefined && second !== undefined && first !== second,
|
|
1542
|
+
secondNoncePresent: second !== undefined,
|
|
1543
|
+
};
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
function outputPath(argument) {
|
|
1547
|
+
const output = resolve(
|
|
1548
|
+
projectRoot,
|
|
1549
|
+
argument?.slice('--output='.length) ??
|
|
1550
|
+
'.ebk/evidence/frontend/security-headers.json',
|
|
1551
|
+
);
|
|
1552
|
+
const fromRoot = relative(projectRoot, output);
|
|
1553
|
+
if (fromRoot === '..' || fromRoot.startsWith('..' + sep)) {
|
|
1554
|
+
throw new Error('Frontend header evidence must remain inside the Product Slice.');
|
|
1555
|
+
}
|
|
1556
|
+
return output;
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
function fetchTimeout() {
|
|
1560
|
+
const value =
|
|
1561
|
+
process.env.EBK_FRONTEND_HEADER_FETCH_TIMEOUT_MS?.trim() ?? '10000';
|
|
1562
|
+
if (!/^[1-9]\\d*$/.test(value)) {
|
|
1563
|
+
throw new Error(
|
|
1564
|
+
'EBK_FRONTEND_HEADER_FETCH_TIMEOUT_MS must be a positive integer.',
|
|
1565
|
+
);
|
|
1566
|
+
}
|
|
1567
|
+
const milliseconds = Number(value);
|
|
1568
|
+
if (milliseconds > MAX_FRONTEND_HEADER_FETCH_TIMEOUT_MS) {
|
|
1569
|
+
throw new Error(
|
|
1570
|
+
'EBK_FRONTEND_HEADER_FETCH_TIMEOUT_MS may not exceed ' +
|
|
1571
|
+
MAX_FRONTEND_HEADER_FETCH_TIMEOUT_MS +
|
|
1572
|
+
' milliseconds.',
|
|
1573
|
+
);
|
|
1574
|
+
}
|
|
1575
|
+
return milliseconds;
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
export async function fetchFrontendResponse(
|
|
1579
|
+
target,
|
|
1580
|
+
timeout,
|
|
1581
|
+
fetchImplementation = fetch,
|
|
1582
|
+
) {
|
|
1583
|
+
const signal = AbortSignal.timeout(timeout);
|
|
1584
|
+
try {
|
|
1585
|
+
return await fetchImplementation(target, { redirect: 'error', signal });
|
|
1586
|
+
} catch (error) {
|
|
1587
|
+
if (signal.aborted) {
|
|
1588
|
+
throw new Error(
|
|
1589
|
+
'Frontend header request did not complete within ' +
|
|
1590
|
+
timeout +
|
|
1591
|
+
' milliseconds.',
|
|
1592
|
+
{ cause: error },
|
|
1593
|
+
);
|
|
1594
|
+
}
|
|
1595
|
+
throw error;
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
async function main() {
|
|
1600
|
+
const arguments_ = process.argv.slice(2);
|
|
1601
|
+
const urlArguments = arguments_.filter((value) => value.startsWith('--url='));
|
|
1602
|
+
const outputArguments = arguments_.filter((value) =>
|
|
1603
|
+
value.startsWith('--output='),
|
|
1604
|
+
);
|
|
1605
|
+
const allowLoopbackTransportProof = arguments_.includes(
|
|
1606
|
+
'--allow-loopback-transport-proof',
|
|
1607
|
+
);
|
|
1608
|
+
const unsupported = arguments_.filter(
|
|
1609
|
+
(value) =>
|
|
1610
|
+
!value.startsWith('--url=') &&
|
|
1611
|
+
!value.startsWith('--output=') &&
|
|
1612
|
+
value !== '--allow-loopback-transport-proof',
|
|
1613
|
+
);
|
|
1614
|
+
if (
|
|
1615
|
+
urlArguments.length > 1 ||
|
|
1616
|
+
outputArguments.length > 1 ||
|
|
1617
|
+
unsupported.length > 0
|
|
1618
|
+
) {
|
|
1619
|
+
throw new Error(
|
|
1620
|
+
'Usage: frontend-security-headers.mjs [--url=<url>] [--output=<path>] [--allow-loopback-transport-proof]',
|
|
1621
|
+
);
|
|
1622
|
+
}
|
|
1623
|
+
const profile = resolveFrontendSecurityHeaderProfile(process.env);
|
|
1624
|
+
const target = safeTarget(
|
|
1625
|
+
urlArguments[0]?.slice('--url='.length) ??
|
|
1626
|
+
process.env.EBK_FRONTEND_URL ??
|
|
1627
|
+
'',
|
|
1628
|
+
profile,
|
|
1629
|
+
allowLoopbackTransportProof,
|
|
1630
|
+
);
|
|
1631
|
+
const timeout = fetchTimeout();
|
|
1632
|
+
const response = await fetchFrontendResponse(target, timeout);
|
|
1633
|
+
const hostedTransportVerified =
|
|
1634
|
+
profile === 'deployed-https' && target.protocol === 'https:';
|
|
1635
|
+
const inspected = inspectFrontendSecurityHeaders({
|
|
1636
|
+
allowLoopbackTransportProof,
|
|
1637
|
+
headers: response.headers,
|
|
1638
|
+
https: target.protocol === 'https:',
|
|
1639
|
+
profile,
|
|
1640
|
+
});
|
|
1641
|
+
const freshnessResponse = hostedTransportVerified
|
|
1642
|
+
? await fetchFrontendResponse(target, timeout)
|
|
1643
|
+
: undefined;
|
|
1644
|
+
const freshnessInspection = freshnessResponse
|
|
1645
|
+
? inspectFrontendSecurityHeaders({
|
|
1646
|
+
allowLoopbackTransportProof,
|
|
1647
|
+
headers: freshnessResponse.headers,
|
|
1648
|
+
https: true,
|
|
1649
|
+
profile,
|
|
1650
|
+
})
|
|
1651
|
+
: undefined;
|
|
1652
|
+
const nonceFreshness = freshnessResponse
|
|
1653
|
+
? inspectFrontendNonceFreshness(response.headers, freshnessResponse.headers)
|
|
1654
|
+
: {
|
|
1655
|
+
firstNoncePresent: false,
|
|
1656
|
+
fresh: false,
|
|
1657
|
+
secondNoncePresent: false,
|
|
1658
|
+
};
|
|
1659
|
+
const nonceFreshnessVerified =
|
|
1660
|
+
hostedTransportVerified &&
|
|
1661
|
+
freshnessResponse !== undefined &&
|
|
1662
|
+
freshnessResponse.status >= 200 &&
|
|
1663
|
+
freshnessResponse.status < 400 &&
|
|
1664
|
+
freshnessInspection?.findings.length === 0 &&
|
|
1665
|
+
nonceFreshness.fresh;
|
|
1666
|
+
const sourceCommit =
|
|
1667
|
+
process.env.EBK_SOURCE_COMMIT?.trim() ?? 'unresolved-local-source';
|
|
1668
|
+
const stage = process.env.EBK_STAGE?.trim() ?? 'development';
|
|
1669
|
+
const findings = [
|
|
1670
|
+
...inspected.findings,
|
|
1671
|
+
...(profile === 'deployed-https' && !gitObjectIdPattern.test(sourceCommit)
|
|
1672
|
+
? ['deployed-source-commit-required']
|
|
1673
|
+
: []),
|
|
1674
|
+
...(response.status >= 200 && response.status < 400
|
|
1675
|
+
? []
|
|
1676
|
+
: ['frontend-response-unsuccessful']),
|
|
1677
|
+
...(hostedTransportVerified && !nonceFreshnessVerified
|
|
1678
|
+
? ['hosted-response-nonce-not-fresh']
|
|
1679
|
+
: []),
|
|
1680
|
+
].sort();
|
|
1681
|
+
const proofScope =
|
|
1682
|
+
profile === 'local-development'
|
|
1683
|
+
? 'local-development'
|
|
1684
|
+
: hostedTransportVerified
|
|
1685
|
+
? 'hosted-deployed'
|
|
1686
|
+
: 'local-response-composition';
|
|
1687
|
+
const result = {
|
|
1688
|
+
...inspected,
|
|
1689
|
+
binding: {
|
|
1690
|
+
foundationPolicyDigest: foundationPolicy.digest,
|
|
1691
|
+
slicePolicyDigest: slicePolicy.digest,
|
|
1692
|
+
sourceCommit,
|
|
1693
|
+
stage,
|
|
1694
|
+
targetOriginDigest:
|
|
1695
|
+
'sha256:' +
|
|
1696
|
+
createHash('sha256').update(target.origin, 'utf8').digest('hex'),
|
|
1697
|
+
},
|
|
1698
|
+
findings,
|
|
1699
|
+
hostedTransportVerified,
|
|
1700
|
+
nonceFreshnessVerified,
|
|
1701
|
+
outcome: findings.length === 0 ? 'passed' : 'failed',
|
|
1702
|
+
proofScope,
|
|
1703
|
+
response: {
|
|
1704
|
+
https: target.protocol === 'https:',
|
|
1705
|
+
pathname: target.pathname,
|
|
1706
|
+
status: response.status,
|
|
1707
|
+
},
|
|
1708
|
+
};
|
|
1709
|
+
const output = outputPath(outputArguments[0]);
|
|
1710
|
+
mkdirSync(dirname(output), { recursive: true, mode: 0o700 });
|
|
1711
|
+
writeFileSync(output, JSON.stringify(result, null, 2) + '\\n', { mode: 0o600 });
|
|
1712
|
+
process.stdout.write(JSON.stringify(result) + '\\n');
|
|
1713
|
+
if (result.outcome !== 'passed') process.exitCode = 1;
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
if (
|
|
1717
|
+
process.argv[1] &&
|
|
1718
|
+
realpathSync(resolve(process.argv[1])) ===
|
|
1719
|
+
realpathSync(fileURLToPath(import.meta.url))
|
|
1720
|
+
) {
|
|
1721
|
+
await main();
|
|
1722
|
+
}
|
|
1723
|
+
`,
|
|
1724
|
+
'scripts/frontend-visual-report.mjs': `import { createHash } from 'node:crypto';
|
|
1725
|
+
import {
|
|
1726
|
+
lstatSync,
|
|
1727
|
+
mkdirSync,
|
|
1728
|
+
readFileSync,
|
|
1729
|
+
realpathSync,
|
|
1730
|
+
writeFileSync,
|
|
1731
|
+
} from 'node:fs';
|
|
1732
|
+
import { dirname, join, relative, resolve, sep } from 'node:path';
|
|
1733
|
+
import { fileURLToPath } from 'node:url';
|
|
1734
|
+
|
|
1735
|
+
const projectRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
1736
|
+
const baselinePath = join(projectRoot, 'frontend', 'visual-baselines.json');
|
|
1737
|
+
const observationPath = join(
|
|
1738
|
+
projectRoot,
|
|
1739
|
+
'.ebk',
|
|
1740
|
+
'evidence',
|
|
1741
|
+
'frontend',
|
|
1742
|
+
'frontend-visual-observations.json',
|
|
1743
|
+
);
|
|
1744
|
+
const resultPath = join(
|
|
1745
|
+
projectRoot,
|
|
1746
|
+
'.ebk',
|
|
1747
|
+
'evidence',
|
|
1748
|
+
'frontend',
|
|
1749
|
+
'frontend-visual-report.json',
|
|
1750
|
+
);
|
|
1751
|
+
const digestPattern = /^sha256:[a-f0-9]{64}$/;
|
|
1752
|
+
const tokenPattern = /^[A-Za-z0-9][A-Za-z0-9._/-]*$/;
|
|
1753
|
+
|
|
1754
|
+
function regularProjectFile(path, label) {
|
|
1755
|
+
const relativePath = relative(projectRoot, path);
|
|
1756
|
+
if (
|
|
1757
|
+
!relativePath ||
|
|
1758
|
+
relativePath === '..' ||
|
|
1759
|
+
relativePath.startsWith('..' + sep) ||
|
|
1760
|
+
resolve(projectRoot, relativePath) !== path
|
|
1761
|
+
) {
|
|
1762
|
+
throw new Error(label + ' must remain inside the Product Slice.');
|
|
1763
|
+
}
|
|
1764
|
+
const stat = lstatSync(path);
|
|
1765
|
+
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
1766
|
+
throw new Error(label + ' must be a regular non-symlink file.');
|
|
1767
|
+
}
|
|
1768
|
+
const real = realpathSync(path);
|
|
1769
|
+
const realRelative = relative(realpathSync(projectRoot), real);
|
|
1770
|
+
if (realRelative === '..' || realRelative.startsWith('..' + sep)) {
|
|
1771
|
+
throw new Error(label + ' resolves outside the Product Slice.');
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
function readJson(path, label) {
|
|
1776
|
+
regularProjectFile(path, label);
|
|
1777
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
function exactKeys(value, allowed) {
|
|
1781
|
+
return (
|
|
1782
|
+
value &&
|
|
1783
|
+
typeof value === 'object' &&
|
|
1784
|
+
Object.keys(value).every((key) => allowed.includes(key))
|
|
1785
|
+
);
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
function records(value, label, { observations = false } = {}) {
|
|
1789
|
+
if (
|
|
1790
|
+
!exactKeys(
|
|
1791
|
+
value,
|
|
1792
|
+
observations
|
|
1793
|
+
? ['records', 'schemaVersion', 'slice']
|
|
1794
|
+
: ['records', 'reviewedAt', 'schemaVersion', 'slice', 'workItem'],
|
|
1795
|
+
) ||
|
|
1796
|
+
value?.schemaVersion !== 1 ||
|
|
1797
|
+
value?.slice !== '${name}' ||
|
|
1798
|
+
!Array.isArray(value?.records)
|
|
1799
|
+
) {
|
|
1800
|
+
throw new Error(label + ' is not a ${name} frontend visual record.');
|
|
1801
|
+
}
|
|
1802
|
+
if (
|
|
1803
|
+
!observations &&
|
|
1804
|
+
((value.reviewedAt === undefined) !== (value.workItem === undefined) ||
|
|
1805
|
+
(value.records.length > 0 &&
|
|
1806
|
+
(value.reviewedAt === undefined || value.workItem === undefined)) ||
|
|
1807
|
+
(value.reviewedAt !== undefined &&
|
|
1808
|
+
(typeof value.reviewedAt !== 'string' ||
|
|
1809
|
+
!Number.isFinite(Date.parse(value.reviewedAt)) ||
|
|
1810
|
+
new Date(value.reviewedAt).toISOString() !== value.reviewedAt ||
|
|
1811
|
+
!/^(?:GH:[1-9][0-9]*|PLAN:[A-Za-z0-9._/-]+)$/.test(
|
|
1812
|
+
value.workItem ?? '',
|
|
1813
|
+
))))
|
|
1814
|
+
) {
|
|
1815
|
+
throw new Error(label + ' has invalid review attribution.');
|
|
1816
|
+
}
|
|
1817
|
+
const ids = new Set();
|
|
1818
|
+
return value.records.map((record, index) => {
|
|
1819
|
+
if (
|
|
1820
|
+
!exactKeys(record, ['artifact', 'digest', 'id', 'profile', 'viewport']) ||
|
|
1821
|
+
!record ||
|
|
1822
|
+
typeof record !== 'object' ||
|
|
1823
|
+
!tokenPattern.test(record.id ?? '') ||
|
|
1824
|
+
!tokenPattern.test(record.profile ?? '') ||
|
|
1825
|
+
!tokenPattern.test(record.viewport ?? '') ||
|
|
1826
|
+
!digestPattern.test(record.digest ?? '') ||
|
|
1827
|
+
typeof record.artifact !== 'string' ||
|
|
1828
|
+
!record.artifact.startsWith('.ebk/evidence/frontend/visuals/') ||
|
|
1829
|
+
record.artifact.includes('..')
|
|
1830
|
+
) {
|
|
1831
|
+
throw new Error(label + ' record ' + index + ' is malformed.');
|
|
1832
|
+
}
|
|
1833
|
+
if (observations) {
|
|
1834
|
+
const artifactPath = resolve(projectRoot, record.artifact);
|
|
1835
|
+
regularProjectFile(artifactPath, label + ' artifact ' + index);
|
|
1836
|
+
const actualDigest =
|
|
1837
|
+
'sha256:' +
|
|
1838
|
+
createHash('sha256').update(readFileSync(artifactPath)).digest('hex');
|
|
1839
|
+
if (actualDigest !== record.digest) {
|
|
1840
|
+
throw new Error(label + ' artifact ' + index + ' does not match its digest.');
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
const key = record.id + '@' + record.viewport + '@' + record.profile;
|
|
1844
|
+
if (ids.has(key)) throw new Error(label + ' contains duplicate record ' + key + '.');
|
|
1845
|
+
ids.add(key);
|
|
1846
|
+
return {
|
|
1847
|
+
artifact: record.artifact,
|
|
1848
|
+
digest: record.digest,
|
|
1849
|
+
id: record.id,
|
|
1850
|
+
profile: record.profile,
|
|
1851
|
+
viewport: record.viewport,
|
|
1852
|
+
};
|
|
1853
|
+
}).sort((left, right) =>
|
|
1854
|
+
left.id + '@' + left.viewport + '@' + left.profile <
|
|
1855
|
+
right.id + '@' + right.viewport + '@' + right.profile
|
|
1856
|
+
? -1
|
|
1857
|
+
: left.id + '@' + left.viewport + '@' + left.profile >
|
|
1858
|
+
right.id + '@' + right.viewport + '@' + right.profile
|
|
1859
|
+
? 1
|
|
1860
|
+
: 0,
|
|
1861
|
+
);
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
function compare(baseline, observed) {
|
|
1865
|
+
const expected = new Map(
|
|
1866
|
+
baseline.map((record) => [
|
|
1867
|
+
record.id + '@' + record.viewport + '@' + record.profile,
|
|
1868
|
+
record,
|
|
1869
|
+
]),
|
|
1870
|
+
);
|
|
1871
|
+
const actual = new Map(
|
|
1872
|
+
observed.map((record) => [
|
|
1873
|
+
record.id + '@' + record.viewport + '@' + record.profile,
|
|
1874
|
+
record,
|
|
1875
|
+
]),
|
|
1876
|
+
);
|
|
1877
|
+
const findings = [];
|
|
1878
|
+
for (const [key, record] of expected) {
|
|
1879
|
+
const observation = actual.get(key);
|
|
1880
|
+
if (!observation) findings.push({ classification: 'observation-missing', id: key });
|
|
1881
|
+
else if (observation.digest !== record.digest) {
|
|
1882
|
+
findings.push({ classification: 'visual-change-unreviewed', id: key });
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
for (const key of actual.keys()) {
|
|
1886
|
+
if (!expected.has(key)) findings.push({ classification: 'baseline-missing', id: key });
|
|
1887
|
+
}
|
|
1888
|
+
return findings.sort((left, right) =>
|
|
1889
|
+
left.id < right.id ? -1 : left.id > right.id ? 1 : 0,
|
|
1890
|
+
);
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
export function verifyFrontendVisualReport() {
|
|
1894
|
+
const baseline = records(
|
|
1895
|
+
readJson(baselinePath, 'Visual baseline'),
|
|
1896
|
+
'Visual baseline',
|
|
1897
|
+
);
|
|
1898
|
+
const observed = records(
|
|
1899
|
+
readJson(observationPath, 'Visual observation'),
|
|
1900
|
+
'Visual observation',
|
|
1901
|
+
{ observations: true },
|
|
1902
|
+
);
|
|
1903
|
+
const observedProfiles = new Set(observed.map(({ profile }) => profile));
|
|
1904
|
+
const applicableBaseline = baseline.filter(({ profile }) =>
|
|
1905
|
+
observedProfiles.has(profile),
|
|
1906
|
+
);
|
|
1907
|
+
const findings =
|
|
1908
|
+
applicableBaseline.length === 0
|
|
1909
|
+
? [{ classification: 'baseline-empty', id: '${name}' }]
|
|
1910
|
+
: compare(applicableBaseline, observed);
|
|
1911
|
+
return {
|
|
1912
|
+
counts: {
|
|
1913
|
+
applicableBaseline: applicableBaseline.length,
|
|
1914
|
+
baseline: baseline.length,
|
|
1915
|
+
observed: observed.length,
|
|
1916
|
+
},
|
|
1917
|
+
findings,
|
|
1918
|
+
kind: 'ebk-frontend-visual-result',
|
|
1919
|
+
outcome: findings.length === 0 ? 'passed' : 'failed',
|
|
1920
|
+
schemaVersion: 1,
|
|
1921
|
+
slice: '${name}',
|
|
1922
|
+
};
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
function updateBaseline(workItem) {
|
|
1926
|
+
if (
|
|
1927
|
+
process.env.CI === 'true' ||
|
|
1928
|
+
process.env.GITHUB_ACTIONS === 'true' ||
|
|
1929
|
+
!['', 'development'].includes(process.env.EBK_STAGE ?? '')
|
|
1930
|
+
) {
|
|
1931
|
+
throw new Error('Frontend visual baselines may be updated only in local development.');
|
|
1932
|
+
}
|
|
1933
|
+
if (!/^(?:GH:[1-9][0-9]*|PLAN:[A-Za-z0-9._/-]+)$/.test(workItem ?? '')) {
|
|
1934
|
+
throw new Error('Visual baseline update requires --work-item=GH:<number> or PLAN:<reference>.');
|
|
1935
|
+
}
|
|
1936
|
+
const observed = records(
|
|
1937
|
+
readJson(observationPath, 'Visual observation'),
|
|
1938
|
+
'Visual observation',
|
|
1939
|
+
{ observations: true },
|
|
1940
|
+
);
|
|
1941
|
+
if (observed.length === 0) throw new Error('Visual baseline update requires observations.');
|
|
1942
|
+
const previous = records(
|
|
1943
|
+
readJson(baselinePath, 'Visual baseline'),
|
|
1944
|
+
'Visual baseline',
|
|
1945
|
+
);
|
|
1946
|
+
const updatedProfiles = new Set(observed.map(({ profile }) => profile));
|
|
1947
|
+
const nextRecords = [
|
|
1948
|
+
...previous.filter(({ profile }) => !updatedProfiles.has(profile)),
|
|
1949
|
+
...observed,
|
|
1950
|
+
].sort((left, right) =>
|
|
1951
|
+
left.id + '@' + left.viewport + '@' + left.profile <
|
|
1952
|
+
right.id + '@' + right.viewport + '@' + right.profile
|
|
1953
|
+
? -1
|
|
1954
|
+
: left.id + '@' + left.viewport + '@' + left.profile >
|
|
1955
|
+
right.id + '@' + right.viewport + '@' + right.profile
|
|
1956
|
+
? 1
|
|
1957
|
+
: 0,
|
|
1958
|
+
);
|
|
1959
|
+
writeFileSync(
|
|
1960
|
+
baselinePath,
|
|
1961
|
+
JSON.stringify(
|
|
1962
|
+
{
|
|
1963
|
+
records: nextRecords,
|
|
1964
|
+
reviewedAt: new Date().toISOString(),
|
|
1965
|
+
schemaVersion: 1,
|
|
1966
|
+
slice: '${name}',
|
|
1967
|
+
workItem,
|
|
1968
|
+
},
|
|
1969
|
+
null,
|
|
1970
|
+
2,
|
|
1971
|
+
) + '\\n',
|
|
1972
|
+
);
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
function writeResult(result) {
|
|
1976
|
+
mkdirSync(dirname(resultPath), { recursive: true, mode: 0o700 });
|
|
1977
|
+
writeFileSync(resultPath, JSON.stringify(result, null, 2) + '\\n', { mode: 0o600 });
|
|
1978
|
+
process.stdout.write(JSON.stringify(result) + '\\n');
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1981
|
+
function main() {
|
|
1982
|
+
const command = process.argv[2];
|
|
1983
|
+
if (command === 'update') {
|
|
1984
|
+
const workItem = process.argv.find((value) => value.startsWith('--work-item='));
|
|
1985
|
+
updateBaseline(workItem?.slice('--work-item='.length));
|
|
1986
|
+
} else if (command !== 'verify') {
|
|
1987
|
+
throw new Error('Usage: frontend-visual-report.mjs <verify|update> [--work-item=GH:123]');
|
|
1988
|
+
}
|
|
1989
|
+
const result = verifyFrontendVisualReport();
|
|
1990
|
+
writeResult(result);
|
|
1991
|
+
if (result.outcome !== 'passed') process.exitCode = 1;
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
main();
|
|
1995
|
+
`,
|
|
1996
|
+
'e2e/frontend-accessibility.spec.ts': `import { createHash } from 'node:crypto';
|
|
1997
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
1998
|
+
import { join } from 'node:path';
|
|
1999
|
+
import AxeBuilder from '@axe-core/playwright';
|
|
2000
|
+
import { expect, test, type Page } from '@playwright/test';
|
|
2001
|
+
|
|
2002
|
+
const states = ${JSON.stringify(states)} as const;
|
|
2003
|
+
|
|
2004
|
+
function requiredEnvironment(name: string): string {
|
|
2005
|
+
const value = process.env[name]?.trim();
|
|
2006
|
+
if (!value) throw new Error(name + ' is required for frontend browser proof.');
|
|
2007
|
+
return value;
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
function fixtureUrl(state: (typeof states)[number]): string {
|
|
2011
|
+
const product = new URL(requiredEnvironment('EBK_SMOKE_URL'));
|
|
2012
|
+
product.pathname =
|
|
2013
|
+
product.pathname.replace(/\\/$/, '') + '/_ebk/frontend-states';
|
|
2014
|
+
product.searchParams.set('state', state);
|
|
2015
|
+
return product.href;
|
|
2016
|
+
}
|
|
2017
|
+
|
|
2018
|
+
function cspProofUrl(): string {
|
|
2019
|
+
const product = new URL(requiredEnvironment('EBK_SMOKE_URL'));
|
|
2020
|
+
product.pathname = product.pathname.replace(/\\/$/, '') + '/_ebk/frontend-csp';
|
|
2021
|
+
return product.href;
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
function observeBrowserFailures(page: Page): string[] {
|
|
2025
|
+
const failures: string[] = [];
|
|
2026
|
+
page.on('console', (message) => {
|
|
2027
|
+
if (message.type() === 'error') {
|
|
2028
|
+
failures.push(
|
|
2029
|
+
'console-error:' +
|
|
2030
|
+
message.text().replace(/\\s+/g, ' ').slice(0, 300),
|
|
2031
|
+
);
|
|
2032
|
+
}
|
|
2033
|
+
});
|
|
2034
|
+
page.on('pageerror', (error) =>
|
|
2035
|
+
failures.push(
|
|
2036
|
+
'uncaught-page-error:' +
|
|
2037
|
+
error.message.replace(/\\s+/g, ' ').slice(0, 300),
|
|
2038
|
+
),
|
|
2039
|
+
);
|
|
2040
|
+
page.on('requestfailed', (request) =>
|
|
2041
|
+
failures.push(
|
|
2042
|
+
'request-failed:' +
|
|
2043
|
+
new URL(request.url()).pathname.slice(0, 200),
|
|
2044
|
+
),
|
|
2045
|
+
);
|
|
2046
|
+
return failures;
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
async function installSecurityPolicyViolationObserver(page: Page): Promise<void> {
|
|
2050
|
+
await page.addInitScript(() => {
|
|
2051
|
+
const observedWindow = window as Window & {
|
|
2052
|
+
__ebkSecurityPolicyViolations?: string[];
|
|
2053
|
+
};
|
|
2054
|
+
const violations: string[] = [];
|
|
2055
|
+
Object.defineProperty(observedWindow, '__ebkSecurityPolicyViolations', {
|
|
2056
|
+
configurable: false,
|
|
2057
|
+
enumerable: false,
|
|
2058
|
+
value: violations,
|
|
2059
|
+
writable: false,
|
|
2060
|
+
});
|
|
2061
|
+
document.addEventListener('securitypolicyviolation', (event) => {
|
|
2062
|
+
if (violations.length < 20) {
|
|
2063
|
+
violations.push(
|
|
2064
|
+
'csp-violation:' + event.effectiveDirective + ':' + event.disposition,
|
|
2065
|
+
);
|
|
2066
|
+
}
|
|
2067
|
+
});
|
|
2068
|
+
});
|
|
2069
|
+
}
|
|
2070
|
+
|
|
2071
|
+
async function securityPolicyViolations(page: Page): Promise<string[]> {
|
|
2072
|
+
return page.evaluate(() => {
|
|
2073
|
+
const observedWindow = window as Window & {
|
|
2074
|
+
__ebkSecurityPolicyViolations?: string[];
|
|
2075
|
+
};
|
|
2076
|
+
return [...(observedWindow.__ebkSecurityPolicyViolations ?? [])];
|
|
2077
|
+
});
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
test.describe('generated frontend accessibility', () => {
|
|
2081
|
+
test('scans every deterministic rendered state', async ({ page }) => {
|
|
2082
|
+
const browserFailures = observeBrowserFailures(page);
|
|
2083
|
+
for (const state of states) {
|
|
2084
|
+
await page.goto(fixtureUrl(state));
|
|
2085
|
+
await expect(
|
|
2086
|
+
page.getByRole('heading', { level: 1, name: '${pascalName} neutral frontend states' }),
|
|
2087
|
+
).toBeVisible();
|
|
2088
|
+
const result = await new AxeBuilder({ page })
|
|
2089
|
+
.withTags(['wcag2a', 'wcag2aa', 'wcag21aa', 'wcag22aa'])
|
|
2090
|
+
.analyze();
|
|
2091
|
+
expect(result.violations, state).toEqual([]);
|
|
2092
|
+
}
|
|
2093
|
+
expect([...new Set(browserFailures)].sort()).toEqual([]);
|
|
2094
|
+
});
|
|
2095
|
+
|
|
2096
|
+
test('operates the dialog by keyboard and restores focus', async ({ page }) => {
|
|
2097
|
+
const frameworkResponse = await page.goto(fixtureUrl('success'));
|
|
2098
|
+
expect(frameworkResponse).not.toBeNull();
|
|
2099
|
+
const trigger = page.getByRole('button', { name: 'Open details dialog' });
|
|
2100
|
+
await trigger.focus();
|
|
2101
|
+
await page.keyboard.press('Enter');
|
|
2102
|
+
const dialog = page.getByRole('dialog', { name: 'Synthetic record details' });
|
|
2103
|
+
await expect(dialog).toBeVisible();
|
|
2104
|
+
await expect(page.getByRole('button', { name: 'Close details' })).toBeFocused();
|
|
2105
|
+
await page.keyboard.press('Escape');
|
|
2106
|
+
await expect(dialog).toBeHidden();
|
|
2107
|
+
await expect(trigger).toBeFocused();
|
|
2108
|
+
});
|
|
2109
|
+
|
|
2110
|
+
test('preserves announcements, narrow reflow, and reduced motion', async ({ page }) => {
|
|
2111
|
+
await page.setViewportSize({ height: 640, width: 320 });
|
|
2112
|
+
await page.emulateMedia({ reducedMotion: 'reduce' });
|
|
2113
|
+
await page.goto(fixtureUrl('validation'));
|
|
2114
|
+
await expect(
|
|
2115
|
+
page.getByRole('alert').filter({ hasText: 'one problem' }),
|
|
2116
|
+
).toContainText('one problem');
|
|
2117
|
+
expect(
|
|
2118
|
+
await page.evaluate(
|
|
2119
|
+
() => document.documentElement.scrollWidth <= document.documentElement.clientWidth,
|
|
2120
|
+
),
|
|
2121
|
+
).toBe(true);
|
|
2122
|
+
const duration = await page
|
|
2123
|
+
.getByRole('button', { name: 'Open details dialog' })
|
|
2124
|
+
.evaluate((element) => getComputedStyle(element).transitionDuration);
|
|
2125
|
+
expect(duration.endsWith('s')).toBe(true);
|
|
2126
|
+
expect(Number.parseFloat(duration)).toBeLessThanOrEqual(.001);
|
|
2127
|
+
});
|
|
2128
|
+
|
|
2129
|
+
test('hydrates framework UI and executes only nonce-authorized production inline script and style', async ({ page }) => {
|
|
2130
|
+
test.skip(
|
|
2131
|
+
process.env.EBK_FRONTEND_SECURITY_PROFILE !== 'deployed-https',
|
|
2132
|
+
'Strict nonce refusal is a deployed-profile proof.',
|
|
2133
|
+
);
|
|
2134
|
+
const browserFailures = observeBrowserFailures(page);
|
|
2135
|
+
await installSecurityPolicyViolationObserver(page);
|
|
2136
|
+
const frameworkResponse = await page.goto(fixtureUrl('success'));
|
|
2137
|
+
expect(frameworkResponse).not.toBeNull();
|
|
2138
|
+
await expect(
|
|
2139
|
+
page.getByRole('heading', { level: 1, name: '${pascalName} neutral frontend states' }),
|
|
2140
|
+
).toBeVisible();
|
|
2141
|
+
const trigger = page.getByRole('button', { name: 'Open details dialog' });
|
|
2142
|
+
await trigger.click();
|
|
2143
|
+
const dialog = page.getByRole('dialog', { name: 'Synthetic record details' });
|
|
2144
|
+
await expect(dialog).toBeVisible();
|
|
2145
|
+
await page.getByRole('button', { name: 'Close details' }).click();
|
|
2146
|
+
await expect(dialog).toBeHidden();
|
|
2147
|
+
await expect(trigger).toBeFocused();
|
|
2148
|
+
expect([...new Set(browserFailures)].sort()).toEqual([]);
|
|
2149
|
+
expect(await securityPolicyViolations(page)).toEqual([]);
|
|
2150
|
+
|
|
2151
|
+
// The dedicated route below deliberately violates CSP. Keep its expected
|
|
2152
|
+
// refusal signals separate from the clean framework-hydration observation.
|
|
2153
|
+
const response = await page.goto(cspProofUrl());
|
|
2154
|
+
expect(response).not.toBeNull();
|
|
2155
|
+
const csp = response?.headers()['content-security-policy'] ?? '';
|
|
2156
|
+
expect(csp).not.toContain("'unsafe-inline'");
|
|
2157
|
+
const nonces = [...csp.matchAll(/'nonce-([A-Za-z0-9+/_-]{16,128}={0,2})'/g)].map(
|
|
2158
|
+
(match) => match[1],
|
|
2159
|
+
);
|
|
2160
|
+
expect(new Set(nonces).size).toBe(1);
|
|
2161
|
+
expect(nonces).toHaveLength(2);
|
|
2162
|
+
const frameworkCsp =
|
|
2163
|
+
frameworkResponse?.headers()['content-security-policy'] ?? '';
|
|
2164
|
+
expect(frameworkCsp).not.toContain("'nonce-" + nonces[0] + "'");
|
|
2165
|
+
const proof = await page.evaluate(() => {
|
|
2166
|
+
const approvedStyle = document.querySelector('#approved-style');
|
|
2167
|
+
const unapprovedStyle = document.querySelector('#unapproved-style');
|
|
2168
|
+
if (!approvedStyle || !unapprovedStyle) {
|
|
2169
|
+
throw new Error('The strict CSP proof fixture is incomplete.');
|
|
2170
|
+
}
|
|
2171
|
+
return {
|
|
2172
|
+
approvedScript:
|
|
2173
|
+
document.documentElement.dataset.ebkApprovedInline === 'executed',
|
|
2174
|
+
approvedStyle: getComputedStyle(approvedStyle).color,
|
|
2175
|
+
refusedScript:
|
|
2176
|
+
document.documentElement.dataset.ebkUnapprovedInline === 'executed',
|
|
2177
|
+
refusedStyle: getComputedStyle(unapprovedStyle).color,
|
|
2178
|
+
};
|
|
2179
|
+
});
|
|
2180
|
+
|
|
2181
|
+
expect(proof).toMatchObject({
|
|
2182
|
+
approvedScript: true,
|
|
2183
|
+
approvedStyle: 'rgb(1, 2, 3)',
|
|
2184
|
+
refusedScript: false,
|
|
2185
|
+
});
|
|
2186
|
+
expect(proof.refusedStyle).not.toBe('rgb(4, 5, 6)');
|
|
2187
|
+
});
|
|
2188
|
+
|
|
2189
|
+
test('captures stable neutral visual observations', async ({ browserName, page }) => {
|
|
2190
|
+
const evidenceRoot = join(
|
|
2191
|
+
process.cwd(),
|
|
2192
|
+
'.ebk',
|
|
2193
|
+
'evidence',
|
|
2194
|
+
'frontend',
|
|
2195
|
+
);
|
|
2196
|
+
const visualRoot = join(evidenceRoot, 'visuals');
|
|
2197
|
+
mkdirSync(visualRoot, { recursive: true, mode: 0o700 });
|
|
2198
|
+
const records = [];
|
|
2199
|
+
const profile = browserName + '-' + process.platform;
|
|
2200
|
+
await page.setViewportSize({ height: 720, width: 1024 });
|
|
2201
|
+
for (const state of ['empty', 'success', 'validation'] as const) {
|
|
2202
|
+
await page.goto(fixtureUrl(state));
|
|
2203
|
+
const artifact =
|
|
2204
|
+
'.ebk/evidence/frontend/visuals/' +
|
|
2205
|
+
state +
|
|
2206
|
+
'-medium-' +
|
|
2207
|
+
profile +
|
|
2208
|
+
'.png';
|
|
2209
|
+
const bytes = await page.screenshot({
|
|
2210
|
+
animations: 'disabled',
|
|
2211
|
+
caret: 'hide',
|
|
2212
|
+
fullPage: true,
|
|
2213
|
+
path: join(process.cwd(), artifact),
|
|
2214
|
+
});
|
|
2215
|
+
records.push({
|
|
2216
|
+
artifact,
|
|
2217
|
+
digest: 'sha256:' + createHash('sha256').update(bytes).digest('hex'),
|
|
2218
|
+
id: state,
|
|
2219
|
+
profile,
|
|
2220
|
+
viewport: 'medium',
|
|
2221
|
+
});
|
|
2222
|
+
}
|
|
2223
|
+
writeFileSync(
|
|
2224
|
+
join(evidenceRoot, 'frontend-visual-observations.json'),
|
|
2225
|
+
JSON.stringify({ records, schemaVersion: 1, slice: '${name}' }, null, 2) + '\\n',
|
|
2226
|
+
{ mode: 0o600 },
|
|
2227
|
+
);
|
|
2228
|
+
});
|
|
2229
|
+
});
|
|
2230
|
+
`,
|
|
2231
|
+
};
|
|
2232
|
+
}
|
|
2233
|
+
//# sourceMappingURL=built-in-plan-frontend-files.js.map
|