rcf-lite 0.20.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 +23 -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-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/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-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-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-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 +11 -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/core/errors/index.js +7 -0
|
@@ -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
|
});
|
package/src/cli/blueprint.js
CHANGED
|
@@ -184,6 +184,14 @@ const OPTION_SPEC = {
|
|
|
184
184
|
// Companion-suggestion mechanism (core-companions spec section 2).
|
|
185
185
|
companion: { type: 'string', multiple: true },
|
|
186
186
|
'no-companion-suggestions': { type: 'boolean' },
|
|
187
|
+
// Capability-declaration mechanism (visual round T-5 spec 5.5.1,
|
|
188
|
+
// 5.5.2). --allow-no-auth-yet is the override for the
|
|
189
|
+
// requiresAppliedCapabilities gate. --answer <id>=<value> is
|
|
190
|
+
// repeatable; --answers <file> reads a JSON object mapping elicit
|
|
191
|
+
// ids to values. Both feed the elicitation phase in applyBlueprint.
|
|
192
|
+
'allow-no-auth-yet': { type: 'boolean' },
|
|
193
|
+
answer: { type: 'string', multiple: true },
|
|
194
|
+
answers: { type: 'string' },
|
|
187
195
|
};
|
|
188
196
|
|
|
189
197
|
/**
|
|
@@ -286,9 +294,54 @@ export async function main(argv, deps = {}) {
|
|
|
286
294
|
}
|
|
287
295
|
}
|
|
288
296
|
}
|
|
297
|
+
// Capability-declaration mechanism (visual round T-5). Compose
|
|
298
|
+
// the operator-supplied elicit answers bag from --answers <file>
|
|
299
|
+
// (JSON object) and --answer <id>=<value> (repeatable). Later
|
|
300
|
+
// --answer entries override --answers file entries so a shell
|
|
301
|
+
// one-liner beats a stored default without editing the file.
|
|
302
|
+
let elicitAnswers = {};
|
|
303
|
+
if (typeof parsed.values.answers === 'string' && parsed.values.answers.length > 0) {
|
|
304
|
+
try {
|
|
305
|
+
const raw = await (await import('node:fs/promises')).readFile(parsed.values.answers, 'utf8');
|
|
306
|
+
const parsedBag = JSON.parse(raw);
|
|
307
|
+
if (typeof parsedBag !== 'object' || parsedBag === null || Array.isArray(parsedBag)) {
|
|
308
|
+
stderr.write(`[error] blueprint add: --answers file must contain a JSON object of id -> value.\n`);
|
|
309
|
+
return 2;
|
|
310
|
+
}
|
|
311
|
+
elicitAnswers = { ...parsedBag };
|
|
312
|
+
} catch (err) {
|
|
313
|
+
stderr.write(`[error] blueprint add: --answers file read failed: ${err.message}\n`);
|
|
314
|
+
return 2;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
if (Array.isArray(parsed.values.answer)) {
|
|
318
|
+
for (const entry of parsed.values.answer) {
|
|
319
|
+
const eq = entry.indexOf('=');
|
|
320
|
+
if (eq < 1) {
|
|
321
|
+
stderr.write(`[error] blueprint add: --answer expects <id>=<value>; got '${entry}'.\n`);
|
|
322
|
+
return 2;
|
|
323
|
+
}
|
|
324
|
+
elicitAnswers[entry.slice(0, eq)] = entry.slice(eq + 1);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
// Custom-auth capability elicits (visual round T-5 spec 5.5
|
|
328
|
+
// "Risks"): providesCapability elicits fire pre-refusal. On a
|
|
329
|
+
// TTY, prompt the operator for any unanswered custom-auth elicits
|
|
330
|
+
// through readline so a fresh project with no shelf auth can pass
|
|
331
|
+
// the requiresAppliedCapabilities gate by declaring what its own
|
|
332
|
+
// auth provides. Non-interactive runs skip the prompt and read
|
|
333
|
+
// answers from the bag only.
|
|
334
|
+
const stdin = deps.stdin ?? process.stdin;
|
|
335
|
+
const isTty = Boolean(stdin && stdin.isTTY);
|
|
336
|
+
const customAuthReadLine = isTty
|
|
337
|
+
? await createCustomAuthReadLine({ stdin, stdout })
|
|
338
|
+
: null;
|
|
289
339
|
const result = await applyBlueprint({
|
|
290
340
|
projectRoot, tree, source,
|
|
291
341
|
namespaceOverride: parsed.values.namespace,
|
|
342
|
+
allowNoAuthYet: parsed.values['allow-no-auth-yet'] === true,
|
|
343
|
+
elicitAnswers,
|
|
344
|
+
customAuthReadLine,
|
|
292
345
|
// Library-qualified resolves rewire the applied identity under
|
|
293
346
|
// the library prefix and forward the library's declared bands
|
|
294
347
|
// for the apply-time gate (spec §5.3, §8.3). The qualified typed
|
|
@@ -321,6 +374,19 @@ export async function main(argv, deps = {}) {
|
|
|
321
374
|
}
|
|
322
375
|
}
|
|
323
376
|
if (isRcfError(result)) {
|
|
377
|
+
// Capability-declaration refusal (visual round T-5 spec 5.5.1)
|
|
378
|
+
// is exit 3, and the message is spec-verbatim (built by
|
|
379
|
+
// buildRefusalMessage in blueprint/capabilities.js): print it
|
|
380
|
+
// as-is on stderr, no [error] prefix. Every other rcfError
|
|
381
|
+
// stays exit 2 with the [error] prefix.
|
|
382
|
+
if (result.kind === 'requiresAppliedCapabilities') {
|
|
383
|
+
if (parsed.values.json) {
|
|
384
|
+
stderr.write(`${JSON.stringify({ refused: true, error: { kind: result.kind, message: result.message } })}\n`);
|
|
385
|
+
} else {
|
|
386
|
+
stderr.write(`${result.message}\n`);
|
|
387
|
+
}
|
|
388
|
+
return 3;
|
|
389
|
+
}
|
|
324
390
|
if (parsed.values.json) {
|
|
325
391
|
stderr.write(`${JSON.stringify({ refused: true, error: { kind: result.kind, message: result.message } })}\n`);
|
|
326
392
|
} else {
|
|
@@ -759,3 +825,29 @@ function parseResolveOptions(rawList, reason) {
|
|
|
759
825
|
}
|
|
760
826
|
return { value: out };
|
|
761
827
|
}
|
|
828
|
+
|
|
829
|
+
/**
|
|
830
|
+
* Build a readline-backed prompt function for custom-auth capability
|
|
831
|
+
* elicits (visual round T-5 spec 5.5 "Risks"). Wraps node:readline in
|
|
832
|
+
* a promise so `runCustomAuthCapabilityElicits` can `await` each line.
|
|
833
|
+
* Only used when stdin is a TTY; the callback closes the interface on
|
|
834
|
+
* first shutdown to avoid leaking file descriptors.
|
|
835
|
+
*
|
|
836
|
+
* @param {{ stdin: NodeJS.ReadableStream, stdout: NodeJS.WritableStream }} deps
|
|
837
|
+
* @returns {Promise<(prompt: string) => Promise<string>>}
|
|
838
|
+
*/
|
|
839
|
+
async function createCustomAuthReadLine({ stdin, stdout }) {
|
|
840
|
+
const { createInterface } = await import('node:readline');
|
|
841
|
+
const rl = createInterface({ input: stdin, output: stdout, terminal: true });
|
|
842
|
+
let closed = false;
|
|
843
|
+
return (prompt) => new Promise((resolve) => {
|
|
844
|
+
if (closed) return resolve('');
|
|
845
|
+
rl.question(prompt, (answer) => {
|
|
846
|
+
if (!closed) {
|
|
847
|
+
try { rl.close(); } catch { /* fine */ }
|
|
848
|
+
closed = true;
|
|
849
|
+
}
|
|
850
|
+
resolve(answer);
|
|
851
|
+
});
|
|
852
|
+
});
|
|
853
|
+
}
|
|
@@ -18,13 +18,18 @@
|
|
|
18
18
|
|
|
19
19
|
import { parseArgs } from 'node:util';
|
|
20
20
|
import process from 'node:process';
|
|
21
|
+
import { isAbsolute, resolve } from 'node:path';
|
|
21
22
|
|
|
22
23
|
import { walkTree } from '#core/store';
|
|
23
24
|
import { writeUnexpectedFailure, rcfError } from '#core/errors';
|
|
24
25
|
|
|
25
26
|
import { findProjectRoot } from '../view/index.js';
|
|
26
27
|
import {
|
|
28
|
+
bootIfNeeded,
|
|
27
29
|
composeOperatorSessionRecord,
|
|
30
|
+
createPackBrowser,
|
|
31
|
+
loadProbePacks,
|
|
32
|
+
pickBootFromPacks,
|
|
28
33
|
runAgentScreenshotCritique,
|
|
29
34
|
stubBrowserDriver,
|
|
30
35
|
writeBrowserVerificationAck,
|
|
@@ -40,6 +45,9 @@ const OPTION_SPEC = {
|
|
|
40
45
|
quiet: { type: 'boolean' },
|
|
41
46
|
ack: { type: 'boolean' },
|
|
42
47
|
notes: { type: 'string' },
|
|
48
|
+
'probe-pack': { type: 'string' },
|
|
49
|
+
'no-boot': { type: 'boolean' },
|
|
50
|
+
'no-browser': { type: 'boolean' },
|
|
43
51
|
help: { type: 'boolean' },
|
|
44
52
|
};
|
|
45
53
|
|
|
@@ -61,6 +69,22 @@ Options:
|
|
|
61
69
|
--quiet Suppress non-error confirmations.
|
|
62
70
|
--ack Mark operatorAckAt on the latest record for
|
|
63
71
|
this FBS (used to clear a warn verdict).
|
|
72
|
+
--probe-pack <name> Restrict this run to one blueprint-shipped
|
|
73
|
+
probe pack by packName. When omitted, every
|
|
74
|
+
discovered pack whose appliesTo() matches
|
|
75
|
+
this FBS runs. Refuses (exit 2) when the
|
|
76
|
+
name resolves to no discovered pack.
|
|
77
|
+
--no-browser Skip the real headless browser (probe packs
|
|
78
|
+
that call browser.* will see browser=null).
|
|
79
|
+
Default: provision a headless browser via
|
|
80
|
+
the pinned Playwright MCP so packs can drive
|
|
81
|
+
real DOM interactions.
|
|
82
|
+
--no-boot Do not run a pack's declared bootCommand
|
|
83
|
+
when the runtime URL is unreachable. Default:
|
|
84
|
+
when any pack declares a boot block and the
|
|
85
|
+
runtime is not answering, spawn bootCommand
|
|
86
|
+
from the project root, wait for waitForUrl
|
|
87
|
+
then waitForSelector, then run packs.
|
|
64
88
|
--help Print this help.
|
|
65
89
|
|
|
66
90
|
Exit codes:
|
|
@@ -163,13 +187,77 @@ export async function main(argv, deps = {}) {
|
|
|
163
187
|
stderr.write('[error] usage browser-verify: no fetch available in the runtime; supply deps.fetch\n');
|
|
164
188
|
return 2;
|
|
165
189
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
190
|
+
// Probe-pack discovery: gather packs from every applied blueprint
|
|
191
|
+
// on the manifest, then apply --probe-pack filter if any.
|
|
192
|
+
const appliedBlueprints = readAppliedBlueprints({ manifest: tree.manifest, projectRoot });
|
|
193
|
+
const { packs, errors: packErrors } = await loadProbePacks({ appliedBlueprints, projectRoot });
|
|
194
|
+
if (packErrors.length > 0) {
|
|
195
|
+
for (const e of packErrors) stderr.write(`[error] usage browser-verify: probe-pack loader refused ${e.packAbsPath}: ${e.message}\n`);
|
|
196
|
+
return 2;
|
|
197
|
+
}
|
|
198
|
+
const packNameFilter = typeof flags['probe-pack'] === 'string' && flags['probe-pack'].length > 0
|
|
199
|
+
? flags['probe-pack']
|
|
200
|
+
: undefined;
|
|
201
|
+
if (packNameFilter && !packs.some((p) => p.packName === packNameFilter)) {
|
|
202
|
+
const discovered = packs.map((p) => p.packName).sort();
|
|
203
|
+
stderr.write(`[error] usage browser-verify: --probe-pack '${packNameFilter}' resolves to no discovered pack. Discovered packs: ${discovered.length === 0 ? '(none)' : discovered.join(', ')}.\n`);
|
|
204
|
+
return 2;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const filteredPacks = packNameFilter
|
|
208
|
+
? packs.filter((p) => p.packName === packNameFilter)
|
|
209
|
+
: packs;
|
|
210
|
+
const provisionBrowser = deps.createPackBrowser ?? createPackBrowser;
|
|
211
|
+
const bootProbe = deps.bootIfNeeded ?? bootIfNeeded;
|
|
212
|
+
|
|
213
|
+
let packBrowser = deps.packBrowser ?? null;
|
|
214
|
+
const cleanups = [];
|
|
215
|
+
try {
|
|
216
|
+
if (packBrowser === null && filteredPacks.length > 0 && !flags['no-browser']) {
|
|
217
|
+
packBrowser = await provisionBrowser({
|
|
218
|
+
projectRoot,
|
|
219
|
+
logger: (line) => { if (!flags.quiet) stderr.write(`${line}\n`); },
|
|
220
|
+
});
|
|
221
|
+
cleanups.push(async () => { try { await packBrowser.close(); } catch { /* fine */ } });
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (!flags['no-boot'] && filteredPacks.length > 0) {
|
|
225
|
+
const boot = pickBootFromPacks(filteredPacks);
|
|
226
|
+
if (boot) {
|
|
227
|
+
const bootOutcome = await bootProbe({
|
|
228
|
+
boot,
|
|
229
|
+
runtimeUrl,
|
|
230
|
+
projectRoot,
|
|
231
|
+
fetch: fetchFn,
|
|
232
|
+
browser: packBrowser,
|
|
233
|
+
logger: (line) => { if (!flags.quiet) stderr.write(`${line}\n`); },
|
|
234
|
+
});
|
|
235
|
+
for (const n of bootOutcome.notes) if (!flags.quiet) stderr.write(`[boot] ${n}\n`);
|
|
236
|
+
if (bootOutcome.started) {
|
|
237
|
+
cleanups.push(async () => { try { await bootOutcome.stop(); } catch { /* fine */ } });
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
record = await runAgentScreenshotCritique({
|
|
243
|
+
tree, fbs, runtimeUrl, runtimeProfile: profile,
|
|
244
|
+
browserDriver, fetch: fetchFn,
|
|
245
|
+
artefactDir: `.rcf/artefacts/${fbsId.toLowerCase()}`,
|
|
246
|
+
critiqueNotes: flags.notes,
|
|
247
|
+
probePacks: packs,
|
|
248
|
+
packBrowser,
|
|
249
|
+
packNameFilter,
|
|
250
|
+
projectRoot,
|
|
251
|
+
now,
|
|
252
|
+
});
|
|
253
|
+
} finally {
|
|
254
|
+
// LIFO cleanup: close browser before stopping the server so a
|
|
255
|
+
// page.close talking to a killed server never hangs the CLI.
|
|
256
|
+
while (cleanups.length > 0) {
|
|
257
|
+
const step = cleanups.pop();
|
|
258
|
+
try { await step(); } catch { /* fine */ }
|
|
259
|
+
}
|
|
260
|
+
}
|
|
173
261
|
}
|
|
174
262
|
} catch (err) {
|
|
175
263
|
writeUnexpectedFailure(
|
|
@@ -203,6 +291,41 @@ export async function main(argv, deps = {}) {
|
|
|
203
291
|
return record.verdict === 'pass' ? 0 : 4;
|
|
204
292
|
}
|
|
205
293
|
|
|
294
|
+
/**
|
|
295
|
+
* Read applied blueprints off the manifest as an array of
|
|
296
|
+
* `{ slug, absPath }` suitable for `loadProbePacks`.
|
|
297
|
+
*
|
|
298
|
+
* The applied-blueprint record carries a `source` field (see
|
|
299
|
+
* `src/blueprint/apply.js`). For a shelf apply the source is the
|
|
300
|
+
* shelf-relative slug; for a local-path apply it is an absolute
|
|
301
|
+
* path; for a library apply it is `<library>:<slug>`. We resolve
|
|
302
|
+
* to the shelf path shipped alongside the CLI package or, in the
|
|
303
|
+
* monorepo, the repo-root shelf. Missing directories are skipped
|
|
304
|
+
* without error (the CLI later checks discovered vs. requested).
|
|
305
|
+
*/
|
|
306
|
+
function readAppliedBlueprints({ manifest, projectRoot }) {
|
|
307
|
+
const list = Array.isArray(manifest?.blueprints) ? manifest.blueprints : [];
|
|
308
|
+
const out = [];
|
|
309
|
+
for (const entry of list) {
|
|
310
|
+
if (!entry || typeof entry.slug !== 'string' || entry.slug.length === 0) continue;
|
|
311
|
+
const absPath = resolveBlueprintAbsPath({ entry, projectRoot });
|
|
312
|
+
if (absPath) out.push({ slug: entry.slug, absPath });
|
|
313
|
+
}
|
|
314
|
+
return out;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function resolveBlueprintAbsPath({ entry, projectRoot }) {
|
|
318
|
+
const { slug, source } = entry;
|
|
319
|
+
if (typeof source === 'string' && source.length > 0) {
|
|
320
|
+
if (isAbsolute(source)) return source;
|
|
321
|
+
if (source.startsWith('./') || source.startsWith('../')) {
|
|
322
|
+
return resolve(projectRoot, source);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
const cwd = resolve(projectRoot, 'blueprints', slug);
|
|
326
|
+
return cwd;
|
|
327
|
+
}
|
|
328
|
+
|
|
206
329
|
function defaultUrlForProfile(profile) {
|
|
207
330
|
if (profile === 'ci') return 'http://127.0.0.1:3000';
|
|
208
331
|
if (profile === 'deployed') return '';
|
|
@@ -227,5 +350,22 @@ function renderSummary({ stdout, record }) {
|
|
|
227
350
|
stdout.write(` [${c.verdict}] ${c.check} (status=${c.status ?? 'n/a'})${detail}\n`);
|
|
228
351
|
}
|
|
229
352
|
}
|
|
353
|
+
if (Array.isArray(record.probePacks) && record.probePacks.length > 0) {
|
|
354
|
+
stdout.write(' probe packs:\n');
|
|
355
|
+
for (const p of record.probePacks) {
|
|
356
|
+
if (p.applicable === false) {
|
|
357
|
+
stdout.write(` [skip] ${p.packName} (${p.blueprintSlug}): ${p.detail ?? 'not applicable to this FBS'}\n`);
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
for (const pc of p.preChecks ?? []) {
|
|
361
|
+
const detail = pc.detail ? ` - ${pc.detail}` : '';
|
|
362
|
+
stdout.write(` [${pc.verdict}] ${p.packName} pre-check ${pc.id}${detail}\n`);
|
|
363
|
+
}
|
|
364
|
+
for (const c of p.checks ?? []) {
|
|
365
|
+
const detail = c.detail ? ` - ${c.detail}` : '';
|
|
366
|
+
stdout.write(` [${c.verdict}] ${p.packName} ${c.id}${detail}\n`);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
230
370
|
stdout.write(` verdict: ${record.verdict}\n\n`);
|
|
231
371
|
}
|
package/src/core/errors/index.js
CHANGED
|
@@ -42,6 +42,13 @@ const VALID_KINDS = new Set([
|
|
|
42
42
|
// individually schema-clean) and from `brokenReference` (nothing
|
|
43
43
|
// dangles; the graph is over-connected, not under-connected).
|
|
44
44
|
'duplicateId',
|
|
45
|
+
// Visual round T-5 (spec section 5.5.1): a consumer blueprint whose
|
|
46
|
+
// requiresAppliedCapabilities gate refuses because no applied
|
|
47
|
+
// blueprint declares any of the required capabilities AND the CLI
|
|
48
|
+
// did not pass the allow-skip flag. The apply verb returns this kind
|
|
49
|
+
// with the spec-verbatim refusal message; the CLI translates it to
|
|
50
|
+
// exit 3 (not exit 2) with the message printed as-is on stderr.
|
|
51
|
+
'requiresAppliedCapabilities',
|
|
45
52
|
]);
|
|
46
53
|
|
|
47
54
|
/**
|