rcf-lite 0.0.1 → 0.8.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 +344 -0
- package/LICENSE +202 -0
- package/README.md +92 -4
- package/bin/rcf-verify.js +122 -0
- package/bin/rcf.js +174 -0
- package/bin/view-supervisor-child.mjs +14 -0
- package/fixtures/canary-manifest.json +103 -0
- package/guidance/README.md +25 -0
- package/guidance/build-cycle-playbook.md +492 -0
- package/guidance/build-cycle.md +40 -0
- package/guidance/document-model.md +45 -0
- package/guidance/elicitation-playbook.md +442 -0
- package/guidance/harness-template.md +246 -0
- package/guidance/managed/README.md +63 -0
- package/guidance/managed/agent-instructions-block.hash +1 -0
- package/guidance/managed/agent-instructions-block.md +220 -0
- package/guidance/managed/legacy-fragment-hashes.json +22 -0
- package/guidance/manifest.json +21 -0
- package/guidance/overview.md +48 -0
- package/guidance/persona-programme.md +44 -0
- package/package.json +71 -8
- package/rcf/adrs/adr-001.json +25 -0
- package/rcf/adrs/adr-002.json +25 -0
- package/rcf/adrs/adr-003.json +25 -0
- package/rcf/adrs/adr-004.json +20 -0
- package/rcf/adrs/adr-005.json +20 -0
- package/rcf/adrs/adr-006.json +25 -0
- package/rcf/adrs/adr-007.json +25 -0
- package/rcf/adrs/adr-008.json +25 -0
- package/rcf/adrs/adr-009.json +25 -0
- package/rcf/build-sequence.json +11 -0
- package/rcf/code-nodes/cn-001.json +14 -0
- package/rcf/code-nodes/cn-002.json +14 -0
- package/rcf/code-nodes/cn-003.json +15 -0
- package/rcf/code-nodes/cn-004.json +17 -0
- package/rcf/code-nodes/cn-005.json +19 -0
- package/rcf/code-nodes/cn-006.json +17 -0
- package/rcf/code-nodes/cn-007.json +16 -0
- package/rcf/code-nodes/cn-008.json +19 -0
- package/rcf/code-nodes/cn-009.json +16 -0
- package/rcf/code-nodes/cn-010.json +18 -0
- package/rcf/code-nodes/cn-011.json +18 -0
- package/rcf/code-nodes/cn-012.json +18 -0
- package/rcf/code-nodes/cn-013.json +19 -0
- package/rcf/code-nodes/cn-014.json +16 -0
- package/rcf/code-nodes/cn-015.json +16 -0
- package/rcf/code-nodes/cn-016.json +16 -0
- package/rcf/code-nodes/cn-017.json +16 -0
- package/rcf/code-nodes/cn-018.json +15 -0
- package/rcf/code-nodes/cn-019.json +16 -0
- package/rcf/code-nodes/cn-020.json +16 -0
- package/rcf/code-nodes/cn-021.json +16 -0
- package/rcf/code-nodes/cn-022.json +16 -0
- package/rcf/code-nodes/cn-023.json +16 -0
- package/rcf/code-nodes/cn-024.json +16 -0
- package/rcf/code-nodes/cn-025.json +16 -0
- package/rcf/code-nodes/cn-026.json +16 -0
- package/rcf/code-nodes/cn-027.json +16 -0
- package/rcf/code-nodes/cn-028.json +16 -0
- package/rcf/code-nodes/cn-029.json +19 -0
- package/rcf/code-nodes/cn-030.json +14 -0
- package/rcf/code-nodes/cn-031.json +14 -0
- package/rcf/code-nodes/cn-032.json +14 -0
- package/rcf/code-nodes/cn-033.json +14 -0
- package/rcf/code-nodes/cn-034.json +14 -0
- package/rcf/code-nodes/cn-035.json +14 -0
- package/rcf/code-nodes/cn-036.json +14 -0
- package/rcf/code-nodes/cn-037.json +14 -0
- package/rcf/code-nodes/cn-038.json +14 -0
- package/rcf/code-nodes/cn-039.json +14 -0
- package/rcf/code-nodes/cn-040.json +14 -0
- package/rcf/code-nodes/cn-041.json +14 -0
- package/rcf/code-nodes/cn-042.json +14 -0
- package/rcf/code-nodes/cn-043.json +14 -0
- package/rcf/code-nodes/cn-044.json +14 -0
- package/rcf/code-nodes/cn-045.json +14 -0
- package/rcf/code-nodes/cn-046.json +14 -0
- package/rcf/code-nodes/cn-047.json +14 -0
- package/rcf/code-nodes/cn-048.json +14 -0
- package/rcf/code-nodes/cn-049.json +14 -0
- package/rcf/code-nodes/cn-050.json +14 -0
- package/rcf/code-nodes/cn-051.json +14 -0
- package/rcf/code-nodes/cn-052.json +14 -0
- package/rcf/code-nodes/cn-053.json +14 -0
- package/rcf/code-nodes/cn-054.json +14 -0
- package/rcf/code-nodes/cn-055.json +14 -0
- package/rcf/code-nodes/cn-056.json +14 -0
- package/rcf/code-nodes/cn-057.json +14 -0
- package/rcf/fbs/fbs-001.json +49 -0
- package/rcf/fbs/fbs-002.json +42 -0
- package/rcf/fbs/fbs-003.json +37 -0
- package/rcf/fbs/fbs-004.json +39 -0
- package/rcf/fbs/fbs-005.json +38 -0
- package/rcf/fbs/fbs-006.json +48 -0
- package/rcf/fbs/fbs-007.json +39 -0
- package/rcf/fbs/fbs-008.json +40 -0
- package/rcf/fbs/fbs-009.json +36 -0
- package/rcf/fbs/fbs-010.json +41 -0
- package/rcf/fbs/fbs-011.json +36 -0
- package/rcf/fbs/fbs-012.json +46 -0
- package/rcf/fbs/fbs-013.json +42 -0
- package/rcf/fbs/fbs-014.json +49 -0
- package/rcf/fbs/fbs-015.json +32 -0
- package/rcf/manifest.json +17 -0
- package/rcf/prd.json +47 -0
- package/rcf/requirements/req-001.json +19 -0
- package/rcf/requirements/req-002.json +19 -0
- package/rcf/requirements/req-003.json +19 -0
- package/rcf/requirements/req-004.json +19 -0
- package/rcf/requirements/req-005.json +19 -0
- package/rcf/requirements/req-006.json +19 -0
- package/rcf/requirements/req-007.json +19 -0
- package/rcf/requirements/req-008.json +19 -0
- package/rcf/requirements/req-009.json +19 -0
- package/rcf/tacs/tac-001.json +45 -0
- package/rcf/tacs/tac-002.json +109 -0
- package/rcf/tacs/tac-003.json +40 -0
- package/rcf/tacs/tac-004.json +51 -0
- package/rcf/tacs/tac-005.json +52 -0
- package/rcf/tacs/tac-006.json +104 -0
- package/rcf/tacs/tac-007.json +38 -0
- package/rcf/tacs/tac-008.json +51 -0
- package/rcf/tad.json +51 -0
- package/rcf/test-suites/PENDING.md +23 -0
- package/rcf/test-suites/ts-001.json +38 -0
- package/rcf/test-suites/ts-002.json +38 -0
- package/rcf/test-suites/ts-003.json +43 -0
- package/rcf/test-suites/ts-004.json +44 -0
- package/rcf/test-suites/ts-005.json +30 -0
- package/rcf/test-suites/ts-006.json +36 -0
- package/rcf/test-suites/ts-007.json +43 -0
- package/rcf/test-suites/ts-008.json +37 -0
- package/rcf/test-suites/ts-009.json +38 -0
- package/rcf/test-suites/ts-010.json +38 -0
- package/rcf/test-suites/ts-011.json +44 -0
- package/rcf/test-suites/ts-012.json +36 -0
- package/rcf/test-suites/ts-013.json +38 -0
- package/rcf/test-suites/ts-014.json +38 -0
- package/rcf/test-suites/ts-015.json +38 -0
- package/rcf/test-suites/ts-016.json +37 -0
- package/rcf/test-suites/ts-017.json +38 -0
- package/rcf/test-suites/ts-018.json +38 -0
- package/rcf/test-suites/ts-019.json +37 -0
- package/rcf/test-suites/ts-020.json +46 -0
- package/rcf/test-suites/ts-021.json +46 -0
- package/rcf/test-suites/ts-022.json +46 -0
- package/rcf/test-suites/ts-023.json +46 -0
- package/rcf/test-suites/ts-024.json +46 -0
- package/rcf/test-suites/ts-025.json +52 -0
- package/rcf/user-stories/us-101.json +40 -0
- package/rcf/user-stories/us-102.json +40 -0
- package/rcf/user-stories/us-201.json +40 -0
- package/rcf/user-stories/us-202.json +40 -0
- package/rcf/user-stories/us-203.json +32 -0
- package/rcf/user-stories/us-301.json +40 -0
- package/rcf/user-stories/us-302.json +40 -0
- package/rcf/user-stories/us-303.json +40 -0
- package/rcf/user-stories/us-304.json +40 -0
- package/rcf/user-stories/us-401.json +40 -0
- package/rcf/user-stories/us-402.json +40 -0
- package/rcf/user-stories/us-403.json +40 -0
- package/rcf/user-stories/us-501.json +40 -0
- package/rcf/user-stories/us-502.json +40 -0
- package/rcf/user-stories/us-503.json +40 -0
- package/rcf/user-stories/us-601.json +40 -0
- package/rcf/user-stories/us-602.json +40 -0
- package/rcf/user-stories/us-701.json +40 -0
- package/rcf/user-stories/us-702.json +40 -0
- package/rcf/user-stories/us-801.json +49 -0
- package/rcf/user-stories/us-802.json +49 -0
- package/rcf/user-stories/us-803.json +49 -0
- package/rcf/user-stories/us-804.json +49 -0
- package/rcf/user-stories/us-805.json +49 -0
- package/rcf/user-stories/us-901.json +40 -0
- package/src/.gitkeep +0 -0
- package/src/admissibility/enforce.js +142 -0
- package/src/admissibility/index.js +8 -0
- package/src/admissibility/markers.js +104 -0
- package/src/admissibility/scope-lint.js +163 -0
- package/src/browser-verify/auth-smoke.js +109 -0
- package/src/browser-verify/index.js +29 -0
- package/src/browser-verify/invariants.js +336 -0
- package/src/browser-verify/manifest-writer.js +189 -0
- package/src/browser-verify/runner.js +171 -0
- package/src/build/bundle.js +198 -0
- package/src/build/formatters/json.js +18 -0
- package/src/build/formatters/markdown.js +406 -0
- package/src/build/index.js +14 -0
- package/src/build/mark.js +177 -0
- package/src/build/queue.js +285 -0
- package/src/cli/browser-verify.js +231 -0
- package/src/cli/build.js +584 -0
- package/src/cli/coverage.js +219 -0
- package/src/cli/create.js +358 -0
- package/src/cli/delete.js +127 -0
- package/src/cli/design.js +353 -0
- package/src/cli/doctor.js +548 -0
- package/src/cli/fbs.js +171 -0
- package/src/cli/finalise.js +377 -0
- package/src/cli/guidance.js +206 -0
- package/src/cli/help.js +156 -0
- package/src/cli/impact.js +119 -0
- package/src/cli/init.js +282 -0
- package/src/cli/intake.js +153 -0
- package/src/cli/link.js +128 -0
- package/src/cli/mcp.js +160 -0
- package/src/cli/preflight.js +220 -0
- package/src/cli/read.js +168 -0
- package/src/cli/req-baseline.js +269 -0
- package/src/cli/req-classify.js +135 -0
- package/src/cli/review.js +295 -0
- package/src/cli/test-suite.js +221 -0
- package/src/cli/trace.js +172 -0
- package/src/cli/ui-baseline.js +292 -0
- package/src/cli/ui-classify.js +108 -0
- package/src/cli/update.js +197 -0
- package/src/cli/validate.js +168 -0
- package/src/cli/view.js +452 -0
- package/src/core/baseline-catalog/data/auth.json +42 -0
- package/src/core/baseline-catalog/data/http-api.json +42 -0
- package/src/core/baseline-catalog/data/notifications.json +33 -0
- package/src/core/baseline-catalog/data/persistence.json +33 -0
- package/src/core/baseline-catalog/data/web-ui.json +60 -0
- package/src/core/baseline-catalog/index.js +121 -0
- package/src/core/errors/index.js +167 -0
- package/src/core/fixtures/register-canary/canary-prompt-01.json +18 -0
- package/src/core/fixtures/register-canary/canary-prompt-02.json +21 -0
- package/src/core/fixtures/register-canary/canary-prompt-03.json +17 -0
- package/src/core/isolation/index.js +60 -0
- package/src/core/mcp/framing.js +103 -0
- package/src/core/mcp/index.js +8 -0
- package/src/core/mcp/server.js +228 -0
- package/src/core/patterns/register-canary.js +209 -0
- package/src/core/patterns/req-shapes.js +158 -0
- package/src/core/patterns/services.js +358 -0
- package/src/core/patterns/ui-shapes.js +166 -0
- package/src/core/store/cn-resolve.js +134 -0
- package/src/core/store/derive-deps.js +93 -0
- package/src/core/store/ids.js +78 -0
- package/src/core/store/index.js +20 -0
- package/src/core/store/init.js +255 -0
- package/src/core/store/loader.js +214 -0
- package/src/core/store/tp-resolve.js +176 -0
- package/src/core/store/validator.js +191 -0
- package/src/core/store/walker.js +944 -0
- package/src/core/store/writer.js +1879 -0
- package/src/design/index.js +11 -0
- package/src/design/writer.js +271 -0
- package/src/finalise/detect.js +151 -0
- package/src/finalise/index.js +31 -0
- package/src/finalise/ingest.js +160 -0
- package/src/finalise/install.js +119 -0
- package/src/finalise/ship-without-verified.js +131 -0
- package/src/finalise/spawn.js +84 -0
- package/src/intake/fidelity.js +105 -0
- package/src/intake/index.js +6 -0
- package/src/intake/manifest-writer.js +100 -0
- package/src/intake/orchestrator.js +138 -0
- package/src/intake/validate.js +80 -0
- package/src/mcp/map-errors.js +131 -0
- package/src/mcp/prompts.js +49 -0
- package/src/mcp/resources.js +244 -0
- package/src/mcp/tools.js +1212 -0
- package/src/preflight/design-shapes.js +185 -0
- package/src/preflight/index.js +90 -0
- package/src/preflight/manifest-writer.js +264 -0
- package/src/preflight/scanner.js +206 -0
- package/src/preflight/secrets.js +134 -0
- package/src/preflight/session.js +246 -0
- package/src/query/attestation.js +285 -0
- package/src/query/coverage.js +308 -0
- package/src/query/formatters/json.js +21 -0
- package/src/query/formatters/mermaid.js +209 -0
- package/src/query/formatters/table.js +203 -0
- package/src/query/impact.js +173 -0
- package/src/query/index.js +13 -0
- package/src/query/refuse-on-admissibility.js +73 -0
- package/src/query/trace.js +345 -0
- package/src/register-canary/fixture-loader.js +87 -0
- package/src/register-canary/index.js +10 -0
- package/src/register-canary/record-writer.js +132 -0
- package/src/register-canary/runner.js +156 -0
- package/src/req-baseline/gate.js +86 -0
- package/src/req-baseline/index.js +27 -0
- package/src/req-baseline/open-candidates.js +143 -0
- package/src/req-baseline/opt-out.js +195 -0
- package/src/req-baseline/sweep.js +230 -0
- package/src/req-detection/classifier.js +181 -0
- package/src/req-detection/index.js +9 -0
- package/src/req-detection/persist.js +55 -0
- package/src/review/index.js +325 -0
- package/src/review/mutation.js +117 -0
- package/src/review/ui-baseline-drift.js +138 -0
- package/src/ruleset/index.js +140 -0
- package/src/ruleset/ruleset.json +146 -0
- package/src/server/index.js +178 -0
- package/src/server/routes.js +110 -0
- package/src/server/sse.js +118 -0
- package/src/setup/agent-setup.js +362 -0
- package/src/setup/identity-seed.js +104 -0
- package/src/setup/knowledge-seed.js +123 -0
- package/src/setup/managed-block.js +193 -0
- package/src/setup/managed-gitignore.js +166 -0
- package/src/setup/managed-markers.js +49 -0
- package/src/ui-baseline/defaults.js +119 -0
- package/src/ui-baseline/index.js +25 -0
- package/src/ui-baseline/manifest-writer.js +282 -0
- package/src/ui-baseline/session.js +178 -0
- package/src/ui-detection/classifier.js +192 -0
- package/src/verify/chain/index.js +221 -0
- package/src/verify/cli/cleanup.js +61 -0
- package/src/verify/cli/help.js +56 -0
- package/src/verify/cli/mcp.js +98 -0
- package/src/verify/cli/provision.js +71 -0
- package/src/verify/cli/report.js +71 -0
- package/src/verify/cli/run.js +155 -0
- package/src/verify/engine/brief.js +87 -0
- package/src/verify/engine/index.js +177 -0
- package/src/verify/engine/launcher.js +307 -0
- package/src/verify/mcp/tools.js +107 -0
- package/src/verify/profile/index.js +146 -0
- package/src/verify/provision/index.js +256 -0
- package/src/verify/report/index.js +139 -0
- package/src/verify/report/renderer.js +118 -0
- package/src/verify/verdict/index.js +313 -0
- package/src/view/doc-renderers/adr.js +44 -0
- package/src/view/doc-renderers/build-sequence.js +40 -0
- package/src/view/doc-renderers/fbs.js +128 -0
- package/src/view/doc-renderers/helpers.js +159 -0
- package/src/view/doc-renderers/index.js +12 -0
- package/src/view/doc-renderers/prd.js +45 -0
- package/src/view/doc-renderers/req.js +43 -0
- package/src/view/doc-renderers/tac.js +38 -0
- package/src/view/doc-renderers/tad.js +74 -0
- package/src/view/doc-renderers/test-suite.js +45 -0
- package/src/view/doc-renderers/user-story.js +63 -0
- package/src/view/html-page.js +462 -0
- package/src/view/index.js +63 -0
- package/src/view/live-client.js +338 -0
- package/src/view/mermaid-diagram.js +178 -0
- package/src/view/style.css +735 -0
- package/src/view/tree-model.js +152 -0
- package/src/view/vendored/mermaid.min.js +2607 -0
- package/src/view-supervisor/index.js +26 -0
- package/src/view-supervisor/logs.js +32 -0
- package/src/view-supervisor/manifest-writer.js +178 -0
- package/src/view-supervisor/persist-until.js +85 -0
- package/src/view-supervisor/supervisor.js +276 -0
- package/src/watch/index.js +152 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// `rcf-verify run` (spec §3 primary verb). Parses flags, runs the
|
|
2
|
+
// verification orchestrator, writes the report artifact (always, regardless of
|
|
3
|
+
// gate — §3 rule 5), and sets the process exit code from the severity gate
|
|
4
|
+
// (§8.2: exit 0 below the gate, non-zero at/above it, and always non-zero for
|
|
5
|
+
// NOT-DEPLOYED / BLOCKED). This is the exact call-path build-lite's finalise
|
|
6
|
+
// step spawns (§8.2).
|
|
7
|
+
|
|
8
|
+
import { writeFile } from 'node:fs/promises';
|
|
9
|
+
import { resolve as resolvePath } from 'node:path';
|
|
10
|
+
import { parseArgs } from 'node:util';
|
|
11
|
+
|
|
12
|
+
import { formatError, isRcfError } from '#core/errors';
|
|
13
|
+
|
|
14
|
+
import { runVerification } from '../engine/index.js';
|
|
15
|
+
import { serialiseReport } from '../report/index.js';
|
|
16
|
+
import { gateTripped, FINDING_SEVERITIES } from '../verdict/index.js';
|
|
17
|
+
|
|
18
|
+
const OPTION_SPEC = {
|
|
19
|
+
repo: { type: 'string' },
|
|
20
|
+
chain: { type: 'string' },
|
|
21
|
+
profile: { type: 'string' },
|
|
22
|
+
url: { type: 'string' },
|
|
23
|
+
'parity-env': { type: 'boolean' },
|
|
24
|
+
provision: { type: 'string' },
|
|
25
|
+
out: { type: 'string' },
|
|
26
|
+
'severity-gate': { type: 'string' },
|
|
27
|
+
'provision-mode': { type: 'string' },
|
|
28
|
+
persona: { type: 'string' },
|
|
29
|
+
help: { type: 'boolean' },
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const HELP = `Usage: rcf-verify run [options]
|
|
33
|
+
|
|
34
|
+
Launch a fresh-context adversarial verifier against a running app and emit a
|
|
35
|
+
structured verdict stamped with the runtime it ran against.
|
|
36
|
+
|
|
37
|
+
Required:
|
|
38
|
+
--repo <path-or-ref> RCF chain source (the acceptance contract)
|
|
39
|
+
--profile <p> Runtime profile: deployed | ci | local-dev
|
|
40
|
+
--url <app-url> The running app for this profile
|
|
41
|
+
--out <report-path> Where to write the structured report artifact
|
|
42
|
+
|
|
43
|
+
Optional:
|
|
44
|
+
--chain <PRD/ref> Which PRD/chain to verify against (default: the repo's)
|
|
45
|
+
--parity-env Assert this ci/local-dev runtime is edge-identical
|
|
46
|
+
to prod — the only path to a SHIP verdict from a
|
|
47
|
+
non-deployed profile. Logged in the report.
|
|
48
|
+
--provision <path> Provisioning spec / prerequisite credentials FILE
|
|
49
|
+
(never inline; secrets never echoed)
|
|
50
|
+
--severity-gate <sev> Exit non-zero at/above this severity:
|
|
51
|
+
PASS | COSMETIC | DEGRADED | BROKEN
|
|
52
|
+
--provision-mode <m> run | skip (default: run)
|
|
53
|
+
--persona <name> Adversarial persona flavour (default: generic-sceptic)
|
|
54
|
+
--help Print this help
|
|
55
|
+
|
|
56
|
+
Exit codes:
|
|
57
|
+
0 report written, verdict below the severity gate
|
|
58
|
+
1 IO / unexpected runtime failure (e.g. the report could not be written)
|
|
59
|
+
2 usage error (missing/invalid flags)
|
|
60
|
+
3 chain could not be loaded (the acceptance contract is unreadable)
|
|
61
|
+
5 severity gate tripped, or NOT-DEPLOYED / BLOCKED / LAUNCH-FAILURE
|
|
62
|
+
(ship cannot be confirmed — a report is still written)
|
|
63
|
+
`;
|
|
64
|
+
|
|
65
|
+
function exitForError(err) {
|
|
66
|
+
switch (err.kind) {
|
|
67
|
+
case 'usage': return 2;
|
|
68
|
+
case 'missingFile':
|
|
69
|
+
case 'parseFailure':
|
|
70
|
+
case 'validation': return 3;
|
|
71
|
+
default: return 1; // ioFailure + anything unexpected
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @param {string[]} argv - argv slice after `run`
|
|
77
|
+
* @param {object} [deps]
|
|
78
|
+
* @returns {Promise<number>}
|
|
79
|
+
*/
|
|
80
|
+
export async function main(argv, deps = {}) {
|
|
81
|
+
const stdout = deps.stdout ?? process.stdout;
|
|
82
|
+
const stderr = deps.stderr ?? process.stderr;
|
|
83
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
84
|
+
|
|
85
|
+
let parsed;
|
|
86
|
+
try {
|
|
87
|
+
parsed = parseArgs({ args: argv, options: OPTION_SPEC, allowPositionals: false, strict: true });
|
|
88
|
+
} catch (err) {
|
|
89
|
+
stderr.write(`[error] usage ${err.message}\n`);
|
|
90
|
+
stderr.write(HELP);
|
|
91
|
+
return 2;
|
|
92
|
+
}
|
|
93
|
+
const flags = parsed.values;
|
|
94
|
+
if (flags.help) {
|
|
95
|
+
stdout.write(HELP);
|
|
96
|
+
return 0;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const gate = flags['severity-gate'];
|
|
100
|
+
if (gate && !FINDING_SEVERITIES.includes(gate)) {
|
|
101
|
+
stderr.write(`[error] usage --severity-gate must be one of ${FINDING_SEVERITIES.join(' | ')}\n`);
|
|
102
|
+
return 2;
|
|
103
|
+
}
|
|
104
|
+
const provisionMode = flags['provision-mode'] ?? 'run';
|
|
105
|
+
if (provisionMode !== 'run' && provisionMode !== 'skip') {
|
|
106
|
+
stderr.write('[error] usage --provision-mode must be run | skip\n');
|
|
107
|
+
return 2;
|
|
108
|
+
}
|
|
109
|
+
if (!flags.out) {
|
|
110
|
+
stderr.write('[error] usage --out <report-path> is required\n');
|
|
111
|
+
return 2;
|
|
112
|
+
}
|
|
113
|
+
// Dash-footgun (§3 rule 3, mirrors build's --body-file discipline): a
|
|
114
|
+
// --provision value that looks like a flag is almost always a swallowed next
|
|
115
|
+
// option or an attempt to pass a credential inline. Refuse it — credentials
|
|
116
|
+
// are FILE-only, never inline, never echoed.
|
|
117
|
+
if (typeof flags.provision === 'string' && flags.provision.startsWith('-')) {
|
|
118
|
+
stderr.write('[error] usage --provision takes a file path, not a flag or inline value (credentials are never accepted inline)\n');
|
|
119
|
+
return 2;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const result = await runVerification({
|
|
123
|
+
repo: flags.repo,
|
|
124
|
+
chainRef: flags.chain,
|
|
125
|
+
profile: flags.profile,
|
|
126
|
+
url: flags.url,
|
|
127
|
+
parityEnv: Boolean(flags['parity-env']),
|
|
128
|
+
provision: flags.provision,
|
|
129
|
+
provisionMode,
|
|
130
|
+
persona: flags.persona,
|
|
131
|
+
severityGate: gate,
|
|
132
|
+
}, deps);
|
|
133
|
+
|
|
134
|
+
if (isRcfError(result)) {
|
|
135
|
+
stderr.write(`${formatError(result, { verbose: true })}\n`);
|
|
136
|
+
return exitForError(result);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const { report } = result;
|
|
140
|
+
|
|
141
|
+
// The report is written ALWAYS, regardless of the gate (§3 rule 5).
|
|
142
|
+
const outPath = resolvePath(cwd, flags.out);
|
|
143
|
+
const writer = deps.writeFile ?? writeFile;
|
|
144
|
+
try {
|
|
145
|
+
await writer(outPath, serialiseReport(report), 'utf8');
|
|
146
|
+
} catch (err) {
|
|
147
|
+
stderr.write(`[rcf-verify] unexpected failure: could not write report: ${err.message}\n`);
|
|
148
|
+
return 1;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
stderr.write(`[rcf-verify] verdict ${report.verdict} [${report.verdictAuthority}] -> ${flags.out}\n`);
|
|
152
|
+
|
|
153
|
+
// Exit code is the gate (§8.2). NOT-DEPLOYED / BLOCKED always trip.
|
|
154
|
+
return gateTripped({ verdict: report.verdict, findings: report.findings, gate }) ? 5 : 0;
|
|
155
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Adversarial brief composition (spec §5, §9 guarantee 4). Verify is thin: it
|
|
2
|
+
// reads the chain and composes an adversarial brief from the ACs, then hands
|
|
3
|
+
// that brief to a fresh isolated agent (engine/launcher.js). The stance is
|
|
4
|
+
// DISPROOF — an adversarial walk derived independently from the ACs does not
|
|
5
|
+
// inherit the build's framing. The brief NEVER references the source tree, the
|
|
6
|
+
// build transcript, or a "this was verified" claim (§9 guarantee 1-2).
|
|
7
|
+
|
|
8
|
+
/** Default adversarial persona flavour (spec §3 --persona default). */
|
|
9
|
+
export const DEFAULT_PERSONA = 'generic-sceptic';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Compose the adversarial brief the verifier agent is launched with. The brief
|
|
13
|
+
* is built purely from the acceptance contract (ACs) + the live URL — the only
|
|
14
|
+
* two inputs (§9 guarantee 2). Returned as structured data so tests can assert
|
|
15
|
+
* the ACs drove it and no build context leaked in.
|
|
16
|
+
*
|
|
17
|
+
* @param {object} opts
|
|
18
|
+
* @param {Array<object>} opts.acs - flattened ACs from the chain
|
|
19
|
+
* @param {string} opts.url - the running app under test
|
|
20
|
+
* @param {string} [opts.persona]
|
|
21
|
+
* @param {string} [opts.chainRef]
|
|
22
|
+
* @returns {{ persona: string, url: string, chainRef: string, stance: string, acCount: number, journeys: object[], instructions: string }}
|
|
23
|
+
*/
|
|
24
|
+
export function composeBrief({ acs = [], url, persona = DEFAULT_PERSONA, chainRef } = {}) {
|
|
25
|
+
const testable = acs.filter((ac) => ac.testable !== false);
|
|
26
|
+
const journeys = testable.map((ac) => {
|
|
27
|
+
// Base disprove line — the AC-only framing verify has always shipped.
|
|
28
|
+
const parts = [
|
|
29
|
+
`Attempt to make the app FAIL "${ac.then}" starting from "${ac.given}" by doing "${ac.when}", and adversarial variations of it.`,
|
|
30
|
+
];
|
|
31
|
+
// 0.7.0: service-attestation suffix (verification-integrity-cluster-spec
|
|
32
|
+
// §5.2). Prompt-only; the chain records the attestation and verify
|
|
33
|
+
// asks the agent to hunt for a delivery observable on the live URL.
|
|
34
|
+
// No source-tree reading — the observable is a browser-reachable
|
|
35
|
+
// artefact (admin log endpoint, receipt confirmation, delivery-log
|
|
36
|
+
// page) or its absence is the honest verdict.
|
|
37
|
+
const attestations = Array.isArray(ac.serviceAttestations) ? ac.serviceAttestations : [];
|
|
38
|
+
for (const a of attestations) {
|
|
39
|
+
if (!a || typeof a.serviceId !== 'string' || typeof a.attestationMode !== 'string') continue;
|
|
40
|
+
parts.push(
|
|
41
|
+
`If the AC depends on service \`${a.serviceId}\` attested \`${a.attestationMode}\`, verify a delivery observable exists on the running URL (e.g. an admin-log endpoint the browser can poll).`,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
// 0.7.0: UI-invariant suffix (ui-design-gate-0.7.0-spec §8.7). Fires
|
|
45
|
+
// when the AC belongs to a UI-bearing FBS. Disproof-oriented; no
|
|
46
|
+
// source-tree read; the check runs against the served HTML.
|
|
47
|
+
if (ac.fbsUiBearing === true) {
|
|
48
|
+
parts.push(
|
|
49
|
+
'The FBS is UI-bearing; a shared nav bar must be present on every enumerated authenticated route, the theme toggle must be reachable, and the signed-in-as affordance must be visible when authenticated. Disprove any of these by observation.',
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
acId: ac.acId,
|
|
54
|
+
usId: ac.usId,
|
|
55
|
+
journey: ac.title || ac.usId || ac.acId,
|
|
56
|
+
given: ac.given,
|
|
57
|
+
when: ac.when,
|
|
58
|
+
then: ac.then,
|
|
59
|
+
// The disproof prompt for THIS criterion: try to make `then` false,
|
|
60
|
+
// plus any 0.7.0 attestation / UI suffixes the chain contributes.
|
|
61
|
+
disprove: parts.join(' '),
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const instructions = [
|
|
66
|
+
'You are an adversarial verifier. Your job is to DISPROVE the application against its acceptance criteria, not to confirm it works.',
|
|
67
|
+
'You have NOT seen how this app was built, its source, or any claim that it was verified. Judge only the running app against the contract below.',
|
|
68
|
+
`Drive the running app at ${url} through each journey using your browser tooling. For each acceptance criterion, actively try to break it: edge inputs, boundary conditions, isolation between users, error paths, and the exact security/quality floors the criterion promises.`,
|
|
69
|
+
'For every defect, record: the acId it maps to, the journey, exact reproduction steps against the live URL, and evidence (screenshot path, response body, or runtime error).',
|
|
70
|
+
'Classify each finding: BROKEN (a journey is dead or wrong), DEGRADED (works but a criterion is materially weakened / a false promise / a missed floor), or COSMETIC (hygiene, no AC touched). Report PASS only for criteria you actively tried and could not break.',
|
|
71
|
+
'Do NOT claim the app is "fully verified" or "safe"; you are producing an independent ship-readiness signal, not a correctness guarantee.',
|
|
72
|
+
'',
|
|
73
|
+
'OUTPUT CONTRACT (mandatory). After any analysis, the FINAL thing you emit must be a single JSON object of exactly this shape, and nothing after it:',
|
|
74
|
+
'{"findings":[{"severity":"BROKEN|DEGRADED|COSMETIC|PASS","acId":"<AC id>","journey":"<journey name>","reproSteps":["..."],"evidence":{"kind":"<http_response|runtimeError|screenshot|note>","detail":"..."}}]}',
|
|
75
|
+
'Emit exactly one finding per acceptance criterion (a PASS finding for any you tried and could not break). The object must be valid JSON on its own; the harness extracts the last {"findings":[...]} object from your reply, so make sure your final object is complete and well-formed.',
|
|
76
|
+
].join('\n');
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
persona,
|
|
80
|
+
url,
|
|
81
|
+
chainRef: chainRef ?? 'PRD-UNKNOWN',
|
|
82
|
+
stance: 'disprove',
|
|
83
|
+
acCount: testable.length,
|
|
84
|
+
journeys,
|
|
85
|
+
instructions,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// Verification orchestrator (spec §5, §8.2). Ties the pieces together in the
|
|
2
|
+
// exact order build-lite's finalise step would drive them: resolve the runtime
|
|
3
|
+
// profile → read the acceptance contract off the chain → (deployed) gate on
|
|
4
|
+
// reachability → provision prerequisites → compose the adversarial brief →
|
|
5
|
+
// launch the isolated verifier agent → validate/aggregate/stamp the verdict →
|
|
6
|
+
// build the ingestible report. Every failure is returned as data (RcfError),
|
|
7
|
+
// never a fabricated PASS (§9 false-confidence prohibition).
|
|
8
|
+
|
|
9
|
+
import { rcfError, isRcfError } from '#core/errors';
|
|
10
|
+
import { isolationProvenance } from '#core/isolation';
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
resolveProfile,
|
|
14
|
+
checkDeployedReachability,
|
|
15
|
+
isNotDeployed,
|
|
16
|
+
verdictAuthorityFor,
|
|
17
|
+
} from '../profile/index.js';
|
|
18
|
+
import { readChain as defaultReadChain } from '../chain/index.js';
|
|
19
|
+
import { runProvisioning, cleanup as defaultCleanup } from '../provision/index.js';
|
|
20
|
+
import { composeBrief } from './brief.js';
|
|
21
|
+
import { resolveLauncher } from './launcher.js';
|
|
22
|
+
import { aggregateVerdict, derivePerAcVerdicts, validateFinding } from '../verdict/index.js';
|
|
23
|
+
import { buildReport } from '../report/index.js';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Validate + normalise the raw findings an agent returned. A malformed finding
|
|
27
|
+
* is an error-as-data, not a silent drop — the report contract (§5.2) requires
|
|
28
|
+
* every finding to be chain-node-addressed.
|
|
29
|
+
*
|
|
30
|
+
* @param {unknown} rawFindings
|
|
31
|
+
* @returns {{ findings: object[] } | import('#core/errors').RcfError}
|
|
32
|
+
*/
|
|
33
|
+
export function normaliseFindings(rawFindings) {
|
|
34
|
+
if (!Array.isArray(rawFindings)) {
|
|
35
|
+
return rcfError({ kind: 'validation', message: 'verifier agent must return a findings array' });
|
|
36
|
+
}
|
|
37
|
+
const findings = [];
|
|
38
|
+
for (const raw of rawFindings) {
|
|
39
|
+
const err = validateFinding(raw);
|
|
40
|
+
if (err) return err;
|
|
41
|
+
findings.push({
|
|
42
|
+
severity: raw.severity,
|
|
43
|
+
acId: raw.acId,
|
|
44
|
+
journey: raw.journey,
|
|
45
|
+
reproSteps: raw.reproSteps,
|
|
46
|
+
evidence: raw.evidence,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
return { findings };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Run a full verification. Returns `{ report }` on completion (including the
|
|
54
|
+
* NOT-DEPLOYED / BLOCKED refusals, which are legitimate reports), or an
|
|
55
|
+
* RcfError for usage / chain-load / agent failures the CLI maps to a non-zero
|
|
56
|
+
* exit.
|
|
57
|
+
*
|
|
58
|
+
* @param {object} opts - the CLI-parsed run options
|
|
59
|
+
* @param {object} [deps] - injectable seams (launchAgent, fetchImpl, signup, teardown, now, readChain)
|
|
60
|
+
* @returns {Promise<{ report: object } | import('#core/errors').RcfError>}
|
|
61
|
+
*/
|
|
62
|
+
export async function runVerification(opts = {}, deps = {}) {
|
|
63
|
+
const now = deps.now ?? (() => new Date().toISOString());
|
|
64
|
+
const readChain = deps.readChain ?? defaultReadChain;
|
|
65
|
+
const startedAt = now();
|
|
66
|
+
|
|
67
|
+
// 1. Resolve + validate the runtime declaration (§4).
|
|
68
|
+
const resolved = resolveProfile({ profile: opts.profile, url: opts.url, parityEnv: opts.parityEnv });
|
|
69
|
+
if (isRcfError(resolved)) return resolved;
|
|
70
|
+
const { profile, url, parityEnv } = resolved;
|
|
71
|
+
|
|
72
|
+
// 2. Read the acceptance contract off the chain (the ONLY structural input, §9).
|
|
73
|
+
const chain = await readChain({ repo: opts.repo, chainRef: opts.chainRef });
|
|
74
|
+
if (isRcfError(chain)) return chain;
|
|
75
|
+
|
|
76
|
+
const verdictAuthority = verdictAuthorityFor(profile, parityEnv);
|
|
77
|
+
|
|
78
|
+
// Per-AC verdicts (0.7.0). Derived from chain data — service attestations
|
|
79
|
+
// (Track A §5.2) and UI-bearing FBS × browserVerification[] (Track B §8.7)
|
|
80
|
+
// — before any structural refusal so the report always carries the
|
|
81
|
+
// honest chain-derived picture, not just the run-time findings. The
|
|
82
|
+
// finalise gate (build/src/finalise/ingest.js) reads this array and
|
|
83
|
+
// refuses `verified` on any MOCK-ONLY-DECLARED / BLOCKED-BY-DECLARATION
|
|
84
|
+
// entry regardless of the run-level verdict.
|
|
85
|
+
const perAcVerdicts = derivePerAcVerdicts({
|
|
86
|
+
acs: chain.acs,
|
|
87
|
+
browserVerification: chain.manifest?.browserVerification ?? [],
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// 3. Deployed reachability gate (§4). Only consulted for profile==='deployed'.
|
|
91
|
+
let reachability = null;
|
|
92
|
+
if (profile === 'deployed') {
|
|
93
|
+
reachability = await checkDeployedReachability(url, { fetchImpl: deps.fetchImpl });
|
|
94
|
+
if (isNotDeployed(profile, reachability)) {
|
|
95
|
+
// Refusal to issue a verdict — never a soft pass (§4, §5.1).
|
|
96
|
+
const report = buildReport({
|
|
97
|
+
profile, url, parityEnv, reachability, chainRef: chain.chainRef, repo: opts.repo,
|
|
98
|
+
persona: opts.persona, startedAt, finishedAt: now(),
|
|
99
|
+
verifierIsolation: isolationProvenance(),
|
|
100
|
+
verdict: 'NOT-DEPLOYED', verdictAuthority,
|
|
101
|
+
findings: [], blockedAcs: [], provisioning: null,
|
|
102
|
+
perAcVerdicts,
|
|
103
|
+
});
|
|
104
|
+
return { report };
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 4. Provision prerequisites (§6). Unprovisionable → BLOCKED (named), dependent ACs BLOCKED.
|
|
109
|
+
const { provisioning, blockedAcs } = await runProvisioning({
|
|
110
|
+
acs: chain.acs,
|
|
111
|
+
url,
|
|
112
|
+
provisionPath: opts.provision,
|
|
113
|
+
mode: opts.provisionMode ?? 'run',
|
|
114
|
+
signup: deps.signup,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// 5. Compose the adversarial brief from the chain ACs (§5, §9 guarantee 4).
|
|
118
|
+
const brief = composeBrief({ acs: chain.acs, url, persona: opts.persona, chainRef: chain.chainRef });
|
|
119
|
+
|
|
120
|
+
// Cleanup runs whether or not the launch succeeds — provisioned artefacts
|
|
121
|
+
// must not be orphaned by a launch failure (§6 cleanup contract).
|
|
122
|
+
const runCleanup = async () => {
|
|
123
|
+
const cleanupFn = deps.cleanup ?? defaultCleanup;
|
|
124
|
+
const cleanupResult = await cleanupFn({ provisioned: provisioning.provisioned, teardown: deps.teardown });
|
|
125
|
+
provisioning.cleanupRan = cleanupResult.cleanupRan;
|
|
126
|
+
provisioning.cleanupRemoved = cleanupResult.cleanupRemoved;
|
|
127
|
+
if (cleanupResult.cleanupBlocked?.length) provisioning.cleanupBlocked = cleanupResult.cleanupBlocked;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// 6. Launch the isolated verifier agent (§7.3 isolation env, §9 fresh session).
|
|
131
|
+
let launchResult;
|
|
132
|
+
try {
|
|
133
|
+
const launchAgent = await resolveLauncher(deps);
|
|
134
|
+
launchResult = await launchAgent({ brief, url, profile });
|
|
135
|
+
} catch (err) {
|
|
136
|
+
// A verifier agent that could not run — or whose output could not be
|
|
137
|
+
// ingested — is NEVER a fabricated PASS (§9). But the report is still
|
|
138
|
+
// build-lite's next input (§5.4, §10 --out-always-written), so we build a
|
|
139
|
+
// LAUNCH-FAILURE report carrying the error + preserved-transcript path so
|
|
140
|
+
// the fix loop has something to ingest. Exit stays non-zero (gate trips).
|
|
141
|
+
await runCleanup();
|
|
142
|
+
const report = buildReport({
|
|
143
|
+
profile, url, parityEnv, reachability, chainRef: chain.chainRef, repo: opts.repo,
|
|
144
|
+
persona: opts.persona, startedAt, finishedAt: now(),
|
|
145
|
+
verifierIsolation: isolationProvenance(),
|
|
146
|
+
verdict: 'LAUNCH-FAILURE', verdictAuthority,
|
|
147
|
+
findings: [], blockedAcs, provisioning,
|
|
148
|
+
launchFailure: { message: err.message, rawOutputPath: err.rawOutputPath ?? null },
|
|
149
|
+
perAcVerdicts,
|
|
150
|
+
});
|
|
151
|
+
return { report };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// 7. Validate + stamp the findings (§5.2 chain-node addressing).
|
|
155
|
+
const normalised = normaliseFindings(launchResult?.findings);
|
|
156
|
+
if (isRcfError(normalised)) return normalised;
|
|
157
|
+
const { findings } = normalised;
|
|
158
|
+
|
|
159
|
+
// 8. Cleanup provisioned artefacts (§6 cleanup contract).
|
|
160
|
+
await runCleanup();
|
|
161
|
+
|
|
162
|
+
// 9. Aggregate the verdict — split, never averaged (§5.1).
|
|
163
|
+
const verdict = aggregateVerdict({ findings, blockedAcs, notDeployed: false });
|
|
164
|
+
|
|
165
|
+
// 10. Build the ingestible report (§5.3).
|
|
166
|
+
const report = buildReport({
|
|
167
|
+
profile, url, parityEnv, reachability, chainRef: chain.chainRef, repo: opts.repo,
|
|
168
|
+
persona: opts.persona, startedAt, finishedAt: now(),
|
|
169
|
+
verifierIsolation: isolationProvenance(),
|
|
170
|
+
verdict, verdictAuthority,
|
|
171
|
+
findings, blockedAcs, provisioning,
|
|
172
|
+
runStats: launchResult?.runStats ?? null,
|
|
173
|
+
perAcVerdicts,
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
return { report };
|
|
177
|
+
}
|