@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.
Files changed (217) hide show
  1. package/README.md +14 -18
  2. package/bin/verax.js +7 -0
  3. package/package.json +15 -5
  4. package/src/cli/commands/baseline.js +104 -0
  5. package/src/cli/commands/default.js +323 -111
  6. package/src/cli/commands/doctor.js +36 -4
  7. package/src/cli/commands/ga.js +243 -0
  8. package/src/cli/commands/gates.js +95 -0
  9. package/src/cli/commands/inspect.js +131 -2
  10. package/src/cli/commands/release-check.js +213 -0
  11. package/src/cli/commands/run.js +498 -103
  12. package/src/cli/commands/security-check.js +211 -0
  13. package/src/cli/commands/truth.js +114 -0
  14. package/src/cli/entry.js +305 -68
  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 +546 -0
  20. package/src/cli/util/ast-network-detector.js +603 -0
  21. package/src/cli/util/ast-usestate-detector.js +602 -0
  22. package/src/cli/util/bootstrap-guard.js +86 -0
  23. package/src/cli/util/detection-engine.js +4 -3
  24. package/src/cli/util/determinism-runner.js +123 -0
  25. package/src/cli/util/determinism-writer.js +129 -0
  26. package/src/cli/util/env-url.js +4 -0
  27. package/src/cli/util/events.js +76 -0
  28. package/src/cli/util/expectation-extractor.js +380 -74
  29. package/src/cli/util/findings-writer.js +126 -15
  30. package/src/cli/util/learn-writer.js +3 -1
  31. package/src/cli/util/observation-engine.js +69 -23
  32. package/src/cli/util/observe-writer.js +3 -1
  33. package/src/cli/util/paths.js +6 -14
  34. package/src/cli/util/project-discovery.js +23 -0
  35. package/src/cli/util/project-writer.js +3 -1
  36. package/src/cli/util/redact.js +2 -2
  37. package/src/cli/util/run-resolver.js +64 -0
  38. package/src/cli/util/runtime-budget.js +147 -0
  39. package/src/cli/util/source-requirement.js +55 -0
  40. package/src/cli/util/summary-writer.js +13 -1
  41. package/src/cli/util/svelte-navigation-detector.js +163 -0
  42. package/src/cli/util/svelte-network-detector.js +80 -0
  43. package/src/cli/util/svelte-sfc-extractor.js +147 -0
  44. package/src/cli/util/svelte-state-detector.js +243 -0
  45. package/src/cli/util/vue-navigation-detector.js +177 -0
  46. package/src/cli/util/vue-sfc-extractor.js +162 -0
  47. package/src/cli/util/vue-state-detector.js +215 -0
  48. package/src/types/global.d.ts +28 -0
  49. package/src/types/ts-ast.d.ts +24 -0
  50. package/src/verax/cli/doctor.js +2 -2
  51. package/src/verax/cli/finding-explainer.js +56 -3
  52. package/src/verax/cli/init.js +1 -1
  53. package/src/verax/cli/url-safety.js +12 -2
  54. package/src/verax/cli/wizard.js +13 -2
  55. package/src/verax/core/artifacts/registry.js +154 -0
  56. package/src/verax/core/artifacts/verifier.js +980 -0
  57. package/src/verax/core/baseline/baseline.enforcer.js +137 -0
  58. package/src/verax/core/baseline/baseline.snapshot.js +231 -0
  59. package/src/verax/core/budget-engine.js +1 -1
  60. package/src/verax/core/capabilities/gates.js +499 -0
  61. package/src/verax/core/capabilities/registry.js +475 -0
  62. package/src/verax/core/confidence/confidence-compute.js +137 -0
  63. package/src/verax/core/confidence/confidence-invariants.js +234 -0
  64. package/src/verax/core/confidence/confidence-report-writer.js +112 -0
  65. package/src/verax/core/confidence/confidence-weights.js +44 -0
  66. package/src/verax/core/confidence/confidence.defaults.js +65 -0
  67. package/src/verax/core/confidence/confidence.loader.js +79 -0
  68. package/src/verax/core/confidence/confidence.schema.js +94 -0
  69. package/src/verax/core/confidence-engine-refactor.js +484 -0
  70. package/src/verax/core/confidence-engine.js +486 -0
  71. package/src/verax/core/confidence-engine.js.backup +471 -0
  72. package/src/verax/core/contracts/index.js +29 -0
  73. package/src/verax/core/contracts/types.js +185 -0
  74. package/src/verax/core/contracts/validators.js +381 -0
  75. package/src/verax/core/decision-snapshot.js +31 -4
  76. package/src/verax/core/decisions/decision.trace.js +276 -0
  77. package/src/verax/core/determinism/contract-writer.js +89 -0
  78. package/src/verax/core/determinism/contract.js +139 -0
  79. package/src/verax/core/determinism/diff.js +364 -0
  80. package/src/verax/core/determinism/engine.js +221 -0
  81. package/src/verax/core/determinism/finding-identity.js +148 -0
  82. package/src/verax/core/determinism/normalize.js +438 -0
  83. package/src/verax/core/determinism/report-writer.js +92 -0
  84. package/src/verax/core/determinism/run-fingerprint.js +118 -0
  85. package/src/verax/core/determinism-model.js +35 -6
  86. package/src/verax/core/dynamic-route-intelligence.js +528 -0
  87. package/src/verax/core/evidence/evidence-capture-service.js +307 -0
  88. package/src/verax/core/evidence/evidence-intent-ledger.js +165 -0
  89. package/src/verax/core/evidence-builder.js +487 -0
  90. package/src/verax/core/execution-mode-context.js +77 -0
  91. package/src/verax/core/execution-mode-detector.js +190 -0
  92. package/src/verax/core/failures/exit-codes.js +86 -0
  93. package/src/verax/core/failures/failure-summary.js +76 -0
  94. package/src/verax/core/failures/failure.factory.js +225 -0
  95. package/src/verax/core/failures/failure.ledger.js +132 -0
  96. package/src/verax/core/failures/failure.types.js +196 -0
  97. package/src/verax/core/failures/index.js +10 -0
  98. package/src/verax/core/ga/ga-report-writer.js +43 -0
  99. package/src/verax/core/ga/ga.artifact.js +49 -0
  100. package/src/verax/core/ga/ga.contract.js +434 -0
  101. package/src/verax/core/ga/ga.enforcer.js +86 -0
  102. package/src/verax/core/guardrails/guardrails-report-writer.js +109 -0
  103. package/src/verax/core/guardrails/policy.defaults.js +210 -0
  104. package/src/verax/core/guardrails/policy.loader.js +83 -0
  105. package/src/verax/core/guardrails/policy.schema.js +110 -0
  106. package/src/verax/core/guardrails/truth-reconciliation.js +136 -0
  107. package/src/verax/core/guardrails-engine.js +505 -0
  108. package/src/verax/core/incremental-store.js +15 -7
  109. package/src/verax/core/observe/run-timeline.js +316 -0
  110. package/src/verax/core/perf/perf.contract.js +186 -0
  111. package/src/verax/core/perf/perf.display.js +65 -0
  112. package/src/verax/core/perf/perf.enforcer.js +91 -0
  113. package/src/verax/core/perf/perf.monitor.js +209 -0
  114. package/src/verax/core/perf/perf.report.js +198 -0
  115. package/src/verax/core/pipeline-tracker.js +238 -0
  116. package/src/verax/core/product-definition.js +127 -0
  117. package/src/verax/core/release/provenance.builder.js +271 -0
  118. package/src/verax/core/release/release-report-writer.js +40 -0
  119. package/src/verax/core/release/release.enforcer.js +159 -0
  120. package/src/verax/core/release/reproducibility.check.js +221 -0
  121. package/src/verax/core/release/sbom.builder.js +283 -0
  122. package/src/verax/core/replay-validator.js +4 -4
  123. package/src/verax/core/replay.js +1 -1
  124. package/src/verax/core/report/cross-index.js +192 -0
  125. package/src/verax/core/report/human-summary.js +222 -0
  126. package/src/verax/core/route-intelligence.js +419 -0
  127. package/src/verax/core/security/secrets.scan.js +326 -0
  128. package/src/verax/core/security/security-report.js +50 -0
  129. package/src/verax/core/security/security.enforcer.js +124 -0
  130. package/src/verax/core/security/supplychain.defaults.json +38 -0
  131. package/src/verax/core/security/supplychain.policy.js +326 -0
  132. package/src/verax/core/security/vuln.scan.js +265 -0
  133. package/src/verax/core/silence-impact.js +1 -1
  134. package/src/verax/core/silence-model.js +9 -7
  135. package/src/verax/core/truth/truth.certificate.js +250 -0
  136. package/src/verax/core/ui-feedback-intelligence.js +515 -0
  137. package/src/verax/detect/comparison.js +8 -3
  138. package/src/verax/detect/confidence-engine.js +645 -57
  139. package/src/verax/detect/confidence-helper.js +33 -0
  140. package/src/verax/detect/detection-engine.js +19 -2
  141. package/src/verax/detect/dynamic-route-findings.js +335 -0
  142. package/src/verax/detect/evidence-index.js +15 -65
  143. package/src/verax/detect/expectation-chain-detector.js +417 -0
  144. package/src/verax/detect/expectation-model.js +56 -3
  145. package/src/verax/detect/explanation-helpers.js +1 -1
  146. package/src/verax/detect/finding-detector.js +2 -2
  147. package/src/verax/detect/findings-writer.js +149 -20
  148. package/src/verax/detect/flow-detector.js +4 -4
  149. package/src/verax/detect/index.js +265 -15
  150. package/src/verax/detect/interactive-findings.js +3 -4
  151. package/src/verax/detect/journey-stall-detector.js +558 -0
  152. package/src/verax/detect/route-findings.js +218 -0
  153. package/src/verax/detect/signal-mapper.js +2 -2
  154. package/src/verax/detect/skip-classifier.js +4 -4
  155. package/src/verax/detect/ui-feedback-findings.js +207 -0
  156. package/src/verax/detect/verdict-engine.js +61 -9
  157. package/src/verax/detect/view-switch-correlator.js +242 -0
  158. package/src/verax/flow/flow-engine.js +3 -2
  159. package/src/verax/flow/flow-spec.js +1 -2
  160. package/src/verax/index.js +413 -33
  161. package/src/verax/intel/effect-detector.js +1 -1
  162. package/src/verax/intel/index.js +2 -2
  163. package/src/verax/intel/route-extractor.js +3 -3
  164. package/src/verax/intel/vue-navigation-extractor.js +81 -18
  165. package/src/verax/intel/vue-router-extractor.js +4 -2
  166. package/src/verax/learn/action-contract-extractor.js +684 -66
  167. package/src/verax/learn/ast-contract-extractor.js +53 -1
  168. package/src/verax/learn/index.js +36 -2
  169. package/src/verax/learn/manifest-writer.js +28 -14
  170. package/src/verax/learn/route-extractor.js +1 -1
  171. package/src/verax/learn/route-validator.js +12 -8
  172. package/src/verax/learn/state-extractor.js +1 -1
  173. package/src/verax/learn/static-extractor-navigation.js +1 -1
  174. package/src/verax/learn/static-extractor-validation.js +2 -2
  175. package/src/verax/learn/static-extractor.js +8 -7
  176. package/src/verax/learn/ts-contract-resolver.js +14 -12
  177. package/src/verax/observe/browser.js +22 -3
  178. package/src/verax/observe/console-sensor.js +2 -2
  179. package/src/verax/observe/expectation-executor.js +2 -1
  180. package/src/verax/observe/focus-sensor.js +1 -1
  181. package/src/verax/observe/human-driver.js +29 -10
  182. package/src/verax/observe/index.js +92 -844
  183. package/src/verax/observe/interaction-discovery.js +27 -15
  184. package/src/verax/observe/interaction-runner.js +31 -14
  185. package/src/verax/observe/loading-sensor.js +6 -0
  186. package/src/verax/observe/navigation-sensor.js +1 -1
  187. package/src/verax/observe/observe-context.js +205 -0
  188. package/src/verax/observe/observe-helpers.js +191 -0
  189. package/src/verax/observe/observe-runner.js +226 -0
  190. package/src/verax/observe/observers/budget-observer.js +185 -0
  191. package/src/verax/observe/observers/console-observer.js +102 -0
  192. package/src/verax/observe/observers/coverage-observer.js +107 -0
  193. package/src/verax/observe/observers/interaction-observer.js +471 -0
  194. package/src/verax/observe/observers/navigation-observer.js +132 -0
  195. package/src/verax/observe/observers/network-observer.js +87 -0
  196. package/src/verax/observe/observers/safety-observer.js +82 -0
  197. package/src/verax/observe/observers/ui-feedback-observer.js +99 -0
  198. package/src/verax/observe/settle.js +1 -0
  199. package/src/verax/observe/state-sensor.js +8 -4
  200. package/src/verax/observe/state-ui-sensor.js +7 -1
  201. package/src/verax/observe/traces-writer.js +27 -16
  202. package/src/verax/observe/ui-feedback-detector.js +742 -0
  203. package/src/verax/observe/ui-signal-sensor.js +155 -2
  204. package/src/verax/scan-summary-writer.js +46 -9
  205. package/src/verax/shared/artifact-manager.js +9 -6
  206. package/src/verax/shared/budget-profiles.js +2 -2
  207. package/src/verax/shared/caching.js +1 -1
  208. package/src/verax/shared/config-loader.js +1 -2
  209. package/src/verax/shared/css-spinner-rules.js +204 -0
  210. package/src/verax/shared/dynamic-route-utils.js +12 -6
  211. package/src/verax/shared/retry-policy.js +1 -6
  212. package/src/verax/shared/root-artifacts.js +1 -1
  213. package/src/verax/shared/view-switch-rules.js +208 -0
  214. package/src/verax/shared/zip-artifacts.js +1 -0
  215. package/src/verax/validate/context-validator.js +1 -1
  216. package/src/verax/observe/index.js.backup +0 -1
  217. package/src/verax/validate/context-validator.js.bak +0 -0
