@veraxhq/verax 0.3.0 → 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 (191) hide show
  1. package/README.md +28 -20
  2. package/bin/verax.js +11 -18
  3. package/package.json +28 -7
  4. package/src/cli/commands/baseline.js +1 -2
  5. package/src/cli/commands/default.js +72 -81
  6. package/src/cli/commands/doctor.js +29 -0
  7. package/src/cli/commands/ga.js +3 -0
  8. package/src/cli/commands/gates.js +1 -1
  9. package/src/cli/commands/inspect.js +6 -133
  10. package/src/cli/commands/release-check.js +2 -0
  11. package/src/cli/commands/run.js +74 -246
  12. package/src/cli/commands/security-check.js +2 -1
  13. package/src/cli/commands/truth.js +0 -1
  14. package/src/cli/entry.js +82 -309
  15. package/src/cli/util/angular-component-extractor.js +2 -2
  16. package/src/cli/util/angular-navigation-detector.js +2 -2
  17. package/src/cli/util/ast-interactive-detector.js +4 -6
  18. package/src/cli/util/ast-network-detector.js +3 -3
  19. package/src/cli/util/ast-promise-extractor.js +581 -0
  20. package/src/cli/util/ast-usestate-detector.js +3 -3
  21. package/src/cli/util/atomic-write.js +12 -1
  22. package/src/cli/util/console-reporter.js +72 -0
  23. package/src/cli/util/detection-engine.js +105 -41
  24. package/src/cli/util/determinism-runner.js +2 -1
  25. package/src/cli/util/determinism-writer.js +1 -1
  26. package/src/cli/util/digest-engine.js +359 -0
  27. package/src/cli/util/dom-diff.js +226 -0
  28. package/src/cli/util/env-url.js +0 -4
  29. package/src/cli/util/evidence-engine.js +287 -0
  30. package/src/cli/util/expectation-extractor.js +217 -367
  31. package/src/cli/util/findings-writer.js +19 -126
  32. package/src/cli/util/framework-detector.js +572 -0
  33. package/src/cli/util/idgen.js +1 -1
  34. package/src/cli/util/interaction-planner.js +529 -0
  35. package/src/cli/util/learn-writer.js +2 -2
  36. package/src/cli/util/ledger-writer.js +110 -0
  37. package/src/cli/util/monorepo-resolver.js +162 -0
  38. package/src/cli/util/observation-engine.js +127 -278
  39. package/src/cli/util/observe-writer.js +2 -2
  40. package/src/cli/util/paths.js +12 -3
  41. package/src/cli/util/project-discovery.js +284 -3
  42. package/src/cli/util/project-writer.js +2 -2
  43. package/src/cli/util/run-id.js +23 -27
  44. package/src/cli/util/run-result.js +778 -0
  45. package/src/cli/util/selector-resolver.js +235 -0
  46. package/src/cli/util/summary-writer.js +2 -1
  47. package/src/cli/util/svelte-navigation-detector.js +3 -3
  48. package/src/cli/util/svelte-sfc-extractor.js +0 -1
  49. package/src/cli/util/svelte-state-detector.js +1 -2
  50. package/src/cli/util/trust-activation-integration.js +496 -0
  51. package/src/cli/util/trust-activation-wrapper.js +85 -0
  52. package/src/cli/util/trust-integration-hooks.js +164 -0
  53. package/src/cli/util/types.js +153 -0
  54. package/src/cli/util/url-validation.js +40 -0
  55. package/src/cli/util/vue-navigation-detector.js +4 -3
  56. package/src/cli/util/vue-sfc-extractor.js +1 -2
  57. package/src/cli/util/vue-state-detector.js +1 -1
  58. package/src/types/fs-augment.d.ts +23 -0
  59. package/src/types/global.d.ts +137 -0
  60. package/src/types/internal-types.d.ts +35 -0
  61. package/src/verax/cli/finding-explainer.js +3 -56
  62. package/src/verax/cli/init.js +4 -18
  63. package/src/verax/core/action-classifier.js +4 -3
  64. package/src/verax/core/artifacts/registry.js +0 -15
  65. package/src/verax/core/artifacts/verifier.js +18 -8
  66. package/src/verax/core/baseline/baseline.snapshot.js +2 -0
  67. package/src/verax/core/capabilities/gates.js +7 -1
  68. package/src/verax/core/confidence/confidence-compute.js +14 -7
  69. package/src/verax/core/confidence/confidence.loader.js +1 -0
  70. package/src/verax/core/confidence-engine-refactor.js +8 -3
  71. package/src/verax/core/confidence-engine.js +162 -23
  72. package/src/verax/core/contracts/types.js +1 -0
  73. package/src/verax/core/contracts/validators.js +79 -4
  74. package/src/verax/core/decision-snapshot.js +3 -30
  75. package/src/verax/core/decisions/decision.trace.js +2 -0
  76. package/src/verax/core/determinism/contract-writer.js +2 -2
  77. package/src/verax/core/determinism/contract.js +1 -1
  78. package/src/verax/core/determinism/diff.js +42 -1
  79. package/src/verax/core/determinism/engine.js +7 -6
  80. package/src/verax/core/determinism/finding-identity.js +3 -2
  81. package/src/verax/core/determinism/normalize.js +32 -4
  82. package/src/verax/core/determinism/report-writer.js +1 -0
  83. package/src/verax/core/determinism/run-fingerprint.js +7 -2
  84. package/src/verax/core/dynamic-route-intelligence.js +8 -7
  85. package/src/verax/core/evidence/evidence-capture-service.js +1 -0
  86. package/src/verax/core/evidence/evidence-intent-ledger.js +2 -1
  87. package/src/verax/core/evidence-builder.js +2 -2
  88. package/src/verax/core/execution-mode-context.js +1 -1
  89. package/src/verax/core/execution-mode-detector.js +5 -3
  90. package/src/verax/core/failures/exit-codes.js +39 -37
  91. package/src/verax/core/failures/failure-summary.js +1 -1
  92. package/src/verax/core/failures/failure.factory.js +3 -3
  93. package/src/verax/core/failures/failure.ledger.js +3 -2
  94. package/src/verax/core/ga/ga.artifact.js +1 -1
  95. package/src/verax/core/ga/ga.contract.js +3 -2
  96. package/src/verax/core/ga/ga.enforcer.js +1 -0
  97. package/src/verax/core/guardrails/policy.loader.js +1 -0
  98. package/src/verax/core/guardrails/truth-reconciliation.js +1 -1
  99. package/src/verax/core/guardrails-engine.js +2 -2
  100. package/src/verax/core/incremental-store.js +1 -0
  101. package/src/verax/core/integrity/budget.js +138 -0
  102. package/src/verax/core/integrity/determinism.js +342 -0
  103. package/src/verax/core/integrity/integrity.js +208 -0
  104. package/src/verax/core/integrity/poisoning.js +108 -0
  105. package/src/verax/core/integrity/transaction.js +140 -0
  106. package/src/verax/core/observe/run-timeline.js +2 -0
  107. package/src/verax/core/perf/perf.report.js +2 -0
  108. package/src/verax/core/pipeline-tracker.js +5 -0
  109. package/src/verax/core/release/provenance.builder.js +73 -214
  110. package/src/verax/core/release/release.enforcer.js +14 -9
  111. package/src/verax/core/release/reproducibility.check.js +1 -0
  112. package/src/verax/core/release/sbom.builder.js +32 -23
  113. package/src/verax/core/replay-validator.js +2 -0
  114. package/src/verax/core/replay.js +4 -0
  115. package/src/verax/core/report/cross-index.js +6 -3
  116. package/src/verax/core/report/human-summary.js +141 -1
  117. package/src/verax/core/route-intelligence.js +4 -3
  118. package/src/verax/core/run-id.js +6 -3
  119. package/src/verax/core/run-manifest.js +4 -3
  120. package/src/verax/core/security/secrets.scan.js +10 -7
  121. package/src/verax/core/security/security.enforcer.js +4 -0
  122. package/src/verax/core/security/supplychain.policy.js +9 -1
  123. package/src/verax/core/security/vuln.scan.js +2 -2
  124. package/src/verax/core/truth/truth.certificate.js +3 -1
  125. package/src/verax/core/ui-feedback-intelligence.js +12 -46
  126. package/src/verax/detect/conditional-ui-silent-failure.js +84 -0
  127. package/src/verax/detect/confidence-engine.js +100 -660
  128. package/src/verax/detect/confidence-helper.js +1 -0
  129. package/src/verax/detect/detection-engine.js +1 -18
  130. package/src/verax/detect/dynamic-route-findings.js +17 -14
  131. package/src/verax/detect/expectation-chain-detector.js +1 -1
  132. package/src/verax/detect/expectation-model.js +3 -5
  133. package/src/verax/detect/failure-cause-inference.js +293 -0
  134. package/src/verax/detect/findings-writer.js +126 -166
  135. package/src/verax/detect/flow-detector.js +2 -2
  136. package/src/verax/detect/form-silent-failure.js +98 -0
  137. package/src/verax/detect/index.js +51 -234
  138. package/src/verax/detect/invariants-enforcer.js +147 -0
  139. package/src/verax/detect/journey-stall-detector.js +4 -4
  140. package/src/verax/detect/navigation-silent-failure.js +82 -0
  141. package/src/verax/detect/problem-aggregator.js +361 -0
  142. package/src/verax/detect/route-findings.js +7 -6
  143. package/src/verax/detect/summary-writer.js +477 -0
  144. package/src/verax/detect/test-failure-cause-inference.js +314 -0
  145. package/src/verax/detect/ui-feedback-findings.js +18 -18
  146. package/src/verax/detect/verdict-engine.js +3 -57
  147. package/src/verax/detect/view-switch-correlator.js +2 -2
  148. package/src/verax/flow/flow-engine.js +2 -1
  149. package/src/verax/flow/flow-spec.js +0 -6
  150. package/src/verax/index.js +48 -412
  151. package/src/verax/intel/ts-program.js +1 -0
  152. package/src/verax/intel/vue-navigation-extractor.js +3 -0
  153. package/src/verax/learn/action-contract-extractor.js +67 -682
  154. package/src/verax/learn/ast-contract-extractor.js +1 -1
  155. package/src/verax/learn/flow-extractor.js +1 -0
  156. package/src/verax/learn/project-detector.js +5 -0
  157. package/src/verax/learn/react-router-extractor.js +2 -0
  158. package/src/verax/learn/route-validator.js +1 -4
  159. package/src/verax/learn/source-instrumenter.js +1 -0
  160. package/src/verax/learn/state-extractor.js +2 -1
  161. package/src/verax/learn/static-extractor.js +1 -0
  162. package/src/verax/observe/coverage-gaps.js +132 -0
  163. package/src/verax/observe/expectation-handler.js +126 -0
  164. package/src/verax/observe/incremental-skip.js +46 -0
  165. package/src/verax/observe/index.js +735 -84
  166. package/src/verax/observe/interaction-executor.js +192 -0
  167. package/src/verax/observe/interaction-runner.js +782 -530
  168. package/src/verax/observe/network-firewall.js +86 -0
  169. package/src/verax/observe/observation-builder.js +169 -0
  170. package/src/verax/observe/observe-context.js +1 -1
  171. package/src/verax/observe/observe-helpers.js +2 -1
  172. package/src/verax/observe/observe-runner.js +28 -24
  173. package/src/verax/observe/observers/budget-observer.js +3 -3
  174. package/src/verax/observe/observers/console-observer.js +4 -4
  175. package/src/verax/observe/observers/coverage-observer.js +4 -4
  176. package/src/verax/observe/observers/interaction-observer.js +3 -3
  177. package/src/verax/observe/observers/navigation-observer.js +4 -4
  178. package/src/verax/observe/observers/network-observer.js +4 -4
  179. package/src/verax/observe/observers/safety-observer.js +1 -1
  180. package/src/verax/observe/observers/ui-feedback-observer.js +4 -4
  181. package/src/verax/observe/page-traversal.js +138 -0
  182. package/src/verax/observe/snapshot-ops.js +94 -0
  183. package/src/verax/observe/ui-signal-sensor.js +2 -148
  184. package/src/verax/scan-summary-writer.js +10 -42
  185. package/src/verax/shared/artifact-manager.js +30 -13
  186. package/src/verax/shared/caching.js +1 -0
  187. package/src/verax/shared/expectation-tracker.js +1 -0
  188. package/src/verax/shared/zip-artifacts.js +6 -0
  189. package/src/verax/core/confidence-engine.js.backup +0 -471
  190. package/src/verax/shared/config-loader.js +0 -169
  191. /package/src/verax/shared/{expectation-proof.js → expectation-validation.js} +0 -0
