@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.
Files changed (213) hide show
  1. package/README.md +10 -6
  2. package/bin/verax.js +11 -11
  3. package/package.json +29 -8
  4. package/src/cli/commands/baseline.js +103 -0
  5. package/src/cli/commands/default.js +51 -6
  6. package/src/cli/commands/doctor.js +29 -0
  7. package/src/cli/commands/ga.js +246 -0
  8. package/src/cli/commands/gates.js +95 -0
  9. package/src/cli/commands/inspect.js +4 -2
  10. package/src/cli/commands/release-check.js +215 -0
  11. package/src/cli/commands/run.js +45 -6
  12. package/src/cli/commands/security-check.js +212 -0
  13. package/src/cli/commands/truth.js +113 -0
  14. package/src/cli/entry.js +30 -20
  15. package/src/cli/util/angular-component-extractor.js +179 -0
  16. package/src/cli/util/angular-navigation-detector.js +141 -0
  17. package/src/cli/util/angular-network-detector.js +161 -0
  18. package/src/cli/util/angular-state-detector.js +162 -0
  19. package/src/cli/util/ast-interactive-detector.js +544 -0
  20. package/src/cli/util/ast-network-detector.js +603 -0
  21. package/src/cli/util/ast-promise-extractor.js +581 -0
  22. package/src/cli/util/ast-usestate-detector.js +602 -0
  23. package/src/cli/util/atomic-write.js +12 -1
  24. package/src/cli/util/bootstrap-guard.js +86 -0
  25. package/src/cli/util/console-reporter.js +72 -0
  26. package/src/cli/util/detection-engine.js +105 -41
  27. package/src/cli/util/determinism-runner.js +124 -0
  28. package/src/cli/util/determinism-writer.js +129 -0
  29. package/src/cli/util/digest-engine.js +359 -0
  30. package/src/cli/util/dom-diff.js +226 -0
  31. package/src/cli/util/evidence-engine.js +287 -0
  32. package/src/cli/util/expectation-extractor.js +151 -5
  33. package/src/cli/util/findings-writer.js +3 -0
  34. package/src/cli/util/framework-detector.js +572 -0
  35. package/src/cli/util/idgen.js +1 -1
  36. package/src/cli/util/interaction-planner.js +529 -0
  37. package/src/cli/util/learn-writer.js +2 -0
  38. package/src/cli/util/ledger-writer.js +110 -0
  39. package/src/cli/util/monorepo-resolver.js +162 -0
  40. package/src/cli/util/observation-engine.js +127 -278
  41. package/src/cli/util/observe-writer.js +2 -0
  42. package/src/cli/util/project-discovery.js +284 -0
  43. package/src/cli/util/project-writer.js +2 -0
  44. package/src/cli/util/run-id.js +23 -27
  45. package/src/cli/util/run-resolver.js +64 -0
  46. package/src/cli/util/run-result.js +778 -0
  47. package/src/cli/util/selector-resolver.js +235 -0
  48. package/src/cli/util/source-requirement.js +55 -0
  49. package/src/cli/util/summary-writer.js +2 -0
  50. package/src/cli/util/svelte-navigation-detector.js +163 -0
  51. package/src/cli/util/svelte-network-detector.js +80 -0
  52. package/src/cli/util/svelte-sfc-extractor.js +146 -0
  53. package/src/cli/util/svelte-state-detector.js +242 -0
  54. package/src/cli/util/trust-activation-integration.js +496 -0
  55. package/src/cli/util/trust-activation-wrapper.js +85 -0
  56. package/src/cli/util/trust-integration-hooks.js +164 -0
  57. package/src/cli/util/types.js +153 -0
  58. package/src/cli/util/url-validation.js +40 -0
  59. package/src/cli/util/vue-navigation-detector.js +178 -0
  60. package/src/cli/util/vue-sfc-extractor.js +161 -0
  61. package/src/cli/util/vue-state-detector.js +215 -0
  62. package/src/types/fs-augment.d.ts +23 -0
  63. package/src/types/global.d.ts +137 -0
  64. package/src/types/internal-types.d.ts +35 -0
  65. package/src/verax/cli/init.js +4 -18
  66. package/src/verax/core/action-classifier.js +4 -3
  67. package/src/verax/core/artifacts/registry.js +139 -0
  68. package/src/verax/core/artifacts/verifier.js +990 -0
  69. package/src/verax/core/baseline/baseline.enforcer.js +137 -0
  70. package/src/verax/core/baseline/baseline.snapshot.js +233 -0
  71. package/src/verax/core/capabilities/gates.js +505 -0
  72. package/src/verax/core/capabilities/registry.js +475 -0
  73. package/src/verax/core/confidence/confidence-compute.js +144 -0
  74. package/src/verax/core/confidence/confidence-invariants.js +234 -0
  75. package/src/verax/core/confidence/confidence-report-writer.js +112 -0
  76. package/src/verax/core/confidence/confidence-weights.js +44 -0
  77. package/src/verax/core/confidence/confidence.defaults.js +65 -0
  78. package/src/verax/core/confidence/confidence.loader.js +80 -0
  79. package/src/verax/core/confidence/confidence.schema.js +94 -0
  80. package/src/verax/core/confidence-engine-refactor.js +489 -0
  81. package/src/verax/core/confidence-engine.js +625 -0
  82. package/src/verax/core/contracts/index.js +29 -0
  83. package/src/verax/core/contracts/types.js +186 -0
  84. package/src/verax/core/contracts/validators.js +456 -0
  85. package/src/verax/core/decisions/decision.trace.js +278 -0
  86. package/src/verax/core/determinism/contract-writer.js +89 -0
  87. package/src/verax/core/determinism/contract.js +139 -0
  88. package/src/verax/core/determinism/diff.js +405 -0
  89. package/src/verax/core/determinism/engine.js +222 -0
  90. package/src/verax/core/determinism/finding-identity.js +149 -0
  91. package/src/verax/core/determinism/normalize.js +466 -0
  92. package/src/verax/core/determinism/report-writer.js +93 -0
  93. package/src/verax/core/determinism/run-fingerprint.js +123 -0
  94. package/src/verax/core/dynamic-route-intelligence.js +529 -0
  95. package/src/verax/core/evidence/evidence-capture-service.js +308 -0
  96. package/src/verax/core/evidence/evidence-intent-ledger.js +166 -0
  97. package/src/verax/core/evidence-builder.js +487 -0
  98. package/src/verax/core/execution-mode-context.js +77 -0
  99. package/src/verax/core/execution-mode-detector.js +192 -0
  100. package/src/verax/core/failures/exit-codes.js +88 -0
  101. package/src/verax/core/failures/failure-summary.js +76 -0
  102. package/src/verax/core/failures/failure.factory.js +225 -0
  103. package/src/verax/core/failures/failure.ledger.js +133 -0
  104. package/src/verax/core/failures/failure.types.js +196 -0
  105. package/src/verax/core/failures/index.js +10 -0
  106. package/src/verax/core/ga/ga-report-writer.js +43 -0
  107. package/src/verax/core/ga/ga.artifact.js +49 -0
  108. package/src/verax/core/ga/ga.contract.js +435 -0
  109. package/src/verax/core/ga/ga.enforcer.js +87 -0
  110. package/src/verax/core/guardrails/guardrails-report-writer.js +109 -0
  111. package/src/verax/core/guardrails/policy.defaults.js +210 -0
  112. package/src/verax/core/guardrails/policy.loader.js +84 -0
  113. package/src/verax/core/guardrails/policy.schema.js +110 -0
  114. package/src/verax/core/guardrails/truth-reconciliation.js +136 -0
  115. package/src/verax/core/guardrails-engine.js +505 -0
  116. package/src/verax/core/incremental-store.js +1 -0
  117. package/src/verax/core/integrity/budget.js +138 -0
  118. package/src/verax/core/integrity/determinism.js +342 -0
  119. package/src/verax/core/integrity/integrity.js +208 -0
  120. package/src/verax/core/integrity/poisoning.js +108 -0
  121. package/src/verax/core/integrity/transaction.js +140 -0
  122. package/src/verax/core/observe/run-timeline.js +318 -0
  123. package/src/verax/core/perf/perf.contract.js +186 -0
  124. package/src/verax/core/perf/perf.display.js +65 -0
  125. package/src/verax/core/perf/perf.enforcer.js +91 -0
  126. package/src/verax/core/perf/perf.monitor.js +209 -0
  127. package/src/verax/core/perf/perf.report.js +200 -0
  128. package/src/verax/core/pipeline-tracker.js +243 -0
  129. package/src/verax/core/product-definition.js +127 -0
  130. package/src/verax/core/release/provenance.builder.js +130 -0
  131. package/src/verax/core/release/release-report-writer.js +40 -0
  132. package/src/verax/core/release/release.enforcer.js +164 -0
  133. package/src/verax/core/release/reproducibility.check.js +222 -0
  134. package/src/verax/core/release/sbom.builder.js +292 -0
  135. package/src/verax/core/replay-validator.js +2 -0
  136. package/src/verax/core/replay.js +4 -0
  137. package/src/verax/core/report/cross-index.js +195 -0
  138. package/src/verax/core/report/human-summary.js +362 -0
  139. package/src/verax/core/route-intelligence.js +420 -0
  140. package/src/verax/core/run-id.js +6 -3
  141. package/src/verax/core/run-manifest.js +4 -3
  142. package/src/verax/core/security/secrets.scan.js +329 -0
  143. package/src/verax/core/security/security-report.js +50 -0
  144. package/src/verax/core/security/security.enforcer.js +128 -0
  145. package/src/verax/core/security/supplychain.defaults.json +38 -0
  146. package/src/verax/core/security/supplychain.policy.js +334 -0
  147. package/src/verax/core/security/vuln.scan.js +265 -0
  148. package/src/verax/core/truth/truth.certificate.js +252 -0
  149. package/src/verax/core/ui-feedback-intelligence.js +481 -0
  150. package/src/verax/detect/conditional-ui-silent-failure.js +84 -0
  151. package/src/verax/detect/confidence-engine.js +62 -34
  152. package/src/verax/detect/confidence-helper.js +34 -0
  153. package/src/verax/detect/dynamic-route-findings.js +338 -0
  154. package/src/verax/detect/expectation-chain-detector.js +417 -0
  155. package/src/verax/detect/expectation-model.js +2 -2
  156. package/src/verax/detect/failure-cause-inference.js +293 -0
  157. package/src/verax/detect/findings-writer.js +131 -35
  158. package/src/verax/detect/flow-detector.js +2 -2
  159. package/src/verax/detect/form-silent-failure.js +98 -0
  160. package/src/verax/detect/index.js +46 -5
  161. package/src/verax/detect/invariants-enforcer.js +147 -0
  162. package/src/verax/detect/journey-stall-detector.js +558 -0
  163. package/src/verax/detect/navigation-silent-failure.js +82 -0
  164. package/src/verax/detect/problem-aggregator.js +361 -0
  165. package/src/verax/detect/route-findings.js +219 -0
  166. package/src/verax/detect/summary-writer.js +477 -0
  167. package/src/verax/detect/test-failure-cause-inference.js +314 -0
  168. package/src/verax/detect/ui-feedback-findings.js +207 -0
  169. package/src/verax/detect/view-switch-correlator.js +242 -0
  170. package/src/verax/flow/flow-engine.js +2 -1
  171. package/src/verax/flow/flow-spec.js +0 -6
  172. package/src/verax/index.js +4 -0
  173. package/src/verax/intel/ts-program.js +1 -0
  174. package/src/verax/intel/vue-navigation-extractor.js +3 -0
  175. package/src/verax/learn/action-contract-extractor.js +3 -0
  176. package/src/verax/learn/ast-contract-extractor.js +1 -1
  177. package/src/verax/learn/flow-extractor.js +1 -0
  178. package/src/verax/learn/project-detector.js +5 -0
  179. package/src/verax/learn/react-router-extractor.js +2 -0
  180. package/src/verax/learn/source-instrumenter.js +1 -0
  181. package/src/verax/learn/state-extractor.js +2 -1
  182. package/src/verax/learn/static-extractor.js +1 -0
  183. package/src/verax/observe/coverage-gaps.js +132 -0
  184. package/src/verax/observe/expectation-handler.js +126 -0
  185. package/src/verax/observe/incremental-skip.js +46 -0
  186. package/src/verax/observe/index.js +51 -155
  187. package/src/verax/observe/interaction-executor.js +192 -0
  188. package/src/verax/observe/interaction-runner.js +782 -513
  189. package/src/verax/observe/network-firewall.js +86 -0
  190. package/src/verax/observe/observation-builder.js +169 -0
  191. package/src/verax/observe/observe-context.js +205 -0
  192. package/src/verax/observe/observe-helpers.js +192 -0
  193. package/src/verax/observe/observe-runner.js +230 -0
  194. package/src/verax/observe/observers/budget-observer.js +185 -0
  195. package/src/verax/observe/observers/console-observer.js +102 -0
  196. package/src/verax/observe/observers/coverage-observer.js +107 -0
  197. package/src/verax/observe/observers/interaction-observer.js +471 -0
  198. package/src/verax/observe/observers/navigation-observer.js +132 -0
  199. package/src/verax/observe/observers/network-observer.js +87 -0
  200. package/src/verax/observe/observers/safety-observer.js +82 -0
  201. package/src/verax/observe/observers/ui-feedback-observer.js +99 -0
  202. package/src/verax/observe/page-traversal.js +138 -0
  203. package/src/verax/observe/snapshot-ops.js +94 -0
  204. package/src/verax/observe/ui-feedback-detector.js +742 -0
  205. package/src/verax/scan-summary-writer.js +2 -0
  206. package/src/verax/shared/artifact-manager.js +25 -5
  207. package/src/verax/shared/caching.js +1 -0
  208. package/src/verax/shared/css-spinner-rules.js +204 -0
  209. package/src/verax/shared/expectation-tracker.js +1 -0
  210. package/src/verax/shared/view-switch-rules.js +208 -0
  211. package/src/verax/shared/zip-artifacts.js +6 -0
  212. package/src/verax/shared/config-loader.js +0 -169
  213. /package/src/verax/shared/{expectation-proof.js → expectation-validation.js} +0 -0
