@veraxhq/verax 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (213) hide show
  1. package/README.md +10 -6
  2. package/bin/verax.js +11 -11
  3. package/package.json +29 -8
  4. package/src/cli/commands/baseline.js +103 -0
  5. package/src/cli/commands/default.js +51 -6
  6. package/src/cli/commands/doctor.js +29 -0
  7. package/src/cli/commands/ga.js +246 -0
  8. package/src/cli/commands/gates.js +95 -0
  9. package/src/cli/commands/inspect.js +4 -2
  10. package/src/cli/commands/release-check.js +215 -0
  11. package/src/cli/commands/run.js +45 -6
  12. package/src/cli/commands/security-check.js +212 -0
  13. package/src/cli/commands/truth.js +113 -0
  14. package/src/cli/entry.js +30 -20
  15. package/src/cli/util/angular-component-extractor.js +179 -0
  16. package/src/cli/util/angular-navigation-detector.js +141 -0
  17. package/src/cli/util/angular-network-detector.js +161 -0
  18. package/src/cli/util/angular-state-detector.js +162 -0
  19. package/src/cli/util/ast-interactive-detector.js +544 -0
  20. package/src/cli/util/ast-network-detector.js +603 -0
  21. package/src/cli/util/ast-promise-extractor.js +581 -0
  22. package/src/cli/util/ast-usestate-detector.js +602 -0
  23. package/src/cli/util/atomic-write.js +12 -1
  24. package/src/cli/util/bootstrap-guard.js +86 -0
  25. package/src/cli/util/console-reporter.js +72 -0
  26. package/src/cli/util/detection-engine.js +105 -41
  27. package/src/cli/util/determinism-runner.js +124 -0
  28. package/src/cli/util/determinism-writer.js +129 -0
  29. package/src/cli/util/digest-engine.js +359 -0
  30. package/src/cli/util/dom-diff.js +226 -0
  31. package/src/cli/util/evidence-engine.js +287 -0
  32. package/src/cli/util/expectation-extractor.js +151 -5
  33. package/src/cli/util/findings-writer.js +3 -0
  34. package/src/cli/util/framework-detector.js +572 -0
  35. package/src/cli/util/idgen.js +1 -1
  36. package/src/cli/util/interaction-planner.js +529 -0
  37. package/src/cli/util/learn-writer.js +2 -0
  38. package/src/cli/util/ledger-writer.js +110 -0
  39. package/src/cli/util/monorepo-resolver.js +162 -0
  40. package/src/cli/util/observation-engine.js +127 -278
  41. package/src/cli/util/observe-writer.js +2 -0
  42. package/src/cli/util/project-discovery.js +284 -0
  43. package/src/cli/util/project-writer.js +2 -0
  44. package/src/cli/util/run-id.js +23 -27
  45. package/src/cli/util/run-resolver.js +64 -0
  46. package/src/cli/util/run-result.js +778 -0
  47. package/src/cli/util/selector-resolver.js +235 -0
  48. package/src/cli/util/source-requirement.js +55 -0
  49. package/src/cli/util/summary-writer.js +2 -0
  50. package/src/cli/util/svelte-navigation-detector.js +163 -0
  51. package/src/cli/util/svelte-network-detector.js +80 -0
  52. package/src/cli/util/svelte-sfc-extractor.js +146 -0
  53. package/src/cli/util/svelte-state-detector.js +242 -0
  54. package/src/cli/util/trust-activation-integration.js +496 -0
  55. package/src/cli/util/trust-activation-wrapper.js +85 -0
  56. package/src/cli/util/trust-integration-hooks.js +164 -0
  57. package/src/cli/util/types.js +153 -0
  58. package/src/cli/util/url-validation.js +40 -0
  59. package/src/cli/util/vue-navigation-detector.js +178 -0
  60. package/src/cli/util/vue-sfc-extractor.js +161 -0
  61. package/src/cli/util/vue-state-detector.js +215 -0
  62. package/src/types/fs-augment.d.ts +23 -0
  63. package/src/types/global.d.ts +137 -0
  64. package/src/types/internal-types.d.ts +35 -0
  65. package/src/verax/cli/init.js +4 -18
  66. package/src/verax/core/action-classifier.js +4 -3
  67. package/src/verax/core/artifacts/registry.js +139 -0
  68. package/src/verax/core/artifacts/verifier.js +990 -0
  69. package/src/verax/core/baseline/baseline.enforcer.js +137 -0
  70. package/src/verax/core/baseline/baseline.snapshot.js +233 -0
  71. package/src/verax/core/capabilities/gates.js +505 -0
  72. package/src/verax/core/capabilities/registry.js +475 -0
  73. package/src/verax/core/confidence/confidence-compute.js +144 -0
  74. package/src/verax/core/confidence/confidence-invariants.js +234 -0
  75. package/src/verax/core/confidence/confidence-report-writer.js +112 -0
  76. package/src/verax/core/confidence/confidence-weights.js +44 -0
  77. package/src/verax/core/confidence/confidence.defaults.js +65 -0
  78. package/src/verax/core/confidence/confidence.loader.js +80 -0
  79. package/src/verax/core/confidence/confidence.schema.js +94 -0
  80. package/src/verax/core/confidence-engine-refactor.js +489 -0
  81. package/src/verax/core/confidence-engine.js +625 -0
  82. package/src/verax/core/contracts/index.js +29 -0
  83. package/src/verax/core/contracts/types.js +186 -0
  84. package/src/verax/core/contracts/validators.js +456 -0
  85. package/src/verax/core/decisions/decision.trace.js +278 -0
  86. package/src/verax/core/determinism/contract-writer.js +89 -0
  87. package/src/verax/core/determinism/contract.js +139 -0
  88. package/src/verax/core/determinism/diff.js +405 -0
  89. package/src/verax/core/determinism/engine.js +222 -0
  90. package/src/verax/core/determinism/finding-identity.js +149 -0
  91. package/src/verax/core/determinism/normalize.js +466 -0
  92. package/src/verax/core/determinism/report-writer.js +93 -0
  93. package/src/verax/core/determinism/run-fingerprint.js +123 -0
  94. package/src/verax/core/dynamic-route-intelligence.js +529 -0
  95. package/src/verax/core/evidence/evidence-capture-service.js +308 -0
  96. package/src/verax/core/evidence/evidence-intent-ledger.js +166 -0
  97. package/src/verax/core/evidence-builder.js +487 -0
  98. package/src/verax/core/execution-mode-context.js +77 -0
  99. package/src/verax/core/execution-mode-detector.js +192 -0
  100. package/src/verax/core/failures/exit-codes.js +88 -0
  101. package/src/verax/core/failures/failure-summary.js +76 -0
  102. package/src/verax/core/failures/failure.factory.js +225 -0
  103. package/src/verax/core/failures/failure.ledger.js +133 -0
  104. package/src/verax/core/failures/failure.types.js +196 -0
  105. package/src/verax/core/failures/index.js +10 -0
  106. package/src/verax/core/ga/ga-report-writer.js +43 -0
  107. package/src/verax/core/ga/ga.artifact.js +49 -0
  108. package/src/verax/core/ga/ga.contract.js +435 -0
  109. package/src/verax/core/ga/ga.enforcer.js +87 -0
  110. package/src/verax/core/guardrails/guardrails-report-writer.js +109 -0
  111. package/src/verax/core/guardrails/policy.defaults.js +210 -0
  112. package/src/verax/core/guardrails/policy.loader.js +84 -0
  113. package/src/verax/core/guardrails/policy.schema.js +110 -0
  114. package/src/verax/core/guardrails/truth-reconciliation.js +136 -0
  115. package/src/verax/core/guardrails-engine.js +505 -0
  116. package/src/verax/core/incremental-store.js +1 -0
  117. package/src/verax/core/integrity/budget.js +138 -0
  118. package/src/verax/core/integrity/determinism.js +342 -0
  119. package/src/verax/core/integrity/integrity.js +208 -0
  120. package/src/verax/core/integrity/poisoning.js +108 -0
  121. package/src/verax/core/integrity/transaction.js +140 -0
  122. package/src/verax/core/observe/run-timeline.js +318 -0
  123. package/src/verax/core/perf/perf.contract.js +186 -0
  124. package/src/verax/core/perf/perf.display.js +65 -0
  125. package/src/verax/core/perf/perf.enforcer.js +91 -0
  126. package/src/verax/core/perf/perf.monitor.js +209 -0
  127. package/src/verax/core/perf/perf.report.js +200 -0
  128. package/src/verax/core/pipeline-tracker.js +243 -0
  129. package/src/verax/core/product-definition.js +127 -0
  130. package/src/verax/core/release/provenance.builder.js +130 -0
  131. package/src/verax/core/release/release-report-writer.js +40 -0
  132. package/src/verax/core/release/release.enforcer.js +164 -0
  133. package/src/verax/core/release/reproducibility.check.js +222 -0
  134. package/src/verax/core/release/sbom.builder.js +292 -0
  135. package/src/verax/core/replay-validator.js +2 -0
  136. package/src/verax/core/replay.js +4 -0
  137. package/src/verax/core/report/cross-index.js +195 -0
  138. package/src/verax/core/report/human-summary.js +362 -0
  139. package/src/verax/core/route-intelligence.js +420 -0
  140. package/src/verax/core/run-id.js +6 -3
  141. package/src/verax/core/run-manifest.js +4 -3
  142. package/src/verax/core/security/secrets.scan.js +329 -0
  143. package/src/verax/core/security/security-report.js +50 -0
  144. package/src/verax/core/security/security.enforcer.js +128 -0
  145. package/src/verax/core/security/supplychain.defaults.json +38 -0
  146. package/src/verax/core/security/supplychain.policy.js +334 -0
  147. package/src/verax/core/security/vuln.scan.js +265 -0
  148. package/src/verax/core/truth/truth.certificate.js +252 -0
  149. package/src/verax/core/ui-feedback-intelligence.js +481 -0
  150. package/src/verax/detect/conditional-ui-silent-failure.js +84 -0
  151. package/src/verax/detect/confidence-engine.js +62 -34
  152. package/src/verax/detect/confidence-helper.js +34 -0
  153. package/src/verax/detect/dynamic-route-findings.js +338 -0
  154. package/src/verax/detect/expectation-chain-detector.js +417 -0
  155. package/src/verax/detect/expectation-model.js +2 -2
  156. package/src/verax/detect/failure-cause-inference.js +293 -0
  157. package/src/verax/detect/findings-writer.js +131 -35
  158. package/src/verax/detect/flow-detector.js +2 -2
  159. package/src/verax/detect/form-silent-failure.js +98 -0
  160. package/src/verax/detect/index.js +46 -5
  161. package/src/verax/detect/invariants-enforcer.js +147 -0
  162. package/src/verax/detect/journey-stall-detector.js +558 -0
  163. package/src/verax/detect/navigation-silent-failure.js +82 -0
  164. package/src/verax/detect/problem-aggregator.js +361 -0
  165. package/src/verax/detect/route-findings.js +219 -0
  166. package/src/verax/detect/summary-writer.js +477 -0
  167. package/src/verax/detect/test-failure-cause-inference.js +314 -0
  168. package/src/verax/detect/ui-feedback-findings.js +207 -0
  169. package/src/verax/detect/view-switch-correlator.js +242 -0
  170. package/src/verax/flow/flow-engine.js +2 -1
  171. package/src/verax/flow/flow-spec.js +0 -6
  172. package/src/verax/index.js +4 -0
  173. package/src/verax/intel/ts-program.js +1 -0
  174. package/src/verax/intel/vue-navigation-extractor.js +3 -0
  175. package/src/verax/learn/action-contract-extractor.js +3 -0
  176. package/src/verax/learn/ast-contract-extractor.js +1 -1
  177. package/src/verax/learn/flow-extractor.js +1 -0
  178. package/src/verax/learn/project-detector.js +5 -0
  179. package/src/verax/learn/react-router-extractor.js +2 -0
  180. package/src/verax/learn/source-instrumenter.js +1 -0
  181. package/src/verax/learn/state-extractor.js +2 -1
  182. package/src/verax/learn/static-extractor.js +1 -0
  183. package/src/verax/observe/coverage-gaps.js +132 -0
  184. package/src/verax/observe/expectation-handler.js +126 -0
  185. package/src/verax/observe/incremental-skip.js +46 -0
  186. package/src/verax/observe/index.js +51 -155
  187. package/src/verax/observe/interaction-executor.js +192 -0
  188. package/src/verax/observe/interaction-runner.js +782 -513
  189. package/src/verax/observe/network-firewall.js +86 -0
  190. package/src/verax/observe/observation-builder.js +169 -0
  191. package/src/verax/observe/observe-context.js +205 -0
  192. package/src/verax/observe/observe-helpers.js +192 -0
  193. package/src/verax/observe/observe-runner.js +230 -0
  194. package/src/verax/observe/observers/budget-observer.js +185 -0
  195. package/src/verax/observe/observers/console-observer.js +102 -0
  196. package/src/verax/observe/observers/coverage-observer.js +107 -0
  197. package/src/verax/observe/observers/interaction-observer.js +471 -0
  198. package/src/verax/observe/observers/navigation-observer.js +132 -0
  199. package/src/verax/observe/observers/network-observer.js +87 -0
  200. package/src/verax/observe/observers/safety-observer.js +82 -0
  201. package/src/verax/observe/observers/ui-feedback-observer.js +99 -0
  202. package/src/verax/observe/page-traversal.js +138 -0
  203. package/src/verax/observe/snapshot-ops.js +94 -0
  204. package/src/verax/observe/ui-feedback-detector.js +742 -0
  205. package/src/verax/scan-summary-writer.js +2 -0
  206. package/src/verax/shared/artifact-manager.js +25 -5
  207. package/src/verax/shared/caching.js +1 -0
  208. package/src/verax/shared/css-spinner-rules.js +204 -0
  209. package/src/verax/shared/expectation-tracker.js +1 -0
  210. package/src/verax/shared/view-switch-rules.js +208 -0
  211. package/src/verax/shared/zip-artifacts.js +6 -0
  212. package/src/verax/shared/config-loader.js +0 -169
  213. /package/src/verax/shared/{expectation-proof.js → expectation-validation.js} +0 -0
