@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,123 @@
1
+ /**
2
+ * PHASE 18 — Determinism Runner
3
+ *
4
+ * Wraps a scan execution to run it multiple times and compare results.
5
+ */
6
+
7
+ import { resolve } from 'path';
8
+ import { readFileSync, existsSync } from 'fs';
9
+ import { runDeterminismCheck } from '../../verax/core/determinism/engine.js';
10
+ import { verifyRun } from '../../verax/core/artifacts/verifier.js';
11
+ import { writeDeterminismReport } from './determinism-writer.js';
12
+
13
+ /**
14
+ * PHASE 18: Run scan with determinism checking
15
+ *
16
+ * @param {Function} scanFn - Function that executes a scan and returns { runId }
17
+ * @param {Object} options - Options
18
+ * @param {number} options.runs - Number of runs (default: 2)
19
+ * @param {string} options.out - Output directory
20
+ * @returns {Promise<Object>} Determinism check results
21
+ */
22
+ export async function runWithDeterminism(scanFn, options = {}) {
23
+ const { runs = 2, out = '.verax' } = options;
24
+
25
+ // Wrap scan function to return artifact paths
26
+ const runFn = async (runConfig) => {
27
+ const result = await scanFn({ ...options, ...runConfig });
28
+
29
+ // Extract artifact paths from result
30
+ const artifactPaths = {};
31
+ if (result.runId) {
32
+ const runDir = resolve(out, 'runs', result.runId);
33
+
34
+ // Map artifact keys to paths
35
+ const artifactMap = {
36
+ findings: resolve(runDir, 'findings.json'),
37
+ runStatus: resolve(runDir, 'run.status.json'),
38
+ summary: resolve(runDir, 'summary.json'),
39
+ learn: resolve(runDir, 'learn.json'),
40
+ observe: resolve(runDir, 'observe.json'),
41
+ };
42
+
43
+ for (const [key, path] of Object.entries(artifactMap)) {
44
+ if (existsSync(path)) {
45
+ artifactPaths[key] = path;
46
+ }
47
+ }
48
+ }
49
+
50
+ return {
51
+ runId: result.runId,
52
+ artifactPaths,
53
+ };
54
+ };
55
+
56
+ // PHASE 25: Load run fingerprints from run.meta.json
57
+ const loadRunFingerprints = async (runMeta) => {
58
+ if (runMeta.runId) {
59
+ const runDir = resolve(out, 'runs', runMeta.runId);
60
+ const metaPath = resolve(runDir, 'run.meta.json');
61
+ if (existsSync(metaPath)) {
62
+ try {
63
+ const metaContent = readFileSync(metaPath, 'utf-8');
64
+ const meta = JSON.parse(metaContent);
65
+ return meta.runFingerprint || null;
66
+ } catch {
67
+ return null;
68
+ }
69
+ }
70
+ }
71
+ return null;
72
+ };
73
+
74
+ // Run determinism check
75
+ const determinismResult = await runDeterminismCheck(runFn, {
76
+ runs,
77
+ config: options,
78
+ normalize: true,
79
+ });
80
+
81
+ // PHASE 25: Load run fingerprints for each run
82
+ for (const runMeta of determinismResult.runsMeta) {
83
+ runMeta.runFingerprint = await loadRunFingerprints(runMeta);
84
+ }
85
+
86
+ // Verify each run
87
+ const verificationResults = [];
88
+ for (const runMeta of determinismResult.runsMeta) {
89
+ if (runMeta.runId) {
90
+ const runDir = resolve(out, 'runs', runMeta.runId);
91
+ if (existsSync(runDir)) {
92
+ try {
93
+ const { getArtifactVersions } = await import('../../verax/core/artifacts/registry.js');
94
+ const verification = verifyRun(runDir, getArtifactVersions());
95
+ verificationResults.push({
96
+ runId: runMeta.runId,
97
+ verification,
98
+ });
99
+ } catch (error) {
100
+ // Verification failed
101
+ verificationResults.push({
102
+ runId: runMeta.runId,
103
+ verification: { ok: false, errors: [error.message] },
104
+ });
105
+ }
106
+ }
107
+ }
108
+ }
109
+
110
+ // Write determinism report
111
+ const reportPath = await writeDeterminismReport(
112
+ determinismResult,
113
+ verificationResults,
114
+ out
115
+ );
116
+
117
+ return {
118
+ ...determinismResult,
119
+ verificationResults,
120
+ reportPath,
121
+ };
122
+ }
123
+
@@ -0,0 +1,129 @@
1
+ /**
2
+ * PHASE 18 — Determinism Report Writer
3
+ *
4
+ * Writes determinism check results to artifact.
5
+ */
6
+
7
+ import { resolve } from 'path';
8
+ import { mkdirSync, writeFileSync } from 'fs';
9
+ import { ARTIFACT_REGISTRY, getArtifactVersions } from '../../verax/core/artifacts/registry.js';
10
+
11
+ /**
12
+ * PHASE 18: Write determinism report
13
+ * PHASE 25: Enhanced with runFingerprint, contract validation, and structured verdicts
14
+ *
15
+ * @param {Object} determinismResult - Result from runDeterminismCheck
16
+ * @param {Array} verificationResults - Verification results for each run
17
+ * @param {string} outDir - Output directory
18
+ * @returns {Promise<string>} Path to determinism report
19
+ */
20
+ export async function writeDeterminismReport(determinismResult, verificationResults, outDir) {
21
+ const reportsDir = resolve(outDir, 'determinism');
22
+ mkdirSync(reportsDir, { recursive: true });
23
+
24
+ const reportId = `determinism-${Date.now()}`;
25
+ const reportPath = resolve(reportsDir, `${reportId}.json`);
26
+
27
+ // PHASE 25: Check run fingerprints
28
+ const runFingerprints = [];
29
+ const runFingerprintMismatches = [];
30
+ for (const runMeta of determinismResult.runsMeta) {
31
+ if (runMeta.runFingerprint) {
32
+ runFingerprints.push(runMeta.runFingerprint);
33
+ }
34
+ }
35
+ if (runFingerprints.length > 1) {
36
+ const firstFingerprint = runFingerprints[0];
37
+ for (let i = 1; i < runFingerprints.length; i++) {
38
+ if (runFingerprints[i] !== firstFingerprint) {
39
+ runFingerprintMismatches.push({
40
+ runIndex: i + 1,
41
+ expected: firstFingerprint,
42
+ actual: runFingerprints[i]
43
+ });
44
+ }
45
+ }
46
+ }
47
+
48
+ // PHASE 25: Check verifier errors
49
+ const verifierErrors = [];
50
+ for (const verification of verificationResults) {
51
+ if (verification.verification && !verification.verification.ok) {
52
+ verifierErrors.push({
53
+ runId: verification.runId,
54
+ errors: verification.verification.errors || [],
55
+ verdictStatus: verification.verification.verdictStatus
56
+ });
57
+ }
58
+ }
59
+
60
+ // PHASE 25: Determine final verdict with expected/unexpected distinction
61
+ let finalVerdict = determinismResult.verdict;
62
+ if (runFingerprintMismatches.length > 0) {
63
+ finalVerdict = 'NON_DETERMINISTIC_UNEXPECTED';
64
+ } else if (verifierErrors.length > 0) {
65
+ finalVerdict = 'NON_DETERMINISTIC_UNEXPECTED';
66
+ } else if (determinismResult.adaptiveEvents && determinismResult.adaptiveEvents.length > 0) {
67
+ finalVerdict = 'NON_DETERMINISTIC_EXPECTED';
68
+ } else if (determinismResult.diffs && determinismResult.diffs.length > 0) {
69
+ const blockerDiffs = determinismResult.diffs.filter(d => d.severity === 'BLOCKER');
70
+ if (blockerDiffs.length > 0) {
71
+ finalVerdict = 'NON_DETERMINISTIC_UNEXPECTED';
72
+ } else {
73
+ finalVerdict = 'NON_DETERMINISTIC_EXPECTED';
74
+ }
75
+ }
76
+
77
+ // PHASE 25: Build top reasons
78
+ const topReasons = [];
79
+ if (runFingerprintMismatches.length > 0) {
80
+ topReasons.push({ code: 'RUN_FINGERPRINT_MISMATCH', count: runFingerprintMismatches.length });
81
+ }
82
+ if (verifierErrors.length > 0) {
83
+ topReasons.push({ code: 'VERIFIER_ERRORS_DETECTED', count: verifierErrors.length });
84
+ }
85
+ if (determinismResult.adaptiveEvents && determinismResult.adaptiveEvents.length > 0) {
86
+ topReasons.push({ code: 'EXPECTED_ADAPTIVE_BEHAVIOR', count: determinismResult.adaptiveEvents.length });
87
+ }
88
+ if (determinismResult.diffs && determinismResult.diffs.length > 0) {
89
+ const blockerCount = determinismResult.diffs.filter(d => d.severity === 'BLOCKER').length;
90
+ if (blockerCount > 0) {
91
+ topReasons.push({ code: 'ARTIFACT_DIFF_DETECTED', count: blockerCount });
92
+ }
93
+ }
94
+
95
+ const report = {
96
+ version: 2, // PHASE 25: Bump version
97
+ contractVersion: 1,
98
+ artifactVersions: getArtifactVersions(),
99
+ generatedAt: new Date().toISOString(),
100
+ verdict: finalVerdict,
101
+ summary: {
102
+ ...determinismResult.summary,
103
+ runFingerprintMismatches: runFingerprintMismatches.length,
104
+ verifierErrors: verifierErrors.length,
105
+ topReasons: topReasons.slice(0, 10)
106
+ },
107
+ runsMeta: determinismResult.runsMeta,
108
+ runFingerprints,
109
+ runFingerprintMismatches,
110
+ verificationResults,
111
+ verifierErrors,
112
+ normalizationRulesApplied: [
113
+ 'Stripped timestamps (startedAt, completedAt, detectedAt, etc.)',
114
+ 'Stripped runId fields',
115
+ 'Normalized absolute paths to relative',
116
+ 'Normalized floating scores to 3 decimals',
117
+ 'Sorted arrays by stable keys',
118
+ 'Normalized evidence paths but preserved presence/absence',
119
+ ],
120
+ diffs: determinismResult.diffs,
121
+ adaptiveEvents: determinismResult.adaptiveEvents || [],
122
+ stabilityScore: determinismResult.summary.stabilityScore,
123
+ };
124
+
125
+ writeFileSync(reportPath, JSON.stringify(report, null, 2) + '\n');
126
+
127
+ return reportPath;
128
+ }
129
+
@@ -1,7 +1,11 @@
1
+ import { assertExecutionBootstrapAllowed } from './bootstrap-guard.js';
2
+
1
3
  /**
2
4
  * Attempt to infer URL from environment variables and common dev configs
3
5
  */
