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,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"createdAt": "2026-09-05T01:30:00.000Z",
|
|
3
|
+
"updatedAt": "2026-09-05T01:30:00.000Z",
|
|
4
|
+
"usId": "US-2301",
|
|
5
|
+
"prdId": "PRD-001",
|
|
6
|
+
"reqId": "REQ-023",
|
|
7
|
+
"version": "0.1.0",
|
|
8
|
+
"status": "draft",
|
|
9
|
+
"title": "The application-admin-console blueprint ships on the shelf; its probe pack fires only on applied capabilities and refuses on a broken admin surface",
|
|
10
|
+
"asA": "shelf consumer applying the application-admin-console v1.0.0 blueprint",
|
|
11
|
+
"iWant": "the shipped blueprint files with a users, roles, orgs, audit-log surface set that self-configures from the applied capability declarations, the Playwright probe pack whose four checks each gate their fire on the applied capability, and a sample-app fixture proving the pack passes on a compliant surface, records applicable: false where the capability is absent, and refuses on the three break switches",
|
|
12
|
+
"soThat": "a project consuming rcf-lite gets a vendor-neutral admin console conditional on what identity blueprint is applied, no dead links to unapplied capabilities, the pack that proves the ARIA APG grid pattern on the permission matrix, the request-log ACs on the audit view, and a golden fixture the pack refuses on a surface that loses the grid roles, drops the request-access control, or writes an audit entry missing the correlation id",
|
|
13
|
+
"acceptanceCriteria": [
|
|
14
|
+
{
|
|
15
|
+
"id": "AC-2301-1",
|
|
16
|
+
"description": "The shelf carries blueprints/application-admin-console/blueprint.json declaring slug application-admin-console, version 1.0.0, category application, 23 contributions in the 6 REQ / 9 US / 4 TAC / 4 ADR groups per spec section 5.5; requiresAppliedCapabilities.capabilities is [principalDirectory] with allowSkipFlag: allow-no-auth-yet; elicits[] declares baselineRoles (when roleModel), tenancyShape (when tenancy), inviteTransport, and auditRetentionDays (when auditLog); suggestedCompanions declares logging and errorHandling with reasons from spec section 5.5; providesRoles is absent per spec 5.5.3.",
|
|
17
|
+
"testable": true
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"id": "AC-2301-2",
|
|
21
|
+
"description": "rcf define blueprint add ./blueprints/application-admin-console --allow-no-auth-yet applies the 23 contributions cleanly on a fresh init project with only application-spa applied; rcf define validate reports the tree clean; both rcf audit coverage --strict and rcf audit eval coverage --strict exit 0 on the scratch project; the sidecar rcf/blueprints/application-admin-console.applied.json declares allowNoAuthYet: true and an appliedCapabilities: [] union.",
|
|
22
|
+
"testable": true
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"id": "AC-2301-3",
|
|
26
|
+
"description": "rcf define blueprint add ./blueprints/application-admin-console on a project with only application-spa applied exits 3 with a stderr message beginning 'application-admin-console requires at least one applied security-auth-*' and naming the currently-applied blueprints; nothing is written to disk (no contributions, no sidecar, no manifest entry).",
|
|
27
|
+
"testable": true
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"id": "AC-2301-4",
|
|
31
|
+
"description": "rcf define blueprint add ./blueprints/application-admin-console on a project with security-auth-magic-link 1.1.0 applied applies cleanly; the sidecar's appliedCapabilities reads exactly [principalDirectory] with no roleModel; rcf define blueprint add on a project with security-auth-clerk 1.2.0 + observability-logging applied produces appliedCapabilities [principalDirectory, roleModel, auditLog] (order-agnostic).",
|
|
32
|
+
"testable": true
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"id": "AC-2301-5",
|
|
36
|
+
"description": "The shipped probe pack blueprints/application-admin-console/probe-packs/application-admin-console.pack.mjs loads via T-0's pack-loader against a scratch project with the blueprint applied; each of its four check ids (AC-21102-1, AC-21103-1, AC-21104-1, AC-21105-1) resolves to an AC id contributed by the blueprint per T-0 AC-1701-3; every check carries a description field per spec section 9; each check's appliesTo predicate reads manifest.blueprints[]/${slug}.applied.json capabilities and returns false when the required capability is absent so the check records applicable: false; the pack-level appliesTo predicate references BOTH tacIds and route.",
|
|
37
|
+
"testable": true
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"id": "AC-2301-6",
|
|
41
|
+
"description": "The sample-app fixture at packages/rcf-lite/test/fixtures/probe-pack-application-admin-console/ ships a dependency-free Node HTTP server (server.js) with a start script; CAPS env or ?caps= query switch selects which surfaces exist mirroring the applied capability set (principalDirectory, roleModel, tenancy, auditLog, or comma combinations thereof); the console shell nav only links applied surfaces; the users directory table enumerates every row by data-user-id inside the role=table region; the permission matrix renders under role=grid with role=row on every rank, role=rowheader on the leftmost cell of each role rank, role=columnheader on every permission header, role=gridcell on inner cells, and each cell announces the exact permission string on focus via aria-label; the org switcher is present only when tenancy is applied; the audit-log table enumerates every row by data-audit-id and every audit entry carries actor, target, before, after, timestamp, and correlationId in inspectable columns; an access-denied route renders a request-access control routed to a POST /api/request-access endpoint; window.__adminFetches and /__requests mirror every request.",
|
|
42
|
+
"testable": true
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"id": "AC-2301-7",
|
|
46
|
+
"description": "The probe pack refuses ship on each broken fixture: with ?break=matrix-grid the permission matrix drops the role=grid attributes so AC-21103-1 fails at severity block; with ?break=denied the access-denied route drops the request-access control so AC-21102-1's negative branch (a principal without admin scope) fails at severity block; with ?break=audit-fields the audit entries omit the correlationId column so AC-21105-1 fails at severity block."
|
|
47
|
+
,
|
|
48
|
+
"testable": true
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
}
|
package/releases/releases.yaml
CHANGED
|
@@ -40,8 +40,18 @@
|
|
|
40
40
|
# `npm install rcf-lite`.
|
|
41
41
|
|
|
42
42
|
feedVersion: 1
|
|
43
|
-
latest: "0.
|
|
43
|
+
latest: "0.21.0"
|
|
44
44
|
releases:
|
|
45
|
+
- version: "0.21.0"
|
|
46
|
+
date: "2026-09-05"
|
|
47
|
+
breaking: false
|
|
48
|
+
headlines:
|
|
49
|
+
- "rcf-lite 0.21.0 adds five visual-feature blueprints to the shelf (data table, charts, dashboard with packaged design guidance, in-app notifications, admin console) and the mechanism that proves them: a blueprint can now ship a Playwright probe pack that rcf verify runs against your running app in a real browser, and the ship gate refuses while a surface check fails."
|
|
50
|
+
- "The admin console only offers the surfaces your applied auth blueprint can support. Blueprints can now ask you questions at apply time."
|
|
51
|
+
- "The schema floor moves to rcf-schemas 0.6.1."
|
|
52
|
+
minAgentAction: null
|
|
53
|
+
notesUrl: "https://stravica.ai/docs/rcf/changelog/"
|
|
54
|
+
|
|
45
55
|
- version: "0.20.0"
|
|
46
56
|
date: "2026-09-04"
|
|
47
57
|
breaking: true
|
package/src/blueprint/apply.js
CHANGED
|
@@ -20,6 +20,7 @@ import { loadBlueprint } from './loader.js';
|
|
|
20
20
|
import { updateManifest } from './manifest-writer.js';
|
|
21
21
|
import { stampId } from './namespace.js';
|
|
22
22
|
import { nextResolutionId } from './resolutions.js';
|
|
23
|
+
import { buildRefusalMessage, discoverAppliedCapabilities, runCustomAuthCapabilityElicits, runElicitationPhase, writeSidecar } from './capabilities.js';
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
26
|
* @typedef {object} ApplyResult
|
|
@@ -79,7 +80,7 @@ import { nextResolutionId } from './resolutions.js';
|
|
|
79
80
|
* prove the rollback runs. Never used in production.
|
|
80
81
|
* @returns {Promise<ApplyResult | import('../core/errors/index.js').RcfError>}
|
|
81
82
|
*/
|
|
82
|
-
export async function applyBlueprint({ projectRoot, tree, source, displaySource, namespaceOverride, effectiveSlug, libraryPrefix, libraryBands, resolveDeclarations, now = new Date(), dryRun = false, _copyFileForTest }) {
|
|
83
|
+
export async function applyBlueprint({ projectRoot, tree, source, displaySource, namespaceOverride, effectiveSlug, libraryPrefix, libraryBands, resolveDeclarations, allowNoAuthYet = false, elicitAnswers = null, customAuthReadLine = null, now = new Date(), dryRun = false, _copyFileForTest }) {
|
|
83
84
|
const hintSource = typeof displaySource === 'string' && displaySource.length > 0 ? displaySource : source;
|
|
84
85
|
const blueprint = await loadBlueprint(source);
|
|
85
86
|
if (blueprint.kind) return blueprint; // RcfError
|
|
@@ -105,6 +106,69 @@ export async function applyBlueprint({ projectRoot, tree, source, displaySource,
|
|
|
105
106
|
if (bandErr) return bandErr;
|
|
106
107
|
}
|
|
107
108
|
|
|
109
|
+
// Capability-declaration mechanism (visual round T-5 spec 5.5).
|
|
110
|
+
// Discover the union of applied capabilities across every applied
|
|
111
|
+
// blueprint via source read-back; the applied-blueprint-record
|
|
112
|
+
// schema is closed (rcf-schemas 0.6.0) and does not carry a
|
|
113
|
+
// capabilities field, so the source blueprint.json is the ground
|
|
114
|
+
// truth for what each applied blueprint declares.
|
|
115
|
+
const discovery = await discoverAppliedCapabilities(applied, projectRoot);
|
|
116
|
+
const discoveredCapabilities = discovery.union;
|
|
117
|
+
|
|
118
|
+
// Custom-auth capability elicitation (visual round T-5 spec 5.5
|
|
119
|
+
// "Risks": custom auth elicits per capability). Fires BEFORE the
|
|
120
|
+
// requiresAppliedCapabilities refusal gate: providesCapability
|
|
121
|
+
// elicits let a project with no shelf auth blueprint declare which
|
|
122
|
+
// capabilities its own custom auth carries. Truthy answers fold
|
|
123
|
+
// into the effective applied set; that set is what the refusal gate
|
|
124
|
+
// and the standard elicitation phase see.
|
|
125
|
+
const customAuth = await runCustomAuthCapabilityElicits({
|
|
126
|
+
elicits: blueprint.elicits,
|
|
127
|
+
answers: elicitAnswers ?? {},
|
|
128
|
+
readLine: customAuthReadLine ?? undefined,
|
|
129
|
+
});
|
|
130
|
+
if (customAuth.kind) return customAuth; // RcfError
|
|
131
|
+
const appliedCapabilities = [...new Set([...discoveredCapabilities, ...customAuth.capsProvided])].sort();
|
|
132
|
+
const customAuthElicitations = customAuth.answered;
|
|
133
|
+
|
|
134
|
+
// Refusal gate for consumer blueprints that require at least one
|
|
135
|
+
// applied capability. When the effective set has no overlap AND
|
|
136
|
+
// the CLI did not pass the allow-skip flag AND no custom-auth
|
|
137
|
+
// capability answers were supplied that satisfy the gate, refuse
|
|
138
|
+
// with the templated message (spec 5.5.1 verbatim for admin-console).
|
|
139
|
+
if (blueprint.requiresAppliedCapabilities && !allowNoAuthYet) {
|
|
140
|
+
const required = blueprint.requiresAppliedCapabilities.capabilities;
|
|
141
|
+
const overlap = required.some((c) => appliedCapabilities.includes(c));
|
|
142
|
+
if (!overlap) {
|
|
143
|
+
const message = buildRefusalMessage({
|
|
144
|
+
slug: appliedSlug,
|
|
145
|
+
refusalMessageId: blueprint.requiresAppliedCapabilities.refusalMessageId,
|
|
146
|
+
requiredCapabilities: required,
|
|
147
|
+
appliedRecords: applied,
|
|
148
|
+
allowSkipFlag: blueprint.requiresAppliedCapabilities.allowSkipFlag,
|
|
149
|
+
});
|
|
150
|
+
return rcfError({ kind: 'requiresAppliedCapabilities', message });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Elicitation phase (visual round T-5 spec section 5.5, T-2 gate).
|
|
155
|
+
// Evaluate each declared elicit against the effective capability set
|
|
156
|
+
// (union of applied blueprints and custom-auth answers). Skip
|
|
157
|
+
// providesCapability entries: those were resolved above and their
|
|
158
|
+
// answers already landed in customAuthElicitations. Coerce operator
|
|
159
|
+
// answers per kind. A missing required answer refuses before any
|
|
160
|
+
// write.
|
|
161
|
+
const regularElicits = Array.isArray(blueprint.elicits)
|
|
162
|
+
? blueprint.elicits.filter((e) => typeof e.providesCapability !== 'string')
|
|
163
|
+
: [];
|
|
164
|
+
const elicitResult = runElicitationPhase({
|
|
165
|
+
elicits: regularElicits,
|
|
166
|
+
appliedCapabilities,
|
|
167
|
+
answers: elicitAnswers ?? {},
|
|
168
|
+
});
|
|
169
|
+
if (elicitResult.kind) return elicitResult; // RcfError
|
|
170
|
+
const appliedElicitations = { ...customAuthElicitations, ...elicitResult.value };
|
|
171
|
+
|
|
108
172
|
// Pre-detection: fold operator-supplied --resolve declarations into a
|
|
109
173
|
// WORKING COPY of the manifest so the detector honours them on this
|
|
110
174
|
// run. The final manifest write later composes the same resolution
|
|
@@ -302,11 +366,44 @@ export async function applyBlueprint({ projectRoot, tree, source, displaySource,
|
|
|
302
366
|
});
|
|
303
367
|
if (manifestResult.kind) return manifestResult; // RcfError
|
|
304
368
|
|
|
369
|
+
// Sidecar write for the capability-declaration mechanism (visual
|
|
370
|
+
// round T-5 spec 5.5.1). Per-project apply-state that the applied-
|
|
371
|
+
// blueprint-record schema cannot carry today: the discovered
|
|
372
|
+
// appliedCapabilities snapshot, the elicit answers, and (when the
|
|
373
|
+
// gate was skipped) an allowNoAuthYet flag plus a notes string that
|
|
374
|
+
// rcf define validate later reads to flag ungated surfaces. The
|
|
375
|
+
// sidecar is written on ANY apply that carries capabilities-related
|
|
376
|
+
// data (declared capabilities, requiresAppliedCapabilities gate,
|
|
377
|
+
// elicits, or an override) so probe packs and audit tooling have
|
|
378
|
+
// one deterministic file to read. dryRun skips the write.
|
|
379
|
+
const wroteSidecar = !dryRun && (
|
|
380
|
+
blueprint.requiresAppliedCapabilities !== undefined ||
|
|
381
|
+
(Array.isArray(blueprint.elicits) && blueprint.elicits.length > 0) ||
|
|
382
|
+
Array.isArray(blueprint.capabilities) ||
|
|
383
|
+
allowNoAuthYet === true
|
|
384
|
+
);
|
|
385
|
+
let sidecarPath = null;
|
|
386
|
+
if (wroteSidecar) {
|
|
387
|
+
sidecarPath = await writeSidecar({
|
|
388
|
+
projectRoot,
|
|
389
|
+
slug: appliedSlug,
|
|
390
|
+
version: blueprint.version,
|
|
391
|
+
appliedAt: now.toISOString(),
|
|
392
|
+
appliedCapabilities,
|
|
393
|
+
appliedElicitations,
|
|
394
|
+
allowNoAuthYet: allowNoAuthYet === true ? true : undefined,
|
|
395
|
+
notes: allowNoAuthYet === true
|
|
396
|
+
? `no auth yet: applied under --${blueprint.requiresAppliedCapabilities?.allowSkipFlag ?? 'allow-no-auth-yet'}; surfaces gated on ${(blueprint.requiresAppliedCapabilities?.capabilities ?? []).join(', ')} will refuse at runtime until an auth blueprint is applied.`
|
|
397
|
+
: undefined,
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
|
|
305
401
|
return {
|
|
306
402
|
applied: true,
|
|
307
403
|
slug: appliedSlug,
|
|
308
404
|
version: blueprint.version,
|
|
309
405
|
contributions: writtenContributions,
|
|
406
|
+
...(wroteSidecar ? { sidecarPath, appliedCapabilities, appliedElicitations } : {}),
|
|
310
407
|
// Companion-suggestion mechanism (spec 2.6). Bubble the source
|
|
311
408
|
// blueprint's `suggestedCompanions[]` up on the result so the CLI
|
|
312
409
|
// can render the resolved-suggestion block after a successful
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
// Capability-declaration mechanism (visual round T-5 spec section
|
|
2
|
+
// 5.5.1, 5.5.2). Consumer blueprints gate their surfaces on the union
|
|
3
|
+
// of currently-applied blueprints' `capabilities[]` declarations.
|
|
4
|
+
//
|
|
5
|
+
// Why source read-back. The applied-blueprint-record schema in
|
|
6
|
+
// @stravica-ai/rcf-schemas is closed under additionalProperties:false
|
|
7
|
+
// (0.6.0), so `appliedCapabilities[]` cannot land on the manifest
|
|
8
|
+
// record without a schema bump. Rather than block the round on that
|
|
9
|
+
// dependency, this file adopts the core-companions T-2 gate precedent:
|
|
10
|
+
// read the source blueprint.json via manifest.blueprints[].source and
|
|
11
|
+
// union its declared capabilities[]. Per-project answers (elicits) and
|
|
12
|
+
// a per-project applied-capability snapshot are written to a sidecar
|
|
13
|
+
// file at rcf/blueprints/${slug}.applied.json.
|
|
14
|
+
//
|
|
15
|
+
// This file is imported by apply.js at apply time and by the shipped
|
|
16
|
+
// probe pack (blueprints/application-admin-console/probe-packs/) at
|
|
17
|
+
// verify time.
|
|
18
|
+
|
|
19
|
+
import { readFile, writeFile, mkdir, stat } from 'node:fs/promises';
|
|
20
|
+
import { dirname, join, resolve, isAbsolute } from 'node:path';
|
|
21
|
+
|
|
22
|
+
import { rcfError } from '../core/errors/index.js';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Read a single applied blueprint's declared capabilities via
|
|
26
|
+
* source read-back. Silently ignores an applied blueprint whose
|
|
27
|
+
* source path no longer exists (the record's ownership fact is the
|
|
28
|
+
* slug on the manifest; the source file is provenance and can move).
|
|
29
|
+
*
|
|
30
|
+
* @param {{ slug: string, source: string }} appliedRecord
|
|
31
|
+
* @param {string} projectRoot
|
|
32
|
+
* @returns {Promise<string[]>}
|
|
33
|
+
*/
|
|
34
|
+
export async function readAppliedBlueprintCapabilities(appliedRecord, projectRoot) {
|
|
35
|
+
if (!appliedRecord || typeof appliedRecord.source !== 'string') return [];
|
|
36
|
+
const src = appliedRecord.source;
|
|
37
|
+
// A qualified typed ref (`wsd:auth-oauth2`) never resolves as a file
|
|
38
|
+
// path. Skip it; the library-resolved source blueprint is not on
|
|
39
|
+
// this local tree.
|
|
40
|
+
if (/^[a-z][a-z0-9-]*:[a-z]/i.test(src) && !src.startsWith('/') && !src.startsWith('.')) return [];
|
|
41
|
+
const abs = isAbsolute(src) ? src : resolve(projectRoot, src);
|
|
42
|
+
const metaPath = join(abs, 'blueprint.json');
|
|
43
|
+
let raw;
|
|
44
|
+
try {
|
|
45
|
+
raw = await readFile(metaPath, 'utf8');
|
|
46
|
+
} catch {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
const doc = JSON.parse(raw);
|
|
51
|
+
if (!Array.isArray(doc.capabilities)) return [];
|
|
52
|
+
return doc.capabilities.filter((s) => typeof s === 'string');
|
|
53
|
+
} catch {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Discover the union of applied capabilities across every applied
|
|
60
|
+
* blueprint on the tree.
|
|
61
|
+
*
|
|
62
|
+
* @param {Array<{ slug: string, source: string }>} appliedRecords
|
|
63
|
+
* @param {string} projectRoot
|
|
64
|
+
* @returns {Promise<{ union: string[], perSlug: Record<string, string[]> }>}
|
|
65
|
+
*/
|
|
66
|
+
export async function discoverAppliedCapabilities(appliedRecords, projectRoot) {
|
|
67
|
+
const perSlug = {};
|
|
68
|
+
const union = new Set();
|
|
69
|
+
for (const rec of appliedRecords ?? []) {
|
|
70
|
+
const caps = await readAppliedBlueprintCapabilities(rec, projectRoot);
|
|
71
|
+
perSlug[rec.slug] = caps;
|
|
72
|
+
for (const c of caps) union.add(c);
|
|
73
|
+
}
|
|
74
|
+
return { union: [...union].sort(), perSlug };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Build the refusal message for a requiresAppliedCapabilities gate.
|
|
79
|
+
* refusalMessageId maps to a known template; a message the loader
|
|
80
|
+
* accepts is used verbatim if the id is unknown (so a future
|
|
81
|
+
* blueprint can ship a bespoke refusal without a core code change).
|
|
82
|
+
*
|
|
83
|
+
* @param {object} args
|
|
84
|
+
* @param {string} args.slug applying blueprint slug
|
|
85
|
+
* @param {string} args.refusalMessageId
|
|
86
|
+
* @param {string[]} args.requiredCapabilities
|
|
87
|
+
* @param {Array<{ slug: string, version: string }>} args.appliedRecords
|
|
88
|
+
* @param {string} args.allowSkipFlag the CLI flag the operator can use to override
|
|
89
|
+
* @returns {string}
|
|
90
|
+
*/
|
|
91
|
+
export function buildRefusalMessage({ slug, refusalMessageId, requiredCapabilities, appliedRecords, allowSkipFlag }) {
|
|
92
|
+
// Applied blueprints list, one per line (indented 2 spaces per spec
|
|
93
|
+
// 5.5.1 formatting).
|
|
94
|
+
const appliedLines = (appliedRecords ?? [])
|
|
95
|
+
.map((r) => ` ${r.slug} v${r.version}`)
|
|
96
|
+
.join('\n');
|
|
97
|
+
const appliedBlock = appliedLines.length > 0 ? appliedLines : ' (none)';
|
|
98
|
+
if (refusalMessageId === 'application-admin-console-bare-spa') {
|
|
99
|
+
return [
|
|
100
|
+
`${slug} requires at least one applied security-auth-* blueprint,`,
|
|
101
|
+
`or an operator override with --${allowSkipFlag}.`,
|
|
102
|
+
'',
|
|
103
|
+
'Applied blueprints on this project:',
|
|
104
|
+
appliedBlock,
|
|
105
|
+
'',
|
|
106
|
+
'Suggested next steps:',
|
|
107
|
+
' 1. Apply an auth blueprint first:',
|
|
108
|
+
' rcf define blueprint add security-auth-magic-link',
|
|
109
|
+
' (or security-auth-clerk, security-auth-oauth2, security-auth-keycloak)',
|
|
110
|
+
` 2. Override for a scaffolding pass (surfaces will refuse at apply until an`,
|
|
111
|
+
` auth blueprint is applied):`,
|
|
112
|
+
` rcf define blueprint add ${slug} --${allowSkipFlag}`,
|
|
113
|
+
].join('\n');
|
|
114
|
+
}
|
|
115
|
+
// Generic fallback. Names the required capabilities and applied set;
|
|
116
|
+
// the operator has enough to diagnose without a bespoke template.
|
|
117
|
+
return [
|
|
118
|
+
`${slug} requires an applied blueprint declaring at least one of:`,
|
|
119
|
+
` ${requiredCapabilities.join(', ')}`,
|
|
120
|
+
`or an operator override with --${allowSkipFlag}.`,
|
|
121
|
+
'',
|
|
122
|
+
'Applied blueprints on this project:',
|
|
123
|
+
appliedBlock,
|
|
124
|
+
].join('\n');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Run the elicitation phase. Reads a bag of operator-supplied answers
|
|
129
|
+
* (`--answer <id>=<value>` merged with `--answers <file>` in the CLI),
|
|
130
|
+
* evaluates each elicit's `when` predicate against the discovered
|
|
131
|
+
* capability set, coerces the answer per kind, and returns a
|
|
132
|
+
* `{id: value}` map. A missing required answer for an elicit whose
|
|
133
|
+
* predicate fires refuses with a validation rcfError.
|
|
134
|
+
*
|
|
135
|
+
* @param {object} args
|
|
136
|
+
* @param {Array<object>} args.elicits from loader
|
|
137
|
+
* @param {string[]} args.appliedCapabilities discovered union
|
|
138
|
+
* @param {Record<string, string|boolean>} args.answers operator bag
|
|
139
|
+
* @returns {{value: Record<string, string|boolean|null>} | import('../core/errors/index.js').RcfError}
|
|
140
|
+
*/
|
|
141
|
+
export function runElicitationPhase({ elicits, appliedCapabilities, answers }) {
|
|
142
|
+
if (!Array.isArray(elicits) || elicits.length === 0) {
|
|
143
|
+
return { value: {} };
|
|
144
|
+
}
|
|
145
|
+
const capSet = new Set(appliedCapabilities ?? []);
|
|
146
|
+
const bag = answers ?? {};
|
|
147
|
+
const out = {};
|
|
148
|
+
for (const e of elicits) {
|
|
149
|
+
const gated = e.when ? Array.isArray(e.when.requiresCapability) && !e.when.requiresCapability.some((c) => capSet.has(c)) : false;
|
|
150
|
+
if (gated) continue;
|
|
151
|
+
const raw = bag[e.id];
|
|
152
|
+
let coerced;
|
|
153
|
+
if (raw === undefined || raw === null || raw === '') {
|
|
154
|
+
if (e.default === undefined) {
|
|
155
|
+
return rcfError({
|
|
156
|
+
kind: 'validation',
|
|
157
|
+
message: `blueprint apply: elicit '${e.id}' is required (no default declared) and no answer was supplied. Pass --answer ${e.id}=<value> or --answers <file>.`,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
coerced = e.default;
|
|
161
|
+
} else if (e.kind === 'boolean') {
|
|
162
|
+
if (typeof raw === 'boolean') coerced = raw;
|
|
163
|
+
else {
|
|
164
|
+
const s = String(raw).trim().toLowerCase();
|
|
165
|
+
if (s === 'y' || s === 'yes' || s === 'true' || s === '1') coerced = true;
|
|
166
|
+
else if (s === 'n' || s === 'no' || s === 'false' || s === '0') coerced = false;
|
|
167
|
+
else return rcfError({ kind: 'validation', message: `blueprint apply: elicit '${e.id}' expects a boolean; got '${raw}'.` });
|
|
168
|
+
}
|
|
169
|
+
} else if (e.kind === 'enum') {
|
|
170
|
+
const s = String(raw).trim();
|
|
171
|
+
if (!e.options.includes(s)) {
|
|
172
|
+
return rcfError({ kind: 'validation', message: `blueprint apply: elicit '${e.id}' expects one of [${e.options.join(', ')}]; got '${raw}'.` });
|
|
173
|
+
}
|
|
174
|
+
coerced = s;
|
|
175
|
+
} else {
|
|
176
|
+
coerced = String(raw).trim();
|
|
177
|
+
}
|
|
178
|
+
out[e.id] = coerced;
|
|
179
|
+
}
|
|
180
|
+
return { value: out };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Sidecar path for a blueprint's per-project apply-state (relative to
|
|
185
|
+
* the project root). One file per applied blueprint slug.
|
|
186
|
+
*/
|
|
187
|
+
export function sidecarRelPath(slug) {
|
|
188
|
+
return join('rcf', 'blueprints', `${slug}.applied.json`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Write the sidecar. Overwrites the file so re-apply on the same
|
|
193
|
+
* version is idempotent.
|
|
194
|
+
*
|
|
195
|
+
* @param {object} args
|
|
196
|
+
* @param {string} args.projectRoot
|
|
197
|
+
* @param {string} args.slug
|
|
198
|
+
* @param {string} args.version
|
|
199
|
+
* @param {string} args.appliedAt
|
|
200
|
+
* @param {string[]} args.appliedCapabilities
|
|
201
|
+
* @param {Record<string, string|boolean|null>} args.appliedElicitations
|
|
202
|
+
* @param {boolean} [args.allowNoAuthYet]
|
|
203
|
+
* @param {string} [args.notes]
|
|
204
|
+
*/
|
|
205
|
+
export async function writeSidecar({ projectRoot, slug, version, appliedAt, appliedCapabilities, appliedElicitations, allowNoAuthYet, notes }) {
|
|
206
|
+
const rel = sidecarRelPath(slug);
|
|
207
|
+
const abs = join(projectRoot, rel);
|
|
208
|
+
await mkdir(dirname(abs), { recursive: true });
|
|
209
|
+
const payload = { slug, version, appliedAt, appliedCapabilities: [...appliedCapabilities].sort(), appliedElicitations };
|
|
210
|
+
if (allowNoAuthYet === true) payload.allowNoAuthYet = true;
|
|
211
|
+
if (typeof notes === 'string' && notes.length > 0) payload.notes = notes;
|
|
212
|
+
await writeFile(abs, `${JSON.stringify(payload, null, 2)}\n`, 'utf8');
|
|
213
|
+
return rel;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Read the sidecar. Missing file returns null (never throws).
|
|
218
|
+
*/
|
|
219
|
+
export async function readSidecar(projectRoot, slug) {
|
|
220
|
+
const abs = join(projectRoot, sidecarRelPath(slug));
|
|
221
|
+
try {
|
|
222
|
+
const raw = await readFile(abs, 'utf8');
|
|
223
|
+
return JSON.parse(raw);
|
|
224
|
+
} catch {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Presence check for the sidecar file (does not parse).
|
|
231
|
+
*/
|
|
232
|
+
export async function sidecarExists(projectRoot, slug) {
|
|
233
|
+
try {
|
|
234
|
+
await stat(join(projectRoot, sidecarRelPath(slug)));
|
|
235
|
+
return true;
|
|
236
|
+
} catch {
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Custom-auth capability elicitation (visual round T-5 spec 5.5
|
|
243
|
+
* "Risks": custom auth elicits per capability). Runs BEFORE the
|
|
244
|
+
* requiresAppliedCapabilities refusal gate. Each elicit entry with
|
|
245
|
+
* a truthy `providesCapability` string is a boolean prompt asking
|
|
246
|
+
* whether the operator's own (non-shelf) auth blueprint provides
|
|
247
|
+
* the named capability. A truthy answer folds that capability into
|
|
248
|
+
* the effective applied set, so a project with no shelf auth
|
|
249
|
+
* blueprint can still pass the refusal gate without the allowNoAuthYet
|
|
250
|
+
* override, and the resulting sidecar records the operator-declared
|
|
251
|
+
* appliedCapabilities honestly.
|
|
252
|
+
*
|
|
253
|
+
* Interactive vs non-interactive:
|
|
254
|
+
* - Non-interactive: only the elicits whose id appears in `answers`
|
|
255
|
+
* resolve; the rest are treated as unanswered.
|
|
256
|
+
* - Interactive (tty=true): unanswered custom-auth elicits are read
|
|
257
|
+
* from stdin via readline with a `y/N` prompt; this branch is
|
|
258
|
+
* driven synchronously by the CLI's `apply` wrapper when it
|
|
259
|
+
* detects `process.stdin.isTTY === true`.
|
|
260
|
+
*
|
|
261
|
+
* A malformed boolean answer refuses with a validation rcfError.
|
|
262
|
+
*
|
|
263
|
+
* @param {object} args
|
|
264
|
+
* @param {Array<object>} args.elicits from loader (may include non-providesCapability entries; they are ignored here)
|
|
265
|
+
* @param {Record<string, string|boolean>} args.answers operator bag
|
|
266
|
+
* @param {(prompt: string) => Promise<string>} [args.readLine] test seam for interactive input
|
|
267
|
+
* @returns {Promise<{ capsProvided: string[], answered: Record<string, boolean>, elicitsPresent: boolean, anyAnswered: boolean } | import('../core/errors/index.js').RcfError>}
|
|
268
|
+
*/
|
|
269
|
+
export async function runCustomAuthCapabilityElicits({ elicits, answers, readLine }) {
|
|
270
|
+
const capsProvided = new Set();
|
|
271
|
+
const answered = {};
|
|
272
|
+
let elicitsPresent = false;
|
|
273
|
+
const bag = answers ?? {};
|
|
274
|
+
for (const e of elicits ?? []) {
|
|
275
|
+
if (typeof e.providesCapability !== 'string' || e.providesCapability.length === 0) continue;
|
|
276
|
+
elicitsPresent = true;
|
|
277
|
+
const raw = bag[e.id];
|
|
278
|
+
let coerced;
|
|
279
|
+
if (raw === undefined || raw === null || raw === '') {
|
|
280
|
+
if (typeof readLine !== 'function') {
|
|
281
|
+
// Non-interactive branch, no answer supplied: skip. Default
|
|
282
|
+
// remains false in that case so a missing operator answer does
|
|
283
|
+
// not silently declare a capability.
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
const reply = await readLine(`Custom auth: does your auth provide '${e.providesCapability}'? [y/N]: `);
|
|
287
|
+
const s = String(reply ?? '').trim().toLowerCase();
|
|
288
|
+
if (s === '' || s === 'n' || s === 'no' || s === 'false' || s === '0') coerced = false;
|
|
289
|
+
else if (s === 'y' || s === 'yes' || s === 'true' || s === '1') coerced = true;
|
|
290
|
+
else {
|
|
291
|
+
return rcfError({
|
|
292
|
+
kind: 'validation',
|
|
293
|
+
message: `blueprint apply: elicit '${e.id}' expects a boolean; got '${reply}'.`,
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
} else if (typeof raw === 'boolean') {
|
|
297
|
+
coerced = raw;
|
|
298
|
+
} else {
|
|
299
|
+
const s = String(raw).trim().toLowerCase();
|
|
300
|
+
if (s === 'y' || s === 'yes' || s === 'true' || s === '1') coerced = true;
|
|
301
|
+
else if (s === 'n' || s === 'no' || s === 'false' || s === '0') coerced = false;
|
|
302
|
+
else {
|
|
303
|
+
return rcfError({
|
|
304
|
+
kind: 'validation',
|
|
305
|
+
message: `blueprint apply: elicit '${e.id}' expects a boolean; got '${raw}'.`,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
answered[e.id] = coerced;
|
|
310
|
+
if (coerced === true) capsProvided.add(e.providesCapability);
|
|
311
|
+
}
|
|
312
|
+
return {
|
|
313
|
+
capsProvided: [...capsProvided].sort(),
|
|
314
|
+
answered,
|
|
315
|
+
elicitsPresent,
|
|
316
|
+
anyAnswered: Object.keys(answered).length > 0,
|
|
317
|
+
};
|
|
318
|
+
}
|