@@ -62,6 +62,102 @@ export const CONFIDENCE_REASON = {
62
62
  SENSOR_MISSING: 'SENSOR_MISSING',
63
63
  };
64
64
 
65
+ // === PHASE 26: CONFIDENCE ENGINE SIMPLIFICATION ===
66
+ // 7 Core Buckets for scoring (Damage Control)
67
+ export const CONFIDENCE_BUCKET = {
68
+ CRITICAL_EVIDENCE: 'critical_evidence',
69
+ MULTI_SOURCE: 'multi_source_corroboration',
70
+ ASSET_CRITICAL: 'asset_criticality',
71
+ ABUSE_KNOWN: 'known_abuse_indicators',
72
+ EXPLOITABLE: 'exploitability_indicator',
73
+ PRIV_ESCALATION: 'privilege_escalation_path',
74
+ IMPACT_RADIUS: 'impact_radius',
75
+ };
76
+
77
+ const CORE_BUCKETS = new Set(Object.values(CONFIDENCE_BUCKET));
78
+
79
+ /**
80
+ * Classify confidence reason codes into buckets.
81
+ * Only CORE_BUCKETS contribute to score01.
82
+ * All other reasons become ADVISORY (tracked, not scored).
83
+ */
84
+ const CORE_BUCKET_CLASSIFICATION = {
85
+ // === CRITICAL_EVIDENCE (Reason codes that prove the finding) ===
86
+ [CONFIDENCE_REASON.PROMISE_PROVEN]: CONFIDENCE_BUCKET.CRITICAL_EVIDENCE,
87
+ [CONFIDENCE_REASON.OBS_UI_FEEDBACK_CONFIRMED]: CONFIDENCE_BUCKET.CRITICAL_EVIDENCE,
88
+ [CONFIDENCE_REASON.OBS_NETWORK_FAILURE]: CONFIDENCE_BUCKET.CRITICAL_EVIDENCE,
89
+
90
+ // === MULTI_SOURCE (Multiple independent signals align) ===
91
+ [CONFIDENCE_REASON.CORR_TIMING_ALIGNED]: CONFIDENCE_BUCKET.MULTI_SOURCE,
92
+ [CONFIDENCE_REASON.CORR_ROUTE_MATCHED]: CONFIDENCE_BUCKET.MULTI_SOURCE,
93
+ [CONFIDENCE_REASON.CORR_REQUEST_MATCHED]: CONFIDENCE_BUCKET.MULTI_SOURCE,
94
+
95
+ // === ASSET_CRITICAL (Critical asset/flow) ===
96
+ [CONFIDENCE_REASON.GUARD_SHALLOW_ROUTING]: CONFIDENCE_BUCKET.ASSET_CRITICAL,
97
+ [CONFIDENCE_REASON.EVIDENCE_TRACES]: CONFIDENCE_BUCKET.ASSET_CRITICAL,
98
+
99
+ // === ABUSE_KNOWN (Known abuse patterns) ===
100
+ [CONFIDENCE_REASON.GUARD_ANALYTICS_FILTERED]: CONFIDENCE_BUCKET.ABUSE_KNOWN,
101
+ [CONFIDENCE_REASON.OBS_NETWORK_SUCCESS]: CONFIDENCE_BUCKET.ABUSE_KNOWN,
102
+
103
+ // === EXPLOITABLE (Finding is actionable) ===
104
+ [CONFIDENCE_REASON.OBS_DOM_CHANGED]: CONFIDENCE_BUCKET.EXPLOITABLE,
105
+ [CONFIDENCE_REASON.OBS_CONSOLE_ERRORS]: CONFIDENCE_BUCKET.EXPLOITABLE,
106
+
107
+ // === PRIV_ESCALATION (Access/privilege changes) ===
108
+ [CONFIDENCE_REASON.OBS_URL_CHANGED]: CONFIDENCE_BUCKET.PRIV_ESCALATION,
109
+ [CONFIDENCE_REASON.CORR_TRACE_LINKED]: CONFIDENCE_BUCKET.PRIV_ESCALATION,
110
+
111
+ // === IMPACT_RADIUS (Scope/impact evidence) ===
112
+ [CONFIDENCE_REASON.EVIDENCE_SCREENSHOTS]: CONFIDENCE_BUCKET.IMPACT_RADIUS,
113
+ [CONFIDENCE_REASON.EVIDENCE_SNIPPETS]: CONFIDENCE_BUCKET.IMPACT_RADIUS,
114
+
115
+ // === ADVISORY (Non-core signals, tracked but not scored) ===
116
+ [CONFIDENCE_REASON.PROMISE_OBSERVED]: null,
117
+ [CONFIDENCE_REASON.PROMISE_WEAK]: null,
118
+ [CONFIDENCE_REASON.PROMISE_UNKNOWN]: null,
119
+ [CONFIDENCE_REASON.PROMISE_AST_BASED]: null,
120
+ [CONFIDENCE_REASON.OBS_NO_SIGNALS]: null,
121
+ [CONFIDENCE_REASON.CORR_WEAK_CORRELATION]: null,
122
+ [CONFIDENCE_REASON.GUARD_UI_FEEDBACK_PRESENT]: null,
123
+ [CONFIDENCE_REASON.GUARD_CONTRADICTION_DETECTED]: null,
124
+ [CONFIDENCE_REASON.GUARD_NETWORK_SUCCESS_NO_UI]: null,
125
+ [CONFIDENCE_REASON.EVIDENCE_SIGNALS]: null,
126
+ [CONFIDENCE_REASON.EVIDENCE_INCOMPLETE]: null,
127
+ [CONFIDENCE_REASON.SENSOR_NETWORK_PRESENT]: null,
128
+ [CONFIDENCE_REASON.SENSOR_CONSOLE_PRESENT]: null,
129
+ [CONFIDENCE_REASON.SENSOR_UI_PRESENT]: null,
130
+ [CONFIDENCE_REASON.SENSOR_UI_FEEDBACK_PRESENT]: null,
131
+ [CONFIDENCE_REASON.SENSOR_MISSING]: null,
132
+ };
133
+
134
+ /**
135
+ * Get bucket for a confidence reason. Returns null if advisory.
136
+ */
137
+ function getReasonBucket(reasonCode) {
138
+ return CORE_BUCKET_CLASSIFICATION[reasonCode] || null;
139
+ }
140
+
141
+ /**
142
+ * Filter reasons to only core bucket reasons.
143
+ */
144
+ function filterCoreReasons(reasons) {
145
+ return reasons.filter(reason => {
146
+ const bucket = getReasonBucket(reason);
147
+ return bucket !== null && CORE_BUCKETS.has(bucket);
148
+ });
149
+ }
150
+
151
+ /**
152
+ * Extract advisory reasons (non-core).
153
+ */
154
+ function extractAdvisoryReasons(reasons) {
155
+ return reasons.filter(reason => {
156
+ const bucket = getReasonBucket(reason);
157
+ return bucket === null;
158
+ });
159
+ }
160
+
65
161
  // Global policy cache
