@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
@@ -0,0 +1,417 @@
1
+ /**
2
+ * EXPECTATION CHAIN DETECTION
3
+ *
4
+ * Detects silent failures where a sequence of promised user expectations breaks mid-journey.
5
+ *
6
+ * Requirements:
7
+ * 1) Build expectation chains from source-derived expectations:
8
+ * - navigation → content → next action
9
+ * 2) Track which chain steps were fulfilled vs broken
10
+ * 3) Emit finding type: "expectation-chain-break"
11
+ * 4) Include metadata:
12
+ * - chainLength
13
+ * - fulfilledSteps
14
+ * - brokenStepIndex
15
+ * 5) Confidence rules:
16
+ * - Longer chains + late break = higher confidence
17
+ */
18
+
19
+ /**
20
+ * Expectation Chain Detector
21
+ *
22
+ * Analyzes sequences of proven expectations to detect when they break
23
+ * in a predictable chain pattern.
24
+ */
25
+ export class ExpectationChainDetector {
26
+ constructor(options = {}) {
27
+ this.minChainLength = options.minChainLength || 2; // Minimum 2 for a "chain"
28
+ this.maxChainLength = options.maxChainLength || 10; // Limit to 10 for analysis
29
+ }
30
+
31
+ /**
32
+ * Detect expectation chains that break
33
+ * @param {Array} expectations - Proven expectations from source analysis
34
+ * @param {Array} traces - Interaction traces from observe phase
35
+ * @returns {Array} Expectation chain break findings
36
+ */
37
+ detectChainBreaks(expectations = [], traces = []) {
38
+ if (!Array.isArray(expectations) || !Array.isArray(traces)) {
39
+ return [];
40
+ }
41
+
42
+ const findings = [];
43
+
44
+ // Build expectation chains from proven expectations
45
+ const chains = this._buildChains(expectations);
46
+
47
+ // Validate each chain against traces
48
+ for (const chain of chains) {
49
+ const chainBreak = this._validateChain(chain, traces);
50
+ if (chainBreak) {
51
+ findings.push(chainBreak);
52
+ }
53
+ }
54
+
55
+ return findings;
56
+ }
57
+
58
+ /**
59
+ * Build expectation chains from a list of expectations
60
+ * Chain pattern: navigation → content → interaction
61
+ * @private
62
+ */
63
+ _buildChains(expectations) {
64
+ const chains = [];
65
+
66
+ // Filter to PROVEN expectations only
67
+ const provenExpectations = expectations.filter(exp => this._isProven(exp));
68
+
69
+ if (provenExpectations.length < this.minChainLength) {
70
+ return chains;
71
+ }
72
+
73
+ // Group expectations by sequence/order
74
+ const ordered = this._orderExpectationsBySequence(provenExpectations);
75
+
76
+ // Build chains from consecutive expectations
77
+ let currentChain = [];
78
+ for (let i = 0; i < ordered.length; i++) {
79
+ const exp = ordered[i];
80
+
81
+ // Add to current chain
82
+ currentChain.push({
83
+ index: i,
84
+ expectation: exp,
85
+ type: this._normalizeExpectationType(exp),
86
+ sourceRef: exp.sourceRef || exp.filename,
87
+ lineNumber: exp.lineNumber
88
+ });
89
+
90
+ // Check if chain should end
91
+ const isLastExp = i === ordered.length - 1;
92
+ const nextExp = !isLastExp ? ordered[i + 1] : null;
93
+
94
+ // Chain ends if:
95
+ // 1. Gap in expectation sequence (next type is unrelated)
96
+ // 2. Chain is at max length
97
+ // 3. We're at the end
98
+ const shouldBreakChain =
99
+ currentChain.length >= this.maxChainLength ||
100
+ isLastExp ||
101
+ (nextExp && !this._isConsecutiveExpectation(exp, nextExp));
102
+
103
+ if (shouldBreakChain && currentChain.length >= this.minChainLength) {
104
+ chains.push(currentChain);
105
+ currentChain = [];
106
+ }
107
+ }
108
+
109
+ return chains;
110
+ }
111
+
112
+ /**
113
+ * Validate a chain against actual traces
114
+ * Returns finding if chain breaks (some steps work, later steps fail)
115
+ * @private
116
+ */
117
+ _validateChain(chain, traces) {
118
+ if (chain.length < this.minChainLength) {
119
+ return null;
120
+ }
121
+
122
+ let fulfilledSteps = 0;
123
+ let brokenStepIndex = -1;
124
+
125
+ // Check each step in the chain
126
+ for (let i = 0; i < chain.length; i++) {
127
+ const step = chain[i];
128
+ const stepType = step.type;
129
+
130
+ // Find matching trace for this step
131
+ const matchingTrace = traces.find(trace =>
132
+ this._traceMatchesExpectation(trace, step.expectation)
133
+ );
134
+
135
+ if (!matchingTrace) {
136
+ // Step not found in traces - chain breaks here
137
+ brokenStepIndex = i;
138
+ break;
139
+ }
140
+
141
+ // Check if step expectation was fulfilled
142
+ const isFulfilled = this._isExpectationFulfilled(
143
+ step.expectation,
144
+ matchingTrace
145
+ );
146
+
147
+ if (isFulfilled) {
148
+ fulfilledSteps++;
149
+ } else {
150
+ // Expectation exists but not fulfilled
151
+ brokenStepIndex = i;
152
+ break;
153
+ }
154
+ }
155
+
156
+ // Only emit finding if:
157
+ // 1. Chain had at least 1 fulfilled step before breaking
158
+ // 2. Breaking happened (not all steps fulfilled)
159
+ if (brokenStepIndex > 0 && fulfilledSteps > 0) {
160
+ return {
161
+ id: `expectation-chain-break-${Date.now()}-${Math.random()
162
+ .toString(36)
163
+ .slice(2, 9)}`,
164
+ type: 'expectation-chain-break',
165
+ severity: this._computeSeverity(chain.length, fulfilledSteps, brokenStepIndex),
166
+ evidence: {
167
+ chainLength: chain.length,
168
+ fulfilledSteps,
169
+ brokenStepIndex,
170
+ chain: chain.map(step => ({
171
+ type: step.type,
172
+ sourceRef: step.sourceRef,
173
+ lineNumber: step.lineNumber
174
+ })),
175
+ brokenStep: chain[brokenStepIndex]
176
+ ? {
177
+ type: chain[brokenStepIndex].type,
178
+ sourceRef: chain[brokenStepIndex].sourceRef,
179
+ lineNumber: chain[brokenStepIndex].lineNumber
180
+ }
181
+ : null,
182
+ description: this._describeChainBreak(
183
+ chain,
184
+ fulfilledSteps,
185
+ brokenStepIndex
186
+ )
187
+ },
188
+ expectation: {
189
+ proof: 'PROVEN_EXPECTATION',
190
+ explicit: true,
191
+ evidence: {
192
+ source: 'expectation-chain-analysis',
193
+ chainAnalysis: true
194
+ }
195
+ },
196
+ actualOutcome: 'expectation_chain_breaks_at_step',
197
+ impact: 'HIGH'
198
+ };
199
+ }
200
+
201
+ return null;
202
+ }
203
+
204
+ /**
205
+ * Check if trace matches expectation
206
+ * @private
207
+ */
208
+ _traceMatchesExpectation(trace, expectation) {
209
+ if (!trace || !expectation) return false;
210
+
211
+ // Match by route if available
212
+ if (expectation.route && trace.after?.url) {
213
+ const routePath = expectation.route.replace(/\/$/, '') || '/';
214
+ const urlPath = this._getUrlPath(trace.after.url);
215
+ return routePath === urlPath;
216
+ }
217
+
218
+ // Match by type
219
+ const expType = this._normalizeExpectationType(expectation);
220
+ if (
221
+ expType === 'navigation' &&
222
+ trace.interaction?.type === 'link'
223
+ ) {
224
+ return true;
225
+ }
226
+
227
+ if (
228
+ expType === 'form_submission' &&
229
+ trace.interaction?.type === 'form'
230
+ ) {
231
+ return true;
232
+ }
233
+
234
+ if (expType === 'interaction' && trace.interaction) {
235
+ return true;
236
+ }
237
+
238
+ return false;
239
+ }
240
+
241
+ /**
242
+ * Check if expectation was fulfilled in the trace
243
+ * @private
244
+ */
245
+ _isExpectationFulfilled(expectation, trace) {
246
+ if (!trace) return false;
247
+
248
+ const expType = this._normalizeExpectationType(expectation);
249
+
250
+ // Navigation expectation: URL must change
251
+ if (expType === 'navigation') {
252
+ const urlChanged = trace.sensors?.navigation?.urlChanged === true;
253
+ const afterUrl = trace.after?.url;
254
+ return urlChanged && afterUrl !== trace.before?.url;
255
+ }
256
+
257
+ // Content expectation: DOM must change
258
+ if (expType === 'content') {
259
+ const domChanged = trace.sensors?.uiSignals?.diff?.domChanged === true;
260
+ const newContent =
261
+ trace.sensors?.uiFeedback?.signals?.domChange?.happened === true;
262
+ return domChanged || newContent;
263
+ }
264
+
265
+ // Interaction expectation: must have been performed
266
+ if (expType === 'interaction') {
267
+ const performed = trace.interaction?.performed === true;
268
+ return performed;
269
+ }
270
+
271
+ // Form submission: must have network request
272
+ if (expType === 'form_submission') {
273
+ const hasNetworkRequest = (trace.sensors?.network?.totalRequests || 0) > 0;
274
+ return hasNetworkRequest;
275
+ }
276
+
277
+ // Default: consider fulfilled if trace exists
278
+ return true;
279
+ }
280
+
281
+ /**
282
+ * Check if expectation is PROVEN
283
+ * @private
284
+ */
285
+ _isProven(expectation) {
286
+ if (!expectation) return false;
287
+
288
+ // Explicit from source code analysis
289
+ if (expectation.explicit === true) return true;
290
+
291
+ // Has proof marker
292
+ if (expectation.proof === 'PROVEN_EXPECTATION') return true;
293
+
294
+ // Has sourceRef (AST-derived)
295
+ if (expectation.sourceRef) return true;
296
+
297
+ // Has source evidence
298
+ if (expectation.evidence?.source) return true;
299
+
300
+ return false;
301
+ }
302
+
303
+ /**
304
+ * Normalize expectation type
305
+ * @private
306
+ */
307
+ _normalizeExpectationType(expectation) {
308
+ if (!expectation) return 'unknown';
309
+
310
+ const type = expectation.type || expectation.expectationType || '';
311
+
312
+ if (type.includes('navigation') || type === 'spa_navigation') {
313
+ return 'navigation';
314
+ }
315
+ if (type.includes('content') || type.includes('dom')) {
316
+ return 'content';
317
+ }
318
+ if (type.includes('form') || type === 'form_submission') {
319
+ return 'form_submission';
320
+ }
321
+ if (type.includes('interaction') || type.includes('action')) {
322
+ return 'interaction';
323
+ }
324
+
325
+ return type || 'unknown';
326
+ }
327
+
328
+ /**
329
+ * Order expectations by sequence (chain order)
330
+ * @private
331
+ */
332
+ _orderExpectationsBySequence(expectations) {
333
+ // If expectations have explicit order/lineNumber, use that
334
+ const withOrder = expectations.filter(exp => exp.lineNumber !== undefined);
335
+ if (withOrder.length > 0) {
336
+ return withOrder.sort((a, b) => a.lineNumber - b.lineNumber);
337
+ }
338
+
339
+ // Otherwise return in given order
340
+ return expectations;
341
+ }
342
+
343
+ /**
344
+ * Check if two expectations are consecutive in chain
345
+ * @private
346
+ */
347
+ _isConsecutiveExpectation(currentExp, nextExp) {
348
+ if (!currentExp || !nextExp) return false;
349
+
350
+ const currentType = this._normalizeExpectationType(currentExp);
351
+ const nextType = this._normalizeExpectationType(nextExp);
352
+
353
+ // Valid chain progressions:
354
+ // navigation → content, interaction, form_submission
355
+ // content → interaction, form_submission
356
+ // form_submission → content, navigation
357
+ // interaction → content, form_submission, navigation
358
+
359
+ const validProgressions = {
360
+ navigation: ['content', 'interaction', 'form_submission'],
361
+ content: ['interaction', 'form_submission', 'navigation'],
362
+ form_submission: ['content', 'navigation', 'interaction'],
363
+ interaction: ['content', 'form_submission', 'navigation']
364
+ };
365
+
366
+ const validNext = validProgressions[currentType] || [];
367
+ return validNext.includes(nextType);
368
+ }
369
+
370
+ /**
371
+ * Compute severity based on chain depth and break position
372
+ * @private
373
+ */
374
+ _computeSeverity(chainLength, fulfilledSteps, brokenStepIndex) {
375
+ // Late breaks (deeper in chain) are higher severity
376
+ const breakDepth = brokenStepIndex / chainLength;
377
+
378
+ if (breakDepth >= 0.7) {
379
+ return 'CRITICAL'; // Broke in final 30%
380
+ }
381
+ if (breakDepth >= 0.5) {
382
+ return 'HIGH'; // Broke in second half
383
+ }
384
+ if (breakDepth >= 0.3) {
385
+ return 'MEDIUM'; // Broke in second third
386
+ }
387
+ return 'LOW'; // Early break
388
+ }
389
+
390
+ /**
391
+ * Generate description of chain break
392
+ * @private
393
+ */
394
+ _describeChainBreak(chain, fulfilledSteps, brokenStepIndex) {
395
+ const chainTypes = chain.map(s => s.type).join(' → ');
396
+ const brokenType = chain[brokenStepIndex]?.type || 'unknown';
397
+
398
+ return `Expectation chain broke at step ${brokenStepIndex + 1}/${
399
+ chain.length
400
+ }: ${chainTypes}. Successfully completed ${fulfilledSteps} step(s) before ${brokenType} failed.`;
401
+ }
402
+
403
+ /**
404
+ * Extract URL path from full URL
405
+ * @private
406
+ */
407
+ _getUrlPath(url) {
408
+ if (!url) return '';
409
+ try {
410
+ return new URL(url).pathname;
411
+ } catch {
412
+ return url;
413
+ }
414
+ }
415
+ }
416
+
417
+ export default ExpectationChainDetector;
@@ -38,7 +38,7 @@ function normalizeExpectationType(expectationType) {
38
38
  */
39
39
  function normalizeSelector(selector) {
40
40
  if (!selector) return '';
41
- return selector.replace(/[\[\]()]/g, '').trim();
41
+ return selector.replace(/[[\]()]/g, '').trim();
42
42
  }
43
43
 
44
44
  /**
@@ -114,7 +114,7 @@ export function matchExpectation(expectation, interaction, beforeUrl) {
114
114
  return null;
115
115
  }
116
116
 
117
- function matchesStaticExpectation(expectation, interaction, afterUrl) {
117
+ function _matchesStaticExpectation(expectation, interaction, afterUrl) {
118
118
  if (interaction.type !== 'link') return false;
119
119
 
120
120
  const afterPath = getUrlPath(afterUrl);
@@ -129,7 +129,7 @@ function matchesStaticExpectation(expectation, interaction, afterUrl) {
129
129
 
130
130
  const selectorHint = expectation.evidence.selectorHint || '';
131
131
  const interactionSelector = interaction.selector || '';
132
- const interactionLabel = (interaction.label || '').toLowerCase().trim();
132
+ const _interactionLabel = (interaction.label || '').toLowerCase().trim();
133
133
 
134
134
  if (selectorHint && interactionSelector) {
135
135
  if (selectorHint.includes(interactionSelector) || interactionSelector.includes(selectorHint.replace(/[\]()]/g, ''))) {
@@ -224,3 +224,56 @@ export function expectsNavigation(manifest, interaction, beforeUrl) {
224
224
  return false;
225
225
  }
226
226
 
227
+ /**
228
+ * Get expectation for interaction from manifest (unified API for static and action contracts)
229
+ * @param {Object} manifest - Project manifest
230
+ * @param {Object} interaction - Interaction object
231
+ * @param {string} beforeUrl - URL before interaction
232
+ * @param {Object} [attemptMeta] - Optional metadata about the interaction attempt
233
+ * @returns {Object} { hasExpectation: boolean, proof: string, ...expectationData }
234
+ */
235
+ export function getExpectation(manifest, interaction, beforeUrl, attemptMeta = {}) {
236
+ // Check static expectations first (for static sites)
237
+ if (manifest.staticExpectations && manifest.staticExpectations.length > 0) {
238
+ for (const expectation of manifest.staticExpectations) {
239
+ const matched = matchExpectation(expectation, interaction, beforeUrl);
240
+ if (matched) {
241
+ return {
242
+ hasExpectation: true,
243
+ proof: expectation.proof || 'PROVEN_EXPECTATION',
244
+ expectationType: expectation.type,
245
+ expectedTargetPath: expectation.targetPath,
246
+ ...expectation
247
+ };
248
+ }
249
+ }
250
+ }
251
+
252
+ // Check action contracts (for apps with source-level contracts)
253
+ if (manifest.actionContracts && manifest.actionContracts.length > 0) {
254
+ const sourceRef = attemptMeta?.sourceRef;
255
+ if (sourceRef) {
256
+ for (const contract of manifest.actionContracts) {
257
+ if (contract.source === sourceRef) {
258
+ const expectationType = contract.kind === 'NETWORK_ACTION' ? 'network_action' : 'action';
259
+ // Dynamic URLs never produce PROVEN_EXPECTATION (truth boundary)
260
+ const proof = contract.isDynamic ? 'UNPROVEN_EXPECTATION' : 'PROVEN_EXPECTATION';
261
+ return {
262
+ hasExpectation: true,
263
+ proof,
264
+ expectationType,
265
+ method: contract.method,
266
+ urlPath: contract.urlPath,
267
+ ...contract
268
+ };
269
+ }
270
+ }
271
+ }
272
+ }
273
+
274
+ return {
275
+ hasExpectation: false,
276
+ proof: 'UNKNOWN_EXPECTATION'
277
+ };
278
+ }
279
+
@@ -8,7 +8,7 @@
8
8
  * Generate a human summary - one short sentence describing what the USER experienced.
9
9
  * NO technical jargon.
10
10
  */
11
- export function generateHumanSummary(finding, trace) {
11
+ export function generateHumanSummary(finding, _trace) {
12
12
  const findingType = finding.type || '';
13
13
  const interaction = finding.interaction || {};
14
14
  const label = interaction.label || interaction.text || 'the button';
@@ -16,7 +16,7 @@ import { hasMeaningfulUrlChange, hasVisibleChange, hasDomChange } from './compar
16
16
  import { computeConfidence } from './confidence-engine.js';
17
17
  import { generateHumanSummary, generateActionHint, deriveConfidenceExplanation } from './explanation-helpers.js';
18
18
  import { mapFindingTypeToOutcome } from '../core/canonical-outcomes.js';
19
- import { inferPromiseFromInteraction, PROMISE_TYPES } from '../core/promise-model.js';
19
+ import { inferPromiseFromInteraction } from '../core/promise-model.js';
20
20
 
21
21
  /**
22
22
  * Map finding type to confidence engine type.
@@ -110,7 +110,7 @@ export function bindPromiseToFinding(finding, trace) {
110
110
  * Create finding from expectation-driven execution outcome.
111
111
  * Called when expectation execution resulted in SILENT_FAILURE.
112
112
  */
113
- export function createFindingFromExpectationOutcome(expectation, trace, beforeUrl, afterUrl, beforeScreenshot, afterScreenshot, projectDir, manifest) {
113
+ export function createFindingFromExpectationOutcome(expectation, trace, beforeUrl, afterUrl, beforeScreenshot, afterScreenshot, projectDir, _manifest) {
114
114
  const interaction = trace.interaction;
115
115
  const sensors = trace.sensors || {};
116
116