@@ -1,11 +1,18 @@
1
- import { resolve, dirname } from 'path';
1
+ import { resolve } from 'path';
2
2
  import { mkdirSync, writeFileSync } from 'fs';
3
- import { getArtifactPath } from '../core/run-id.js';
4
3
  import { CANONICAL_OUTCOMES } from '../core/canonical-outcomes.js';
4
+ import { enforceContractsOnFindings, FINDING_STATUS } from '../core/contracts/index.js';
5
+ import { ARTIFACT_REGISTRY, getArtifactVersions } from '../core/artifacts/registry.js';
6
+ import { buildAndEnforceEvidencePackage, EvidenceBuildError, validateEvidencePackageStrict } from '../core/evidence-builder.js';
7
+ import { writeEvidenceIntentLedger } from '../core/evidence/evidence-intent-ledger.js';
5
8
 
6
9
  /**
7
- * Write findings to canonical artifact root. If a runDir can be inferred,
8
- * write to .verax/runs/<runId>/findings.json; otherwise fall back to legacy path.
10
+ * Write findings to canonical artifact root.
11
+ * Writes to .verax/runs/<runId>/findings.json.
12
+ *
13
+ * PHASE 0 ENFORCEMENT: Applies contracts enforcement
14
+ * - Downgrades findings without evidence from CONFIRMED to SUSPECTED
15
+ * - Drops findings that violate critical contracts
9
16
  *
10
17
  * PHASE 2: Includes outcome classification summary.
11
18
  * PHASE 3: Includes promise type summary.
@@ -14,22 +21,119 @@ import { CANONICAL_OUTCOMES } from '../core/canonical-outcomes.js';
14
21
  * @param {string} url
15
22
  * @param {Array} findings
16
23
  * @param {Array} coverageGaps
17
- * @param {string|null} runDirOpt - Optional absolute run directory path
24
+ * @param {string} runDirOpt - Required absolute run directory path
25
+ * @returns {Object} findings report with enforcement metadata
18
26
  */