66
162
  let cachedPolicy = null;
67
163
 
@@ -77,6 +173,10 @@ function getConfidencePolicy(policyPath = null, projectDir = null) {
77
173
 
78
174
  /**
79
175
  * PHASE 21.4: Compute unified confidence using policy
176
+ * PHASE 26: Bucket-gated scoring (Confidence Simplification Contract v1)
177
+ *
178
+ * Note: For backward compatibility, ALL reasons are returned in `reasons` array.
179
+ * Use `advisoryReasons` to identify which ones didn't contribute to score01.
80
180
  *
81
181
  * @param {Object} params - Confidence computation parameters
82
182
  * @param {string} params.findingType - Type of finding
@@ -85,7 +185,7 @@ function getConfidencePolicy(policyPath = null, projectDir = null) {
85
185
  * @param {Object} params.comparisons - Comparison data
86
186
  * @param {Object} params.evidence - Evidence data (optional)
87
187
  * @param {Object} params.options - Options { policyPath, projectDir, determinismVerdict, evidencePackage }
88
- * @returns {Object} { score, level, reasons[], policyReport }
188
+ * @returns {Object} { score, level, reasons[], advisoryReasons[], policyReport }
89
189
  */
90
190
  export function computeUnifiedConfidence({ findingType, expectation, sensors = {}, comparisons = {}, evidence = {}, options = {} }) {
91
191
  // Backward compatibility: options is optional
@@ -107,6 +207,10 @@ export function computeUnifiedConfidence({ findingType, expectation, sensors = {
107
207
  // === PILLAR E: Evidence Completeness ===
108
208
  const evidenceCompleteness = assessEvidenceCompleteness(evidence, sensors, reasons, policy);
109
209
 
210
+ // === PHASE 26: Identify core vs advisory reasons ===
211
+ const coreReasons = filterCoreReasons(reasons);
212
+ const advisoryReasons = extractAdvisoryReasons(reasons);
213
+
110
214
  // === COMPUTE BASE SCORE using policy weights ===
111
215
  const baseScore = (
112
216
  promiseStrength * policy.weights.promiseStrength +
@@ -115,16 +219,27 @@ export function computeUnifiedConfidence({ findingType, expectation, sensors = {
115
219
  guardrails * policy.weights.guardrails +
116
220
  evidenceCompleteness * policy.weights.evidenceCompleteness
117
221
  );
118
-
222
+
223
+ // === APPLY MISSING SENSORS PENALTY ===
224
+ // Legacy rule: -15 penalty when any core sensor object is missing (network, console, uiSignals)
225
+ const requiredSensors = ['network', 'console', 'uiSignals'];
226
+ const missingSensors = requiredSensors.filter(key => sensors[key] === undefined);
227
+ // Increase penalty so missing core sensors are clearly visible in score100
228
+ const missingSensorsPenalty = missingSensors.length > 0 ? 0.25 : 0; // -25 in 0..1 scale
229
+ if (missingSensorsPenalty > 0) {
230
+ reasons.push(CONFIDENCE_REASON.SENSOR_MISSING);
231
+ }
232
+
119
233
  // === APPLY CONTRADICTIONS using policy ===
234
+ // Apply contradiction penalty only when guardrails are materially degraded
120
235
  const contradictionPenalty = guardrails < 0.5 ? policy.truthLocks.contradictionPenalty : 0;
121
- let finalScore = Math.max(0, Math.min(1, baseScore - contradictionPenalty));
236
+ let finalScore = Math.max(0, Math.min(1, baseScore - contradictionPenalty - missingSensorsPenalty));
122
237
 
123
238
  // === TRUTH LOCKS: Apply determinism cap ===
124
239
  if (options?.determinismVerdict === 'NON_DETERMINISTIC') {
125
240
  const maxConfidence = policy.truthLocks.nonDeterministicMaxConfidence;
126
241
  if (finalScore > maxConfidence) {
127
- reasons.push('TRUTH_LOCK_NON_DETERMINISTIC_CAP');
242
+ coreReasons.push('TRUTH_LOCK_NON_DETERMINISTIC_CAP');
128
243
  finalScore = Math.min(finalScore, maxConfidence);
129
244
  }
130
245
  }
@@ -133,21 +248,28 @@ export function computeUnifiedConfidence({ findingType, expectation, sensors = {
133
248
  const evidencePackage = options?.evidencePackage || evidence?.evidencePackage || {};
134
249
  if (evidencePackage?.severity === 'CONFIRMED' || evidencePackage?.status === 'CONFIRMED') {
135
250
  if (policy.truthLocks.evidenceCompleteRequired && !evidencePackage.isComplete) {
136
- reasons.push('TRUTH_LOCK_EVIDENCE_INCOMPLETE');
251
+ coreReasons.push('TRUTH_LOCK_EVIDENCE_INCOMPLETE');
137
252
  // Force downgrade from CONFIRMED
138
253
  finalScore = Math.min(finalScore, 0.6); // Cap at MEDIUM
139
254
  }
140
255
  }
141
256
 
142
- // === DETERMINE LEVEL using policy thresholds ===
257
+ // === DETERMINE LEVEL using Contract v1 thresholds ===
143
258
  const level = determineConfidenceLevel(finalScore, promiseStrength, evidenceCompleteness, policy);
144
259
 
145
260
  const policyReport = getPolicyReport(policy);
146
261
 
262
+ // Contract v1: score01 is canonical, derive score100 and topReasons (core only)
263
+ const topReasons = coreReasons.slice(0, 4);
264
+
147
265
  return {
148
- score: finalScore,
266
+ score: finalScore, // alias for score01 for backward compatibility
267
+ score01: finalScore,
268
+ score100: Math.round(finalScore * 100),
149
269
  level,
150
- reasons: reasons.slice(0, 10),
270
+ reasons: reasons.slice(0, 10), // ALL reasons for backward compat (core + advisory)
271
+ advisoryReasons: advisoryReasons.slice(0, 10), // Advisory reasons (for analyst/context)
272
+ topReasons,
151
273
  policyReport: {
152
274
  version: policyReport.version,
153
275
  source: policyReport.source,
@@ -284,7 +406,7 @@ function assessCorrelationQuality(expectation, sensors, comparisons, evidence, r
284
406
  /**
285
407
  * Assess guardrails & contradictions (Pillar D) using policy
286
408
  */
287
- function assessGuardrails(sensors, comparisons, findingType, reasons, policy) {
409
+ function assessGuardrails(sensors, comparisons, findingType, reasons, _policy) {
288
410
  let guardrailScore = 1.0;
289
411
 
290
412
  const networkSensor = sensors.network || {};
@@ -355,23 +477,20 @@ function assessEvidenceCompleteness(evidence, sensors, reasons, policy) {
355
477
  }
356
478
 
357
479
  /**
358
- * Determine confidence level using policy thresholds
480
+ * Determine confidence level using Contract v1 thresholds
481
+ * HIGH: score01 >= 0.85
482
+ * MEDIUM: 0.60 <= score01 < 0.85
483
+ * UNPROVEN: score01 < 0.60
359
484
  */
360
- function determineConfidenceLevel(score, promiseStrength, evidenceCompleteness, policy) {
361
- const thresholds = policy.thresholds;
362
-
363
- if (score >= thresholds.high && promiseStrength >= 0.9 && evidenceCompleteness >= 0.7) {
485
+ function determineConfidenceLevel(score, _promiseStrength, _evidenceCompleteness, _policy) {
486
+ if (score >= 0.85) {
364
487
  return CONFIDENCE_LEVEL.HIGH;
365
488
  }
366
489
 
367
- if (score >= thresholds.medium || (score >= 0.5 && promiseStrength >= 0.7)) {
490
+ if (score >= 0.60) {
368
491
  return CONFIDENCE_LEVEL.MEDIUM;
369
492
  }
370
493
 
371
- if (score >= thresholds.low) {
372
- return CONFIDENCE_LEVEL.LOW;
373
- }
374
-
375
494
  return CONFIDENCE_LEVEL.UNPROVEN;
376
495
  }
377
496
 
@@ -379,6 +498,11 @@ function determineConfidenceLevel(score, promiseStrength, evidenceCompleteness,
379
498
  * PHASE 21.4: Compute confidence for finding (wrapper with policy support)
380
499
  *
381
500
  * @param {Object} params - Confidence computation parameters
501
+ * @param {string} params.findingType - Type of finding
502
+ * @param {Object} params.expectation - Promise/expectation
503
+ * @param {Object} params.sensors - Sensor data
504
+ * @param {Object} params.comparisons - Comparison data
505
+ * @param {Object} params.evidence - Evidence data
382
506
  * @param {Object} params.options - Options { policyPath, projectDir, determinismVerdict, evidencePackage }
383
507
  * @returns {Object} { score, level, reasons[] }
384
508
  */
@@ -387,10 +511,19 @@ export function computeConfidenceForFinding(params) {
387
511
  const options = params.options || {};
388
512
 
389
513
  // Use legacy system for base computation
390
- const legacyResult = computeConfidenceLegacy(params);
514
+ const legacyParams = {
515
+ findingType: params.findingType,
516
+ expectation: params.expectation,
517
+ sensors: params.sensors,
518
+ comparisons: params.comparisons,
519
+ attemptMeta: params['attemptMeta'] || {},
520
+ executionModeCeiling: options.executionModeCeiling || 1.0
521
+ };
522
+ const legacyResult = computeConfidenceLegacy(legacyParams);
391
523
 
392
- // Normalize score from 0-100 to 0-1
524
+ // Legacy returns score in 0-100; normalize to 0-1 for unified engine
393
525
  let normalizedScore = (legacyResult.score || 0) / 100;
526
+ normalizedScore = Math.max(0, Math.min(1, normalizedScore));
394
527
 
395
528
  // Extract reasons from legacy explain/factors
396
529
  const reasons = extractReasonsFromLegacy(legacyResult, params);
@@ -408,7 +541,7 @@ export function computeConfidenceForFinding(params) {
408
541
  }
409
542
 
410
543
  // === TRUTH LOCKS: Evidence Law cap ===
411
- const evidencePackage = options?.evidencePackage || params.evidence?.evidencePackage || {};
544
+ const evidencePackage = options?.evidencePackage || (params.evidence && params.evidence.evidencePackage) || {};
412
545
  if (evidencePackage?.severity === 'CONFIRMED' || evidencePackage?.status === 'CONFIRMED') {
413
546
  if (policy.truthLocks.evidenceCompleteRequired && !evidencePackage.isComplete) {
414
547
  reasons.push('TRUTH_LOCK_EVIDENCE_INCOMPLETE');
@@ -424,10 +557,16 @@ export function computeConfidenceForFinding(params) {
424
557
  policy
425
558
  );
426
559
 
560
+ // Contract v1: Return score01, score100, level, and topReasons
561
+ const topReasons = reasons.slice(0, 4);
562
+
427
563
  return {
428
- score: normalizedScore,
564
+ score01: normalizedScore,
565
+ score100: Math.round(normalizedScore * 100),
566
+ score: normalizedScore, // Backward compat - should be 0..1
429
567
  level,
430
568
  reasons,
569
+ topReasons,
431
570
  };
432
571
  }
433
572
 
@@ -25,6 +25,7 @@ export const FINDING_STATUS = {
25
25
  CONFIRMED: 'CONFIRMED', // Evidence law satisfied: sufficient evidence exists
26
26
  SUSPECTED: 'SUSPECTED', // Needs evidence: signal observed but evidence incomplete
27
27
  INFORMATIONAL: 'INFORMATIONAL', // Observation recorded, no claim of failure
28
+ UNPROVEN: 'UNPROVEN', // Evidence Law v1: insufficient evidence to support claim
28
29
  DROPPED: 'DROPPED' // Violated contracts, removed from report
29
30
  };
30
31
 
@@ -97,7 +97,23 @@ export function validateFinding(finding) {
97
97
  // *** EVIDENCE LAW ENFORCEMENT ***
98
98
  // PHASE 16: Check evidencePackage completeness for CONFIRMED findings
99
99
  // PHASE 21.1: HARD LOCK - CONFIRMED without complete evidencePackage is IMPOSSIBLE
100
+ // PHASE 21: Evidence Law validation for CONFIRMED findings
100
101
  if (finding.status === FINDING_STATUS.CONFIRMED || finding.severity === 'CONFIRMED') {
102
+ // EVIDENCE LAW v1: Check evidence structure first (context anchor + effect evidence)
103
+ const evidenceLawResult = enforceEvidenceLawV1(finding.evidence);
104
+ if (!evidenceLawResult.ok && evidenceLawResult.downgrade) {
105
+ console.log(
106
+ `EVIDENCE_LAW v1: downgraded CONFIRMED -> ${evidenceLawResult.downgrade} ` +
107
+ `(missing: ${evidenceLawResult.missing.join(', ')})`
108
+ );
109
+ return {
110
+ ok: true,
111
+ errors: [],
112
+ shouldDowngrade: true,
113
+ suggestedStatus: evidenceLawResult.downgrade
114
+ };
115
+ }
116
+
101
117
  // PHASE 21.1: Strict invariant - CONFIRMED findings MUST have complete evidencePackage
102
118
  if (finding.evidencePackage) {
103
119
  const missingFields = finding.evidencePackage.missingEvidence || [];
@@ -222,10 +238,17 @@ export function validateConfidence(confidence) {
222
238
  );
223
239
  }
224
240
 
225
- if (confidence.score === undefined || confidence.score === null) {
226
- errors.push('Missing required field: confidence.score');
227
- } else if (typeof confidence.score !== 'number' || confidence.score < 0 || confidence.score > 100) {
228
- errors.push(`Invalid confidence.score: ${confidence.score}. Must be a number 0-100`);
241
+ // Contract v1: Accept both score01 (0-1) and legacy score (0-100) for backward compat
242
+ if (confidence.score01 !== undefined) {
243
+ if (typeof confidence.score01 !== 'number' || confidence.score01 < 0 || confidence.score01 > 1) {
244
+ errors.push(`Invalid confidence.score01: ${confidence.score01}. Must be a number 0-1`);
245
+ }
246
+ } else if (confidence.score !== undefined) {
247
+ if (typeof confidence.score !== 'number' || confidence.score < 0 || confidence.score > 100) {
248
+ errors.push(`Invalid confidence.score: ${confidence.score}. Must be a number 0-100`);
249
+ }
250
+ } else {
251
+ errors.push('Missing required field: confidence.score01 or confidence.score');
229
252
  }
230
253
 
231
254
  return {
@@ -285,6 +308,57 @@ export function validateSignals(signals) {
285
308
  };
286
309
  }
287
310
 
311
+ /**
312
+ * EVIDENCE LAW v1: Check if CONFIRMED finding has complete evidence structure
313
+ *
314
+ * Rule A (Context Anchor): Must have beforeUrl OR beforeScreenshot OR before
315
+ * Rule B (Effect Evidence): Must have afterUrl OR after OR flags OR quantitative indicators
316
+ *
317
+ * @param {Object} evidence - Evidence object to validate
318
+ * @returns {Object} { ok: boolean, downgrade: 'UNPROVEN'|'SUSPECTED'|null, missing: string[] }
319
+ */
320
+ export function enforceEvidenceLawV1(evidence) {
321
+ if (!evidence || typeof evidence !== 'object' || Object.keys(evidence).length === 0) {
322
+ return { ok: false, downgrade: 'UNPROVEN', missing: ['evidence object'] };
323
+ }
324
+
325
+ const missing = [];
326
+
327
+ // Rule A: Context anchor (before state)
328
+ const hasContextAnchor = evidence.beforeUrl || evidence.beforeScreenshot || evidence.before;
329
+ if (!hasContextAnchor) {
330
+ missing.push('context anchor (beforeUrl/beforeScreenshot/before)');
331
+ }
332
+
333
+ // Rule B: Effect evidence (after state or change indicators)
334
+ const hasEffectEvidence =
335
+ evidence.afterUrl ||
336
+ evidence.afterScreenshot ||
337
+ evidence.after ||
338
+ evidence.urlChanged === true ||
339
+ evidence.domChanged === true ||
340
+ evidence.uiChanged === true ||
341
+ (typeof evidence.networkRequests === 'number' && evidence.networkRequests > 0) ||
342
+ (Array.isArray(evidence.networkRequests) && evidence.networkRequests.length > 0) ||
343
+ (typeof evidence.consoleErrors === 'number' && evidence.consoleErrors > 0) ||
344
+ (Array.isArray(evidence.consoleErrors) && evidence.consoleErrors.length > 0) ||
345
+ evidence.timingBreakdown;
346
+
347
+ if (!hasEffectEvidence) {
348
+ missing.push('effect evidence (after/flags/quantitative)');
349
+ }
350
+
351
+ // Downgrade logic
352
+ if (!hasContextAnchor && !hasEffectEvidence) {
353
+ return { ok: false, downgrade: 'UNPROVEN', missing };
354
+ }
355
+ if (!hasContextAnchor || !hasEffectEvidence) {
356
+ return { ok: false, downgrade: 'SUSPECTED', missing };
357
+ }
358
+
359
+ return { ok: true, downgrade: null, missing: [] };
360
+ }
361
+
288
362
  /**
289
363
  * EVIDENCE LAW: Determine if evidence is sufficient for CONFIRMED status
290
364
  *
@@ -377,5 +451,6 @@ export default {
377
451
  validateConfidence,
378
452
  validateSignals,
379
453
  isEvidenceSubstantive,
454
+ enforceEvidenceLawV1,
380
455
  enforceContractsOnFindings
381
456
  };
@@ -176,11 +176,9 @@ function extractUnverified(detectTruth, observeTruth) {
176
176
  * @param {Object} detectTruth - Detect phase truth
177
177
  * @param {Object} observeTruth - Observe phase truth
178
178
  * @param {Object} _silences - Silence data (unused parameter, kept for API compatibility)
179
- * @param {Object} options - Additional options { executionMode, executionModeCeiling }
180
- * @returns {Object} - Decision snapshot answering 6 mandatory questions + execution mode info
179
+ * @returns {Object} - Decision snapshot answering 6 mandatory questions
181
180
  */
182
- export function computeDecisionSnapshot(findings, detectTruth, observeTruth, _silences, options = {}) {
183
- const { executionMode = null, executionModeCeiling = 1.0 } = options;
181
+ export function computeDecisionSnapshot(findings, detectTruth, observeTruth, _silences) {
184
182
  // Question 1: Do we have confirmed SILENT FAILURES?
185
183
  const confirmedFailures = findings.filter(f =>
186
184
  f.outcome === 'broken' || f.type === 'silent_failure'
@@ -266,35 +264,10 @@ export function computeDecisionSnapshot(findings, detectTruth, observeTruth, _si
266
264
  score: confidence.score,
267
265
  coverageRatio: confidence.coverageRatio,
268
266
  factors: confidence.factors
269
- },
270
-
271
- // EXECUTION MODE
272
- executionMode: executionMode,
273
- executionModeCeiling: executionModeCeiling,
274
- executionModeExplanation: generateExecutionModeExplanation(executionMode, executionModeCeiling)
267
+ }
275
268
  };
276
269
  }
277
270
 
278
- /**
279
- * Generate explanation for execution mode awareness
280
- * @param {string} mode - Execution mode (PROJECT_SCAN or WEB_SCAN_LIMITED)
281
- * @param {number} ceiling - Confidence ceiling (0..1)
282
- * @returns {string} - Human-readable explanation
283
- */
284
- function generateExecutionModeExplanation(mode, ceiling) {
285
- if (!mode) {
286
- return null;
287
- }
288
-
289
- if (mode === 'WEB_SCAN_LIMITED') {
290
- return `Analysis limited to runtime behavior observation (no source code). Confidence capped at ${Math.round(ceiling * 100)}%. Use source code scanning for fuller analysis.`;
291
- } else if (mode === 'PROJECT_SCAN') {
292
- return `Full project analysis with source code. Confidence unrestricted based on evidence quality.`;
293
- }
294
-
295
- return null;
296
- }
297
-
298
271
  /**
299
272
  * Format decision snapshot for human reading
300
273
  * @param {Object} snapshot - Decision snapshot
@@ -27,6 +27,7 @@ export function buildDecisionTrace(projectDir, runId) {
27
27
  return null;
28
28
  }
29
29
 
30
+ // @ts-expect-error - readFileSync with encoding returns string
30
31
  const findings = JSON.parse(readFileSync(findingsPath, 'utf-8'));
31
32
  const traces = [];
32
33
 
@@ -268,6 +269,7 @@ export function loadDecisionTrace(projectDir, runId) {
268
269
  }
269
270
 
270
271
  try {
272
+ // @ts-expect-error - readFileSync with encoding returns string
271
273
  return JSON.parse(readFileSync(tracePath, 'utf-8'));
272
274
  } catch {
273
275
  return null;
@@ -7,14 +7,14 @@
7
7
 
8
8
  import { writeFileSync } from 'fs';
9
9
  import { resolve } from 'path';
10
- import { DecisionRecorder } from '../determinism-model.js';
10
+ import { DecisionRecorder as _DecisionRecorder } from '../determinism-model.js';
11
11
  import { ARTIFACT_REGISTRY } from '../artifacts/registry.js';
12
12
 
13
13
  /**
14
14
  * Write determinism contract artifact
15
15
  *
16
16
  * @param {string} runDir - Absolute run directory path
17
- * @param {DecisionRecorder} decisionRecorder - Decision recorder instance
17
+ * @param {Object} decisionRecorder - Decision recorder instance
18
18
  * @returns {string} Path to written contract
19
19
  */
20
20
  export function writeDeterminismContract(runDir, decisionRecorder) {
@@ -71,7 +71,7 @@ export function isAdaptiveEventCategory(category) {
71
71
  *
72
72
  * HARD RULE: If any adaptive event occurred → NON_DETERMINISTIC
73
73
  *
74
- * @param {DecisionRecorder} decisionRecorder - Decision recorder instance
74
+ * @param {Object} decisionRecorder - Decision recorder instance
75
75
  * @returns {Object} { verdict, reasons, adaptiveEvents }
76
76
  */
77
77
  export function computeDeterminismVerdict(decisionRecorder) {
@@ -4,7 +4,7 @@
4
4
  * Generates structured diffs between normalized artifacts from different runs.
5
5
  */
6
6
 
7
- import { computeFindingIdentity } from './finding-identity.js';
7
+ import { computeFindingIdentity as _computeFindingIdentity } from './finding-identity.js';
8
8
 
9
9
  /**
10
10
  * PHASE 18: Diff reason codes
@@ -48,6 +48,18 @@ export const DIFF_SEVERITY = {
48
48
  INFO: 'INFO',
49
49
  };
50
50
 
51
+ function diffRunMeta(artifactA, artifactB) {
52
+ return diffGeneric(artifactA, artifactB, 'runMeta');
53
+ }
54
+
55
+ function diffDeterminismContract(artifactA, artifactB) {
56
+ return diffGeneric(artifactA, artifactB, 'determinismContract');
57
+ }
58
+
59
+ function diffReportArtifact(artifactA, artifactB, artifactName) {
60
+ return diffGeneric(artifactA, artifactB, artifactName);
61
+ }
62
+
51
63
  /**
52
64
  * PHASE 18: Diff artifacts
53
65
  *
@@ -320,6 +332,35 @@ function diffFinding(findingA, findingB, identity) {
320
332
  findingIdentity: identity,
321
333
  field: 'evidencePackage',
322
334
  });
335
+ } else if (evidencePackageA && evidencePackageB) {
336
+ if (evidencePackageA.isComplete !== evidencePackageB.isComplete) {
337
+ diffs.push({
338
+ category: DIFF_CATEGORY.EVIDENCE,
339
+ severity: DIFF_SEVERITY.BLOCKER,
340
+ reasonCode: DIFF_REASON.EVIDENCE_COMPLETENESS_CHANGED,
341
+ message: `Evidence completeness changed: ${evidencePackageA.isComplete} → ${evidencePackageB.isComplete}`,
342
+ artifact: 'findings',
343
+ findingIdentity: identity,
344
+ field: 'evidencePackage.isComplete',
345
+ oldValue: evidencePackageA.isComplete,
346
+ newValue: evidencePackageB.isComplete,
347
+ });
348
+ }
349
+ const missingA = Array.isArray(evidencePackageA.missingEvidence) ? evidencePackageA.missingEvidence.sort() : [];
350
+ const missingB = Array.isArray(evidencePackageB.missingEvidence) ? evidencePackageB.missingEvidence.sort() : [];
351
+ if (missingA.join('|') !== missingB.join('|')) {
352
+ diffs.push({
353
+ category: DIFF_CATEGORY.EVIDENCE,
354
+ severity: DIFF_SEVERITY.BLOCKER,
355
+ reasonCode: DIFF_REASON.EVIDENCE_MISSING,
356
+ message: `Missing evidence changed`,
357
+ artifact: 'findings',
358
+ findingIdentity: identity,
359
+ field: 'evidencePackage.missingEvidence',
360
+ oldValue: missingA,
361
+ newValue: missingB,
362
+ });
363
+ }
323
364
  }
324
365
 
325
366
  return diffs;
@@ -7,12 +7,11 @@
7
7
  */
8
8
 
9
9
  import { readFileSync, existsSync } from 'fs';
10
- import { join, resolve } from 'path';
10
+ import { join as _join, resolve } from 'path';
11
11
  import { normalizeArtifact } from './normalize.js';
12
12
  import { diffArtifacts } from './diff.js';
13
13
  import { computeFindingIdentity } from './finding-identity.js';
14
- import { ARTIFACT_REGISTRY } from '../artifacts/registry.js';
15
- import { computeDeterminismVerdict, DETERMINISM_VERDICT, DETERMINISM_REASON } from './contract.js';
14
+ import { computeDeterminismVerdict, DETERMINISM_VERDICT } from './contract.js';
16
15
  import { DecisionRecorder } from '../determinism-model.js';
17
16
 
18
17
  /**
@@ -28,9 +27,9 @@ export { DETERMINISM_VERDICT, DETERMINISM_REASON } from './contract.js';
28
27
  * @param {number} options.runs - Number of runs (default: 2)
29
28
  * @param {Object} options.config - Configuration for runs
30
29
  * @param {boolean} options.normalize - Whether to normalize artifacts (default: true)
31
- * @returns {Object} { verdict, summary, diffs, runsMeta }
30
+ * @returns {Promise<Object>} { verdict, summary, diffs, runsMeta }
32
31
  */
33
- export async function runDeterminismCheck(runFn, options = {}) {
32
+ export async function runDeterminismCheck(runFn, options = { runs: 2, config: {}, normalize: true }) {
34
33
  const { runs = 2, config = {}, normalize = true } = options;
35
34
 
36
35
  const runsMeta = [];
@@ -53,6 +52,7 @@ export async function runDeterminismCheck(runFn, options = {}) {
53
52
  for (const [key, path] of Object.entries(runResult.artifactPaths)) {
54
53
  try {
55
54
  const content = readFileSync(path, 'utf-8');
55
+ // @ts-expect-error - readFileSync with encoding returns string
56
56
  artifacts[key] = JSON.parse(content);
57
57
  } catch (error) {
58
58
  // Artifact not found or invalid
@@ -121,6 +121,7 @@ export async function runDeterminismCheck(runFn, options = {}) {
121
121
 
122
122
  if (existsSync(decisionsPath)) {
123
123
  try {
124
+ // @ts-expect-error - readFileSync with encoding returns string
124
125
  const decisionsData = JSON.parse(readFileSync(decisionsPath, 'utf-8'));
125
126
  const decisionRecorder = DecisionRecorder.fromExport(decisionsData);
126
127
  const adaptiveCheck = computeDeterminismVerdict(decisionRecorder);
@@ -187,7 +188,7 @@ function buildFindingIdentityMap(artifactA, artifactB) {
187
188
  /**
188
189
  * Build summary from diffs
189
190
  */
190
- function buildSummary(diffs, runsMeta) {
191
+ function buildSummary(diffs, _runsMeta) {
191
192
  const blockerCount = diffs.filter(d => d.severity === 'BLOCKER').length;
192
193
  const warnCount = diffs.filter(d => d.severity === 'WARN').length;
193
194
  const infoCount = diffs.filter(d => d.severity === 'INFO').length;
@@ -120,8 +120,8 @@ function normalizePath(path) {
120
120
  // Normalize separators
121
121
  let normalized = path.replace(/\\/g, '/');
122
122
  // Remove absolute path prefixes (keep relative structure)
123
- normalized = normalized.replace(/^[A-Z]:\/[^\/]+/, '');
124
- normalized = normalized.replace(/^\/[^\/]+/, '');
123
+ normalized = normalized.replace(/^[A-Z]:\/[^/]+/, '');
124
+ normalized = normalized.replace(/^\/[^/]+/, '');
125
125
  return normalized;
126
126
  }
127
127
 
@@ -143,6 +143,7 @@ function normalizeUrl(url) {
143
143
  * Hash string for stable identity
144
144
  */
145
145
  function hashString(str) {
146
+ // @ts-expect-error - digest returns string
146
147
  return createHash('sha256').update(str).digest('hex').substring(0, 16);
147
148
  }
148
149