rcf-lite 0.19.0 → 0.21.0
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/CHANGELOG.md +38 -0
- package/bin/rcf.js +5 -0
- package/blueprints/application-admin-console/CHANGELOG.md +11 -0
- package/blueprints/application-admin-console/README.md +98 -0
- package/blueprints/application-admin-console/blueprint.json +226 -0
- package/blueprints/application-admin-console/contributions/adrs/adr-2201-application-admin-console-capability-vocabulary.json +25 -0
- package/blueprints/application-admin-console/contributions/adrs/adr-2202-application-admin-console-baseline-roles.json +25 -0
- package/blueprints/application-admin-console/contributions/adrs/adr-2203-application-admin-console-invite-transport.json +20 -0
- package/blueprints/application-admin-console/contributions/adrs/adr-2204-application-admin-console-audit-retention.json +20 -0
- package/blueprints/application-admin-console/contributions/requirements/application-admin-console-req-001.json +18 -0
- package/blueprints/application-admin-console/contributions/requirements/application-admin-console-req-002.json +18 -0
- package/blueprints/application-admin-console/contributions/requirements/application-admin-console-req-003.json +18 -0
- package/blueprints/application-admin-console/contributions/requirements/application-admin-console-req-004.json +18 -0
- package/blueprints/application-admin-console/contributions/requirements/application-admin-console-req-005.json +18 -0
- package/blueprints/application-admin-console/contributions/requirements/application-admin-console-req-006.json +18 -0
- package/blueprints/application-admin-console/contributions/tacs/tac-2201-application-admin-console-shell.json +40 -0
- package/blueprints/application-admin-console/contributions/tacs/tac-2202-application-admin-console-capability-discovery.json +44 -0
- package/blueprints/application-admin-console/contributions/tacs/tac-2203-application-admin-console-permission-matrix.json +34 -0
- package/blueprints/application-admin-console/contributions/tacs/tac-2204-application-admin-console-audit-view.json +40 -0
- package/blueprints/application-admin-console/contributions/user-stories/application-admin-console-us-21101.json +34 -0
- package/blueprints/application-admin-console/contributions/user-stories/application-admin-console-us-21102.json +34 -0
- package/blueprints/application-admin-console/contributions/user-stories/application-admin-console-us-21103.json +34 -0
- package/blueprints/application-admin-console/contributions/user-stories/application-admin-console-us-21104.json +28 -0
- package/blueprints/application-admin-console/contributions/user-stories/application-admin-console-us-21105.json +34 -0
- package/blueprints/application-admin-console/contributions/user-stories/application-admin-console-us-21106.json +27 -0
- package/blueprints/application-admin-console/contributions/user-stories/application-admin-console-us-21107.json +27 -0
- package/blueprints/application-admin-console/contributions/user-stories/application-admin-console-us-21108.json +27 -0
- package/blueprints/application-admin-console/contributions/user-stories/application-admin-console-us-21109.json +33 -0
- package/blueprints/application-admin-console/docs/topics.md +35 -0
- package/blueprints/application-admin-console/guide/application-admin-console.md +29 -0
- package/blueprints/application-admin-console/probe-packs/application-admin-console.pack.mjs +182 -0
- package/blueprints/application-api-rest/docs/topics.md +5 -0
- package/blueprints/application-charts/CHANGELOG.md +12 -0
- package/blueprints/application-charts/README.md +73 -0
- package/blueprints/application-charts/blueprint.json +26 -0
- package/blueprints/application-charts/contributions/adrs/adr-1901-application-charts-engine-choice.json +25 -0
- package/blueprints/application-charts/contributions/adrs/adr-1902-application-charts-accessible-palette.json +25 -0
- package/blueprints/application-charts/contributions/adrs/adr-1903-application-charts-motion.json +25 -0
- package/blueprints/application-charts/contributions/requirements/application-charts-req-001.json +15 -0
- package/blueprints/application-charts/contributions/requirements/application-charts-req-002.json +15 -0
- package/blueprints/application-charts/contributions/requirements/application-charts-req-003.json +15 -0
- package/blueprints/application-charts/contributions/requirements/application-charts-req-004.json +15 -0
- package/blueprints/application-charts/contributions/tacs/tac-1901-application-charts-render-shell.json +42 -0
- package/blueprints/application-charts/contributions/tacs/tac-1902-application-charts-keyboard-traversal.json +40 -0
- package/blueprints/application-charts/contributions/user-stories/application-charts-us-18101.json +35 -0
- package/blueprints/application-charts/contributions/user-stories/application-charts-us-18102.json +35 -0
- package/blueprints/application-charts/contributions/user-stories/application-charts-us-18103.json +35 -0
- package/blueprints/application-charts/contributions/user-stories/application-charts-us-18104.json +35 -0
- package/blueprints/application-charts/contributions/user-stories/application-charts-us-18105.json +35 -0
- package/blueprints/application-charts/contributions/user-stories/application-charts-us-18106.json +35 -0
- package/blueprints/application-charts/docs/topics.md +37 -0
- package/blueprints/application-charts/guide/application-charts.md +44 -0
- package/blueprints/application-charts/probe-packs/application-charts.pack.mjs +191 -0
- package/blueprints/application-dashboard/CHANGELOG.md +10 -0
- package/blueprints/application-dashboard/README.md +102 -0
- package/blueprints/application-dashboard/assets/guidance/dashboard-design.md +124 -0
- package/blueprints/application-dashboard/blueprint.json +29 -0
- package/blueprints/application-dashboard/contributions/adrs/adr-2001-application-dashboard-primary-kpi-enum.json +25 -0
- package/blueprints/application-dashboard/contributions/adrs/adr-2002-application-dashboard-timeframe-presets.json +25 -0
- package/blueprints/application-dashboard/contributions/adrs/adr-2003-application-dashboard-export-formats.json +25 -0
- package/blueprints/application-dashboard/contributions/requirements/application-dashboard-req-001.json +15 -0
- package/blueprints/application-dashboard/contributions/requirements/application-dashboard-req-002.json +15 -0
- package/blueprints/application-dashboard/contributions/requirements/application-dashboard-req-003.json +15 -0
- package/blueprints/application-dashboard/contributions/requirements/application-dashboard-req-004.json +15 -0
- package/blueprints/application-dashboard/contributions/requirements/application-dashboard-req-005.json +15 -0
- package/blueprints/application-dashboard/contributions/tacs/tac-2001-application-dashboard-tile-grid.json +47 -0
- package/blueprints/application-dashboard/contributions/tacs/tac-2002-application-dashboard-filter-chrome.json +41 -0
- package/blueprints/application-dashboard/contributions/tacs/tac-2003-application-dashboard-export-handle.json +35 -0
- package/blueprints/application-dashboard/contributions/user-stories/application-dashboard-us-19101.json +35 -0
- package/blueprints/application-dashboard/contributions/user-stories/application-dashboard-us-19102.json +35 -0
- package/blueprints/application-dashboard/contributions/user-stories/application-dashboard-us-19103.json +35 -0
- package/blueprints/application-dashboard/contributions/user-stories/application-dashboard-us-19104.json +35 -0
- package/blueprints/application-dashboard/contributions/user-stories/application-dashboard-us-19105.json +35 -0
- package/blueprints/application-dashboard/contributions/user-stories/application-dashboard-us-19106.json +35 -0
- package/blueprints/application-dashboard/contributions/user-stories/application-dashboard-us-19107.json +35 -0
- package/blueprints/application-dashboard/docs/topics.md +43 -0
- package/blueprints/application-dashboard/guide/application-dashboard.md +48 -0
- package/blueprints/application-dashboard/probe-packs/application-dashboard.pack.mjs +231 -0
- package/blueprints/application-datatable/CHANGELOG.md +8 -0
- package/blueprints/application-datatable/README.md +43 -0
- package/blueprints/application-datatable/blueprint.json +33 -0
- package/blueprints/application-datatable/contributions/adrs/adr-1801-application-datatable-pattern-choice.json +25 -0
- package/blueprints/application-datatable/contributions/adrs/adr-1802-application-datatable-url-state.json +20 -0
- package/blueprints/application-datatable/contributions/adrs/adr-1803-application-datatable-page-size.json +20 -0
- package/blueprints/application-datatable/contributions/adrs/adr-1804-application-datatable-selection-persistence.json +20 -0
- package/blueprints/application-datatable/contributions/requirements/application-datatable-req-001.json +15 -0
- package/blueprints/application-datatable/contributions/requirements/application-datatable-req-002.json +15 -0
- package/blueprints/application-datatable/contributions/requirements/application-datatable-req-003.json +15 -0
- package/blueprints/application-datatable/contributions/requirements/application-datatable-req-004.json +15 -0
- package/blueprints/application-datatable/contributions/requirements/application-datatable-req-005.json +15 -0
- package/blueprints/application-datatable/contributions/requirements/application-datatable-req-006.json +15 -0
- package/blueprints/application-datatable/contributions/tacs/tac-1801-application-datatable-shell.json +50 -0
- package/blueprints/application-datatable/contributions/tacs/tac-1802-application-datatable-query-adapter.json +39 -0
- package/blueprints/application-datatable/contributions/tacs/tac-1803-application-datatable-selection-model.json +28 -0
- package/blueprints/application-datatable/contributions/user-stories/application-datatable-us-17101.json +44 -0
- package/blueprints/application-datatable/contributions/user-stories/application-datatable-us-17102.json +44 -0
- package/blueprints/application-datatable/contributions/user-stories/application-datatable-us-17103.json +44 -0
- package/blueprints/application-datatable/contributions/user-stories/application-datatable-us-17104.json +44 -0
- package/blueprints/application-datatable/contributions/user-stories/application-datatable-us-17105.json +44 -0
- package/blueprints/application-datatable/contributions/user-stories/application-datatable-us-17106.json +44 -0
- package/blueprints/application-datatable/contributions/user-stories/application-datatable-us-17107.json +44 -0
- package/blueprints/application-datatable/contributions/user-stories/application-datatable-us-17108.json +44 -0
- package/blueprints/application-datatable/contributions/user-stories/application-datatable-us-17109.json +44 -0
- package/blueprints/application-datatable/docs/topics.md +35 -0
- package/blueprints/application-datatable/guide/application-datatable.md +38 -0
- package/blueprints/application-datatable/probe-packs/application-datatable.pack.mjs +240 -0
- package/blueprints/application-error-handling/docs/topics.md +5 -0
- package/blueprints/application-notifications-in-app/CHANGELOG.md +9 -0
- package/blueprints/application-notifications-in-app/README.md +80 -0
- package/blueprints/application-notifications-in-app/blueprint.json +30 -0
- package/blueprints/application-notifications-in-app/contributions/adrs/adr-2101-application-notifications-in-app-live-region-role-mapping.json +25 -0
- package/blueprints/application-notifications-in-app/contributions/adrs/adr-2102-application-notifications-in-app-toast-timeout-floor.json +25 -0
- package/blueprints/application-notifications-in-app/contributions/adrs/adr-2103-application-notifications-in-app-retention-window.json +25 -0
- package/blueprints/application-notifications-in-app/contributions/requirements/application-notifications-in-app-req-001.json +15 -0
- package/blueprints/application-notifications-in-app/contributions/requirements/application-notifications-in-app-req-002.json +15 -0
- package/blueprints/application-notifications-in-app/contributions/requirements/application-notifications-in-app-req-003.json +15 -0
- package/blueprints/application-notifications-in-app/contributions/requirements/application-notifications-in-app-req-004.json +15 -0
- package/blueprints/application-notifications-in-app/contributions/requirements/application-notifications-in-app-req-005.json +15 -0
- package/blueprints/application-notifications-in-app/contributions/tacs/tac-2101-application-notifications-in-app-live-region.json +46 -0
- package/blueprints/application-notifications-in-app/contributions/tacs/tac-2102-application-notifications-in-app-centre.json +52 -0
- package/blueprints/application-notifications-in-app/contributions/tacs/tac-2103-application-notifications-in-app-preferences.json +40 -0
- package/blueprints/application-notifications-in-app/contributions/user-stories/application-notifications-in-app-us-20101.json +35 -0
- package/blueprints/application-notifications-in-app/contributions/user-stories/application-notifications-in-app-us-20102.json +35 -0
- package/blueprints/application-notifications-in-app/contributions/user-stories/application-notifications-in-app-us-20103.json +35 -0
- package/blueprints/application-notifications-in-app/contributions/user-stories/application-notifications-in-app-us-20104.json +35 -0
- package/blueprints/application-notifications-in-app/contributions/user-stories/application-notifications-in-app-us-20105.json +35 -0
- package/blueprints/application-notifications-in-app/contributions/user-stories/application-notifications-in-app-us-20106.json +26 -0
- package/blueprints/application-notifications-in-app/contributions/user-stories/application-notifications-in-app-us-20107.json +26 -0
- package/blueprints/application-notifications-in-app/contributions/user-stories/application-notifications-in-app-us-20108.json +26 -0
- package/blueprints/application-notifications-in-app/docs/topics.md +39 -0
- package/blueprints/application-notifications-in-app/guide/application-notifications-in-app.md +47 -0
- package/blueprints/application-notifications-in-app/probe-packs/application-notifications-in-app.pack.mjs +328 -0
- package/blueprints/application-spa/docs/topics.md +5 -0
- package/blueprints/delivery-ci-workflows/docs/topics.md +5 -0
- package/blueprints/deploy-cloudflare-workers/docs/topics.md +5 -0
- package/blueprints/email-smtp-resend/docs/topics.md +5 -0
- package/blueprints/observability-essentials/docs/topics.md +5 -0
- package/blueprints/observability-logging/CHANGELOG.md +5 -0
- package/blueprints/observability-logging/README.md +1 -1
- package/blueprints/observability-logging/blueprint.json +113 -21
- package/blueprints/observability-logging/docs/topics.md +5 -0
- package/blueprints/observability-probe-endpoints/docs/topics.md +5 -0
- package/blueprints/persistence-data-d1/docs/topics.md +5 -0
- package/blueprints/persistence-data-sqlite/docs/topics.md +5 -0
- package/blueprints/security-auth-clerk/CHANGELOG.md +5 -0
- package/blueprints/security-auth-clerk/blueprint.json +2 -1
- package/blueprints/security-auth-clerk/docs/topics.md +5 -0
- package/blueprints/security-auth-keycloak/CHANGELOG.md +5 -0
- package/blueprints/security-auth-keycloak/blueprint.json +2 -1
- package/blueprints/security-auth-keycloak/docs/topics.md +5 -0
- package/blueprints/security-auth-magic-link/CHANGELOG.md +5 -0
- package/blueprints/security-auth-magic-link/blueprint.json +2 -1
- package/blueprints/security-auth-magic-link/docs/topics.md +5 -0
- package/blueprints/security-auth-oauth2/CHANGELOG.md +5 -0
- package/blueprints/security-auth-oauth2/blueprint.json +2 -1
- package/blueprints/security-auth-oauth2/docs/topics.md +5 -0
- package/blueprints/security-secrets-management/docs/topics.md +5 -0
- package/fixtures/canary-manifest.json +9 -9
- package/package.json +2 -2
- package/rcf/code-nodes/cn-098.json +13 -0
- package/rcf/code-nodes/cn-099.json +13 -0
- package/rcf/code-nodes/cn-100.json +13 -0
- package/rcf/code-nodes/cn-101.json +13 -0
- package/rcf/code-nodes/cn-102.json +13 -0
- package/rcf/code-nodes/cn-103.json +13 -0
- package/rcf/code-nodes/cn-104.json +13 -0
- package/rcf/code-nodes/cn-105.json +13 -0
- package/rcf/code-nodes/cn-106.json +13 -0
- package/rcf/code-nodes/cn-107.json +14 -0
- package/rcf/code-nodes/cn-108.json +13 -0
- package/rcf/code-nodes/cn-109.json +12 -0
- package/rcf/code-nodes/cn-110.json +12 -0
- package/rcf/code-nodes/cn-111.json +14 -0
- package/rcf/code-nodes/cn-112.json +14 -0
- package/rcf/code-nodes/cn-113.json +12 -0
- package/rcf/code-nodes/cn-114.json +13 -0
- package/rcf/code-nodes/cn-115.json +13 -0
- package/rcf/code-nodes/cn-116.json +12 -0
- package/rcf/code-nodes/cn-117.json +13 -0
- package/rcf/code-nodes/cn-118.json +13 -0
- package/rcf/code-nodes/cn-119.json +13 -0
- package/rcf/code-nodes/cn-120.json +13 -0
- package/rcf/code-nodes/cn-121.json +12 -0
- package/rcf/code-nodes/cn-122.json +12 -0
- package/rcf/code-nodes/cn-123.json +13 -0
- package/rcf/code-nodes/cn-124.json +13 -0
- package/rcf/code-nodes/cn-125.json +13 -0
- package/rcf/code-nodes/cn-126.json +13 -0
- package/rcf/code-nodes/cn-127.json +12 -0
- package/rcf/code-nodes/cn-128.json +14 -0
- package/rcf/code-nodes/cn-129.json +14 -0
- package/rcf/code-nodes/cn-130.json +14 -0
- package/rcf/code-nodes/cn-131.json +15 -0
- package/rcf/code-nodes/cn-132.json +12 -0
- package/rcf/code-nodes/cn-133.json +13 -0
- package/rcf/evals/eval-001.json +55 -0
- package/rcf/fbs/fbs-035.json +18 -0
- package/rcf/fbs/fbs-036.json +22 -0
- package/rcf/fbs/fbs-037.json +19 -0
- package/rcf/fbs/fbs-038.json +19 -0
- package/rcf/fbs/fbs-039.json +21 -0
- package/rcf/fbs/fbs-040.json +20 -0
- package/rcf/fbs/fbs-041.json +13 -0
- package/rcf/fbs/fbs-042.json +13 -0
- package/rcf/requirements/req-016.json +37 -0
- package/rcf/requirements/req-017.json +18 -0
- package/rcf/requirements/req-018.json +18 -0
- package/rcf/requirements/req-019.json +38 -0
- package/rcf/requirements/req-020.json +13 -0
- package/rcf/requirements/req-021.json +13 -0
- package/rcf/requirements/req-022.json +13 -0
- package/rcf/requirements/req-023.json +14 -0
- package/rcf/test-suites/ts-045.json +46 -0
- package/rcf/test-suites/ts-046.json +78 -0
- package/rcf/test-suites/ts-047.json +54 -0
- package/rcf/test-suites/ts-048.json +54 -0
- package/rcf/test-suites/ts-049.json +70 -0
- package/rcf/test-suites/ts-050.json +69 -0
- package/rcf/test-suites/ts-051.json +22 -0
- package/rcf/test-suites/ts-052.json +20 -0
- package/rcf/user-stories/us-1601.json +37 -0
- package/rcf/user-stories/us-1701.json +56 -0
- package/rcf/user-stories/us-1801.json +40 -0
- package/rcf/user-stories/us-1901.json +40 -0
- package/rcf/user-stories/us-2001.json +50 -0
- package/rcf/user-stories/us-2101.json +45 -0
- package/rcf/user-stories/us-2201.json +60 -0
- package/rcf/user-stories/us-2301.json +51 -0
- package/releases/releases.yaml +20 -1
- package/src/blueprint/apply.js +98 -1
- package/src/blueprint/capabilities.js +318 -0
- package/src/blueprint/loader.js +247 -0
- package/src/browser-verify/boot.js +180 -0
- package/src/browser-verify/index.js +13 -0
- package/src/browser-verify/manifest-writer.js +31 -6
- package/src/browser-verify/pack-browser.js +294 -0
- package/src/browser-verify/pack-loader.js +176 -0
- package/src/browser-verify/pack-runner.js +193 -0
- package/src/browser-verify/pack-schema.js +155 -0
- package/src/browser-verify/runner.js +24 -1
- package/src/cli/blueprint.js +92 -0
- package/src/cli/browser-verify.js +147 -7
- package/src/cli/create.js +14 -0
- package/src/cli/eval-coverage.js +221 -0
- package/src/cli/eval.js +43 -0
- package/src/cli/finalise.js +64 -0
- package/src/cli/help.js +4 -0
- package/src/core/errors/index.js +7 -0
- package/src/core/store/ids.js +5 -1
- package/src/core/store/init.js +4 -0
- package/src/core/store/loader.js +4 -0
- package/src/core/store/validator.js +8 -1
- package/src/core/store/walker.js +71 -2
- package/src/core/store/writer.js +4 -0
- package/src/eval/judge.js +338 -0
- package/src/finalise/index.js +8 -0
- package/src/finalise/ingest.js +27 -0
- package/src/finalise/ship-without-eval.js +123 -0
- package/src/query/eval-coverage.js +162 -0
- package/src/verify/chain/index.js +67 -0
- package/src/verify/cli/run.js +15 -0
- package/src/verify/engine/index.js +10 -0
- package/src/verify/verdict/index.js +46 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
// Pack loader for blueprint-shipped probe packs
|
|
2
|
+
// (visual round T-0, US-1701 AC-1701-1 / 2 / 3). CN-107.
|
|
3
|
+
//
|
|
4
|
+
// Given a list of applied blueprints, discovers each blueprint's
|
|
5
|
+
// probe packs under `<blueprintAbsPath>/probe-packs/*.pack.{js,mjs}`,
|
|
6
|
+
// imports each module dynamically, validates against the pack schema
|
|
7
|
+
// (`pack-schema.js`), and cross-checks every check id against the AC
|
|
8
|
+
// ids the blueprint contributes via `blueprint.json:contributions[]`.
|
|
9
|
+
//
|
|
10
|
+
// The loader is pure with respect to the filesystem it reads: no
|
|
11
|
+
// writes, no side effects on the imported modules. It returns an
|
|
12
|
+
// aggregate result (`packs`, `warnings`, `errors`) so the CLI can
|
|
13
|
+
// emit one refusal diagnostic per fault rather than stop at the first.
|
|
14
|
+
|
|
15
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
16
|
+
import { existsSync } from 'node:fs';
|
|
17
|
+
import { join, resolve, isAbsolute } from 'node:path';
|
|
18
|
+
import { pathToFileURL } from 'node:url';
|
|
19
|
+
|
|
20
|
+
import { validatePackModule } from './pack-schema.js';
|
|
21
|
+
|
|
22
|
+
const PACK_FILENAME_RE = /\.pack\.(?:js|mjs)$/;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Load and validate probe packs under a set of applied blueprints.
|
|
26
|
+
*
|
|
27
|
+
* @param {object} args
|
|
28
|
+
* @param {Array<{ slug: string, absPath: string }>} args.appliedBlueprints
|
|
29
|
+
* List of blueprints on the applied shelf. `absPath` is the
|
|
30
|
+
* directory containing the blueprint's `blueprint.json`.
|
|
31
|
+
* @param {string} [args.projectRoot] reserved; unused today.
|
|
32
|
+
* @returns {Promise<{ packs: Array<LoadedPack>, warnings: Array<{ packAbsPath: string, message: string }>, errors: Array<{ packAbsPath: string, message: string }> }>}
|
|
33
|
+
*/
|
|
34
|
+
export async function loadProbePacks({ appliedBlueprints, projectRoot: _projectRoot }) {
|
|
35
|
+
const packs = [];
|
|
36
|
+
const warnings = [];
|
|
37
|
+
const errors = [];
|
|
38
|
+
|
|
39
|
+
for (const bp of Array.isArray(appliedBlueprints) ? appliedBlueprints : []) {
|
|
40
|
+
if (!bp || typeof bp.slug !== 'string' || typeof bp.absPath !== 'string' || !isAbsolute(bp.absPath)) {
|
|
41
|
+
errors.push({
|
|
42
|
+
packAbsPath: '(no path)',
|
|
43
|
+
message: `pack-loader: blueprint entry must carry { slug: string, absPath: absolute path } (got ${JSON.stringify(bp)})`,
|
|
44
|
+
});
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const packsDir = join(bp.absPath, 'probe-packs');
|
|
48
|
+
if (!existsSync(packsDir)) continue;
|
|
49
|
+
|
|
50
|
+
let contributedAcIds;
|
|
51
|
+
try {
|
|
52
|
+
contributedAcIds = await readContributedAcIds({ blueprintAbsPath: bp.absPath });
|
|
53
|
+
} catch (err) {
|
|
54
|
+
errors.push({ packAbsPath: bp.absPath, message: `pack-loader: cannot read blueprint contributions for '${bp.slug}': ${err.message}` });
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let entries;
|
|
59
|
+
try {
|
|
60
|
+
entries = await readdir(packsDir, { withFileTypes: true });
|
|
61
|
+
} catch (err) {
|
|
62
|
+
errors.push({ packAbsPath: packsDir, message: `pack-loader: cannot list probe-packs dir for '${bp.slug}': ${err.message}` });
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const packFiles = entries
|
|
67
|
+
.filter((e) => e.isFile() && PACK_FILENAME_RE.test(e.name))
|
|
68
|
+
.map((e) => join(packsDir, e.name))
|
|
69
|
+
.sort();
|
|
70
|
+
|
|
71
|
+
for (const packAbsPath of packFiles) {
|
|
72
|
+
let mod;
|
|
73
|
+
try {
|
|
74
|
+
mod = await import(pathToFileURL(packAbsPath).href);
|
|
75
|
+
} catch (err) {
|
|
76
|
+
errors.push({ packAbsPath, message: `pack-loader: import failed for ${packAbsPath}: ${err.message}` });
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const validation = validatePackModule({ mod, blueprintSlug: bp.slug, packAbsPath });
|
|
80
|
+
if (!validation.ok) {
|
|
81
|
+
for (const e of validation.errors) errors.push({ packAbsPath, message: e.message });
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const pack = validation.pack;
|
|
85
|
+
|
|
86
|
+
const badCheckIds = pack.checks
|
|
87
|
+
.map((c) => c.id)
|
|
88
|
+
.filter((id) => !contributedAcIds.has(id));
|
|
89
|
+
if (badCheckIds.length > 0) {
|
|
90
|
+
errors.push({
|
|
91
|
+
packAbsPath,
|
|
92
|
+
message: `pack-loader: pack '${pack.packName}' names checks [${badCheckIds.join(', ')}] that the blueprint '${bp.slug}' does not contribute; contributed AC ids on this blueprint: ${contributedAcIds.size === 0 ? '(none)' : [...contributedAcIds].sort().join(', ')}`,
|
|
93
|
+
});
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
packs.push({
|
|
98
|
+
packName: pack.packName,
|
|
99
|
+
version: pack.version,
|
|
100
|
+
blueprintSlug: bp.slug,
|
|
101
|
+
packAbsPath,
|
|
102
|
+
appliesTo: pack.appliesTo,
|
|
103
|
+
boot: pack.boot ?? null,
|
|
104
|
+
checks: pack.checks,
|
|
105
|
+
preChecks: Array.isArray(pack.preChecks) ? pack.preChecks : [],
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return { packs, warnings, errors };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Read every AC id contributed by a blueprint. A blueprint contributes ACs
|
|
115
|
+
* inside its US contribution files (`contributions[].kind === 'us'`).
|
|
116
|
+
*
|
|
117
|
+
* @param {object} args
|
|
118
|
+
* @param {string} args.blueprintAbsPath
|
|
119
|
+
* @returns {Promise<Set<string>>}
|
|
120
|
+
*/
|
|
121
|
+
export async function readContributedAcIds({ blueprintAbsPath }) {
|
|
122
|
+
const bpJsonPath = join(blueprintAbsPath, 'blueprint.json');
|
|
123
|
+
const bpRaw = await readFile(bpJsonPath, 'utf8');
|
|
124
|
+
let bpJson;
|
|
125
|
+
try { bpJson = JSON.parse(bpRaw); } catch (err) {
|
|
126
|
+
throw new Error(`invalid JSON in ${bpJsonPath}: ${err.message}`);
|
|
127
|
+
}
|
|
128
|
+
const contributions = Array.isArray(bpJson?.contributions) ? bpJson.contributions : [];
|
|
129
|
+
const acIds = new Set();
|
|
130
|
+
for (const c of contributions) {
|
|
131
|
+
if (c?.kind !== 'us' || typeof c.path !== 'string') continue;
|
|
132
|
+
// blueprint.json contribution paths are relative to the blueprint's
|
|
133
|
+
// `contributions/` subdirectory (see src/blueprint/loader.js). Older
|
|
134
|
+
// tests supplied paths that included `contributions/` at the root
|
|
135
|
+
// instead; probe for both shapes so shelf blueprints and hand-rolled
|
|
136
|
+
// fixtures both load without a per-caller convention.
|
|
137
|
+
const insideContributions = resolve(blueprintAbsPath, 'contributions', c.path);
|
|
138
|
+
const legacyRootRelative = resolve(blueprintAbsPath, c.path);
|
|
139
|
+
let usAbs = insideContributions;
|
|
140
|
+
let usRaw;
|
|
141
|
+
try {
|
|
142
|
+
usRaw = await readFile(insideContributions, 'utf8');
|
|
143
|
+
} catch (primaryErr) {
|
|
144
|
+
try {
|
|
145
|
+
usRaw = await readFile(legacyRootRelative, 'utf8');
|
|
146
|
+
usAbs = legacyRootRelative;
|
|
147
|
+
} catch {
|
|
148
|
+
throw new Error(`cannot read US contribution ${c.path}: ${primaryErr.message}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
let usJson;
|
|
152
|
+
try {
|
|
153
|
+
usJson = JSON.parse(usRaw);
|
|
154
|
+
} catch (err) {
|
|
155
|
+
throw new Error(`cannot read US contribution ${c.path}: ${err.message}`);
|
|
156
|
+
}
|
|
157
|
+
void usAbs;
|
|
158
|
+
const inlineAcs = Array.isArray(usJson?.acceptanceCriteria) ? usJson.acceptanceCriteria : [];
|
|
159
|
+
for (const ac of inlineAcs) {
|
|
160
|
+
if (typeof ac?.id === 'string') acIds.add(ac.id);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return acIds;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* @typedef {object} LoadedPack
|
|
168
|
+
* @property {string} packName
|
|
169
|
+
* @property {string} version
|
|
170
|
+
* @property {string} blueprintSlug
|
|
171
|
+
* @property {string} packAbsPath
|
|
172
|
+
* @property {(ctx: { fbs: object, uiBaseline: object|null, manifest: object|null }) => boolean} appliesTo
|
|
173
|
+
* @property {object|null} boot
|
|
174
|
+
* @property {Array<{ id: string, severity: 'block'|'warn'|'advisory', description: string, run: (ctx: object) => Promise<{ verdict: 'pass'|'warn'|'fail'|'skipped', detail?: string }>, dependsOn?: string|string[] }>} checks
|
|
175
|
+
* @property {Array<{ id: string, severity: 'block'|'warn'|'advisory', description: string, run: () => Promise<{ verdict: 'pass'|'warn'|'fail', detail?: string }> }>} preChecks
|
|
176
|
+
*/
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
// Pack pass runner (visual round T-0, US-1701 AC-1701-4 / 6). CN-108.
|
|
2
|
+
//
|
|
3
|
+
// Invokes every applicable loaded pack against a FBS and folds the
|
|
4
|
+
// results into a `probePacks[]` array on the browserVerification
|
|
5
|
+
// record. Applicability is enforced by the pack's own `appliesTo`
|
|
6
|
+
// predicate: a false return records `applicable: false` and no
|
|
7
|
+
// check runs. `preChecks[]` (if any) run first; a failing pre-check
|
|
8
|
+
// causes browser checks that name it under `dependsOn` to record
|
|
9
|
+
// `skipped` with detail `skipped-by-pre-check:<preCheckId>`.
|
|
10
|
+
//
|
|
11
|
+
// The runner is browser-driver-agnostic: the caller injects a
|
|
12
|
+
// `browser` seam (the same shape a pack's `run` expects on its `browser`
|
|
13
|
+
// argument) plus `fetch`, `runtimeUrl` and the route/theme cursor.
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Run applicable packs for one FBS.
|
|
17
|
+
*
|
|
18
|
+
* @param {object} args
|
|
19
|
+
* @param {Array<import('./pack-loader.js').LoadedPack>} args.packs
|
|
20
|
+
* @param {object} args.fbs
|
|
21
|
+
* @param {object|null} args.uiBaseline
|
|
22
|
+
* @param {object|null} args.manifest
|
|
23
|
+
* @param {object|null} args.browser injected browser (or null in unit tests)
|
|
24
|
+
* @param {(url: string, init?: object) => Promise<any>|null} args.fetch
|
|
25
|
+
* @param {string} args.runtimeUrl
|
|
26
|
+
* @param {Array<object>} args.routes FBS routes (nav model)
|
|
27
|
+
* @param {Array<'light'|'dark'>} args.themes
|
|
28
|
+
* @param {string} [args.packNameFilter] when set, only packs whose packName === filter are considered
|
|
29
|
+
* @param {(pack: import('./pack-loader.js').LoadedPack) => object} [args.buildContext] optional context override for unit tests
|
|
30
|
+
* @returns {Promise<{ probePacks: Array<object>, packHighestSeverityFail: 'block'|'warn'|null }>}
|
|
31
|
+
*/
|
|
32
|
+
export async function runProbePacksForFbs({
|
|
33
|
+
packs,
|
|
34
|
+
fbs,
|
|
35
|
+
uiBaseline,
|
|
36
|
+
manifest,
|
|
37
|
+
projectRoot = null,
|
|
38
|
+
browser,
|
|
39
|
+
fetch,
|
|
40
|
+
runtimeUrl,
|
|
41
|
+
routes,
|
|
42
|
+
themes,
|
|
43
|
+
packNameFilter,
|
|
44
|
+
buildContext,
|
|
45
|
+
}) {
|
|
46
|
+
const probePacks = [];
|
|
47
|
+
let highest = null;
|
|
48
|
+
const chosen = Array.isArray(packs) ? packs : [];
|
|
49
|
+
const primaryRoute = Array.isArray(routes) && routes.length > 0 ? routes[0] : null;
|
|
50
|
+
const primaryTheme = Array.isArray(themes) && themes.length > 0 ? themes[0] : 'light';
|
|
51
|
+
|
|
52
|
+
for (const pack of chosen) {
|
|
53
|
+
if (typeof packNameFilter === 'string' && packNameFilter.length > 0 && pack.packName !== packNameFilter) {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let applicable;
|
|
58
|
+
let applicabilityDetail;
|
|
59
|
+
try {
|
|
60
|
+
applicable = Boolean(pack.appliesTo({ fbs, uiBaseline, manifest }));
|
|
61
|
+
} catch (err) {
|
|
62
|
+
applicable = false;
|
|
63
|
+
applicabilityDetail = `appliesTo threw: ${err.message}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!applicable) {
|
|
67
|
+
probePacks.push({
|
|
68
|
+
packName: pack.packName,
|
|
69
|
+
packVersion: pack.version,
|
|
70
|
+
blueprintSlug: pack.blueprintSlug,
|
|
71
|
+
applicable: false,
|
|
72
|
+
...(applicabilityDetail ? { detail: applicabilityDetail } : { detail: `appliesTo returned false for ${fbs?.fbsId ?? '(no fbs)'}` }),
|
|
73
|
+
checks: [],
|
|
74
|
+
});
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Pre-checks first.
|
|
79
|
+
const preCheckRecords = [];
|
|
80
|
+
const failedPreCheckIds = new Set();
|
|
81
|
+
for (const pre of pack.preChecks) {
|
|
82
|
+
let verdict = 'pass';
|
|
83
|
+
let detail;
|
|
84
|
+
try {
|
|
85
|
+
const outcome = await pre.run({ fbs, uiBaseline, manifest, runtimeUrl, fetch, browser });
|
|
86
|
+
verdict = outcome?.verdict ?? 'pass';
|
|
87
|
+
detail = outcome?.detail;
|
|
88
|
+
} catch (err) {
|
|
89
|
+
verdict = 'fail';
|
|
90
|
+
detail = `pre-check threw: ${err.message}`;
|
|
91
|
+
}
|
|
92
|
+
preCheckRecords.push({
|
|
93
|
+
id: pre.id,
|
|
94
|
+
verdict,
|
|
95
|
+
severity: pre.severity,
|
|
96
|
+
...(typeof detail === 'string' && detail.length > 0 ? { detail } : {}),
|
|
97
|
+
});
|
|
98
|
+
if (verdict === 'fail') failedPreCheckIds.add(pre.id);
|
|
99
|
+
if (verdict === 'fail' && pre.severity === 'block') highest = 'block';
|
|
100
|
+
else if (verdict === 'fail' && pre.severity === 'warn' && highest !== 'block') highest = 'warn';
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const checkRecords = [];
|
|
104
|
+
for (const check of pack.checks) {
|
|
105
|
+
const deps = normaliseDependsOn(check.dependsOn);
|
|
106
|
+
const failingDep = deps.find((d) => failedPreCheckIds.has(d));
|
|
107
|
+
if (failingDep) {
|
|
108
|
+
checkRecords.push({
|
|
109
|
+
id: check.id,
|
|
110
|
+
verdict: 'skipped',
|
|
111
|
+
severity: check.severity,
|
|
112
|
+
detail: `skipped-by-pre-check:${failingDep}`,
|
|
113
|
+
});
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
// Per-check applicability (visual round T-5 spec section 5.5).
|
|
117
|
+
// A pack check may declare its own `appliesTo` predicate that
|
|
118
|
+
// reads the applied capability set (from projectRoot) so an
|
|
119
|
+
// absent capability records `applicable: false` and the
|
|
120
|
+
// aggregate verdict treats the check as neither pass nor fail.
|
|
121
|
+
// A check without a predicate always runs (backward compatible
|
|
122
|
+
// with T-0 through T-4 packs).
|
|
123
|
+
if (typeof check.appliesTo === 'function') {
|
|
124
|
+
let checkApplicable;
|
|
125
|
+
let checkAppliesDetail;
|
|
126
|
+
try {
|
|
127
|
+
checkApplicable = Boolean(await check.appliesTo({ fbs, uiBaseline, manifest, projectRoot }));
|
|
128
|
+
} catch (err) {
|
|
129
|
+
checkApplicable = false;
|
|
130
|
+
checkAppliesDetail = `appliesTo threw: ${err.message}`;
|
|
131
|
+
}
|
|
132
|
+
if (!checkApplicable) {
|
|
133
|
+
// Spec section 5.5 says the residual cure records
|
|
134
|
+
// applicable: false at the check level. rcf-schemas 0.6.1
|
|
135
|
+
// (browserVerificationProbePackCheck) closes the schema and
|
|
136
|
+
// still requires a verdict enum, so the manifest write path
|
|
137
|
+
// refuses a check that carries `applicable: false` alone.
|
|
138
|
+
// Until the schema minor promotes an `applicable` field at
|
|
139
|
+
// the check level (follow-up rcf-schemas bump), emit
|
|
140
|
+
// `verdict: 'skipped'` with a detail naming the applicability
|
|
141
|
+
// gate. Semantically the aggregate verdict still treats this
|
|
142
|
+
// as neither pass nor fail (skipped does not raise `highest`).
|
|
143
|
+
checkRecords.push({
|
|
144
|
+
id: check.id,
|
|
145
|
+
verdict: 'skipped',
|
|
146
|
+
severity: check.severity,
|
|
147
|
+
detail: checkAppliesDetail ?? 'check appliesTo returned false (required capability not applied)',
|
|
148
|
+
});
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const context = typeof buildContext === 'function'
|
|
153
|
+
? buildContext(pack)
|
|
154
|
+
: { browser, fetch, runtimeUrl, route: primaryRoute, theme: primaryTheme, projectRoot };
|
|
155
|
+
let verdict = 'pass';
|
|
156
|
+
let detail;
|
|
157
|
+
try {
|
|
158
|
+
const outcome = await check.run(context);
|
|
159
|
+
verdict = outcome?.verdict ?? 'pass';
|
|
160
|
+
detail = outcome?.detail;
|
|
161
|
+
} catch (err) {
|
|
162
|
+
verdict = 'fail';
|
|
163
|
+
detail = `check threw: ${err.message}`;
|
|
164
|
+
}
|
|
165
|
+
checkRecords.push({
|
|
166
|
+
id: check.id,
|
|
167
|
+
verdict,
|
|
168
|
+
severity: check.severity,
|
|
169
|
+
...(typeof detail === 'string' && detail.length > 0 ? { detail } : {}),
|
|
170
|
+
});
|
|
171
|
+
if (verdict === 'fail' && check.severity === 'block') highest = 'block';
|
|
172
|
+
else if (verdict === 'fail' && check.severity === 'warn' && highest !== 'block') highest = 'warn';
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const record = {
|
|
176
|
+
packName: pack.packName,
|
|
177
|
+
packVersion: pack.version,
|
|
178
|
+
blueprintSlug: pack.blueprintSlug,
|
|
179
|
+
applicable: true,
|
|
180
|
+
checks: checkRecords,
|
|
181
|
+
};
|
|
182
|
+
if (preCheckRecords.length > 0) record.preChecks = preCheckRecords;
|
|
183
|
+
probePacks.push(record);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return { probePacks, packHighestSeverityFail: highest };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function normaliseDependsOn(dependsOn) {
|
|
190
|
+
if (typeof dependsOn === 'string' && dependsOn.length > 0) return [dependsOn];
|
|
191
|
+
if (Array.isArray(dependsOn)) return dependsOn.filter((d) => typeof d === 'string' && d.length > 0);
|
|
192
|
+
return [];
|
|
193
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// Pack schema validator for blueprint-shipped probe packs
|
|
2
|
+
// (visual round T-0, US-1701 AC-1701-1/2). CN-106.
|
|
3
|
+
//
|
|
4
|
+
// Validates a pack module's exported default object against the
|
|
5
|
+
// section 3.2 shape. Enforces the field-level rules the loader must
|
|
6
|
+
// refuse at load time (packName grammar and blueprint-slug prefix,
|
|
7
|
+
// version semver, blueprintSlug matches directory, appliesTo function
|
|
8
|
+
// referencing one of the three legal scoping seams, checks[] non-empty
|
|
9
|
+
// with an id / severity / description / async run per entry).
|
|
10
|
+
//
|
|
11
|
+
// Returns a plain-JS shape (not RcfError): the loader wraps refusals
|
|
12
|
+
// in its own diagnostic aggregate, so a single validation call reports
|
|
13
|
+
// every issue on one pack instead of stopping at the first.
|
|
14
|
+
|
|
15
|
+
const PACK_NAME_RE = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
16
|
+
const SEMVER_RE = /^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/;
|
|
17
|
+
const CHECK_ID_RE = /^AC-\d{3,}-\d+$/;
|
|
18
|
+
const SEVERITIES = new Set(['block', 'warn', 'advisory']);
|
|
19
|
+
|
|
20
|
+
const APPLIES_TO_TOKENS = [
|
|
21
|
+
{ key: 'route', matcher: /\broute\b|\bnavModel\b|\bpath\b/ },
|
|
22
|
+
{ key: 'tacIds', matcher: /\btacIds?\b/ },
|
|
23
|
+
{ key: 'blueprintTag', matcher: /blueprint:/ },
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Validate a pack module's exported default object.
|
|
28
|
+
*
|
|
29
|
+
* @param {object} args
|
|
30
|
+
* @param {object} args.mod the imported module (its `.default` is the pack object).
|
|
31
|
+
* @param {string} args.blueprintSlug the owning blueprint slug (directory).
|
|
32
|
+
* @param {string} args.packAbsPath absolute path of the pack file, for error context.
|
|
33
|
+
* @returns {{ ok: true, pack: object } | { ok: false, errors: Array<{ field: string, message: string }> }}
|
|
34
|
+
*/
|
|
35
|
+
export function validatePackModule({ mod, blueprintSlug, packAbsPath }) {
|
|
36
|
+
const errors = [];
|
|
37
|
+
const pack = mod?.default;
|
|
38
|
+
if (!pack || typeof pack !== 'object') {
|
|
39
|
+
return { ok: false, errors: [{ field: 'default', message: `pack ${packAbsPath}: missing default export (expected an object)` }] };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (typeof pack.packName !== 'string' || pack.packName.length === 0) {
|
|
43
|
+
errors.push({ field: 'packName', message: `pack ${packAbsPath}: packName must be a non-empty string` });
|
|
44
|
+
} else if (!PACK_NAME_RE.test(pack.packName)) {
|
|
45
|
+
errors.push({ field: 'packName', message: `pack ${packAbsPath}: packName '${pack.packName}' must match ${PACK_NAME_RE}` });
|
|
46
|
+
} else if (pack.packName !== blueprintSlug && !pack.packName.startsWith(`${blueprintSlug}-`)) {
|
|
47
|
+
errors.push({ field: 'packName', message: `pack ${packAbsPath}: packName '${pack.packName}' must equal the blueprint slug '${blueprintSlug}' or start with '${blueprintSlug}-'` });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (typeof pack.version !== 'string' || !SEMVER_RE.test(pack.version)) {
|
|
51
|
+
errors.push({ field: 'version', message: `pack ${packAbsPath}: version must be semver (got ${JSON.stringify(pack.version)})` });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (typeof pack.blueprintSlug !== 'string' || pack.blueprintSlug.length === 0) {
|
|
55
|
+
errors.push({ field: 'blueprintSlug', message: `pack ${packAbsPath}: blueprintSlug must be a non-empty string` });
|
|
56
|
+
} else if (pack.blueprintSlug !== blueprintSlug) {
|
|
57
|
+
errors.push({ field: 'blueprintSlug', message: `pack ${packAbsPath}: blueprintSlug '${pack.blueprintSlug}' must match the enclosing directory '${blueprintSlug}'` });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (typeof pack.appliesTo !== 'function') {
|
|
61
|
+
errors.push({ field: 'appliesTo', message: `pack ${packAbsPath}: appliesTo is required and must be a function ({ fbs, uiBaseline, manifest }) => boolean` });
|
|
62
|
+
} else {
|
|
63
|
+
const source = String(pack.appliesTo);
|
|
64
|
+
const matched = APPLIES_TO_TOKENS.filter((t) => t.matcher.test(source)).map((t) => t.key);
|
|
65
|
+
if (matched.length === 0) {
|
|
66
|
+
errors.push({
|
|
67
|
+
field: 'appliesTo',
|
|
68
|
+
message: `pack ${packAbsPath}: appliesTo must reference one of { route (or navModel/path), tacIds, blueprint:<slug> tag }; got source ${trimForError(source)}. The default '() => true' predicate is refused; scope the pack to the FBS surface it probes.`,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (pack.boot !== undefined && pack.boot !== null && typeof pack.boot !== 'object') {
|
|
74
|
+
errors.push({ field: 'boot', message: `pack ${packAbsPath}: boot must be an object or null when present (fields bootCommand, waitForUrl, waitForSelector are optional)` });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!Array.isArray(pack.checks) || pack.checks.length === 0) {
|
|
78
|
+
errors.push({ field: 'checks', message: `pack ${packAbsPath}: checks must be a non-empty array` });
|
|
79
|
+
} else {
|
|
80
|
+
const seenIds = new Set();
|
|
81
|
+
pack.checks.forEach((check, index) => {
|
|
82
|
+
const at = `checks[${index}]`;
|
|
83
|
+
if (!check || typeof check !== 'object') {
|
|
84
|
+
errors.push({ field: at, message: `pack ${packAbsPath}: ${at} must be an object` });
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (typeof check.id !== 'string' || !CHECK_ID_RE.test(check.id)) {
|
|
88
|
+
errors.push({ field: `${at}.id`, message: `pack ${packAbsPath}: ${at}.id must match ${CHECK_ID_RE} (got ${JSON.stringify(check.id)})` });
|
|
89
|
+
} else if (seenIds.has(check.id)) {
|
|
90
|
+
errors.push({ field: `${at}.id`, message: `pack ${packAbsPath}: ${at}.id '${check.id}' duplicates an earlier check on the same pack` });
|
|
91
|
+
} else {
|
|
92
|
+
seenIds.add(check.id);
|
|
93
|
+
}
|
|
94
|
+
if (!SEVERITIES.has(check.severity)) {
|
|
95
|
+
errors.push({ field: `${at}.severity`, message: `pack ${packAbsPath}: ${at}.severity must be one of ${[...SEVERITIES].join(' | ')} (got ${JSON.stringify(check.severity)})` });
|
|
96
|
+
}
|
|
97
|
+
if (typeof check.description !== 'string' || check.description.length === 0) {
|
|
98
|
+
errors.push({ field: `${at}.description`, message: `pack ${packAbsPath}: ${at}.description must be a non-empty string` });
|
|
99
|
+
}
|
|
100
|
+
if (typeof check.run !== 'function') {
|
|
101
|
+
errors.push({ field: `${at}.run`, message: `pack ${packAbsPath}: ${at}.run must be an async function` });
|
|
102
|
+
}
|
|
103
|
+
if (check.dependsOn !== undefined && !(typeof check.dependsOn === 'string' || (Array.isArray(check.dependsOn) && check.dependsOn.every((d) => typeof d === 'string')))) {
|
|
104
|
+
errors.push({ field: `${at}.dependsOn`, message: `pack ${packAbsPath}: ${at}.dependsOn must be a string or an array of strings (pre-check ids)` });
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (pack.preChecks !== undefined) {
|
|
110
|
+
if (!Array.isArray(pack.preChecks)) {
|
|
111
|
+
errors.push({ field: 'preChecks', message: `pack ${packAbsPath}: preChecks must be an array when present` });
|
|
112
|
+
} else {
|
|
113
|
+
const seenPre = new Set();
|
|
114
|
+
pack.preChecks.forEach((pre, index) => {
|
|
115
|
+
const at = `preChecks[${index}]`;
|
|
116
|
+
if (!pre || typeof pre !== 'object') {
|
|
117
|
+
errors.push({ field: at, message: `pack ${packAbsPath}: ${at} must be an object` });
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (typeof pre.id !== 'string' || pre.id.length === 0) {
|
|
121
|
+
errors.push({ field: `${at}.id`, message: `pack ${packAbsPath}: ${at}.id must be a non-empty string` });
|
|
122
|
+
} else if (seenPre.has(pre.id)) {
|
|
123
|
+
errors.push({ field: `${at}.id`, message: `pack ${packAbsPath}: ${at}.id '${pre.id}' duplicates an earlier pre-check on the same pack` });
|
|
124
|
+
} else {
|
|
125
|
+
seenPre.add(pre.id);
|
|
126
|
+
}
|
|
127
|
+
if (!SEVERITIES.has(pre.severity)) {
|
|
128
|
+
errors.push({ field: `${at}.severity`, message: `pack ${packAbsPath}: ${at}.severity must be one of ${[...SEVERITIES].join(' | ')} (got ${JSON.stringify(pre.severity)})` });
|
|
129
|
+
}
|
|
130
|
+
if (typeof pre.description !== 'string' || pre.description.length === 0) {
|
|
131
|
+
errors.push({ field: `${at}.description`, message: `pack ${packAbsPath}: ${at}.description must be a non-empty string` });
|
|
132
|
+
}
|
|
133
|
+
if (typeof pre.run !== 'function') {
|
|
134
|
+
errors.push({ field: `${at}.run`, message: `pack ${packAbsPath}: ${at}.run must be an async function` });
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (errors.length > 0) return { ok: false, errors };
|
|
141
|
+
return { ok: true, pack };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function trimForError(s) {
|
|
145
|
+
const trimmed = String(s).replace(/\s+/g, ' ').trim();
|
|
146
|
+
return trimmed.length > 80 ? `${trimmed.slice(0, 77)}...` : trimmed;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export const PACK_SCHEMA_INTERNALS = Object.freeze({
|
|
150
|
+
PACK_NAME_RE,
|
|
151
|
+
SEMVER_RE,
|
|
152
|
+
CHECK_ID_RE,
|
|
153
|
+
SEVERITIES,
|
|
154
|
+
APPLIES_TO_TOKENS,
|
|
155
|
+
});
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
import { runInvariantsForCapture, foldInvariantsForRecord, compareTopLevelStructure } from './invariants.js';
|
|
21
21
|
import { runAuthSmokeChecks, shouldRunAuthSmokeChecks } from './auth-smoke.js';
|
|
22
22
|
import { composeBrowserVerificationRecord } from './manifest-writer.js';
|
|
23
|
+
import { runProbePacksForFbs } from './pack-runner.js';
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
26
|
* @typedef {object} BrowserCapture
|
|
@@ -63,7 +64,8 @@ export const stubBrowserDriver = Object.freeze({
|
|
|
63
64
|
* @returns {Promise<object>} the composed browserVerification record
|
|
64
65
|
*/
|
|
65
66
|
export async function runAgentScreenshotCritique({
|
|
66
|
-
tree, fbs, runtimeUrl, runtimeProfile, browserDriver, fetch, artefactDir, critiqueNotes,
|
|
67
|
+
tree, fbs, runtimeUrl, runtimeProfile, browserDriver, fetch, artefactDir, critiqueNotes,
|
|
68
|
+
probePacks = [], packBrowser = null, packNameFilter, projectRoot = null, now = new Date(),
|
|
67
69
|
}) {
|
|
68
70
|
const routes = fbs?.designStage?.navModel?.routes ?? [];
|
|
69
71
|
const themes = readThemesFromBaseline(tree.manifest?.uiBaseline);
|
|
@@ -124,6 +126,26 @@ export async function runAgentScreenshotCritique({
|
|
|
124
126
|
authSmokeChecks = await runAuthSmokeChecks({ fetch, runtimeUrl });
|
|
125
127
|
}
|
|
126
128
|
|
|
129
|
+
// Probe-pack pass: runs after invariants and auth-smoke
|
|
130
|
+
// (visual-round-spec-2026-09-04 §3.4).
|
|
131
|
+
let packRecords = [];
|
|
132
|
+
if (Array.isArray(probePacks) && probePacks.length > 0) {
|
|
133
|
+
const result = await runProbePacksForFbs({
|
|
134
|
+
packs: probePacks,
|
|
135
|
+
fbs,
|
|
136
|
+
uiBaseline: tree.manifest?.uiBaseline ?? null,
|
|
137
|
+
manifest: tree.manifest ?? null,
|
|
138
|
+
projectRoot,
|
|
139
|
+
browser: packBrowser,
|
|
140
|
+
fetch,
|
|
141
|
+
runtimeUrl,
|
|
142
|
+
routes,
|
|
143
|
+
themes,
|
|
144
|
+
packNameFilter,
|
|
145
|
+
});
|
|
146
|
+
packRecords = result.probePacks;
|
|
147
|
+
}
|
|
148
|
+
|
|
127
149
|
return composeBrowserVerificationRecord({
|
|
128
150
|
manifest: tree.manifest,
|
|
129
151
|
fbsId: fbs.fbsId,
|
|
@@ -133,6 +155,7 @@ export async function runAgentScreenshotCritique({
|
|
|
133
155
|
routesChecked,
|
|
134
156
|
invariantChecks,
|
|
135
157
|
authSmokeChecks,
|
|
158
|
+
probePacks: packRecords,
|
|
136
159
|
notes,
|
|
137
160
|
now,
|
|
138
161
|
});
|