4
6
  export function tryResolveUrlFromEnv() {
7
+ // PHASE 21.6.1: Runtime guard - crash if called during inspection
8
+ assertExecutionBootstrapAllowed('tryResolveUrlFromEnv');
5
9
  // Check common environment variables
6
10
  const candidates = [
7
11
  process.env.VERCEL_URL,
@@ -5,6 +5,10 @@ export class RunEventEmitter {
5
5
  constructor() {
6
6
  this.events = [];
7
7
  this.listeners = [];
8
+ this.heartbeatInterval = null;
9
+ this.heartbeatStartTime = null;
10
+ this.currentPhase = null;
11
+ this.heartbeatIntervalMs = 2500; // 2.5 seconds
8
12
  }
9
13
 
10
14
  on(event, handler) {
@@ -31,4 +35,76 @@ export class RunEventEmitter {
31
35
  getEvents() {
32
36
  return this.events;
33
37
  }
38
+
39
+ /**
40
+ * Start heartbeat for a phase
41
+ * @param {string} phase - Current phase name
42
+ * @param {boolean} jsonMode - Whether in JSON output mode
43
+ */
44
+ startHeartbeat(phase, jsonMode = false) {
45
+ this.currentPhase = phase;
46
+ this.heartbeatStartTime = Date.now();
47
+
48
+ if (this.heartbeatInterval) {
49
+ clearInterval(this.heartbeatInterval);
50
+ }
51
+
52
+ this.heartbeatInterval = setInterval(() => {
53
+ const elapsedMs = Date.now() - this.heartbeatStartTime;
54
+ const elapsedSeconds = Math.floor(elapsedMs / 1000);
55
+
56
+ const heartbeatEvent = {
57
+ type: 'heartbeat',
58
+ phase: this.currentPhase,
59
+ elapsedMs,
60
+ elapsedSeconds,
61
+ timestamp: new Date().toISOString(),
62
+ };
63
+
64
+ // Add to events array
65
+ this.events.push(heartbeatEvent);
66
+
67
+ // Emit to listeners
68
+ this.listeners.forEach(({ event: listenEvent, handler }) => {
69
+ if (listenEvent === 'heartbeat' || listenEvent === '*') {
70
+ if (jsonMode) {
71
+ // In JSON mode, emit as JSON line
72
+ handler(heartbeatEvent);
73
+ } else {
74
+ // Human-readable format: single line, overwrite-friendly
75
+ process.stdout.write(`\r…still working (phase=${this.currentPhase}, elapsed=${elapsedSeconds}s)`);
76
+ }
77
+ }
78
+ });
79
+ }, this.heartbeatIntervalMs);
80
+
81
+ // CRITICAL: Unref the interval so it doesn't keep the process alive
82
+ // This allows tests to exit cleanly even if stopHeartbeat() is not called
83
+ if (this.heartbeatInterval && this.heartbeatInterval.unref) {
84
+ this.heartbeatInterval.unref();
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Stop heartbeat
90
+ */
91
+ stopHeartbeat() {
92
+ if (this.heartbeatInterval) {
93
+ clearInterval(this.heartbeatInterval);
94
+ this.heartbeatInterval = null;
95
+ }
96
+ // Clear the progress line in human mode
97
+ process.stdout.write('\r' + ' '.repeat(60) + '\r');
98
+ this.currentPhase = null;
99
+ this.heartbeatStartTime = null;
100
+ }
101
+
102
+ /**
103
+ * Update current phase for heartbeat
104
+ * @param {string} phase - New phase name
105
+ */
106
+ updatePhase(phase) {
107
+ this.currentPhase = phase;
108
+ this.heartbeatStartTime = Date.now();
109
+ }
34
110
  }