@@ -0,0 +1,278 @@
1
+ /**
2
+ * PHASE 21.10 — Decision Trace
3
+ *
4
+ * Traces why each finding was detected, which signals contributed,
5
+ * which guardrails applied, and why confidence/status decisions were made.
6
+ */
7
+
8
+ import { readFileSync, existsSync, writeFileSync } from 'fs';
9
+ import { resolve } from 'path';
10
+
11
+ /**
12
+ * Build decision trace for findings
13
+ *
14
+ * @param {string} projectDir - Project directory
15
+ * @param {string} runId - Run ID
16
+ * @returns {Object} Decision trace
17
+ */
18
+ export function buildDecisionTrace(projectDir, runId) {
19
+ const runDir = resolve(projectDir, '.verax', 'runs', runId);
20
+
21
+ if (!existsSync(runDir)) {
22
+ return null;
23
+ }
24
+
25
+ const findingsPath = resolve(runDir, 'findings.json');
26
+ if (!existsSync(findingsPath)) {
27
+ return null;
28
+ }
29
+
30
+ // @ts-expect-error - readFileSync with encoding returns string
31
+ const findings = JSON.parse(readFileSync(findingsPath, 'utf-8'));
32
+ const traces = [];
33
+
34
+ if (!Array.isArray(findings.findings)) {
35
+ return {
36
+ runId,
37
+ findings: [],
38
+ summary: { total: 0 },
39
+ generatedAt: new Date().toISOString()
40
+ };
41
+ }
42
+
43
+ for (const finding of findings.findings) {
44
+ const findingId = finding.findingId || finding.id || `finding-${traces.length}`;
45
+
46
+ // Why detected?
47
+ const detectionReasons = [];
48
+ if (finding.type) {
49
+ detectionReasons.push({
50
+ code: 'FINDING_TYPE',
51
+ reason: `Finding type: ${finding.type}`,
52
+ value: finding.type
53
+ });
54
+ }
55
+ if (finding.outcome) {
56
+ detectionReasons.push({
57
+ code: 'OUTCOME_CLASSIFICATION',
58
+ reason: `Outcome: ${finding.outcome}`,
59
+ value: finding.outcome
60
+ });
61
+ }
62
+ if (finding.promise?.type) {
63
+ detectionReasons.push({
64
+ code: 'PROMISE_TYPE',
65
+ reason: `Promise type: ${finding.promise.type}`,
66
+ value: finding.promise.type
67
+ });
68
+ }
69
+
70
+ // Which signals contributed?
71
+ const signals = [];
72
+ if (finding.evidence?.sensors) {
73
+ const sensors = finding.evidence.sensors;
74
+
75
+ if (sensors.network) {
76
+ signals.push({
77
+ type: 'NETWORK',
78
+ contributed: sensors.network.totalRequests > 0 || sensors.network.failedRequests > 0,
79
+ data: {
80
+ totalRequests: sensors.network.totalRequests || 0,
81
+ failedRequests: sensors.network.failedRequests || 0
82
+ }
83
+ });
84
+ }
85
+
86
+ if (sensors.console) {
87
+ signals.push({
88
+ type: 'CONSOLE',
89
+ contributed: (sensors.console.errors || 0) > 0 || (sensors.console.warnings || 0) > 0,
90
+ data: {
91
+ errors: sensors.console.errors || 0,
92
+ warnings: sensors.console.warnings || 0
93
+ }
94
+ });
95
+ }
96
+
97
+ if (sensors.uiSignals) {
98
+ signals.push({
99
+ type: 'UI_SIGNALS',
100
+ contributed: sensors.uiSignals.diff?.changed === true,
101
+ data: {
102
+ changed: sensors.uiSignals.diff?.changed || false
103
+ }
104
+ });
105
+ }
106
+ }
107
+
108
+ // Which guardrails applied?
109
+ const guardrailsApplied = [];
110
+ if (finding.guardrails?.appliedRules) {
111
+ for (const rule of finding.guardrails.appliedRules) {
112
+ guardrailsApplied.push({
113
+ ruleId: rule.id || rule,
114
+ category: rule.category || null,
115
+ action: rule.action || null,
116
+ matched: rule.matched || true
117
+ });
118
+ }
119
+ }
120
+
121
+ // Why confidence = X?
122
+ const confidenceReasons = [];
123
+ if (finding.confidenceLevel) {
124
+ confidenceReasons.push({
125
+ code: 'CONFIDENCE_LEVEL',
126
+ reason: `Confidence level: ${finding.confidenceLevel}`,
127
+ value: finding.confidenceLevel
128
+ });
129
+ }
130
+ if (finding.confidence !== undefined) {
131
+ confidenceReasons.push({
132
+ code: 'CONFIDENCE_SCORE',
133
+ reason: `Confidence score: ${finding.confidence}`,
134
+ value: finding.confidence
135
+ });
136
+ }
137
+ if (finding.confidenceReasons && Array.isArray(finding.confidenceReasons)) {
138
+ for (const reason of finding.confidenceReasons) {
139
+ confidenceReasons.push({
140
+ code: 'CONFIDENCE_FACTOR',
141
+ reason: reason,
142
+ value: reason
143
+ });
144
+ }
145
+ }
146
+
147
+ // Why status = CONFIRMED / SUSPECTED / DROPPED?
148
+ const statusReasons = [];
149
+ const status = finding.severity || finding.status || 'SUSPECTED';
150
+
151
+ statusReasons.push({
152
+ code: 'STATUS_ASSIGNED',
153
+ reason: `Status: ${status}`,
154
+ value: status
155
+ });
156
+
157
+ if (finding.evidencePackage) {
158
+ if (finding.evidencePackage.isComplete) {
159
+ statusReasons.push({
160
+ code: 'EVIDENCE_COMPLETE',
161
+ reason: 'Evidence package is complete',
162
+ value: true
163
+ });
164
+ } else {
165
+ statusReasons.push({
166
+ code: 'EVIDENCE_INCOMPLETE',
167
+ reason: 'Evidence package is incomplete',
168
+ value: false
169
+ });
170
+ }
171
+ }
172
+
173
+ if (finding.guardrails?.finalDecision) {
174
+ statusReasons.push({
175
+ code: 'GUARDRAILS_DECISION',
176
+ reason: `Guardrails decision: ${finding.guardrails.finalDecision}`,
177
+ value: finding.guardrails.finalDecision
178
+ });
179
+ }
180
+
181
+ if (status === 'CONFIRMED' && finding.evidencePackage && !finding.evidencePackage.isComplete) {
182
+ statusReasons.push({
183
+ code: 'EVIDENCE_LAW_VIOLATION',
184
+ reason: 'CONFIRMED finding with incomplete evidence violates Evidence Law',
185
+ value: 'VIOLATION'
186
+ });
187
+ }
188
+
189
+ traces.push({
190
+ findingId,
191
+ detection: {
192
+ why: detectionReasons,
193
+ signals: signals.filter(s => s.contributed),
194
+ expectationId: finding.expectationId || null,
195
+ interactionId: finding.interaction?.selector || null
196
+ },
197
+ guardrails: {
198
+ applied: guardrailsApplied,
199
+ finalDecision: finding.guardrails?.finalDecision || null,
200
+ contradictions: finding.guardrails?.contradictions || []
201
+ },
202
+ confidence: {
203
+ level: finding.confidenceLevel || null,
204
+ score: finding.confidence !== undefined ? finding.confidence : null,
205
+ why: confidenceReasons
206
+ },
207
+ status: {
208
+ value: status,
209
+ why: statusReasons
210
+ },
211
+ evidence: {
212
+ packageId: finding.evidencePackage?.id || null,
213
+ isComplete: finding.evidencePackage?.isComplete || false,
214
+ files: finding.evidencePackage?.files || []
215
+ }
216
+ });
217
+ }
218
+
219
+ return {
220
+ runId,
221
+ findings: traces,
222
+ summary: {
223
+ total: traces.length,
224
+ byStatus: traces.reduce((acc, t) => {
225
+ const status = t.status.value;
226
+ acc[status] = (acc[status] || 0) + 1;
227
+ return acc;
228
+ }, {}),
229
+ byConfidence: traces.reduce((acc, t) => {
230
+ const level = t.confidence.level || 'UNKNOWN';
231
+ acc[level] = (acc[level] || 0) + 1;
232
+ return acc;
233
+ }, {}),
234
+ withGuardrails: traces.filter(t => t.guardrails.applied.length > 0).length,
235
+ withCompleteEvidence: traces.filter(t => t.evidence.isComplete).length
236
+ },
237
+ generatedAt: new Date().toISOString()
238
+ };
239
+ }
240
+
241
+ /**
242
+ * Write decision trace to file
243
+ *
244
+ * @param {string} projectDir - Project directory
245
+ * @param {string} runId - Run ID
246
+ * @param {Object} trace - Decision trace
247
+ * @returns {string} Path to written file
248
+ */
249
+ export function writeDecisionTrace(projectDir, runId, trace) {
250
+ const runDir = resolve(projectDir, '.verax', 'runs', runId);
251
+ const outputPath = resolve(runDir, 'decisions.trace.json');
252
+ writeFileSync(outputPath, JSON.stringify(trace, null, 2), 'utf-8');
253
+ return outputPath;
254
+ }
255
+
256
+ /**
257
+ * Load decision trace from file
258
+ *
259
+ * @param {string} projectDir - Project directory
260
+ * @param {string} runId - Run ID
261
+ * @returns {Object|null} Decision trace or null
262
+ */
263
+ export function loadDecisionTrace(projectDir, runId) {
264
+ const runDir = resolve(projectDir, '.verax', 'runs', runId);
265
+ const tracePath = resolve(runDir, 'decisions.trace.json');
266
+
267
+ if (!existsSync(tracePath)) {
268
+ return null;
269
+ }
270
+
271
+ try {
272
+ // @ts-expect-error - readFileSync with encoding returns string
273
+ return JSON.parse(readFileSync(tracePath, 'utf-8'));
274
+ } catch {
275
+ return null;
276
+ }
277
+ }
278
+
@@ -0,0 +1,89 @@
1
+ /**
2
+ * PHASE 25 — Determinism Contract Writer
3
+ *
4
+ * Writes determinism.contract.json artifact capturing adaptive events,
5
+ * retries, timing adjustments, and other non-deterministic behaviors.
6
+ */
7
+
8
+ import { writeFileSync } from 'fs';
9
+ import { resolve } from 'path';
10
+ import { DecisionRecorder as _DecisionRecorder } from '../determinism-model.js';
11
+ import { ARTIFACT_REGISTRY } from '../artifacts/registry.js';
12
+
13
+ /**
14
+ * Write determinism contract artifact
15
+ *
16
+ * @param {string} runDir - Absolute run directory path
17
+ * @param {Object} decisionRecorder - Decision recorder instance
18
+ * @returns {string} Path to written contract
19
+ */
20
+ export function writeDeterminismContract(runDir, decisionRecorder) {
21
+ const contractPath = resolve(runDir, ARTIFACT_REGISTRY.determinismContract.filename);
22
+
23
+ const adaptiveEvents = [];
24
+ const retryEvents = [];
25
+ const timingAdjustments = [];
26
+
27
+ if (decisionRecorder) {
28
+ // Extract adaptive stabilization events
29
+ const adaptiveStabilization = decisionRecorder.getByCategory('ADAPTIVE_STABILIZATION');
30
+ for (const decision of adaptiveStabilization) {
31
+ adaptiveEvents.push({
32
+ decision_id: decision.decision_id,
33
+ category: decision.category,
34
+ timestamp: decision.timestamp,
35
+ reason: decision.reason,
36
+ context: decision.context || null,
37
+ chosen_value: decision.chosen_value,
38
+ inputs: decision.inputs || {}
39
+ });
40
+ }
41
+
42
+ // Extract retry events
43
+ const retries = decisionRecorder.getByCategory('RETRY');
44
+ for (const decision of retries) {
45
+ retryEvents.push({
46
+ decision_id: decision.decision_id,
47
+ category: decision.category,
48
+ timestamp: decision.timestamp,
49
+ reason: decision.reason,
50
+ context: decision.context || null,
51
+ chosen_value: decision.chosen_value,
52
+ inputs: decision.inputs || {}
53
+ });
54
+ }
55
+
56
+ // Extract timing adjustments (timeout decisions)
57
+ const timeouts = decisionRecorder.getByCategory('TIMEOUT');
58
+ for (const decision of timeouts) {
59
+ timingAdjustments.push({
60
+ decision_id: decision.decision_id,
61
+ category: decision.category,
62
+ timestamp: decision.timestamp,
63
+ reason: decision.reason,
64
+ context: decision.context || null,
65
+ chosen_value: decision.chosen_value,
66
+ inputs: decision.inputs || {}
67
+ });
68
+ }
69
+ }
70
+
71
+ const contract = {
72
+ version: 1,
73
+ generatedAt: new Date().toISOString(),
74
+ adaptiveEvents,
75
+ retryEvents,
76
+ timingAdjustments,
77
+ summary: {
78
+ adaptiveEventsCount: adaptiveEvents.length,
79
+ retryEventsCount: retryEvents.length,
80
+ timingAdjustmentsCount: timingAdjustments.length,
81
+ isDeterministic: adaptiveEvents.length === 0 && retryEvents.length === 0
82
+ }
83
+ };
84
+
85
+ writeFileSync(contractPath, JSON.stringify(contract, null, 2), 'utf8');
86
+
87
+ return contractPath;
88
+ }
89
+
@@ -0,0 +1,139 @@
1
+ /**
2
+ * PHASE 21.2 — Determinism Truth Lock: Strict Contract
3
+ *
4
+ * DETERMINISM CONTRACT:
5
+ *
6
+ * DETERMINISTIC means:
7
+ * - Same inputs (source code, URL, config)
8
+ * - Same environment (browser, OS, Node version)
9
+ * - Same config (budget, timeouts, flags)
10
+ * → identical normalized artifacts (findings, traces, evidence)
11
+ *
12
+ * NON_DETERMINISTIC means:
13
+ * - Any adaptive behavior occurred (adaptive stabilization, retries, dynamic timeouts)
14
+ * - Any timing variance that affects results
15
+ * - Any environment-dependent behavior
16
+ * - Tracking adaptive decisions is NOT determinism
17
+ *
18
+ * HARD RULE: If adaptiveEvents.length > 0 → verdict MUST be NON_DETERMINISTIC
19
+ */
20
+
21
+ /**
22
+ * PHASE 21.2: Determinism verdict (binary)
23
+ * PHASE 25: Extended with expected/unexpected distinction
24
+ */
25
+ export const DETERMINISM_VERDICT = {
26
+ DETERMINISTIC: 'DETERMINISTIC',
27
+ NON_DETERMINISTIC_EXPECTED: 'NON_DETERMINISTIC_EXPECTED',
28
+ NON_DETERMINISTIC_UNEXPECTED: 'NON_DETERMINISTIC_UNEXPECTED',
29
+ NON_DETERMINISTIC: 'NON_DETERMINISTIC' // Backward compatibility
30
+ };
31
+
32
+ /**
33
+ * PHASE 21.2: Determinism reason codes (stable)
34
+ * PHASE 25: Extended with new reason codes
35
+ */
36
+ export const DETERMINISM_REASON = {
37
+ ADAPTIVE_STABILIZATION_USED: 'ADAPTIVE_STABILIZATION_USED',
38
+ RETRY_TRIGGERED: 'RETRY_TRIGGERED',
39
+ TIMING_VARIANCE: 'TIMING_VARIANCE',
40
+ TRUNCATION_OCCURRED: 'TRUNCATION_OCCURRED',
41
+ ENVIRONMENT_VARIANCE: 'ENVIRONMENT_VARIANCE',
42
+ NO_ADAPTIVE_EVENTS: 'NO_ADAPTIVE_EVENTS',
43
+ RUN_FINGERPRINT_MISMATCH: 'RUN_FINGERPRINT_MISMATCH',
44
+ ARTIFACT_DIFF_DETECTED: 'ARTIFACT_DIFF_DETECTED',
45
+ VERIFIER_ERRORS_DETECTED: 'VERIFIER_ERRORS_DETECTED',
46
+ EXPECTED_ADAPTIVE_BEHAVIOR: 'EXPECTED_ADAPTIVE_BEHAVIOR',
47
+ UNEXPECTED_DIFF_WITHOUT_ADAPTIVE: 'UNEXPECTED_DIFF_WITHOUT_ADAPTIVE'
48
+ };
49
+
50
+ /**
51
+ * PHASE 21.2: Adaptive event categories that break determinism
52
+ */
53
+ export const ADAPTIVE_EVENT_CATEGORIES = [
54
+ 'ADAPTIVE_STABILIZATION',
55
+ 'RETRY',
56
+ 'TRUNCATION'
57
+ ];
58
+
59
+ /**
60
+ * PHASE 21.2: Check if a decision category breaks determinism
61
+ *
62
+ * @param {string} category - Decision category
63
+ * @returns {boolean} True if this category breaks determinism
64
+ */
65
+ export function isAdaptiveEventCategory(category) {
66
+ return ADAPTIVE_EVENT_CATEGORIES.includes(category);
67
+ }
68
+
69
+ /**
70
+ * PHASE 21.2: Compute determinism verdict from DecisionRecorder
71
+ *
72
+ * HARD RULE: If any adaptive event occurred → NON_DETERMINISTIC
73
+ *
74
+ * @param {Object} decisionRecorder - Decision recorder instance
75
+ * @returns {Object} { verdict, reasons, adaptiveEvents }
76
+ */
77
+ export function computeDeterminismVerdict(decisionRecorder) {
78
+ if (!decisionRecorder) {
79
+ return {
80
+ verdict: DETERMINISM_VERDICT.NON_DETERMINISTIC,
81
+ reasons: [DETERMINISM_REASON.ENVIRONMENT_VARIANCE],
82
+ adaptiveEvents: [],
83
+ message: 'DecisionRecorder not available - cannot verify determinism'
84
+ };
85
+ }
86
+
87
+ const adaptiveEvents = [];
88
+ const reasons = [];
89
+
90
+ // Check for adaptive stabilization
91
+ const adaptiveStabilization = decisionRecorder.getByCategory('ADAPTIVE_STABILIZATION');
92
+ const adaptiveExtensions = adaptiveStabilization.filter(d =>
93
+ d.decision_id === 'ADAPTIVE_STABILIZATION_extended'
94
+ );
95
+
96
+ if (adaptiveExtensions.length > 0) {
97
+ adaptiveEvents.push(...adaptiveExtensions);
98
+ reasons.push(DETERMINISM_REASON.ADAPTIVE_STABILIZATION_USED);
99
+ }
100
+
101
+ // Check for retries
102
+ const retries = decisionRecorder.getByCategory('RETRY');
103
+ if (retries.length > 0) {
104
+ adaptiveEvents.push(...retries);
105
+ reasons.push(DETERMINISM_REASON.RETRY_TRIGGERED);
106
+ }
107
+
108
+ // Check for truncations
109
+ const truncations = decisionRecorder.getByCategory('TRUNCATION');
110
+ if (truncations.length > 0) {
111
+ adaptiveEvents.push(...truncations);
112
+ reasons.push(DETERMINISM_REASON.TRUNCATION_OCCURRED);
113
+ }
114
+
115
+ // HARD RULE: If any adaptive events → NON_DETERMINISTIC
116
+ if (adaptiveEvents.length > 0) {
117
+ return {
118
+ verdict: DETERMINISM_VERDICT.NON_DETERMINISTIC,
119
+ reasons,
120
+ adaptiveEvents: adaptiveEvents.map(e => ({
121
+ decision_id: e.decision_id,
122
+ category: e.category,
123
+ timestamp: e.timestamp,
124
+ reason: e.reason,
125
+ context: e.context || null
126
+ })),
127
+ message: `Non-deterministic: ${adaptiveEvents.length} adaptive event(s) detected`
128
+ };
129
+ }
130
+
131
+ // No adaptive events → DETERMINISTIC
132
+ return {
133
+ verdict: DETERMINISM_VERDICT.DETERMINISTIC,
134
+ reasons: [DETERMINISM_REASON.NO_ADAPTIVE_EVENTS],
135
+ adaptiveEvents: [],
136
+ message: 'Deterministic: No adaptive events detected'
137
+ };
138
+ }
139
+