@@ -0,0 +1,149 @@
1
+ /**
2
+ * PHASE 18 — Stable Finding Identity
3
+ *
4
+ * Computes stable identity keys for findings that are consistent across runs.
5
+ * Used for matching findings between runs for comparison.
6
+ */
7
+
8
+ import { createHash } from 'crypto';
9
+
10
+ /**
11
+ * PHASE 18: Compute stable finding identity
12
+ *
13
+ * Uses type + promise signature + route/network target + interaction selector/context chain
14
+ * Includes evidence trigger source signature where available (AST snippet hash or location)
15
+ * Must NOT include runId/timestamps
16
+ *
17
+ * @param {Object} finding - Finding object
18
+ * @returns {string} Stable identity key
19
+ */
20
+ export function computeFindingIdentity(finding) {
21
+ const parts = [];
22
+
23
+ // Part 1: Finding type
24
+ parts.push(`type:${finding.type || 'unknown'}`);
25
+
26
+ // Part 2: Interaction signature
27
+ const interaction = finding.interaction || {};
28
+ if (interaction.type && interaction.selector) {
29
+ parts.push(`interaction:${interaction.type}:${normalizeSelector(interaction.selector)}`);
30
+ }
31
+ if (interaction.label) {
32
+ parts.push(`label:${interaction.label}`);
33
+ }
34
+
35
+ // Part 3: Promise signature
36
+ const expectation = finding.expectation || {};
37
+ const promise = finding.promise || {};
38
+
39
+ if (expectation.type) {
40
+ parts.push(`promiseType:${expectation.type}`);
41
+ }
42
+ if (expectation.targetPath) {
43
+ parts.push(`targetPath:${normalizePath(expectation.targetPath)}`);
44
+ }
45
+ if (expectation.urlPath) {
46
+ parts.push(`urlPath:${normalizePath(expectation.urlPath)}`);
47
+ }
48
+ if (promise.type) {
49
+ parts.push(`promise:${promise.type}`);
50
+ }
51
+
52
+ // Part 4: Route/network target
53
+ if (finding.route) {
54
+ const route = finding.route;
55
+ if (route.path) {
56
+ parts.push(`route:${normalizePath(route.path)}`);
57
+ }
58
+ if (route.originalPattern) {
59
+ parts.push(`routePattern:${normalizePath(route.originalPattern)}`);
60
+ }
61
+ }
62
+
63
+ if (finding.evidence?.networkRequest?.url) {
64
+ parts.push(`networkUrl:${normalizeUrl(finding.evidence.networkRequest.url)}`);
65
+ }
66
+
67
+ // Part 5: Evidence trigger source signature
68
+ const source = finding.source || expectation.source || {};
69
+ if (source.file) {
70
+ parts.push(`sourceFile:${normalizePath(source.file)}`);
71
+ }
72
+ if (source.line) {
73
+ parts.push(`sourceLine:${source.line}`);
74
+ }
75
+ if (source.astSource) {
76
+ // Hash AST source for stability
77
+ const astHash = hashString(source.astSource);
78
+ parts.push(`astHash:${astHash}`);
79
+ }
80
+
81
+ // Part 6: Evidence trigger from evidencePackage
82
+ if (finding.evidencePackage?.trigger?.astSource) {
83
+ const triggerHash = hashString(finding.evidencePackage.trigger.astSource);
84
+ parts.push(`triggerHash:${triggerHash}`);
85
+ }
86
+ if (finding.evidencePackage?.trigger?.source?.file) {
87
+ parts.push(`triggerFile:${normalizePath(finding.evidencePackage.trigger.source.file)}`);
88
+ }
89
+ if (finding.evidencePackage?.trigger?.source?.line) {
90
+ parts.push(`triggerLine:${finding.evidencePackage.trigger.source.line}`);
91
+ }
92
+
93
+ // Part 7: Context chain (if available)
94
+ if (finding.contextChain && Array.isArray(finding.contextChain)) {
95
+ const contextSig = finding.contextChain.map(c => `${c.type}:${c.name || ''}`).join('>');
96
+ parts.push(`context:${contextSig}`);
97
+ }
98
+
99
+ // Combine all parts into stable identity
100
+ const identity = parts.join('|');
101
+
102
+ // Return hash for compactness and stability
103
+ return hashString(identity);
104
+ }
105
+
106
+ /**
107
+ * Normalize selector (remove volatile parts)
108
+ */
109
+ function normalizeSelector(selector) {
110
+ if (!selector || typeof selector !== 'string') return '';
111
+ // Remove any dynamic IDs or classes that might change
112
+ return selector.replace(/\[data-[^\]]+\]/g, '').replace(/\.\w+-\d+/g, '');
113
+ }
114
+
115
+ /**
116
+ * Normalize path (remove absolute paths, normalize separators)
117
+ */
118
+ function normalizePath(path) {
119
+ if (!path || typeof path !== 'string') return '';
120
+ // Normalize separators
121
+ let normalized = path.replace(/\\/g, '/');
122
+ // Remove absolute path prefixes (keep relative structure)
123
+ normalized = normalized.replace(/^[A-Z]:\/[^/]+/, '');
124
+ normalized = normalized.replace(/^\/[^/]+/, '');
125
+ return normalized;
126
+ }
127
+
128
+ /**
129
+ * Normalize URL (remove query params, hash, normalize domain)
130
+ */
131
+ function normalizeUrl(url) {
132
+ if (!url || typeof url !== 'string') return '';
133
+ try {
134
+ const urlObj = new URL(url);
135
+ // Keep only pathname for comparison
136
+ return urlObj.pathname;
137
+ } catch {
138
+ return url;
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Hash string for stable identity
144
+ */
145
+ function hashString(str) {
146
+ // @ts-expect-error - digest returns string
147
+ return createHash('sha256').update(str).digest('hex').substring(0, 16);
148
+ }
149
+
@@ -0,0 +1,466 @@
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
+ function normalizeDeterminismContract(artifact) {
63
+ return normalizeGeneric(artifact);
64
+ }
65
+
66
+ function normalizeRunMeta(artifact) {
67
+ return normalizeGeneric(artifact);
68
+ }
69
+
70
+ function normalizeTraces(artifact) {
71
+ return normalizeGeneric(artifact);
72
+ }
73
+
74
+ function normalizePerformanceReport(artifact) {
75
+ return normalizeGeneric(artifact);
76
+ }
77
+
78
+ function normalizeSecurityReport(artifact) {
79
+ return normalizeGeneric(artifact);
80
+ }
81
+
82
+ function normalizeGAReport(artifact) {
83
+ return normalizeGeneric(artifact);
84
+ }
85
+
86
+ function normalizeReleaseReport(artifact) {
87
+ return normalizeGeneric(artifact);
88
+ }
89
+
90
+ /**
91
+ * Normalize findings artifact
92
+ */
93
+ function normalizeFindings(artifact) {
94
+ const normalized = { ...artifact };
95
+
96
+ // Remove volatile top-level fields
97
+ delete normalized.detectedAt;
98
+ delete normalized.runId;
99
+ delete normalized.timestamp;
100
+
101
+ // Normalize findings array
102
+ if (Array.isArray(normalized.findings)) {
103
+ normalized.findings = normalized.findings.map(f => normalizeFinding(f));
104
+ // Sort by stable identity (if we had it, but for now sort by type + interaction)
105
+ normalized.findings.sort((a, b) => {
106
+ const keyA = `${a.type || ''}|${a.interaction?.selector || ''}|${a.interaction?.type || ''}`;
107
+ const keyB = `${b.type || ''}|${b.interaction?.selector || ''}|${b.interaction?.type || ''}`;
108
+ return keyA.localeCompare(keyB);
109
+ });
110
+ }
111
+
112
+ // Normalize enforcement metadata (keep structure, remove timestamps)
113
+ if (normalized.enforcement) {
114
+ const enforcement = { ...normalized.enforcement };
115
+ // Keep enforcement data but normalize any timestamps
116
+ normalized.enforcement = enforcement;
117
+ }
118
+
119
+ return normalized;
120
+ }
121
+
122
+ /**
123
+ * Normalize individual finding
124
+ */
125
+ function normalizeFinding(finding) {
126
+ const normalized = { ...finding };
127
+
128
+ // Remove volatile fields
129
+ delete normalized.id;
130
+ delete normalized.findingId;
131
+ delete normalized.timestamp;
132
+ delete normalized.detectedAt;
133
+
134
+ // Normalize confidence (round to 3 decimals)
135
+ if (typeof normalized.confidence === 'number') {
136
+ normalized.confidence = Math.round(normalized.confidence * 1000) / 1000;
137
+ }
138
+
139
+ // Normalize evidence paths (keep structure, normalize paths)
140
+ if (normalized.evidence) {
141
+ normalized.evidence = normalizeEvidence(normalized.evidence);
142
+ }
143
+
144
+ // Normalize evidencePackage
145
+ if (normalized.evidencePackage) {
146
+ normalized.evidencePackage = normalizeEvidencePackage(normalized.evidencePackage);
147
+ }
148
+
149
+ // Normalize guardrails (keep structure, normalize confidence deltas)
150
+ if (normalized.guardrails) {
151
+ const guardrails = { ...normalized.guardrails };
152
+ if (typeof guardrails.confidenceDelta === 'number') {
153
+ guardrails.confidenceDelta = Math.round(guardrails.confidenceDelta * 1000) / 1000;
154
+ }
155
+ normalized.guardrails = guardrails;
156
+ }
157
+
158
+ return normalized;
159
+ }
160
+
161
+ /**
162
+ * Normalize evidence
163
+ */
164
+ function normalizeEvidence(evidence) {
165
+ const normalized = { ...evidence };
166
+
167
+ // Normalize screenshot paths (keep presence, normalize path)
168
+ if (normalized.before) {
169
+ normalized.before = normalizePath(normalized.before);
170
+ }
171
+ if (normalized.after) {
172
+ normalized.after = normalizePath(normalized.after);
173
+ }
174
+
175
+ // Normalize URLs (remove query params, hash)
176
+ if (normalized.beforeUrl) {
177
+ normalized.beforeUrl = normalizeUrl(normalized.beforeUrl);
178
+ }
179
+ if (normalized.afterUrl) {
180
+ normalized.afterUrl = normalizeUrl(normalized.afterUrl);
181
+ }
182
+
183
+ // Normalize source paths
184
+ if (normalized.source && typeof normalized.source === 'string') {
185
+ normalized.source = normalizePath(normalized.source);
186
+ }
187
+
188
+ return normalized;
189
+ }
190
+
191
+ /**
192
+ * Normalize evidencePackage
193
+ */
194
+ function normalizeEvidencePackage(evidencePackage) {
195
+ const normalized = { ...evidencePackage };
196
+
197
+ // Normalize before/after paths
198
+ if (normalized.before) {
199
+ normalized.before = {
200
+ ...normalized.before,
201
+ screenshot: normalized.before.screenshot ? normalizePath(normalized.before.screenshot) : null,
202
+ url: normalized.before.url ? normalizeUrl(normalized.before.url) : null,
203
+ };
204
+ }
205
+
206
+ if (normalized.after) {
207
+ normalized.after = {
208
+ ...normalized.after,
209
+ screenshot: normalized.after.screenshot ? normalizePath(normalized.after.screenshot) : null,
210
+ url: normalized.after.url ? normalizeUrl(normalized.after.url) : null,
211
+ };
212
+ }
213
+
214
+ // Normalize trigger source paths
215
+ if (normalized.trigger?.source?.file) {
216
+ normalized.trigger.source.file = normalizePath(normalized.trigger.source.file);
217
+ }
218
+
219
+ return normalized;
220
+ }
221
+
222
+ /**
223
+ * Normalize runStatus artifact
224
+ */
225
+ function normalizeRunStatus(artifact) {
226
+ const normalized = { ...artifact };
227
+
228
+ // Remove volatile fields
229
+ delete normalized.startedAt;
230
+ delete normalized.completedAt;
231
+ delete normalized.timestamp;
232
+ delete normalized.runId;
233
+
234
+ // Keep structure but remove timestamps from nested objects
235
+ if (normalized.verification) {
236
+ const verification = { ...normalized.verification };
237
+ delete verification.verifiedAt;
238
+ normalized.verification = verification;
239
+ }
240
+
241
+ return normalized;
242
+ }
243
+
244
+ /**
245
+ * Normalize summary artifact
246
+ */
247
+ function normalizeSummary(artifact) {
248
+ const normalized = { ...artifact };
249
+
250
+ // Remove volatile fields
251
+ delete normalized.timestamp;
252
+ delete normalized.runId;
253
+
254
+ // Normalize metrics (round durations)
255
+ if (normalized.metrics) {
256
+ const metrics = { ...normalized.metrics };
257
+ for (const key in metrics) {
258
+ if (typeof metrics[key] === 'number' && key.includes('Ms')) {
259
+ metrics[key] = Math.round(metrics[key]);
260
+ }
261
+ }
262
+ normalized.metrics = metrics;
263
+ }
264
+
265
+ return normalized;
266
+ }
267
+
268
+ /**
269
+ * Normalize learn artifact
270
+ */
271
+ function normalizeLearn(artifact) {
272
+ const normalized = { ...artifact };
273
+
274
+ // Remove volatile fields
275
+ delete normalized.learnedAt;
276
+ delete normalized.timestamp;
277
+ delete normalized.runId;
278
+
279
+ // Normalize routes array (sort by path)
280
+ if (Array.isArray(normalized.routes)) {
281
+ normalized.routes = [...normalized.routes].sort((a, b) => {
282
+ const pathA = a.path || '';
283
+ const pathB = b.path || '';
284
+ return pathA.localeCompare(pathB);
285
+ });
286
+ }
287
+
288
+ // Normalize expectations array (sort by type + target)
289
+ if (Array.isArray(normalized.expectations)) {
290
+ normalized.expectations = [...normalized.expectations].sort((a, b) => {
291
+ const keyA = `${a.type || ''}|${a.targetPath || ''}`;
292
+ const keyB = `${b.type || ''}|${b.targetPath || ''}`;
293
+ return keyA.localeCompare(keyB);
294
+ });
295
+ }
296
+
297
+ return normalized;
298
+ }
299
+
300
+ /**
301
+ * PHASE 22: Normalize evidence intent ledger
302
+ */
303
+ function normalizeEvidenceIntent(artifact) {
304
+ const normalized = { ...artifact };
305
+
306
+ // Remove volatile fields
307
+ delete normalized.generatedAt;
308
+
309
+ // Normalize entries (already sorted by findingIdentity, but normalize timestamps)
310
+ if (Array.isArray(normalized.entries)) {
311
+ normalized.entries = normalized.entries.map(entry => {
312
+ const normalizedEntry = { ...entry };
313
+ delete normalizedEntry.timestamp;
314
+
315
+ // Normalize capture outcomes (preserve pass/fail, normalize failure details)
316
+ if (normalizedEntry.captureOutcomes) {
317
+ const outcomes = {};
318
+ for (const [field, outcome] of Object.entries(normalizedEntry.captureOutcomes)) {
319
+ outcomes[field] = {
320
+ required: outcome.required,
321
+ captured: outcome.captured,
322
+ failure: outcome.failure ? {
323
+ stage: outcome.failure.stage,
324
+ reasonCode: outcome.failure.reasonCode,
325
+ reason: outcome.failure.reason
326
+ // Exclude stackSummary and timestamp from failure for determinism
327
+ } : null
328
+ };
329
+ }
330
+ normalizedEntry.captureOutcomes = outcomes;
331
+ }
332
+
333
+ return normalizedEntry;
334
+ });
335
+ }
336
+
337
+ return normalized;
338
+ }
339
+
340
+ /**
341
+ * PHASE 23: Normalize guardrails report
342
+ */
343
+ function normalizeGuardrailsReport(artifact) {
344
+ const normalized = { ...artifact };
345
+
346
+ // Remove volatile fields
347
+ delete normalized.generatedAt;
348
+
349
+ // Normalize summary (preserve counts, normalize topRules ordering)
350
+ if (normalized.summary && normalized.summary.topRules) {
351
+ normalized.summary.topRules = normalized.summary.topRules.map(r => ({
352
+ code: r.code,
353
+ count: r.count
354
+ }));
355
+ }
356
+
357
+ // Normalize perFinding (already sorted by findingIdentity, but normalize timestamps if any)
358
+ if (normalized.perFinding && typeof normalized.perFinding === 'object') {
359
+ const perFindingNormalized = {};
360
+ const sortedKeys = Object.keys(normalized.perFinding).sort();
361
+ for (const key of sortedKeys) {
362
+ const entry = normalized.perFinding[key];
363
+ perFindingNormalized[key] = {
364
+ ...entry,
365
+ // Remove any volatile fields from entry
366
+ };
367
+ }
368
+ normalized.perFinding = perFindingNormalized;
369
+ }
370
+
371
+ return normalized;
372
+ }
373
+
374
+ /**
375
+ * PHASE 24: Normalize confidence report
376
+ */
377
+ function normalizeConfidenceReport(artifact) {
378
+ const normalized = { ...artifact };
379
+
380
+ // Remove volatile fields
381
+ delete normalized.generatedAt;
382
+
383
+ // Normalize summary (preserve counts)
384
+ if (normalized.summary) {
385
+ // Summary counts are already deterministic, no normalization needed
386
+ }
387
+
388
+ // Normalize perFinding (already sorted by findingIdentity, but normalize timestamps if any)
389
+ if (normalized.perFinding && typeof normalized.perFinding === 'object') {
390
+ const perFindingNormalized = {};
391
+ const sortedKeys = Object.keys(normalized.perFinding).sort();
392
+ for (const key of sortedKeys) {
393
+ const entry = normalized.perFinding[key];
394
+ perFindingNormalized[key] = {
395
+ ...entry,
396
+ // Remove any volatile fields from entry
397
+ // Round confidence values to 3 decimal places for determinism
398
+ confidenceBefore: Math.round(entry.confidenceBefore * 1000) / 1000,
399
+ confidenceAfter: Math.round(entry.confidenceAfter * 1000) / 1000
400
+ };
401
+ }
402
+ normalized.perFinding = perFindingNormalized;
403
+ }
404
+
405
+ return normalized;
406
+ }
407
+
408
+ /**
409
+ * Normalize generic artifact
410
+ */
411
+ function normalizeGeneric(artifact) {
412
+ const normalized = { ...artifact };
413
+
414
+ // Remove common volatile fields
415
+ delete normalized.timestamp;
416
+ delete normalized.runId;
417
+ delete normalized.detectedAt;
418
+ delete normalized.startedAt;
419
+ delete normalized.completedAt;
420
+ delete normalized.verifiedAt;
421
+ delete normalized.learnedAt;
422
+
423
+ // Recursively normalize nested objects
424
+ for (const key in normalized) {
425
+ if (normalized[key] && typeof normalized[key] === 'object') {
426
+ if (Array.isArray(normalized[key])) {
427
+ // Sort arrays if they contain objects with stable keys
428
+ normalized[key] = [...normalized[key]];
429
+ } else {
430
+ normalized[key] = normalizeGeneric(normalized[key]);
431
+ }
432
+ }
433
+ }
434
+
435
+ return normalized;
436
+ }
437
+
438
+ /**
439
+ * Normalize path (remove absolute paths, normalize separators)
440
+ */
441
+ function normalizePath(path) {
442
+ if (!path || typeof path !== 'string') return path;
443
+ let normalized = path.replace(/\\/g, '/');
444
+ // Remove absolute path prefixes
445
+ normalized = normalized.replace(/^[A-Z]:\/[^/]+/, '');
446
+ normalized = normalized.replace(/^\/[^/]+/, '');
447
+ // Remove temp dirs
448
+ normalized = normalized.replace(/\/tmp\/[^/]+/g, '/tmp/...');
449
+ normalized = normalized.replace(/\/\.verax\/runs\/[^/]+/g, '/.verax/runs/...');
450
+ return normalized;
451
+ }
452
+
453
+ /**
454
+ * Normalize URL (remove query params, hash, normalize domain)
455
+ */
456
+ function normalizeUrl(url) {
457
+ if (!url || typeof url !== 'string') return url;
458
+ try {
459
+ const urlObj = new URL(url);
460
+ // Keep only pathname for comparison
461
+ return urlObj.pathname;
462
+ } catch {
463
+ return url;
464
+ }
465
+ }
466
+
@@ -0,0 +1,93 @@
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
+ // @ts-expect-error - readFileSync with encoding returns string
67
+ const decisionsData = JSON.parse(readFileSync(decisionsPath, 'utf-8'));
68
+ const decisionRecorder = DecisionRecorder.fromExport(decisionsData);
69
+ return writeDeterminismReport(runDir, decisionRecorder);
70
+ } catch (error) {
71
+ // If we can't read decisions, we can't determine determinism
72
+ const reportPath = resolve(runDir, ARTIFACT_REGISTRY.determinismReport.filename);
73
+ const report = {
74
+ version: 1,
75
+ contractVersion: 1,
76
+ artifactVersions: getArtifactVersions(),
77
+ generatedAt: new Date().toISOString(),
78
+ verdict: DETERMINISM_VERDICT.NON_DETERMINISTIC,
79
+ message: `Cannot determine determinism: ${error.message}`,
80
+ reasons: [DETERMINISM_REASON.ENVIRONMENT_VARIANCE],
81
+ adaptiveEvents: [],
82
+ decisionSummary: null,
83
+ contract: {
84
+ deterministic: 'Same inputs + same environment + same config → identical normalized artifacts',
85
+ nonDeterministic: 'Any adaptive behavior (adaptive stabilization, retries, truncations) → NON_DETERMINISTIC',
86
+ tracking: 'Tracking adaptive decisions is NOT determinism. Only absence of adaptive events = DETERMINISTIC.'
87
+ }
88
+ };
89
+ writeFileSync(reportPath, JSON.stringify(report, null, 2) + '\n');
90
+ return reportPath;
91
+ }
92
+ }
93
+