@veraxhq/verax 0.2.0 → 0.3.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 +14 -18
- package/bin/verax.js +7 -0
- package/package.json +15 -5
- package/src/cli/commands/baseline.js +104 -0
- package/src/cli/commands/default.js +323 -111
- package/src/cli/commands/doctor.js +36 -4
- package/src/cli/commands/ga.js +243 -0
- package/src/cli/commands/gates.js +95 -0
- package/src/cli/commands/inspect.js +131 -2
- package/src/cli/commands/release-check.js +213 -0
- package/src/cli/commands/run.js +498 -103
- package/src/cli/commands/security-check.js +211 -0
- package/src/cli/commands/truth.js +114 -0
- package/src/cli/entry.js +305 -68
- 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 +546 -0
- package/src/cli/util/ast-network-detector.js +603 -0
- package/src/cli/util/ast-usestate-detector.js +602 -0
- package/src/cli/util/bootstrap-guard.js +86 -0
- package/src/cli/util/detection-engine.js +4 -3
- package/src/cli/util/determinism-runner.js +123 -0
- package/src/cli/util/determinism-writer.js +129 -0
- package/src/cli/util/env-url.js +4 -0
- package/src/cli/util/events.js +76 -0
- package/src/cli/util/expectation-extractor.js +380 -74
- package/src/cli/util/findings-writer.js +126 -15
- package/src/cli/util/learn-writer.js +3 -1
- package/src/cli/util/observation-engine.js +69 -23
- package/src/cli/util/observe-writer.js +3 -1
- package/src/cli/util/paths.js +6 -14
- package/src/cli/util/project-discovery.js +23 -0
- package/src/cli/util/project-writer.js +3 -1
- package/src/cli/util/redact.js +2 -2
- package/src/cli/util/run-resolver.js +64 -0
- package/src/cli/util/runtime-budget.js +147 -0
- package/src/cli/util/source-requirement.js +55 -0
- package/src/cli/util/summary-writer.js +13 -1
- 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 +147 -0
- package/src/cli/util/svelte-state-detector.js +243 -0
- package/src/cli/util/vue-navigation-detector.js +177 -0
- package/src/cli/util/vue-sfc-extractor.js +162 -0
- package/src/cli/util/vue-state-detector.js +215 -0
- package/src/types/global.d.ts +28 -0
- package/src/types/ts-ast.d.ts +24 -0
- package/src/verax/cli/doctor.js +2 -2
- package/src/verax/cli/finding-explainer.js +56 -3
- package/src/verax/cli/init.js +1 -1
- package/src/verax/cli/url-safety.js +12 -2
- package/src/verax/cli/wizard.js +13 -2
- package/src/verax/core/artifacts/registry.js +154 -0
- package/src/verax/core/artifacts/verifier.js +980 -0
- package/src/verax/core/baseline/baseline.enforcer.js +137 -0
- package/src/verax/core/baseline/baseline.snapshot.js +231 -0
- package/src/verax/core/budget-engine.js +1 -1
- package/src/verax/core/capabilities/gates.js +499 -0
- package/src/verax/core/capabilities/registry.js +475 -0
- package/src/verax/core/confidence/confidence-compute.js +137 -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 +79 -0
- package/src/verax/core/confidence/confidence.schema.js +94 -0
- package/src/verax/core/confidence-engine-refactor.js +484 -0
- package/src/verax/core/confidence-engine.js +486 -0
- package/src/verax/core/confidence-engine.js.backup +471 -0
- package/src/verax/core/contracts/index.js +29 -0
- package/src/verax/core/contracts/types.js +185 -0
- package/src/verax/core/contracts/validators.js +381 -0
- package/src/verax/core/decision-snapshot.js +31 -4
- package/src/verax/core/decisions/decision.trace.js +276 -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 +364 -0
- package/src/verax/core/determinism/engine.js +221 -0
- package/src/verax/core/determinism/finding-identity.js +148 -0
- package/src/verax/core/determinism/normalize.js +438 -0
- package/src/verax/core/determinism/report-writer.js +92 -0
- package/src/verax/core/determinism/run-fingerprint.js +118 -0
- package/src/verax/core/determinism-model.js +35 -6
- package/src/verax/core/dynamic-route-intelligence.js +528 -0
- package/src/verax/core/evidence/evidence-capture-service.js +307 -0
- package/src/verax/core/evidence/evidence-intent-ledger.js +165 -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 +190 -0
- package/src/verax/core/failures/exit-codes.js +86 -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 +132 -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 +434 -0
- package/src/verax/core/ga/ga.enforcer.js +86 -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 +83 -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 +15 -7
- package/src/verax/core/observe/run-timeline.js +316 -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 +198 -0
- package/src/verax/core/pipeline-tracker.js +238 -0
- package/src/verax/core/product-definition.js +127 -0
- package/src/verax/core/release/provenance.builder.js +271 -0
- package/src/verax/core/release/release-report-writer.js +40 -0
- package/src/verax/core/release/release.enforcer.js +159 -0
- package/src/verax/core/release/reproducibility.check.js +221 -0
- package/src/verax/core/release/sbom.builder.js +283 -0
- package/src/verax/core/replay-validator.js +4 -4
- package/src/verax/core/replay.js +1 -1
- package/src/verax/core/report/cross-index.js +192 -0
- package/src/verax/core/report/human-summary.js +222 -0
- package/src/verax/core/route-intelligence.js +419 -0
- package/src/verax/core/security/secrets.scan.js +326 -0
- package/src/verax/core/security/security-report.js +50 -0
- package/src/verax/core/security/security.enforcer.js +124 -0
- package/src/verax/core/security/supplychain.defaults.json +38 -0
- package/src/verax/core/security/supplychain.policy.js +326 -0
- package/src/verax/core/security/vuln.scan.js +265 -0
- package/src/verax/core/silence-impact.js +1 -1
- package/src/verax/core/silence-model.js +9 -7
- package/src/verax/core/truth/truth.certificate.js +250 -0
- package/src/verax/core/ui-feedback-intelligence.js +515 -0
- package/src/verax/detect/comparison.js +8 -3
- package/src/verax/detect/confidence-engine.js +645 -57
- package/src/verax/detect/confidence-helper.js +33 -0
- package/src/verax/detect/detection-engine.js +19 -2
- package/src/verax/detect/dynamic-route-findings.js +335 -0
- package/src/verax/detect/evidence-index.js +15 -65
- package/src/verax/detect/expectation-chain-detector.js +417 -0
- package/src/verax/detect/expectation-model.js +56 -3
- package/src/verax/detect/explanation-helpers.js +1 -1
- package/src/verax/detect/finding-detector.js +2 -2
- package/src/verax/detect/findings-writer.js +149 -20
- package/src/verax/detect/flow-detector.js +4 -4
- package/src/verax/detect/index.js +265 -15
- package/src/verax/detect/interactive-findings.js +3 -4
- package/src/verax/detect/journey-stall-detector.js +558 -0
- package/src/verax/detect/route-findings.js +218 -0
- package/src/verax/detect/signal-mapper.js +2 -2
- package/src/verax/detect/skip-classifier.js +4 -4
- package/src/verax/detect/ui-feedback-findings.js +207 -0
- package/src/verax/detect/verdict-engine.js +61 -9
- package/src/verax/detect/view-switch-correlator.js +242 -0
- package/src/verax/flow/flow-engine.js +3 -2
- package/src/verax/flow/flow-spec.js +1 -2
- package/src/verax/index.js +413 -33
- package/src/verax/intel/effect-detector.js +1 -1
- package/src/verax/intel/index.js +2 -2
- package/src/verax/intel/route-extractor.js +3 -3
- package/src/verax/intel/vue-navigation-extractor.js +81 -18
- package/src/verax/intel/vue-router-extractor.js +4 -2
- package/src/verax/learn/action-contract-extractor.js +684 -66
- package/src/verax/learn/ast-contract-extractor.js +53 -1
- package/src/verax/learn/index.js +36 -2
- package/src/verax/learn/manifest-writer.js +28 -14
- package/src/verax/learn/route-extractor.js +1 -1
- package/src/verax/learn/route-validator.js +12 -8
- package/src/verax/learn/state-extractor.js +1 -1
- package/src/verax/learn/static-extractor-navigation.js +1 -1
- package/src/verax/learn/static-extractor-validation.js +2 -2
- package/src/verax/learn/static-extractor.js +8 -7
- package/src/verax/learn/ts-contract-resolver.js +14 -12
- package/src/verax/observe/browser.js +22 -3
- package/src/verax/observe/console-sensor.js +2 -2
- package/src/verax/observe/expectation-executor.js +2 -1
- package/src/verax/observe/focus-sensor.js +1 -1
- package/src/verax/observe/human-driver.js +29 -10
- package/src/verax/observe/index.js +92 -844
- package/src/verax/observe/interaction-discovery.js +27 -15
- package/src/verax/observe/interaction-runner.js +31 -14
- package/src/verax/observe/loading-sensor.js +6 -0
- package/src/verax/observe/navigation-sensor.js +1 -1
- package/src/verax/observe/observe-context.js +205 -0
- package/src/verax/observe/observe-helpers.js +191 -0
- package/src/verax/observe/observe-runner.js +226 -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/settle.js +1 -0
- package/src/verax/observe/state-sensor.js +8 -4
- package/src/verax/observe/state-ui-sensor.js +7 -1
- package/src/verax/observe/traces-writer.js +27 -16
- package/src/verax/observe/ui-feedback-detector.js +742 -0
- package/src/verax/observe/ui-signal-sensor.js +155 -2
- package/src/verax/scan-summary-writer.js +46 -9
- package/src/verax/shared/artifact-manager.js +9 -6
- package/src/verax/shared/budget-profiles.js +2 -2
- package/src/verax/shared/caching.js +1 -1
- package/src/verax/shared/config-loader.js +1 -2
- package/src/verax/shared/css-spinner-rules.js +204 -0
- package/src/verax/shared/dynamic-route-utils.js +12 -6
- package/src/verax/shared/retry-policy.js +1 -6
- package/src/verax/shared/root-artifacts.js +1 -1
- package/src/verax/shared/view-switch-rules.js +208 -0
- package/src/verax/shared/zip-artifacts.js +1 -0
- package/src/verax/validate/context-validator.js +1 -1
- package/src/verax/observe/index.js.backup +0 -1
- package/src/verax/validate/context-validator.js.bak +0 -0
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 18 — Determinism Normalization Layer
|
|
3
|
+
*
|
|
4
|
+
* Normalizes artifacts for deterministic comparison by:
|
|
5
|
+
* - Stripping volatile fields (timestamps, runId, absolute paths, temp dirs)
|
|
6
|
+
* - Normalizing ordering (sort arrays by stable keys)
|
|
7
|
+
* - Normalizing evidence paths but preserving evidence presence/absence
|
|
8
|
+
* - Normalizing floating scores with fixed rounding
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* PHASE 18: Normalize artifact for comparison
|
|
13
|
+
*
|
|
14
|
+
* @param {string} artifactName - Name of artifact (findings, runStatus, etc.)
|
|
15
|
+
* @param {Object} json - Artifact JSON object
|
|
16
|
+
* @returns {Object} Normalized artifact
|
|
17
|
+
*/
|
|
18
|
+
export function normalizeArtifact(artifactName, json) {
|
|
19
|
+
if (!json || typeof json !== 'object') {
|
|
20
|
+
return json;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Deep clone to avoid mutating original
|
|
24
|
+
const normalized = JSON.parse(JSON.stringify(json));
|
|
25
|
+
|
|
26
|
+
// Apply artifact-specific normalization
|
|
27
|
+
switch (artifactName) {
|
|
28
|
+
case 'findings':
|
|
29
|
+
return normalizeFindings(normalized);
|
|
30
|
+
case 'runStatus':
|
|
31
|
+
return normalizeRunStatus(normalized);
|
|
32
|
+
case 'summary':
|
|
33
|
+
return normalizeSummary(normalized);
|
|
34
|
+
case 'learn':
|
|
35
|
+
return normalizeLearn(normalized);
|
|
36
|
+
case 'evidenceIntent':
|
|
37
|
+
return normalizeEvidenceIntent(normalized);
|
|
38
|
+
case 'guardrailsReport':
|
|
39
|
+
return normalizeGuardrailsReport(normalized);
|
|
40
|
+
case 'confidenceReport':
|
|
41
|
+
return normalizeConfidenceReport(normalized);
|
|
42
|
+
case 'determinismContract':
|
|
43
|
+
return normalizeDeterminismContract(normalized);
|
|
44
|
+
case 'runMeta':
|
|
45
|
+
return normalizeRunMeta(normalized);
|
|
46
|
+
case 'observe':
|
|
47
|
+
case 'traces':
|
|
48
|
+
return normalizeTraces(normalized);
|
|
49
|
+
case 'performanceReport':
|
|
50
|
+
return normalizePerformanceReport(normalized);
|
|
51
|
+
case 'securityReport':
|
|
52
|
+
return normalizeSecurityReport(normalized);
|
|
53
|
+
case 'gaReport':
|
|
54
|
+
return normalizeGAReport(normalized);
|
|
55
|
+
case 'releaseReport':
|
|
56
|
+
return normalizeReleaseReport(normalized);
|
|
57
|
+
default:
|
|
58
|
+
return normalizeGeneric(normalized);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Normalize findings artifact
|
|
64
|
+
*/
|
|
65
|
+
function normalizeFindings(artifact) {
|
|
66
|
+
const normalized = { ...artifact };
|
|
67
|
+
|
|
68
|
+
// Remove volatile top-level fields
|
|
69
|
+
delete normalized.detectedAt;
|
|
70
|
+
delete normalized.runId;
|
|
71
|
+
delete normalized.timestamp;
|
|
72
|
+
|
|
73
|
+
// Normalize findings array
|
|
74
|
+
if (Array.isArray(normalized.findings)) {
|
|
75
|
+
normalized.findings = normalized.findings.map(f => normalizeFinding(f));
|
|
76
|
+
// Sort by stable identity (if we had it, but for now sort by type + interaction)
|
|
77
|
+
normalized.findings.sort((a, b) => {
|
|
78
|
+
const keyA = `${a.type || ''}|${a.interaction?.selector || ''}|${a.interaction?.type || ''}`;
|
|
79
|
+
const keyB = `${b.type || ''}|${b.interaction?.selector || ''}|${b.interaction?.type || ''}`;
|
|
80
|
+
return keyA.localeCompare(keyB);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Normalize enforcement metadata (keep structure, remove timestamps)
|
|
85
|
+
if (normalized.enforcement) {
|
|
86
|
+
const enforcement = { ...normalized.enforcement };
|
|
87
|
+
// Keep enforcement data but normalize any timestamps
|
|
88
|
+
normalized.enforcement = enforcement;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return normalized;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Normalize individual finding
|
|
96
|
+
*/
|
|
97
|
+
function normalizeFinding(finding) {
|
|
98
|
+
const normalized = { ...finding };
|
|
99
|
+
|
|
100
|
+
// Remove volatile fields
|
|
101
|
+
delete normalized.id;
|
|
102
|
+
delete normalized.findingId;
|
|
103
|
+
delete normalized.timestamp;
|
|
104
|
+
delete normalized.detectedAt;
|
|
105
|
+
|
|
106
|
+
// Normalize confidence (round to 3 decimals)
|
|
107
|
+
if (typeof normalized.confidence === 'number') {
|
|
108
|
+
normalized.confidence = Math.round(normalized.confidence * 1000) / 1000;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Normalize evidence paths (keep structure, normalize paths)
|
|
112
|
+
if (normalized.evidence) {
|
|
113
|
+
normalized.evidence = normalizeEvidence(normalized.evidence);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Normalize evidencePackage
|
|
117
|
+
if (normalized.evidencePackage) {
|
|
118
|
+
normalized.evidencePackage = normalizeEvidencePackage(normalized.evidencePackage);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Normalize guardrails (keep structure, normalize confidence deltas)
|
|
122
|
+
if (normalized.guardrails) {
|
|
123
|
+
const guardrails = { ...normalized.guardrails };
|
|
124
|
+
if (typeof guardrails.confidenceDelta === 'number') {
|
|
125
|
+
guardrails.confidenceDelta = Math.round(guardrails.confidenceDelta * 1000) / 1000;
|
|
126
|
+
}
|
|
127
|
+
normalized.guardrails = guardrails;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return normalized;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Normalize evidence
|
|
135
|
+
*/
|
|
136
|
+
function normalizeEvidence(evidence) {
|
|
137
|
+
const normalized = { ...evidence };
|
|
138
|
+
|
|
139
|
+
// Normalize screenshot paths (keep presence, normalize path)
|
|
140
|
+
if (normalized.before) {
|
|
141
|
+
normalized.before = normalizePath(normalized.before);
|
|
142
|
+
}
|
|
143
|
+
if (normalized.after) {
|
|
144
|
+
normalized.after = normalizePath(normalized.after);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Normalize URLs (remove query params, hash)
|
|
148
|
+
if (normalized.beforeUrl) {
|
|
149
|
+
normalized.beforeUrl = normalizeUrl(normalized.beforeUrl);
|
|
150
|
+
}
|
|
151
|
+
if (normalized.afterUrl) {
|
|
152
|
+
normalized.afterUrl = normalizeUrl(normalized.afterUrl);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Normalize source paths
|
|
156
|
+
if (normalized.source && typeof normalized.source === 'string') {
|
|
157
|
+
normalized.source = normalizePath(normalized.source);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return normalized;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Normalize evidencePackage
|
|
165
|
+
*/
|
|
166
|
+
function normalizeEvidencePackage(evidencePackage) {
|
|
167
|
+
const normalized = { ...evidencePackage };
|
|
168
|
+
|
|
169
|
+
// Normalize before/after paths
|
|
170
|
+
if (normalized.before) {
|
|
171
|
+
normalized.before = {
|
|
172
|
+
...normalized.before,
|
|
173
|
+
screenshot: normalized.before.screenshot ? normalizePath(normalized.before.screenshot) : null,
|
|
174
|
+
url: normalized.before.url ? normalizeUrl(normalized.before.url) : null,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (normalized.after) {
|
|
179
|
+
normalized.after = {
|
|
180
|
+
...normalized.after,
|
|
181
|
+
screenshot: normalized.after.screenshot ? normalizePath(normalized.after.screenshot) : null,
|
|
182
|
+
url: normalized.after.url ? normalizeUrl(normalized.after.url) : null,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Normalize trigger source paths
|
|
187
|
+
if (normalized.trigger?.source?.file) {
|
|
188
|
+
normalized.trigger.source.file = normalizePath(normalized.trigger.source.file);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return normalized;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Normalize runStatus artifact
|
|
196
|
+
*/
|
|
197
|
+
function normalizeRunStatus(artifact) {
|
|
198
|
+
const normalized = { ...artifact };
|
|
199
|
+
|
|
200
|
+
// Remove volatile fields
|
|
201
|
+
delete normalized.startedAt;
|
|
202
|
+
delete normalized.completedAt;
|
|
203
|
+
delete normalized.timestamp;
|
|
204
|
+
delete normalized.runId;
|
|
205
|
+
|
|
206
|
+
// Keep structure but remove timestamps from nested objects
|
|
207
|
+
if (normalized.verification) {
|
|
208
|
+
const verification = { ...normalized.verification };
|
|
209
|
+
delete verification.verifiedAt;
|
|
210
|
+
normalized.verification = verification;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return normalized;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Normalize summary artifact
|
|
218
|
+
*/
|
|
219
|
+
function normalizeSummary(artifact) {
|
|
220
|
+
const normalized = { ...artifact };
|
|
221
|
+
|
|
222
|
+
// Remove volatile fields
|
|
223
|
+
delete normalized.timestamp;
|
|
224
|
+
delete normalized.runId;
|
|
225
|
+
|
|
226
|
+
// Normalize metrics (round durations)
|
|
227
|
+
if (normalized.metrics) {
|
|
228
|
+
const metrics = { ...normalized.metrics };
|
|
229
|
+
for (const key in metrics) {
|
|
230
|
+
if (typeof metrics[key] === 'number' && key.includes('Ms')) {
|
|
231
|
+
metrics[key] = Math.round(metrics[key]);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
normalized.metrics = metrics;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return normalized;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Normalize learn artifact
|
|
242
|
+
*/
|
|
243
|
+
function normalizeLearn(artifact) {
|
|
244
|
+
const normalized = { ...artifact };
|
|
245
|
+
|
|
246
|
+
// Remove volatile fields
|
|
247
|
+
delete normalized.learnedAt;
|
|
248
|
+
delete normalized.timestamp;
|
|
249
|
+
delete normalized.runId;
|
|
250
|
+
|
|
251
|
+
// Normalize routes array (sort by path)
|
|
252
|
+
if (Array.isArray(normalized.routes)) {
|
|
253
|
+
normalized.routes = [...normalized.routes].sort((a, b) => {
|
|
254
|
+
const pathA = a.path || '';
|
|
255
|
+
const pathB = b.path || '';
|
|
256
|
+
return pathA.localeCompare(pathB);
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Normalize expectations array (sort by type + target)
|
|
261
|
+
if (Array.isArray(normalized.expectations)) {
|
|
262
|
+
normalized.expectations = [...normalized.expectations].sort((a, b) => {
|
|
263
|
+
const keyA = `${a.type || ''}|${a.targetPath || ''}`;
|
|
264
|
+
const keyB = `${b.type || ''}|${b.targetPath || ''}`;
|
|
265
|
+
return keyA.localeCompare(keyB);
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return normalized;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* PHASE 22: Normalize evidence intent ledger
|
|
274
|
+
*/
|
|
275
|
+
function normalizeEvidenceIntent(artifact) {
|
|
276
|
+
const normalized = { ...artifact };
|
|
277
|
+
|
|
278
|
+
// Remove volatile fields
|
|
279
|
+
delete normalized.generatedAt;
|
|
280
|
+
|
|
281
|
+
// Normalize entries (already sorted by findingIdentity, but normalize timestamps)
|
|
282
|
+
if (Array.isArray(normalized.entries)) {
|
|
283
|
+
normalized.entries = normalized.entries.map(entry => {
|
|
284
|
+
const normalizedEntry = { ...entry };
|
|
285
|
+
delete normalizedEntry.timestamp;
|
|
286
|
+
|
|
287
|
+
// Normalize capture outcomes (preserve pass/fail, normalize failure details)
|
|
288
|
+
if (normalizedEntry.captureOutcomes) {
|
|
289
|
+
const outcomes = {};
|
|
290
|
+
for (const [field, outcome] of Object.entries(normalizedEntry.captureOutcomes)) {
|
|
291
|
+
outcomes[field] = {
|
|
292
|
+
required: outcome.required,
|
|
293
|
+
captured: outcome.captured,
|
|
294
|
+
failure: outcome.failure ? {
|
|
295
|
+
stage: outcome.failure.stage,
|
|
296
|
+
reasonCode: outcome.failure.reasonCode,
|
|
297
|
+
reason: outcome.failure.reason
|
|
298
|
+
// Exclude stackSummary and timestamp from failure for determinism
|
|
299
|
+
} : null
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
normalizedEntry.captureOutcomes = outcomes;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return normalizedEntry;
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return normalized;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* PHASE 23: Normalize guardrails report
|
|
314
|
+
*/
|
|
315
|
+
function normalizeGuardrailsReport(artifact) {
|
|
316
|
+
const normalized = { ...artifact };
|
|
317
|
+
|
|
318
|
+
// Remove volatile fields
|
|
319
|
+
delete normalized.generatedAt;
|
|
320
|
+
|
|
321
|
+
// Normalize summary (preserve counts, normalize topRules ordering)
|
|
322
|
+
if (normalized.summary && normalized.summary.topRules) {
|
|
323
|
+
normalized.summary.topRules = normalized.summary.topRules.map(r => ({
|
|
324
|
+
code: r.code,
|
|
325
|
+
count: r.count
|
|
326
|
+
}));
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Normalize perFinding (already sorted by findingIdentity, but normalize timestamps if any)
|
|
330
|
+
if (normalized.perFinding && typeof normalized.perFinding === 'object') {
|
|
331
|
+
const perFindingNormalized = {};
|
|
332
|
+
const sortedKeys = Object.keys(normalized.perFinding).sort();
|
|
333
|
+
for (const key of sortedKeys) {
|
|
334
|
+
const entry = normalized.perFinding[key];
|
|
335
|
+
perFindingNormalized[key] = {
|
|
336
|
+
...entry,
|
|
337
|
+
// Remove any volatile fields from entry
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
normalized.perFinding = perFindingNormalized;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return normalized;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* PHASE 24: Normalize confidence report
|
|
348
|
+
*/
|
|
349
|
+
function normalizeConfidenceReport(artifact) {
|
|
350
|
+
const normalized = { ...artifact };
|
|
351
|
+
|
|
352
|
+
// Remove volatile fields
|
|
353
|
+
delete normalized.generatedAt;
|
|
354
|
+
|
|
355
|
+
// Normalize summary (preserve counts)
|
|
356
|
+
if (normalized.summary) {
|
|
357
|
+
// Summary counts are already deterministic, no normalization needed
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Normalize perFinding (already sorted by findingIdentity, but normalize timestamps if any)
|
|
361
|
+
if (normalized.perFinding && typeof normalized.perFinding === 'object') {
|
|
362
|
+
const perFindingNormalized = {};
|
|
363
|
+
const sortedKeys = Object.keys(normalized.perFinding).sort();
|
|
364
|
+
for (const key of sortedKeys) {
|
|
365
|
+
const entry = normalized.perFinding[key];
|
|
366
|
+
perFindingNormalized[key] = {
|
|
367
|
+
...entry,
|
|
368
|
+
// Remove any volatile fields from entry
|
|
369
|
+
// Round confidence values to 3 decimal places for determinism
|
|
370
|
+
confidenceBefore: Math.round(entry.confidenceBefore * 1000) / 1000,
|
|
371
|
+
confidenceAfter: Math.round(entry.confidenceAfter * 1000) / 1000
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
normalized.perFinding = perFindingNormalized;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return normalized;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Normalize generic artifact
|
|
382
|
+
*/
|
|
383
|
+
function normalizeGeneric(artifact) {
|
|
384
|
+
const normalized = { ...artifact };
|
|
385
|
+
|
|
386
|
+
// Remove common volatile fields
|
|
387
|
+
delete normalized.timestamp;
|
|
388
|
+
delete normalized.runId;
|
|
389
|
+
delete normalized.detectedAt;
|
|
390
|
+
delete normalized.startedAt;
|
|
391
|
+
delete normalized.completedAt;
|
|
392
|
+
delete normalized.verifiedAt;
|
|
393
|
+
delete normalized.learnedAt;
|
|
394
|
+
|
|
395
|
+
// Recursively normalize nested objects
|
|
396
|
+
for (const key in normalized) {
|
|
397
|
+
if (normalized[key] && typeof normalized[key] === 'object') {
|
|
398
|
+
if (Array.isArray(normalized[key])) {
|
|
399
|
+
// Sort arrays if they contain objects with stable keys
|
|
400
|
+
normalized[key] = [...normalized[key]];
|
|
401
|
+
} else {
|
|
402
|
+
normalized[key] = normalizeGeneric(normalized[key]);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return normalized;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Normalize path (remove absolute paths, normalize separators)
|
|
412
|
+
*/
|
|
413
|
+
function normalizePath(path) {
|
|
414
|
+
if (!path || typeof path !== 'string') return path;
|
|
415
|
+
let normalized = path.replace(/\\/g, '/');
|
|
416
|
+
// Remove absolute path prefixes
|
|
417
|
+
normalized = normalized.replace(/^[A-Z]:\/[^\/]+/, '');
|
|
418
|
+
normalized = normalized.replace(/^\/[^\/]+/, '');
|
|
419
|
+
// Remove temp dirs
|
|
420
|
+
normalized = normalized.replace(/\/tmp\/[^\/]+/g, '/tmp/...');
|
|
421
|
+
normalized = normalized.replace(/\/\.verax\/runs\/[^\/]+/g, '/.verax/runs/...');
|
|
422
|
+
return normalized;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Normalize URL (remove query params, hash, normalize domain)
|
|
427
|
+
*/
|
|
428
|
+
function normalizeUrl(url) {
|
|
429
|
+
if (!url || typeof url !== 'string') return url;
|
|
430
|
+
try {
|
|
431
|
+
const urlObj = new URL(url);
|
|
432
|
+
// Keep only pathname for comparison
|
|
433
|
+
return urlObj.pathname;
|
|
434
|
+
} catch {
|
|
435
|
+
return url;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 21.2 — Determinism Report Writer
|
|
3
|
+
*
|
|
4
|
+
* Writes determinism.report.json with HARD TRUTH about determinism.
|
|
5
|
+
* No marketing language. Only binary verdict: DETERMINISTIC or NON_DETERMINISTIC.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { writeFileSync, readFileSync, existsSync } from 'fs';
|
|
9
|
+
import { resolve } from 'path';
|
|
10
|
+
import { ARTIFACT_REGISTRY, getArtifactVersions } from '../artifacts/registry.js';
|
|
11
|
+
import { computeDeterminismVerdict, DETERMINISM_VERDICT, DETERMINISM_REASON } from './contract.js';
|
|
12
|
+
import { DecisionRecorder } from '../determinism-model.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* PHASE 21.2: Write determinism report from DecisionRecorder
|
|
16
|
+
*
|
|
17
|
+
* @param {string} runDir - Run directory path
|
|
18
|
+
* @param {DecisionRecorder} decisionRecorder - Decision recorder instance
|
|
19
|
+
* @returns {string} Path to determinism report
|
|
20
|
+
*/
|
|
21
|
+
export function writeDeterminismReport(runDir, decisionRecorder) {
|
|
22
|
+
const reportPath = resolve(runDir, ARTIFACT_REGISTRY.determinismReport.filename);
|
|
23
|
+
|
|
24
|
+
// PHASE 21.2: Compute HARD verdict from adaptive events
|
|
25
|
+
const verdict = computeDeterminismVerdict(decisionRecorder);
|
|
26
|
+
|
|
27
|
+
const report = {
|
|
28
|
+
version: 1,
|
|
29
|
+
contractVersion: 1,
|
|
30
|
+
artifactVersions: getArtifactVersions(),
|
|
31
|
+
generatedAt: new Date().toISOString(),
|
|
32
|
+
// PHASE 21.2: HARD TRUTH - binary verdict only
|
|
33
|
+
verdict: verdict.verdict,
|
|
34
|
+
message: verdict.message,
|
|
35
|
+
reasons: verdict.reasons,
|
|
36
|
+
adaptiveEvents: verdict.adaptiveEvents,
|
|
37
|
+
// PHASE 21.2: Decision summary for transparency
|
|
38
|
+
decisionSummary: decisionRecorder ? decisionRecorder.getSummary() : null,
|
|
39
|
+
// PHASE 21.2: Contract definition
|
|
40
|
+
contract: {
|
|
41
|
+
deterministic: 'Same inputs + same environment + same config → identical normalized artifacts',
|
|
42
|
+
nonDeterministic: 'Any adaptive behavior (adaptive stabilization, retries, truncations) → NON_DETERMINISTIC',
|
|
43
|
+
tracking: 'Tracking adaptive decisions is NOT determinism. Only absence of adaptive events = DETERMINISTIC.'
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
writeFileSync(reportPath, JSON.stringify(report, null, 2) + '\n');
|
|
48
|
+
|
|
49
|
+
return reportPath;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* PHASE 21.2: Write determinism report from decisions.json file
|
|
54
|
+
*
|
|
55
|
+
* @param {string} runDir - Run directory path
|
|
56
|
+
* @returns {string|null} Path to determinism report, or null if decisions.json not found
|
|
57
|
+
*/
|
|
58
|
+
export function writeDeterminismReportFromFile(runDir) {
|
|
59
|
+
const decisionsPath = resolve(runDir, 'decisions.json');
|
|
60
|
+
|
|
61
|
+
if (!existsSync(decisionsPath)) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const decisionsData = JSON.parse(readFileSync(decisionsPath, 'utf-8'));
|
|
67
|
+
const decisionRecorder = DecisionRecorder.fromExport(decisionsData);
|
|
68
|
+
return writeDeterminismReport(runDir, decisionRecorder);
|
|
69
|
+
} catch (error) {
|
|
70
|
+
// If we can't read decisions, we can't determine determinism
|
|
71
|
+
const reportPath = resolve(runDir, ARTIFACT_REGISTRY.determinismReport.filename);
|
|
72
|
+
const report = {
|
|
73
|
+
version: 1,
|
|
74
|
+
contractVersion: 1,
|
|
75
|
+
artifactVersions: getArtifactVersions(),
|
|
76
|
+
generatedAt: new Date().toISOString(),
|
|
77
|
+
verdict: DETERMINISM_VERDICT.NON_DETERMINISTIC,
|
|
78
|
+
message: `Cannot determine determinism: ${error.message}`,
|
|
79
|
+
reasons: [DETERMINISM_REASON.ENVIRONMENT_VARIANCE],
|
|
80
|
+
adaptiveEvents: [],
|
|
81
|
+
decisionSummary: null,
|
|
82
|
+
contract: {
|
|
83
|
+
deterministic: 'Same inputs + same environment + same config → identical normalized artifacts',
|
|
84
|
+
nonDeterministic: 'Any adaptive behavior (adaptive stabilization, retries, truncations) → NON_DETERMINISTIC',
|
|
85
|
+
tracking: 'Tracking adaptive decisions is NOT determinism. Only absence of adaptive events = DETERMINISTIC.'
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
writeFileSync(reportPath, JSON.stringify(report, null, 2) + '\n');
|
|
89
|
+
return reportPath;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 25 — Run Fingerprint
|
|
3
|
+
*
|
|
4
|
+
* Computes stable run fingerprint from deterministic inputs.
|
|
5
|
+
* Used to verify that repeated runs have identical inputs.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createHash } from 'crypto';
|
|
9
|
+
import { readFileSync, existsSync } from 'fs';
|
|
10
|
+
import { resolve, dirname } from 'path';
|
|
11
|
+
import { getVeraxVersion } from '../run-id.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Compute run fingerprint from deterministic inputs
|
|
15
|
+
*
|
|
16
|
+
* @param {Object} params - Run parameters
|
|
17
|
+
* @param {string} params.url - Target URL
|
|
18
|
+
* @param {string} params.projectDir - Project directory
|
|
19
|
+
* @param {string} params.manifestPath - Manifest path (optional)
|
|
20
|
+
* @param {Object} params.policyHash - Policy hash (optional)
|
|
21
|
+
* @param {string} params.fixtureId - Fixture ID (optional)
|
|
22
|
+
* @returns {string} Run fingerprint (hex hash)
|
|
23
|
+
*/
|
|
24
|
+
export function computeRunFingerprint(params) {
|
|
25
|
+
const {
|
|
26
|
+
url,
|
|
27
|
+
projectDir,
|
|
28
|
+
manifestPath = null,
|
|
29
|
+
policyHash = null,
|
|
30
|
+
fixtureId = null
|
|
31
|
+
} = params;
|
|
32
|
+
|
|
33
|
+
// Compute source hash (hash of all source files)
|
|
34
|
+
const srcHash = computeSourceHash(projectDir);
|
|
35
|
+
|
|
36
|
+
// Compute policy hash if not provided
|
|
37
|
+
const computedPolicyHash = policyHash || computePolicyHash(projectDir);
|
|
38
|
+
|
|
39
|
+
// Get VERAX version
|
|
40
|
+
const veraxVersion = getVeraxVersion();
|
|
41
|
+
|
|
42
|
+
// Build fingerprint input
|
|
43
|
+
const fingerprintInput = {
|
|
44
|
+
url: normalizeUrl(url),
|
|
45
|
+
srcHash,
|
|
46
|
+
policyHash: computedPolicyHash,
|
|
47
|
+
veraxVersion,
|
|
48
|
+
fixtureId: fixtureId || null
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Generate stable hash
|
|
52
|
+
const configString = JSON.stringify(fingerprintInput, Object.keys(fingerprintInput).sort());
|
|
53
|
+
const hash = createHash('sha256').update(configString).digest('hex');
|
|
54
|
+
|
|
55
|
+
return hash.substring(0, 32); // 32 chars for readability
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Compute source hash (hash of all source files in project)
|
|
60
|
+
*/
|
|
61
|
+
function computeSourceHash(projectDir) {
|
|
62
|
+
try {
|
|
63
|
+
// For now, use a simple approach: hash package.json + src directory structure
|
|
64
|
+
// In production, this should hash all source files
|
|
65
|
+
const packagePath = resolve(projectDir, 'package.json');
|
|
66
|
+
if (existsSync(packagePath)) {
|
|
67
|
+
const pkgContent = readFileSync(packagePath, 'utf-8');
|
|
68
|
+
return createHash('sha256').update(pkgContent).digest('hex').substring(0, 16);
|
|
69
|
+
}
|
|
70
|
+
return 'no-source';
|
|
71
|
+
} catch {
|
|
72
|
+
return 'unknown';
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Compute policy hash (hash of guardrails/confidence policies)
|
|
78
|
+
*/
|
|
79
|
+
function computePolicyHash(projectDir) {
|
|
80
|
+
try {
|
|
81
|
+
// Hash default policies (in production, should hash all policy files)
|
|
82
|
+
const policyPaths = [
|
|
83
|
+
resolve(projectDir, '.verax', 'guardrails.policy.json'),
|
|
84
|
+
resolve(projectDir, '.verax', 'confidence.policy.json')
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
const hashes = [];
|
|
88
|
+
for (const path of policyPaths) {
|
|
89
|
+
if (existsSync(path)) {
|
|
90
|
+
const content = readFileSync(path, 'utf-8');
|
|
91
|
+
hashes.push(createHash('sha256').update(content).digest('hex').substring(0, 8));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (hashes.length > 0) {
|
|
96
|
+
return createHash('sha256').update(hashes.join('|')).digest('hex').substring(0, 16);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return 'default-policy';
|
|
100
|
+
} catch {
|
|
101
|
+
return 'unknown-policy';
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Normalize URL for fingerprint
|
|
107
|
+
*/
|
|
108
|
+
function normalizeUrl(url) {
|
|
109
|
+
if (!url || typeof url !== 'string') return '';
|
|
110
|
+
try {
|
|
111
|
+
const urlObj = new URL(url);
|
|
112
|
+
// Normalize: remove trailing slash, lowercase host
|
|
113
|
+
return `${urlObj.protocol}//${urlObj.host.toLowerCase()}${urlObj.pathname.replace(/\/$/, '') || '/'}`;
|
|
114
|
+
} catch {
|
|
115
|
+
return url;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|