19
- export function writeFindings(projectDir, url, findings, coverageGaps = [], runDirOpt = null) {
20
- let findingsPath;
21
- if (runDirOpt) {
22
- // Canonical location
23
- mkdirSync(runDirOpt, { recursive: true });
24
- findingsPath = resolve(runDirOpt, 'findings.json');
25
- } else {
26
- // Legacy location for backward compatibility (tests without runId)
27
- const detectDir = resolve(projectDir, '.veraxverax', 'detect');
28
- mkdirSync(detectDir, { recursive: true });
29
- findingsPath = resolve(detectDir, 'findings.json');
27
+ export function writeFindings(projectDir, url, findings, coverageGaps = [], runDirOpt) {
28
+ if (!runDirOpt) {
29
+ throw new Error('runDirOpt is required');
30
30
  }
31
+ mkdirSync(runDirOpt, { recursive: true });
32
+ const findingsPath = resolve(runDirOpt, ARTIFACT_REGISTRY.findings.filename);
31
33
 
32
- // PHASE 2: Compute outcome summary
34
+ // ARCHITECTURAL HARDENING: Evidence capture failures downgrade findings, never drop them
35
+ // A finding must NEVER disappear. If evidence capture fails, downgrade explicitly.
36
+ const findingsWithEvidence = [];
37
+ const evidenceBuildFailures = [];
38
+
39
+ for (const finding of (findings || [])) {
40
+ // If evidencePackage already exists, validate it strictly for CONFIRMED findings
41
+ if (finding.evidencePackage) {
42
+ const severity = finding.severity || finding.status || 'SUSPECTED';
43
+ if (severity === 'CONFIRMED') {
44
+ try {
45
+ validateEvidencePackageStrict(finding.evidencePackage, severity);
46
+ findingsWithEvidence.push(finding);
47
+ } catch (error) {
48
+ // CONFIRMED finding with incomplete evidencePackage → DOWNGRADE to SUSPECTED
49
+ const downgradedFinding = {
50
+ ...finding,
51
+ severity: 'SUSPECTED',
52
+ status: 'SUSPECTED',
53
+ evidenceCompleteness: {
54
+ downgraded: true,
55
+ reason: `Evidence package incomplete: ${error.message}`,
56
+ originalSeverity: 'CONFIRMED'
57
+ }
58
+ };
59
+ findingsWithEvidence.push(downgradedFinding);
60
+ evidenceBuildFailures.push({
61
+ finding: { type: finding.type || 'unknown', id: finding.findingId || finding.id },
62
+ reason: `EVIDENCE_VALIDATION_FAILED: ${error.message}`,
63
+ errorCode: error.code || 'EVIDENCE_VALIDATION_FAILED',
64
+ action: 'DOWNGRADED'
65
+ });
66
+ }
67
+ } else {
68
+ findingsWithEvidence.push(finding);
69
+ }
70
+ continue;
71
+ }
72
+
73
+ // Build evidence package from finding data
74
+ // If building fails → DOWNGRADE finding (never drop)
75
+ try {
76
+ const findingWithEvidence = buildAndEnforceEvidencePackage(finding, {
77
+ expectation: finding.expectation || null,
78
+ trace: {
79
+ interaction: finding.interaction || {},
80
+ before: finding.evidence?.before ? { screenshot: finding.evidence.before, url: finding.evidence.beforeUrl } : null,
81
+ after: finding.evidence?.after ? { screenshot: finding.evidence.after, url: finding.evidence.afterUrl } : null,
82
+ sensors: finding.evidence?.sensors || {},
83
+ dom: finding.evidence?.dom || {},
84
+ },
85
+ evidence: finding.evidence || {},
86
+ confidence: finding.confidenceLevel ? {
87
+ level: finding.confidenceLevel,
88
+ score: finding.confidence,
89
+ reasons: finding.confidenceReasons || [],
90
+ } : null,
91
+ });
92
+ findingsWithEvidence.push(findingWithEvidence);
93
+ } catch (error) {
94
+ // Evidence building failure → DOWNGRADE finding (never drop)
95
+ const originalSeverity = finding.severity || finding.status || 'SUSPECTED';
96
+ const downgradedFinding = {
97
+ ...finding,
98
+ severity: 'SUSPECTED',
99
+ status: 'SUSPECTED',
100
+ evidenceCompleteness: {
101
+ downgraded: true,
102
+ reason: `Evidence capture failed: ${error.message}`,
103
+ originalSeverity: originalSeverity,
104
+ errorCode: error.code || 'EVIDENCE_BUILD_FAILED',
105
+ missingFields: error.missingFields || []
106
+ }
107
+ };
108
+ findingsWithEvidence.push(downgradedFinding);
109
+
110
+ if (error instanceof EvidenceBuildError) {
111
+ evidenceBuildFailures.push({
112
+ finding: { type: finding.type || 'unknown', id: finding.findingId || finding.id },
113
+ reason: `EVIDENCE_BUILD_FAILED: ${error.message}`,
114
+ errorCode: error.code || 'EVIDENCE_BUILD_FAILED',
115
+ missingFields: error.missingFields || [],
116
+ action: 'DOWNGRADED'
117
+ });
118
+ } else {
119
+ evidenceBuildFailures.push({
120
+ finding: { type: finding.type || 'unknown', id: finding.findingId || finding.id },
121
+ reason: `EVIDENCE_BUILD_FAILED: Unexpected error: ${error.message}`,
122
+ errorCode: 'EVIDENCE_BUILD_FAILED',
123
+ action: 'DOWNGRADED'
124
+ });
125
+ }
126
+ }
127
+ }
128
+
129
+ // PHASE 0: Enforce contracts on all findings (Evidence Law)
130
+ const { valid: enforcedFindings, dropped, downgrades } = enforceContractsOnFindings(findingsWithEvidence);
131
+
132
+ // ARCHITECTURAL HARDENING: Track evidence build failures separately (they result in downgrades, not drops)
133
+ // Only contract violations result in drops
134
+ const allDropped = dropped;
135
+
136
+ // PHASE 2: Compute outcome summary (using enforced findings)
33
137
  const outcomeSummary = {};
34
138
  Object.values(CANONICAL_OUTCOMES).forEach(outcome => {
35
139
  outcomeSummary[outcome] = 0;
@@ -38,7 +142,7 @@ export function writeFindings(projectDir, url, findings, coverageGaps = [], runD
38
142
  // PHASE 3: Compute promise summary
39
143
  const promiseSummary = {};
40
144
 
41
- for (const finding of (findings || [])) {
145
+ for (const finding of enforcedFindings) {
42
146
  const outcome = finding.outcome || CANONICAL_OUTCOMES.SILENT_FAILURE;
43
147
  outcomeSummary[outcome] = (outcomeSummary[outcome] || 0) + 1;
44
148
 
@@ -48,20 +152,45 @@ export function writeFindings(projectDir, url, findings, coverageGaps = [], runD
48
152
 
49
153
  const findingsReport = {
50
154
  version: 1,
155
+ contractVersion: 1, // Track schema changes from contracts enforcement
156
+ artifactVersions: getArtifactVersions(),
51
157
  detectedAt: new Date().toISOString(),
52
158
  url: url,
53
159
  outcomeSummary: outcomeSummary, // PHASE 2
54
160
  promiseSummary: promiseSummary, // PHASE 3
55
- findings: findings,
161
+ findings: enforcedFindings,
56
162
  coverageGaps: coverageGaps,
163
+ // PHASE 0: Enforcement metadata
164
+ // PHASE 21.1: Include evidence build failures in dropped count
165
+ enforcement: {
166
+ droppedCount: allDropped.length,
167
+ downgradedCount: downgrades.length,
168
+ downgrades: downgrades.map(d => ({
169
+ reason: d.reason,
170
+ originalStatus: d.original.status,
171
+ downgradeToStatus: d.downgraded.status
172
+ })),
173
+ evidenceBuildFailures: evidenceBuildFailures.map(f => ({
174
+ reason: f.reason,
175
+ errorCode: f.errorCode,
176
+ findingType: f.finding.type || 'unknown',
177
+ findingId: f.finding.id || null,
178
+ missingFields: f.missingFields || [],
179
+ action: f.action || 'DOWNGRADED'
180
+ }))
181
+ },
57
182
  notes: []
58
183
  };
59
184
 
60
185
  writeFileSync(findingsPath, JSON.stringify(findingsReport, null, 2) + '\n');
61
186
 
187
+ // PHASE 22: Write evidence intent ledger
188
+ const evidenceIntentPath = writeEvidenceIntentLedger(runDirOpt, enforcedFindings, captureFailuresMap);
189
+
62
190
  return {
63
191
  ...findingsReport,
64
- findingsPath: findingsPath
192
+ findingsPath: findingsPath,
193
+ evidenceIntentPath: evidenceIntentPath
65
194
  };
66
195
  }
67
196
 
@@ -133,9 +133,9 @@ export function detectFlowSilentFailures(traces, manifest, findings, coverageGap
133
133
  }
134
134
 
135
135
  // Look for silent failures followed by lack of recovery
136
- let hasSilentFailure = false;
136
+ const _hasSilentFailure = false;
137
137
  let failedStepIndex = -1;
138
- let failedExpectation = null;
138
+ const _failedExpectation = null;
139
139
 
140
140
  for (let i = 0; i < flowTraces.length; i++) {
141
141
  const trace = flowTraces[i];
@@ -248,9 +248,9 @@ export function detectFlowSilentFailures(traces, manifest, findings, coverageGap
248
248
 
249
249
  if (isSilentFailure && matchedExpectation) {
250
250
  // Silent failure detected at this step
251
- hasSilentFailure = true;
251
+ const _hasSilentFailure = true;
252
252
  failedStepIndex = i;
253
- failedExpectation = matchedExpectation;
253
+ const _failedExpectation = matchedExpectation;
254
254
 
255
255
  // Check if subsequent steps show UI recovery
256
256
  let hasSubsequentRecovery = false;
@@ -1,13 +1,29 @@
1
1
  import { readFileSync, existsSync } from 'fs';
2
- import { dirname } from 'path';
2
+ import { dirname, basename, join } from 'path';
3
3
  import { expectsNavigation } from './expectation-model.js';
4
4
  import { hasMeaningfulUrlChange, hasVisibleChange, hasDomChange } from './comparison.js';
5
5
  import { writeFindings } from './findings-writer.js';
6
6
  import { getUrlPath } from './evidence-validator.js';
7
7
  import { classifySkipReason, collectSkipReasons } from './skip-classifier.js';
8
8
  import { detectInteractiveFindings } from './interactive-findings.js';
9
+ import { detectRouteFindings } from './route-findings.js';
10
+ import { detectUIFeedbackFindings } from './ui-feedback-findings.js';
11
+ import { detectDynamicRouteFindings } from './dynamic-route-findings.js';
12
+ import { addUnifiedConfidence } from './confidence-helper.js';
13
+ import { applyGuardrails } from '../core/guardrails-engine.js';
14
+ import { finalizeFindingTruth } from '../core/guardrails/truth-reconciliation.js';
15
+ import { writeGuardrailsReport } from '../core/guardrails/guardrails-report-writer.js';
16
+ import { computeFinalConfidence } from '../core/confidence/confidence-compute.js';
17
+ import { enforceConfidenceInvariants } from '../core/confidence/confidence-invariants.js';
18
+ import { writeConfidenceReport } from '../core/confidence/confidence-report-writer.js';
9
19
 
10
- export async function detect(manifestPath, tracesPath, validation = null) {
20
+ /**
21
+ * @param {string} manifestPath
22
+ * @param {string} tracesPath
23
+ * @param {Object} [validation]
24
+ * @returns {Promise<any>}
25
+ */
26
+ export async function detect(manifestPath, tracesPath, validation = null, _expectationCoverageGaps = null, _silenceTracker = null) {
11
27
  if (!existsSync(manifestPath)) {
12
28
  throw new Error(`Manifest not found: ${manifestPath}`);
13
29
  }
@@ -25,8 +41,21 @@ export async function detect(manifestPath, tracesPath, validation = null) {
25
41
  const projectDir = manifest.projectDir;
26
42
  const findings = [];
27
43
 
44
+ // Extract runId from tracesPath: .verax/runs/<runId>/observation-traces.json
45
+ let runId = null;
46
+ try {
47
+ const runDir = dirname(tracesPath);
48
+ const runDirBasename = basename(runDir);
49
+ // Check if runDir is in .verax/runs/<runId> structure
50
+ const parentDir = dirname(runDir);
51
+ if (basename(parentDir) === 'runs' && basename(dirname(parentDir)) === '.verax') {
52
+ runId = runDirBasename;
53
+ }
54
+ } catch {
55
+ // Ignore path parsing errors
56
+ }
57
+
28
58
  let interactionsAnalyzed = 0;
29
- let interactionsSkippedNoExpectation = 0;
30
59
  const skips = [];
31
60
 
32
61
  for (const trace of observation.traces) {
@@ -39,7 +68,6 @@ export async function detect(manifestPath, tracesPath, validation = null) {
39
68
  const expectsNav = expectsNavigation(manifest, interaction, beforeUrl);
40
69
 
41
70
  if (!expectsNav) {
42
- interactionsSkippedNoExpectation++;
43
71
  const skipReason = classifySkipReason(manifest, interaction, beforeUrl, validation);
44
72
  if (skipReason) {
45
73
  skips.push({
@@ -73,8 +101,8 @@ export async function detect(manifestPath, tracesPath, validation = null) {
73
101
  const interactionSelector = interaction.selector || '';
74
102
 
75
103
  if (selectorHint && interactionSelector) {
76
- const normalizedSelectorHint = selectorHint.replace(/[\[\]()]/g, '');
77
- const normalizedInteractionSelector = interactionSelector.replace(/[\[\]()]/g, '');
104
+ const normalizedSelectorHint = selectorHint.replace(/[[\]()]/g, '');
105
+ const normalizedInteractionSelector = interactionSelector.replace(/[[\]()]/g, '');
78
106
 
79
107
  if (selectorHint === interactionSelector ||
80
108
  selectorHint.includes(interactionSelector) ||
@@ -117,7 +145,6 @@ export async function detect(manifestPath, tracesPath, validation = null) {
117
145
  label: interaction.label
118
146
  }
119
147
  });
120
- interactionsSkippedNoExpectation++;
121
148
  continue;
122
149
  }
123
150
  }
@@ -161,7 +188,6 @@ export async function detect(manifestPath, tracesPath, validation = null) {
161
188
  label: interaction.label
162
189
  }
163
190
  });
164
- interactionsSkippedNoExpectation++;
165
191
  continue;
166
192
  } else if (matchingRoutes.length === 1) {
167
193
  expectedTargetPath = matchingRoutes[0];
@@ -180,14 +206,22 @@ export async function detect(manifestPath, tracesPath, validation = null) {
180
206
  label: interaction.label
181
207
  }
182
208
  });
183
- interactionsSkippedNoExpectation++;
184
209
  continue;
185
210
  }
186
211
 
187
212
  interactionsAnalyzed++;
188
213
 
189
214
  const hasUrlChange = hasMeaningfulUrlChange(beforeUrl, afterUrl);
190
- const hasVisibleChangeResult = hasVisibleChange(beforeScreenshot, afterScreenshot, projectDir);
215
+ // hasVisibleChange requires runId, skip comparison if runId unavailable
216
+ let hasVisibleChangeResult = false;
217
+ if (runId) {
218
+ try {
219
+ hasVisibleChangeResult = hasVisibleChange(beforeScreenshot, afterScreenshot, projectDir, runId);
220
+ } catch (e) {
221
+ // If screenshot comparison fails, treat as no visible change
222
+ hasVisibleChangeResult = false;
223
+ }
224
+ }
191
225
  const hasDomChangeResult = hasDomChange(trace);
192
226
 
193
227
  if (expectedTargetPath) {
@@ -197,7 +231,7 @@ export async function detect(manifestPath, tracesPath, validation = null) {
197
231
 
198
232
  if (expectationType === 'form_submission') {
199
233
  if (normalizedAfter !== normalizedTarget && !hasUrlChange && !hasDomChangeResult) {
200
- findings.push({
234
+ const finding = {
201
235
  type: 'silent_failure',
202
236
  interaction: {
203
237
  type: interaction.type,
@@ -211,7 +245,27 @@ export async function detect(manifestPath, tracesPath, validation = null) {
211
245
  beforeUrl: beforeUrl,
212
246
  afterUrl: afterUrl
213
247
  }
248
+ };
249
+
250
+ // PHASE 15: Add unified confidence
251
+ const findingWithConfidence = addUnifiedConfidence(finding, {
252
+ expectation: { targetPath: expectedTargetPath, type: expectationType },
253
+ sensors: trace.sensors || {},
254
+ comparisons: {
255
+ urlChanged: hasUrlChange,
256
+ domChanged: hasDomChangeResult,
257
+ },
258
+ evidence: {
259
+ beforeAfter: {
260
+ beforeScreenshot,
261
+ afterScreenshot,
262
+ beforeUrl,
263
+ afterUrl,
264
+ },
265
+ },
214
266
  });
267
+
268
+ findings.push(findingWithConfidence);
215
269
  }
216
270
  } else if (expectationType === 'navigation') {
217
271
  const urlMatchesTarget = normalizedAfter === normalizedTarget;
@@ -237,7 +291,7 @@ export async function detect(manifestPath, tracesPath, validation = null) {
237
291
  }
238
292
  } else {
239
293
  if (!hasUrlChange && !hasVisibleChangeResult && !hasDomChangeResult) {
240
- findings.push({
294
+ const finding = {
241
295
  type: 'silent_failure',
242
296
  interaction: {
243
297
  type: interaction.type,
@@ -251,7 +305,28 @@ export async function detect(manifestPath, tracesPath, validation = null) {
251
305
  beforeUrl: beforeUrl,
252
306
  afterUrl: afterUrl
253
307
  }
308
+ };
309
+
310
+ // PHASE 15: Add unified confidence
311
+ const findingWithConfidence = addUnifiedConfidence(finding, {
312
+ expectation: null,
313
+ sensors: trace.sensors || {},
314
+ comparisons: {
315
+ urlChanged: hasUrlChange,
316
+ domChanged: hasDomChangeResult,
317
+ visibleChanged: hasVisibleChangeResult,
318
+ },
319
+ evidence: {
320
+ beforeAfter: {
321
+ beforeScreenshot,
322
+ afterScreenshot,
323
+ beforeUrl,
324
+ afterUrl,
325
+ },
326
+ },
254
327
  });
328
+
329
+ findings.push(findingWithConfidence);
255
330
  }
256
331
  }
257
332
  }
@@ -259,14 +334,187 @@ export async function detect(manifestPath, tracesPath, validation = null) {
259
334
  // Interactive and accessibility intelligence
260
335
  detectInteractiveFindings(observation.traces, manifest, findings);
261
336
 
262
- // Infer canonical run directory from tracesPath when available
337
+ // PHASE 12: Route intelligence findings
338
+ const routeFindings = detectRouteFindings(observation.traces, manifest, findings);
339
+ findings.push(...routeFindings);
340
+
341
+ // PHASE 13: UI feedback findings
342
+ const uiFeedbackFindings = detectUIFeedbackFindings(observation.traces, manifest, findings);
343
+ findings.push(...uiFeedbackFindings);
344
+
345
+ // PHASE 14: Dynamic route findings
346
+ const dynamicRouteResult = detectDynamicRouteFindings(observation.traces, manifest, findings);
347
+ findings.push(...dynamicRouteResult.findings);
348
+ // Note: skips are handled separately and should be included in skip summary
349
+
350
+ // PHASE 23: Apply guardrails + truth reconciliation (AFTER evidence builder, BEFORE writing artifacts)
351
+ const guardrailsSummary = {
352
+ totalFindingsProcessed: 0,
353
+ preventedConfirmedCount: 0,
354
+ downgradedCount: 0,
355
+ informationalCount: 0,
356
+ };
357
+
358
+ const truthDecisions = {}; // Map of findingIdentity -> truthDecision
359
+
360
+ const findingsWithGuardrails = findings.map(finding => {
361
+ guardrailsSummary.totalFindingsProcessed++;
362
+
363
+ // Capture initial confidence before guardrails
364
+ const initialConfidence = finding.confidence || 0;
365
+ const initialConfidenceLevel = finding.confidenceLevel ||
366
+ (initialConfidence >= 0.8 ? 'HIGH' : initialConfidence >= 0.5 ? 'MEDIUM' : initialConfidence >= 0.2 ? 'LOW' : 'UNPROVEN');
367
+
368
+ // Build context for guardrails
369
+ const context = {
370
+ evidencePackage: finding.evidencePackage,
371
+ signals: finding.evidencePackage?.signals || finding.evidence || {},
372
+ confidenceReasons: finding.confidenceReasons || [],
373
+ promiseType: finding.expectation?.type || finding.promise?.type || null,
374
+ };
375
+
376
+ // Apply guardrails
377
+ const guardrailsResult = applyGuardrails(finding, context);
378
+
379
+ // Finalize truth (reconcile confidence with guardrails outcome)
380
+ const { finalFinding, truthDecision } = finalizeFindingTruth(
381
+ guardrailsResult.finding,
382
+ guardrailsResult,
383
+ {
384
+ initialConfidence,
385
+ initialConfidenceLevel
386
+ }
387
+ );
388
+
389
+ // Store truth decision for report
390
+ const findingIdentity = finalFinding.findingId || finalFinding.id || `finding-${findings.indexOf(finding)}`;
391
+ truthDecisions[findingIdentity] = truthDecision;
392
+
393
+ // Track guardrails impact
394
+ const originalSeverity = finding.severity || 'SUSPECTED';
395
+ const finalSeverity = truthDecision.finalStatus;
396
+
397
+ if (finalSeverity !== originalSeverity && originalSeverity === 'CONFIRMED') {
398
+ guardrailsSummary.preventedConfirmedCount++;
399
+ }
400
+ if (finalSeverity === 'SUSPECTED' && originalSeverity === 'CONFIRMED') {
401
+ guardrailsSummary.downgradedCount++;
402
+ }
403
+ if (finalSeverity === 'INFORMATIONAL') {
404
+ guardrailsSummary.informationalCount++;
405
+ }
406
+
407
+ return finalFinding;
408
+ });
409
+
410
+ // Replace findings with guardrails-applied + truth-reconciled findings
411
+ findings.length = 0;
412
+ findings.push(...findingsWithGuardrails);
413
+
414
+ // PHASE 24: Apply confidence invariants and compute final confidence
415
+ const confidenceData = {}; // Map of findingIdentity -> confidence computation result
416
+
417
+ // Load evidence intent if available
418
+ let evidenceIntentLedger = null;
263
419
  let runDir = null;
264
420
  try {
265
421
  runDir = dirname(tracesPath);
266
- } catch {}
422
+ const evidenceIntentPath = join(runDir, 'evidence.intent.json');
423
+ if (existsSync(evidenceIntentPath)) {
424
+ try {
425
+ const intentContent = readFileSync(evidenceIntentPath, 'utf-8');
426
+ evidenceIntentLedger = JSON.parse(intentContent);
427
+ } catch {
428
+ // Ignore parse errors
429
+ }
430
+ }
431
+ } catch {
432
+ // Ignore path parsing errors
433
+ }
434
+
435
+ const findingsWithConfidence = findings.map(finding => {
436
+ const findingIdentity = finding.findingId || finding.id || `finding-${findings.indexOf(finding)}`;
437
+
438
+ // Get evidence intent entry for this finding
439
+ const evidenceIntentEntry = evidenceIntentLedger?.entries?.find(e => e.findingIdentity === findingIdentity) || null;
440
+
441
+ // Get guardrails outcome
442
+ const guardrailsOutcome = finding.guardrails || truthDecisions[findingIdentity] || null;
443
+
444
+ // Compute final confidence
445
+ const confidenceResult = computeFinalConfidence({
446
+ findingType: finding.type || 'unknown',
447
+ rawSignals: finding.evidencePackage?.signals || finding.evidence || {},
448
+ evidenceIntent: evidenceIntentEntry,
449
+ guardrailsOutcome,
450
+ truthStatus: finding.severity || finding.status || 'SUSPECTED',
451
+ expectation: finding.expectation || null,
452
+ sensors: finding.evidencePackage?.signals || finding.evidence || {},
453
+ comparisons: {},
454
+ evidence: finding.evidence || {},
455
+ options: {
456
+ verificationStatus: null // Will be set by verifier
457
+ }
458
+ });
459
+
460
+ // Store confidence data for report
461
+ confidenceData[findingIdentity] = confidenceResult;
462
+
463
+ // Enforce invariants
464
+ const invariantResult = enforceConfidenceInvariants(finding, {
465
+ expectationProof: confidenceResult.expectationProof,
466
+ verificationStatus: confidenceResult.verificationStatus,
467
+ guardrailsOutcome
468
+ });
469
+
470
+ // Update finding with final confidence
471
+ const finalFinding = {
472
+ ...invariantResult.finding,
473
+ confidence: confidenceResult.confidenceAfter,
474
+ confidenceLevel: confidenceResult.confidenceLevel,
475
+ confidenceReasons: confidenceResult.explanation
476
+ };
477
+
478
+ return finalFinding;
479
+ });
480
+
481
+ // Replace findings with confidence-enforced findings
482
+ findings.length = 0;
483
+ findings.push(...findingsWithConfidence);
484
+
485
+ // Infer canonical run directory from tracesPath when available
486
+ if (!runDir) {
487
+ try {
488
+ runDir = dirname(tracesPath);
489
+ } catch {
490
+ // Ignore path parsing errors
491
+ }
492
+ }
267
493
 
268
494
  const findingsResult = writeFindings(projectDir, observation.url, findings, [], runDir);
269
495
 
496
+ // PHASE 23: Write guardrails report
497
+ let guardrailsReportPath = null;
498
+ if (runDir) {
499
+ try {
500
+ guardrailsReportPath = writeGuardrailsReport(runDir, findings, truthDecisions);
501
+ } catch (error) {
502
+ // Log but don't fail the run
503
+ console.error('Failed to write guardrails report:', error.message);
504
+ }
505
+ }
506
+
507
+ // PHASE 24: Write confidence report
508
+ let confidenceReportPath = null;
509
+ if (runDir) {
510
+ try {
511
+ confidenceReportPath = writeConfidenceReport(runDir, findings, confidenceData);
512
+ } catch (error) {
513
+ // Log but don't fail the run
514
+ console.error('Failed to write confidence report:', error.message);
515
+ }
516
+ }
517
+
270
518
  const skipSummary = collectSkipReasons(skips);
271
519
 
272
520
  const detectTruth = {
@@ -278,6 +526,8 @@ export async function detect(manifestPath, tracesPath, validation = null) {
278
526
 
279
527
  return {
280
528
  ...findingsResult,
281
- detectTruth: detectTruth
529
+ detectTruth: detectTruth,
530
+ guardrailsSummary: guardrailsSummary, // PHASE 17: Include guardrails summary
531
+ guardrailsReportPath: guardrailsReportPath // PHASE 23: Include guardrails report path
282
532
  };
283
533
  }
@@ -2,7 +2,7 @@
2
2
  // Handles keyboard, hover, file_upload, login, logout, auth_guard interactions
3
3
  // Plus accessibility detections: focus, ARIA, keyboard trap, feedback gap, freeze
4
4
 
5
- import { hasMeaningfulUrlChange, hasVisibleChange, hasDomChange } from './comparison.js';
5
+ import { hasMeaningfulUrlChange, hasDomChange } from './comparison.js';
6
6
  import { computeConfidence } from './confidence-engine.js';
7
7
  import { enrichFindingWithExplanations } from './finding-detector.js';
8
8
 
@@ -14,10 +14,9 @@ import { enrichFindingWithExplanations } from './finding-detector.js';
14
14
  * @param {Array} traces - Interaction traces to analyze
15
15
  * @param {Object} manifest - Project manifest (not used currently)
16
16
  * @param {Array} findings - Findings array to append to
17
- * @param {Object} helpers - Helper functions (not used currently)
18
17
  * @returns {Array} Array of detected interactive findings
19
18
  */
20
- export function detectInteractiveFindings(traces, manifest, findings, helpers = {}) {
19
+ export function detectInteractiveFindings(traces, manifest, findings, _helpers = {}) {
21
20
  const interactiveFindings = [];
22
21
 
23
22
  for (const trace of traces) {
@@ -165,7 +164,7 @@ export function detectInteractiveFindings(traces, manifest, findings, helpers =
165
164
  const domChanged = hasDomChange(trace);
166
165
  const urlChanged = hasMeaningfulUrlChange(beforeUrl, afterUrl);
167
166
  const network = sensors.network || {};
168
- const hasNetwork = (network.totalRequests || 0) > 0;
167
+ const _hasNetwork = (network.totalRequests || 0) > 0;
169
168
  const loading = sensors.loading || {};
170
169
  const stateData = sensors.state || {};
171
170