@veraxhq/verax 0.2.1 → 0.4.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/README.md +10 -6
- package/bin/verax.js +11 -11
- package/package.json +29 -8
- package/src/cli/commands/baseline.js +103 -0
- package/src/cli/commands/default.js +51 -6
- package/src/cli/commands/doctor.js +29 -0
- package/src/cli/commands/ga.js +246 -0
- package/src/cli/commands/gates.js +95 -0
- package/src/cli/commands/inspect.js +4 -2
- package/src/cli/commands/release-check.js +215 -0
- package/src/cli/commands/run.js +45 -6
- package/src/cli/commands/security-check.js +212 -0
- package/src/cli/commands/truth.js +113 -0
- package/src/cli/entry.js +30 -20
- package/src/cli/util/angular-component-extractor.js +179 -0
- package/src/cli/util/angular-navigation-detector.js +141 -0
- package/src/cli/util/angular-network-detector.js +161 -0
- package/src/cli/util/angular-state-detector.js +162 -0
- package/src/cli/util/ast-interactive-detector.js +544 -0
- package/src/cli/util/ast-network-detector.js +603 -0
- package/src/cli/util/ast-promise-extractor.js +581 -0
- package/src/cli/util/ast-usestate-detector.js +602 -0
- package/src/cli/util/atomic-write.js +12 -1
- package/src/cli/util/bootstrap-guard.js +86 -0
- package/src/cli/util/console-reporter.js +72 -0
- package/src/cli/util/detection-engine.js +105 -41
- package/src/cli/util/determinism-runner.js +124 -0
- package/src/cli/util/determinism-writer.js +129 -0
- package/src/cli/util/digest-engine.js +359 -0
- package/src/cli/util/dom-diff.js +226 -0
- package/src/cli/util/evidence-engine.js +287 -0
- package/src/cli/util/expectation-extractor.js +151 -5
- package/src/cli/util/findings-writer.js +3 -0
- package/src/cli/util/framework-detector.js +572 -0
- package/src/cli/util/idgen.js +1 -1
- package/src/cli/util/interaction-planner.js +529 -0
- package/src/cli/util/learn-writer.js +2 -0
- package/src/cli/util/ledger-writer.js +110 -0
- package/src/cli/util/monorepo-resolver.js +162 -0
- package/src/cli/util/observation-engine.js +127 -278
- package/src/cli/util/observe-writer.js +2 -0
- package/src/cli/util/project-discovery.js +284 -0
- package/src/cli/util/project-writer.js +2 -0
- package/src/cli/util/run-id.js +23 -27
- package/src/cli/util/run-resolver.js +64 -0
- package/src/cli/util/run-result.js +778 -0
- package/src/cli/util/selector-resolver.js +235 -0
- package/src/cli/util/source-requirement.js +55 -0
- package/src/cli/util/summary-writer.js +2 -0
- package/src/cli/util/svelte-navigation-detector.js +163 -0
- package/src/cli/util/svelte-network-detector.js +80 -0
- package/src/cli/util/svelte-sfc-extractor.js +146 -0
- package/src/cli/util/svelte-state-detector.js +242 -0
- package/src/cli/util/trust-activation-integration.js +496 -0
- package/src/cli/util/trust-activation-wrapper.js +85 -0
- package/src/cli/util/trust-integration-hooks.js +164 -0
- package/src/cli/util/types.js +153 -0
- package/src/cli/util/url-validation.js +40 -0
- package/src/cli/util/vue-navigation-detector.js +178 -0
- package/src/cli/util/vue-sfc-extractor.js +161 -0
- package/src/cli/util/vue-state-detector.js +215 -0
- package/src/types/fs-augment.d.ts +23 -0
- package/src/types/global.d.ts +137 -0
- package/src/types/internal-types.d.ts +35 -0
- package/src/verax/cli/init.js +4 -18
- package/src/verax/core/action-classifier.js +4 -3
- package/src/verax/core/artifacts/registry.js +139 -0
- package/src/verax/core/artifacts/verifier.js +990 -0
- package/src/verax/core/baseline/baseline.enforcer.js +137 -0
- package/src/verax/core/baseline/baseline.snapshot.js +233 -0
- package/src/verax/core/capabilities/gates.js +505 -0
- package/src/verax/core/capabilities/registry.js +475 -0
- package/src/verax/core/confidence/confidence-compute.js +144 -0
- package/src/verax/core/confidence/confidence-invariants.js +234 -0
- package/src/verax/core/confidence/confidence-report-writer.js +112 -0
- package/src/verax/core/confidence/confidence-weights.js +44 -0
- package/src/verax/core/confidence/confidence.defaults.js +65 -0
- package/src/verax/core/confidence/confidence.loader.js +80 -0
- package/src/verax/core/confidence/confidence.schema.js +94 -0
- package/src/verax/core/confidence-engine-refactor.js +489 -0
- package/src/verax/core/confidence-engine.js +625 -0
- package/src/verax/core/contracts/index.js +29 -0
- package/src/verax/core/contracts/types.js +186 -0
- package/src/verax/core/contracts/validators.js +456 -0
- package/src/verax/core/decisions/decision.trace.js +278 -0
- package/src/verax/core/determinism/contract-writer.js +89 -0
- package/src/verax/core/determinism/contract.js +139 -0
- package/src/verax/core/determinism/diff.js +405 -0
- package/src/verax/core/determinism/engine.js +222 -0
- package/src/verax/core/determinism/finding-identity.js +149 -0
- package/src/verax/core/determinism/normalize.js +466 -0
- package/src/verax/core/determinism/report-writer.js +93 -0
- package/src/verax/core/determinism/run-fingerprint.js +123 -0
- package/src/verax/core/dynamic-route-intelligence.js +529 -0
- package/src/verax/core/evidence/evidence-capture-service.js +308 -0
- package/src/verax/core/evidence/evidence-intent-ledger.js +166 -0
- package/src/verax/core/evidence-builder.js +487 -0
- package/src/verax/core/execution-mode-context.js +77 -0
- package/src/verax/core/execution-mode-detector.js +192 -0
- package/src/verax/core/failures/exit-codes.js +88 -0
- package/src/verax/core/failures/failure-summary.js +76 -0
- package/src/verax/core/failures/failure.factory.js +225 -0
- package/src/verax/core/failures/failure.ledger.js +133 -0
- package/src/verax/core/failures/failure.types.js +196 -0
- package/src/verax/core/failures/index.js +10 -0
- package/src/verax/core/ga/ga-report-writer.js +43 -0
- package/src/verax/core/ga/ga.artifact.js +49 -0
- package/src/verax/core/ga/ga.contract.js +435 -0
- package/src/verax/core/ga/ga.enforcer.js +87 -0
- package/src/verax/core/guardrails/guardrails-report-writer.js +109 -0
- package/src/verax/core/guardrails/policy.defaults.js +210 -0
- package/src/verax/core/guardrails/policy.loader.js +84 -0
- package/src/verax/core/guardrails/policy.schema.js +110 -0
- package/src/verax/core/guardrails/truth-reconciliation.js +136 -0
- package/src/verax/core/guardrails-engine.js +505 -0
- package/src/verax/core/incremental-store.js +1 -0
- package/src/verax/core/integrity/budget.js +138 -0
- package/src/verax/core/integrity/determinism.js +342 -0
- package/src/verax/core/integrity/integrity.js +208 -0
- package/src/verax/core/integrity/poisoning.js +108 -0
- package/src/verax/core/integrity/transaction.js +140 -0
- package/src/verax/core/observe/run-timeline.js +318 -0
- package/src/verax/core/perf/perf.contract.js +186 -0
- package/src/verax/core/perf/perf.display.js +65 -0
- package/src/verax/core/perf/perf.enforcer.js +91 -0
- package/src/verax/core/perf/perf.monitor.js +209 -0
- package/src/verax/core/perf/perf.report.js +200 -0
- package/src/verax/core/pipeline-tracker.js +243 -0
- package/src/verax/core/product-definition.js +127 -0
- package/src/verax/core/release/provenance.builder.js +130 -0
- package/src/verax/core/release/release-report-writer.js +40 -0
- package/src/verax/core/release/release.enforcer.js +164 -0
- package/src/verax/core/release/reproducibility.check.js +222 -0
- package/src/verax/core/release/sbom.builder.js +292 -0
- package/src/verax/core/replay-validator.js +2 -0
- package/src/verax/core/replay.js +4 -0
- package/src/verax/core/report/cross-index.js +195 -0
- package/src/verax/core/report/human-summary.js +362 -0
- package/src/verax/core/route-intelligence.js +420 -0
- package/src/verax/core/run-id.js +6 -3
- package/src/verax/core/run-manifest.js +4 -3
- package/src/verax/core/security/secrets.scan.js +329 -0
- package/src/verax/core/security/security-report.js +50 -0
- package/src/verax/core/security/security.enforcer.js +128 -0
- package/src/verax/core/security/supplychain.defaults.json +38 -0
- package/src/verax/core/security/supplychain.policy.js +334 -0
- package/src/verax/core/security/vuln.scan.js +265 -0
- package/src/verax/core/truth/truth.certificate.js +252 -0
- package/src/verax/core/ui-feedback-intelligence.js +481 -0
- package/src/verax/detect/conditional-ui-silent-failure.js +84 -0
- package/src/verax/detect/confidence-engine.js +62 -34
- package/src/verax/detect/confidence-helper.js +34 -0
- package/src/verax/detect/dynamic-route-findings.js +338 -0
- package/src/verax/detect/expectation-chain-detector.js +417 -0
- package/src/verax/detect/expectation-model.js +2 -2
- package/src/verax/detect/failure-cause-inference.js +293 -0
- package/src/verax/detect/findings-writer.js +131 -35
- package/src/verax/detect/flow-detector.js +2 -2
- package/src/verax/detect/form-silent-failure.js +98 -0
- package/src/verax/detect/index.js +46 -5
- package/src/verax/detect/invariants-enforcer.js +147 -0
- package/src/verax/detect/journey-stall-detector.js +558 -0
- package/src/verax/detect/navigation-silent-failure.js +82 -0
- package/src/verax/detect/problem-aggregator.js +361 -0
- package/src/verax/detect/route-findings.js +219 -0
- package/src/verax/detect/summary-writer.js +477 -0
- package/src/verax/detect/test-failure-cause-inference.js +314 -0
- package/src/verax/detect/ui-feedback-findings.js +207 -0
- package/src/verax/detect/view-switch-correlator.js +242 -0
- package/src/verax/flow/flow-engine.js +2 -1
- package/src/verax/flow/flow-spec.js +0 -6
- package/src/verax/index.js +4 -0
- package/src/verax/intel/ts-program.js +1 -0
- package/src/verax/intel/vue-navigation-extractor.js +3 -0
- package/src/verax/learn/action-contract-extractor.js +3 -0
- package/src/verax/learn/ast-contract-extractor.js +1 -1
- package/src/verax/learn/flow-extractor.js +1 -0
- package/src/verax/learn/project-detector.js +5 -0
- package/src/verax/learn/react-router-extractor.js +2 -0
- package/src/verax/learn/source-instrumenter.js +1 -0
- package/src/verax/learn/state-extractor.js +2 -1
- package/src/verax/learn/static-extractor.js +1 -0
- package/src/verax/observe/coverage-gaps.js +132 -0
- package/src/verax/observe/expectation-handler.js +126 -0
- package/src/verax/observe/incremental-skip.js +46 -0
- package/src/verax/observe/index.js +51 -155
- package/src/verax/observe/interaction-executor.js +192 -0
- package/src/verax/observe/interaction-runner.js +782 -513
- package/src/verax/observe/network-firewall.js +86 -0
- package/src/verax/observe/observation-builder.js +169 -0
- package/src/verax/observe/observe-context.js +205 -0
- package/src/verax/observe/observe-helpers.js +192 -0
- package/src/verax/observe/observe-runner.js +230 -0
- package/src/verax/observe/observers/budget-observer.js +185 -0
- package/src/verax/observe/observers/console-observer.js +102 -0
- package/src/verax/observe/observers/coverage-observer.js +107 -0
- package/src/verax/observe/observers/interaction-observer.js +471 -0
- package/src/verax/observe/observers/navigation-observer.js +132 -0
- package/src/verax/observe/observers/network-observer.js +87 -0
- package/src/verax/observe/observers/safety-observer.js +82 -0
- package/src/verax/observe/observers/ui-feedback-observer.js +99 -0
- package/src/verax/observe/page-traversal.js +138 -0
- package/src/verax/observe/snapshot-ops.js +94 -0
- package/src/verax/observe/ui-feedback-detector.js +742 -0
- package/src/verax/scan-summary-writer.js +2 -0
- package/src/verax/shared/artifact-manager.js +25 -5
- package/src/verax/shared/caching.js +1 -0
- package/src/verax/shared/css-spinner-rules.js +204 -0
- package/src/verax/shared/expectation-tracker.js +1 -0
- package/src/verax/shared/view-switch-rules.js +208 -0
- package/src/verax/shared/zip-artifacts.js +6 -0
- package/src/verax/shared/config-loader.js +0 -169
- /package/src/verax/shared/{expectation-proof.js → expectation-validation.js} +0 -0
package/src/verax/core/replay.js
CHANGED
|
@@ -32,6 +32,7 @@ export function loadRunArtifacts(runDir) {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
try {
|
|
35
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
35
36
|
artifacts.runManifest = JSON.parse(readFileSync(runManifestPath, 'utf-8'));
|
|
36
37
|
} catch (error) {
|
|
37
38
|
throw new Error(`Failed to parse run manifest: ${error.message}`);
|
|
@@ -44,6 +45,7 @@ export function loadRunArtifacts(runDir) {
|
|
|
44
45
|
const tracesPath = join(runDir, 'traces.json');
|
|
45
46
|
if (existsSync(tracesPath)) {
|
|
46
47
|
try {
|
|
48
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
47
49
|
artifacts.traces = JSON.parse(readFileSync(tracesPath, 'utf-8'));
|
|
48
50
|
|
|
49
51
|
// Verify hash
|
|
@@ -80,6 +82,7 @@ export function loadRunArtifacts(runDir) {
|
|
|
80
82
|
const findingsPath = join(runDir, 'findings.json');
|
|
81
83
|
if (existsSync(findingsPath)) {
|
|
82
84
|
try {
|
|
85
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
83
86
|
artifacts.findings = JSON.parse(readFileSync(findingsPath, 'utf-8'));
|
|
84
87
|
|
|
85
88
|
// Verify hash
|
|
@@ -109,6 +112,7 @@ export function loadRunArtifacts(runDir) {
|
|
|
109
112
|
const manifestPath = join(runDir, 'manifest.json');
|
|
110
113
|
if (existsSync(manifestPath)) {
|
|
111
114
|
try {
|
|
115
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
112
116
|
artifacts.manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
|
|
113
117
|
|
|
114
118
|
// Verify hash
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 21.10 — Artifact Cross-Index
|
|
3
|
+
*
|
|
4
|
+
* Builds cross-index linking findingId to all related artifacts.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { readFileSync, existsSync, writeFileSync, readdirSync as _readdirSync } from 'fs';
|
|
8
|
+
import { resolve, relative as _relative } from 'path';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Build cross-index
|
|
12
|
+
*
|
|
13
|
+
* @param {string} projectDir - Project directory
|
|
14
|
+
* @param {string} runId - Run ID
|
|
15
|
+
* @returns {Object} Cross-index
|
|
16
|
+
*/
|
|
17
|
+
export function buildCrossIndex(projectDir, runId) {
|
|
18
|
+
const runDir = resolve(projectDir, '.verax', 'runs', runId);
|
|
19
|
+
|
|
20
|
+
if (!existsSync(runDir)) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const index = {};
|
|
25
|
+
|
|
26
|
+
// Load findings
|
|
27
|
+
const findingsPath = resolve(runDir, 'findings.json');
|
|
28
|
+
if (!existsSync(findingsPath)) {
|
|
29
|
+
return {
|
|
30
|
+
runId,
|
|
31
|
+
findings: {},
|
|
32
|
+
summary: { total: 0 },
|
|
33
|
+
generatedAt: new Date().toISOString()
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
38
|
+
const findings = JSON.parse(readFileSync(findingsPath, 'utf-8'));
|
|
39
|
+
const _evidenceIndex = loadArtifact(runDir, 'evidence.index.json');
|
|
40
|
+
const decisionTrace = loadArtifact(runDir, 'decisions.trace.json');
|
|
41
|
+
const timeline = loadArtifact(runDir, 'run.timeline.json');
|
|
42
|
+
const failureLedger = loadArtifact(runDir, 'failure.ledger.json');
|
|
43
|
+
const performanceReport = loadArtifact(runDir, 'performance.report.json');
|
|
44
|
+
|
|
45
|
+
if (!Array.isArray(findings.findings)) {
|
|
46
|
+
return {
|
|
47
|
+
runId,
|
|
48
|
+
findings: {},
|
|
49
|
+
summary: { total: 0 },
|
|
50
|
+
generatedAt: new Date().toISOString()
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
for (const finding of findings.findings) {
|
|
55
|
+
const findingId = finding.findingId || finding.id || `finding-${Object.keys(index).length}`;
|
|
56
|
+
|
|
57
|
+
const entry = {
|
|
58
|
+
findingId,
|
|
59
|
+
type: finding.type || null,
|
|
60
|
+
status: finding.severity || finding.status || null,
|
|
61
|
+
|
|
62
|
+
// Evidence files
|
|
63
|
+
evidence: {
|
|
64
|
+
packageId: finding.evidencePackage?.id || null,
|
|
65
|
+
files: finding.evidencePackage?.files || [],
|
|
66
|
+
isComplete: finding.evidencePackage?.isComplete || false,
|
|
67
|
+
beforeScreenshot: finding.evidence?.before || null,
|
|
68
|
+
afterScreenshot: finding.evidence?.after || null
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
// Confidence reasons
|
|
72
|
+
confidence: {
|
|
73
|
+
level: finding.confidenceLevel || null,
|
|
74
|
+
score: finding.confidence !== undefined ? finding.confidence : null,
|
|
75
|
+
reasons: finding.confidenceReasons || [],
|
|
76
|
+
trace: decisionTrace?.findings?.find(t => t.findingId === findingId)?.confidence || null
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
// Guardrails rules
|
|
80
|
+
guardrails: {
|
|
81
|
+
applied: finding.guardrails?.appliedRules?.map(r => ({
|
|
82
|
+
id: r.id || r,
|
|
83
|
+
category: r.category || null,
|
|
84
|
+
action: r.action || null
|
|
85
|
+
})) || [],
|
|
86
|
+
finalDecision: finding.guardrails?.finalDecision || null,
|
|
87
|
+
contradictions: finding.guardrails?.contradictions || [],
|
|
88
|
+
trace: decisionTrace?.findings?.find(t => t.findingId === findingId)?.guardrails || null
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
// Failures (if any related)
|
|
92
|
+
failures: failureLedger?.failures?.filter(f =>
|
|
93
|
+
f.context?.findingId === findingId ||
|
|
94
|
+
f.message?.includes(findingId)
|
|
95
|
+
).map(f => ({
|
|
96
|
+
code: f.code,
|
|
97
|
+
message: f.message,
|
|
98
|
+
severity: f.severity,
|
|
99
|
+
timestamp: f.timestamp
|
|
100
|
+
})) || [],
|
|
101
|
+
|
|
102
|
+
// Performance impacts (if any)
|
|
103
|
+
performance: performanceReport?.violations?.some(v =>
|
|
104
|
+
v.message?.includes(findingId)
|
|
105
|
+
) ? {
|
|
106
|
+
impacted: true,
|
|
107
|
+
violations: performanceReport.violations.filter(v =>
|
|
108
|
+
v.message?.includes(findingId)
|
|
109
|
+
)
|
|
110
|
+
} : null,
|
|
111
|
+
|
|
112
|
+
// Timeline entries
|
|
113
|
+
timeline: timeline?.events?.filter(e =>
|
|
114
|
+
e.data?.findingId === findingId ||
|
|
115
|
+
(e.event === 'guardrails_applied' && e.data?.findingId === findingId) ||
|
|
116
|
+
(e.event === 'evidence_enforced' && e.data?.findingId === findingId)
|
|
117
|
+
).map(e => ({
|
|
118
|
+
timestamp: e.timestamp,
|
|
119
|
+
phase: e.phase,
|
|
120
|
+
event: e.event,
|
|
121
|
+
data: e.data
|
|
122
|
+
})) || []
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
index[findingId] = entry;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
runId,
|
|
130
|
+
findings: index,
|
|
131
|
+
summary: {
|
|
132
|
+
total: Object.keys(index).length,
|
|
133
|
+
withEvidence: Object.values(index).filter(e => e.evidence.files.length > 0).length,
|
|
134
|
+
withGuardrails: Object.values(index).filter(e => e.guardrails.applied.length > 0).length,
|
|
135
|
+
withFailures: Object.values(index).filter(e => e.failures.length > 0).length,
|
|
136
|
+
withTimeline: Object.values(index).filter(e => e.timeline.length > 0).length
|
|
137
|
+
},
|
|
138
|
+
generatedAt: new Date().toISOString()
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Load artifact JSON
|
|
144
|
+
*/
|
|
145
|
+
function loadArtifact(runDir, filename) {
|
|
146
|
+
const path = resolve(runDir, filename);
|
|
147
|
+
if (!existsSync(path)) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
152
|
+
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
153
|
+
} catch {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Write cross-index to file
|
|
160
|
+
*
|
|
161
|
+
* @param {string} projectDir - Project directory
|
|
162
|
+
* @param {string} runId - Run ID
|
|
163
|
+
* @param {Object} index - Cross-index
|
|
164
|
+
* @returns {string} Path to written file
|
|
165
|
+
*/
|
|
166
|
+
export function writeCrossIndex(projectDir, runId, index) {
|
|
167
|
+
const runDir = resolve(projectDir, '.verax', 'runs', runId);
|
|
168
|
+
const outputPath = resolve(runDir, 'artifacts.index.json');
|
|
169
|
+
writeFileSync(outputPath, JSON.stringify(index, null, 2), 'utf-8');
|
|
170
|
+
return outputPath;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Load cross-index from file
|
|
175
|
+
*
|
|
176
|
+
* @param {string} projectDir - Project directory
|
|
177
|
+
* @param {string} runId - Run ID
|
|
178
|
+
* @returns {Object|null} Cross-index or null
|
|
179
|
+
*/
|
|
180
|
+
export function loadCrossIndex(projectDir, runId) {
|
|
181
|
+
const runDir = resolve(projectDir, '.verax', 'runs', runId);
|
|
182
|
+
const indexPath = resolve(runDir, 'artifacts.index.json');
|
|
183
|
+
|
|
184
|
+
if (!existsSync(indexPath)) {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
190
|
+
return JSON.parse(readFileSync(indexPath, 'utf-8'));
|
|
191
|
+
} catch {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 21.10 — Human Summary
|
|
3
|
+
*
|
|
4
|
+
* Generates human-readable summary for Enterprise UX.
|
|
5
|
+
* Clear, direct, no marketing.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readFileSync, existsSync } from 'fs';
|
|
9
|
+
import { resolve } from 'path';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Load artifact JSON
|
|
13
|
+
*/
|
|
14
|
+
function loadArtifact(runDir, filename) {
|
|
15
|
+
const path = resolve(runDir, filename);
|
|
16
|
+
if (!existsSync(path)) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
21
|
+
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
22
|
+
} catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Generate human summary
|
|
29
|
+
*
|
|
30
|
+
* @param {string} projectDir - Project directory
|
|
31
|
+
* @param {string} runId - Run ID
|
|
32
|
+
* @returns {Promise<Object>} Human summary
|
|
33
|
+
*/
|
|
34
|
+
export async function generateHumanSummary(projectDir, runId) {
|
|
35
|
+
const runDir = resolve(projectDir, '.verax', 'runs', runId);
|
|
36
|
+
|
|
37
|
+
if (!existsSync(runDir)) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const summary = loadArtifact(runDir, 'summary.json');
|
|
42
|
+
const findings = loadArtifact(runDir, 'findings.json');
|
|
43
|
+
const determinism = loadArtifact(runDir, 'decisions.json');
|
|
44
|
+
const performanceReport = loadArtifact(runDir, 'performance.report.json');
|
|
45
|
+
|
|
46
|
+
// Security reports are in release/ directory (project root)
|
|
47
|
+
// Use projectDir parameter directly (already resolved)
|
|
48
|
+
const releaseDir = resolve(projectDir, 'release');
|
|
49
|
+
const securitySecrets = loadArtifact(releaseDir, 'security.secrets.report.json');
|
|
50
|
+
const securityVuln = loadArtifact(releaseDir, 'security.vuln.report.json');
|
|
51
|
+
|
|
52
|
+
const gaStatus = loadArtifact(runDir, 'ga.status.json');
|
|
53
|
+
|
|
54
|
+
if (!summary) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const findingsArray = Array.isArray(findings?.findings) ? findings.findings : [];
|
|
59
|
+
const confirmedFindings = findingsArray.filter(f => (f.severity || f.status) === 'CONFIRMED');
|
|
60
|
+
const suspectedFindings = findingsArray.filter(f => (f.severity || f.status) === 'SUSPECTED');
|
|
61
|
+
|
|
62
|
+
// What VERAX is confident about
|
|
63
|
+
const confident = {
|
|
64
|
+
findings: confirmedFindings.length,
|
|
65
|
+
message: confirmedFindings.length > 0
|
|
66
|
+
? `${confirmedFindings.length} finding(s) with complete evidence`
|
|
67
|
+
: 'No findings with complete evidence',
|
|
68
|
+
details: confirmedFindings.map(f => ({
|
|
69
|
+
type: f.type,
|
|
70
|
+
outcome: f.outcome,
|
|
71
|
+
confidence: f.confidenceLevel || 'UNKNOWN'
|
|
72
|
+
}))
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// What VERAX is NOT confident about
|
|
76
|
+
const notConfident = {
|
|
77
|
+
findings: suspectedFindings.length,
|
|
78
|
+
message: suspectedFindings.length > 0
|
|
79
|
+
? `${suspectedFindings.length} finding(s) with incomplete evidence (SUSPECTED)`
|
|
80
|
+
: 'No findings with incomplete evidence',
|
|
81
|
+
details: suspectedFindings.map(f => ({
|
|
82
|
+
type: f.type,
|
|
83
|
+
outcome: f.outcome,
|
|
84
|
+
confidence: f.confidenceLevel || 'UNKNOWN',
|
|
85
|
+
missingEvidence: f.evidencePackage?.isComplete === false
|
|
86
|
+
}))
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// Why some things were skipped
|
|
90
|
+
const skips = [];
|
|
91
|
+
if (summary.truth?.observe?.skips) {
|
|
92
|
+
for (const skip of summary.truth.observe.skips) {
|
|
93
|
+
skips.push({
|
|
94
|
+
reason: skip.reason || skip.code || 'UNKNOWN',
|
|
95
|
+
count: skip.count || 1,
|
|
96
|
+
message: skip.message || `Skipped: ${skip.reason || skip.code}`
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Determinism verdict
|
|
102
|
+
let determinismVerdict = 'UNKNOWN';
|
|
103
|
+
if (determinism) {
|
|
104
|
+
try {
|
|
105
|
+
// @ts-expect-error - Dynamic import path
|
|
106
|
+
const { DecisionRecorder } = await import('../../../core/determinism-model.js');
|
|
107
|
+
const recorder = DecisionRecorder.fromExport(determinism);
|
|
108
|
+
// @ts-expect-error - Dynamic import path
|
|
109
|
+
const { computeDeterminismVerdict } = await import('../../../core/determinism/contract.js');
|
|
110
|
+
const verdict = computeDeterminismVerdict(recorder);
|
|
111
|
+
determinismVerdict = verdict.verdict;
|
|
112
|
+
} catch {
|
|
113
|
+
determinismVerdict = summary.determinism?.verdict || 'UNKNOWN';
|
|
114
|
+
}
|
|
115
|
+
} else if (summary.determinism) {
|
|
116
|
+
determinismVerdict = summary.determinism.verdict || 'UNKNOWN';
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Performance verdict
|
|
120
|
+
const performanceVerdict = performanceReport?.verdict || 'UNKNOWN';
|
|
121
|
+
const performanceOk = performanceReport?.ok !== false;
|
|
122
|
+
|
|
123
|
+
// Security verdict
|
|
124
|
+
const securityOk = !securitySecrets?.hasSecrets &&
|
|
125
|
+
!securityVuln?.blocking &&
|
|
126
|
+
(securitySecrets !== null || securityVuln !== null); // At least one report exists
|
|
127
|
+
|
|
128
|
+
// GA verdict
|
|
129
|
+
const gaReady = gaStatus?.gaReady === true;
|
|
130
|
+
const gaVerdict = gaReady ? 'GA-READY' : (gaStatus ? 'GA-BLOCKED' : 'UNKNOWN');
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
runId,
|
|
134
|
+
whatWeKnow: {
|
|
135
|
+
confident: confident,
|
|
136
|
+
notConfident: notConfident,
|
|
137
|
+
skips: skips.length > 0 ? {
|
|
138
|
+
total: skips.reduce((sum, s) => sum + s.count, 0),
|
|
139
|
+
reasons: skips
|
|
140
|
+
} : null
|
|
141
|
+
},
|
|
142
|
+
verdicts: {
|
|
143
|
+
determinism: {
|
|
144
|
+
verdict: determinismVerdict,
|
|
145
|
+
message: determinismVerdict === 'DETERMINISTIC'
|
|
146
|
+
? 'Run was reproducible (same inputs = same outputs)'
|
|
147
|
+
: determinismVerdict === 'NON_DETERMINISTIC'
|
|
148
|
+
? 'Run was not reproducible (adaptive events detected)'
|
|
149
|
+
: 'Determinism not evaluated'
|
|
150
|
+
},
|
|
151
|
+
performance: {
|
|
152
|
+
verdict: performanceVerdict,
|
|
153
|
+
ok: performanceOk,
|
|
154
|
+
message: performanceOk
|
|
155
|
+
? 'Performance within budget'
|
|
156
|
+
: performanceReport?.violations?.length > 0
|
|
157
|
+
? `${performanceReport.violations.length} BLOCKING performance violation(s)`
|
|
158
|
+
: 'Performance not evaluated'
|
|
159
|
+
},
|
|
160
|
+
security: {
|
|
161
|
+
ok: securityOk,
|
|
162
|
+
message: securityOk
|
|
163
|
+
? 'Security baseline passed'
|
|
164
|
+
: securitySecrets?.hasSecrets
|
|
165
|
+
? 'Secrets detected'
|
|
166
|
+
: securityVuln?.blocking
|
|
167
|
+
? 'Critical vulnerabilities detected'
|
|
168
|
+
: 'Security not evaluated'
|
|
169
|
+
},
|
|
170
|
+
ga: {
|
|
171
|
+
verdict: gaVerdict,
|
|
172
|
+
ready: gaReady,
|
|
173
|
+
message: gaReady
|
|
174
|
+
? 'GA-READY: All gates passed'
|
|
175
|
+
: gaStatus
|
|
176
|
+
? `GA-BLOCKED: ${gaStatus.blockers?.length || 0} blocker(s)`
|
|
177
|
+
: 'GA not evaluated'
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
generatedAt: new Date().toISOString()
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Format finding with Human-Readable Report Contract v1
|
|
186
|
+
* Six-line template per finding
|
|
187
|
+
*/
|
|
188
|
+
function formatFinding(finding, index) {
|
|
189
|
+
const lines = [];
|
|
190
|
+
lines.push(`\nFinding #${index + 1}`);
|
|
191
|
+
|
|
192
|
+
// Summary: what the user did (handle both old and new format)
|
|
193
|
+
const summary = finding.what_happened ||
|
|
194
|
+
(finding.interaction ? `User interacted with ${finding.interaction.target || 'element'}` : '') ||
|
|
195
|
+
(finding.promise ? `Expected ${finding.promise.kind}: ${finding.promise.value}` : '') ||
|
|
196
|
+
'Interaction occurred';
|
|
197
|
+
lines.push(` Summary: ${summary}`);
|
|
198
|
+
|
|
199
|
+
// Expected: what user expected (handle both old and new format)
|
|
200
|
+
const expected = finding.what_was_expected ||
|
|
201
|
+
(finding.promise ? `${finding.promise.kind} ${finding.promise.value}` : '') ||
|
|
202
|
+
'Expected behavior not specified';
|
|
203
|
+
lines.push(` Expected: ${expected}`);
|
|
204
|
+
|
|
205
|
+
// Observed: what actually happened (handle both old and new format)
|
|
206
|
+
const observed = finding.what_was_observed ||
|
|
207
|
+
finding.reason ||
|
|
208
|
+
(finding.classification ? `${finding.classification}` : '') ||
|
|
209
|
+
'Actual behavior not specified';
|
|
210
|
+
lines.push(` Observed: ${observed}`);
|
|
211
|
+
|
|
212
|
+
// Evidence (before) - Use interaction ID or finding ID as stable reference
|
|
213
|
+
const beforeId = finding.interaction?.sequenceId
|
|
214
|
+
? `UI#${finding.interaction.sequenceId}`
|
|
215
|
+
: finding.id ? `REF#${finding.id}`
|
|
216
|
+
: 'UI#?';
|
|
217
|
+
lines.push(` Evidence (before): ${beforeId}`);
|
|
218
|
+
|
|
219
|
+
// Evidence (after) - Use evidence package references if available
|
|
220
|
+
const afterIds = [];
|
|
221
|
+
if (finding.evidencePackage?.evidence?.dom) afterIds.push('DOM#' + (finding.interaction?.sequenceId || finding.id || '?'));
|
|
222
|
+
if (finding.evidencePackage?.evidence?.network) afterIds.push('NET#' + (finding.interaction?.sequenceId || finding.id || '?'));
|
|
223
|
+
if (finding.evidencePackage?.evidence?.console) afterIds.push('LOG#' + (finding.interaction?.sequenceId || finding.id || '?'));
|
|
224
|
+
const afterId = afterIds.length > 0
|
|
225
|
+
? afterIds.join(', ')
|
|
226
|
+
: finding.id ? `REF#${finding.id}`
|
|
227
|
+
: 'UI#?';
|
|
228
|
+
lines.push(` Evidence (after): ${afterId}`);
|
|
229
|
+
|
|
230
|
+
// Why this matters - Neutral impact statement
|
|
231
|
+
const impact = finding.signals?.impact || finding.impact || 'UNKNOWN';
|
|
232
|
+
const userRisk = finding.signals?.userRisk || 'affects user workflow';
|
|
233
|
+
lines.push(` Why this matters: ${impact} impact, ${userRisk}`);
|
|
234
|
+
|
|
235
|
+
return lines.join('\n');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Format coverage transparency block
|
|
240
|
+
*/
|
|
241
|
+
function formatCoverage(summary) {
|
|
242
|
+
const lines = [];
|
|
243
|
+
lines.push('\n' + '='.repeat(80));
|
|
244
|
+
lines.push('COVERAGE');
|
|
245
|
+
lines.push('='.repeat(80));
|
|
246
|
+
|
|
247
|
+
// Tested interactions (confirmed + suspected findings)
|
|
248
|
+
const testedCount = summary.whatWeKnow.confident.findings + summary.whatWeKnow.notConfident.findings;
|
|
249
|
+
lines.push(`\nTested interactions: ${testedCount}`);
|
|
250
|
+
if (testedCount > 0) {
|
|
251
|
+
lines.push(` ${summary.whatWeKnow.confident.findings} with complete evidence`);
|
|
252
|
+
lines.push(` ${summary.whatWeKnow.notConfident.findings} with incomplete evidence`);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Skipped interactions (aggregated by canonical enum)
|
|
256
|
+
if (summary.whatWeKnow.skips && summary.whatWeKnow.skips.canonicalReasons) {
|
|
257
|
+
lines.push(`\nSkipped interactions: ${summary.whatWeKnow.skips.total}`);
|
|
258
|
+
for (const skip of summary.whatWeKnow.skips.canonicalReasons) {
|
|
259
|
+
lines.push(` ${skip.canonical}: ${skip.count}`);
|
|
260
|
+
}
|
|
261
|
+
} else if (summary.whatWeKnow.skips) {
|
|
262
|
+
// Backward compatibility: use raw reasons if canonical not available
|
|
263
|
+
lines.push(`\nSkipped interactions: ${summary.whatWeKnow.skips.total}`);
|
|
264
|
+
for (const skip of summary.whatWeKnow.skips.reasons) {
|
|
265
|
+
lines.push(` ${skip.code || skip.reason}: ${skip.count}`);
|
|
266
|
+
}
|
|
267
|
+
} else {
|
|
268
|
+
lines.push(`\nSkipped interactions: 0`);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Coverage disclaimer
|
|
272
|
+
lines.push('');
|
|
273
|
+
lines.push('Coverage indicates what was observed in this run; it does not guarantee absence of issues.');
|
|
274
|
+
lines.push('='.repeat(80));
|
|
275
|
+
|
|
276
|
+
return lines.join('\n');
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Format human summary for CLI display
|
|
281
|
+
*
|
|
282
|
+
* @param {Object} summary - Human summary
|
|
283
|
+
* @returns {string} Formatted string
|
|
284
|
+
*/
|
|
285
|
+
export function formatHumanSummary(summary) {
|
|
286
|
+
if (!summary) {
|
|
287
|
+
return 'Summary: Not available';
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const lines = [];
|
|
291
|
+
lines.push('\n' + '='.repeat(80));
|
|
292
|
+
lines.push('HUMAN SUMMARY');
|
|
293
|
+
lines.push('='.repeat(80));
|
|
294
|
+
|
|
295
|
+
// What we know
|
|
296
|
+
lines.push('\nWhat VERAX is confident about:');
|
|
297
|
+
lines.push(` ${summary.whatWeKnow.confident.message}`);
|
|
298
|
+
|
|
299
|
+
lines.push('\nWhat VERAX is NOT confident about:');
|
|
300
|
+
lines.push(` ${summary.whatWeKnow.notConfident.message}`);
|
|
301
|
+
|
|
302
|
+
if (summary.whatWeKnow.skips) {
|
|
303
|
+
lines.push('\nWhy some things were skipped:');
|
|
304
|
+
for (const skip of summary.whatWeKnow.skips.reasons) {
|
|
305
|
+
lines.push(` - ${skip.message} (${skip.count}x)`);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Verdicts
|
|
310
|
+
lines.push('\nVerdicts:');
|
|
311
|
+
lines.push(` Determinism: ${summary.verdicts.determinism.verdict} - ${summary.verdicts.determinism.message}`);
|
|
312
|
+
lines.push(` Performance: ${summary.verdicts.performance.verdict} - ${summary.verdicts.performance.message}`);
|
|
313
|
+
lines.push(` Security: ${summary.verdicts.security.ok ? 'OK' : 'BLOCKED'} - ${summary.verdicts.security.message}`);
|
|
314
|
+
lines.push(` GA: ${summary.verdicts.ga.verdict} - ${summary.verdicts.ga.message}`);
|
|
315
|
+
|
|
316
|
+
lines.push('='.repeat(80) + '\n');
|
|
317
|
+
|
|
318
|
+
return lines.join('\n');
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Format detailed findings report with Human-Readable Report Contract v1
|
|
323
|
+
* Adds six-line finding summaries + coverage transparency
|
|
324
|
+
*
|
|
325
|
+
* @param {string} projectDir - Project directory
|
|
326
|
+
* @param {string} runId - Run ID
|
|
327
|
+
* @returns {Promise<string>} Formatted report
|
|
328
|
+
*/
|
|
329
|
+
export async function formatFindingsReport(projectDir, runId) {
|
|
330
|
+
const runDir = resolve(projectDir, '.verax', 'runs', runId);
|
|
331
|
+
const findings = loadArtifact(runDir, 'findings.json');
|
|
332
|
+
const summary = await generateHumanSummary(projectDir, runId);
|
|
333
|
+
|
|
334
|
+
if (!findings || !summary) {
|
|
335
|
+
return 'Findings report not available';
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const lines = [];
|
|
339
|
+
lines.push('\n' + '='.repeat(80));
|
|
340
|
+
lines.push('FINDINGS REPORT (Human-Readable Contract v1)');
|
|
341
|
+
lines.push('='.repeat(80));
|
|
342
|
+
|
|
343
|
+
const findingsArray = findings.findings || [];
|
|
344
|
+
|
|
345
|
+
if (findingsArray.length === 0) {
|
|
346
|
+
lines.push('\nNo findings detected');
|
|
347
|
+
} else {
|
|
348
|
+
lines.push(`\nTotal findings: ${findingsArray.length}`);
|
|
349
|
+
|
|
350
|
+
// Format each finding with 6-line template
|
|
351
|
+
for (let i = 0; i < findingsArray.length; i++) {
|
|
352
|
+
lines.push(formatFinding(findingsArray[i], i));
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Add coverage transparency block
|
|
357
|
+
lines.push(formatCoverage(summary));
|
|
358
|
+
lines.push('='.repeat(80) + '\n');
|
|
359
|
+
|
|
360
|
+
return lines.join('\n');
|
|
361
|
+
}
|
|
362
|
+
|