@veraxhq/verax 0.1.0 → 0.2.1

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 (135) hide show
  1. package/README.md +123 -88
  2. package/bin/verax.js +11 -452
  3. package/package.json +24 -36
  4. package/src/cli/commands/default.js +681 -0
  5. package/src/cli/commands/doctor.js +197 -0
  6. package/src/cli/commands/inspect.js +109 -0
  7. package/src/cli/commands/run.js +586 -0
  8. package/src/cli/entry.js +196 -0
  9. package/src/cli/util/atomic-write.js +37 -0
  10. package/src/cli/util/detection-engine.js +297 -0
  11. package/src/cli/util/env-url.js +33 -0
  12. package/src/cli/util/errors.js +44 -0
  13. package/src/cli/util/events.js +110 -0
  14. package/src/cli/util/expectation-extractor.js +388 -0
  15. package/src/cli/util/findings-writer.js +32 -0
  16. package/src/cli/util/idgen.js +87 -0
  17. package/src/cli/util/learn-writer.js +39 -0
  18. package/src/cli/util/observation-engine.js +412 -0
  19. package/src/cli/util/observe-writer.js +25 -0
  20. package/src/cli/util/paths.js +30 -0
  21. package/src/cli/util/project-discovery.js +297 -0
  22. package/src/cli/util/project-writer.js +26 -0
  23. package/src/cli/util/redact.js +128 -0
  24. package/src/cli/util/run-id.js +30 -0
  25. package/src/cli/util/runtime-budget.js +147 -0
  26. package/src/cli/util/summary-writer.js +43 -0
  27. package/src/types/global.d.ts +28 -0
  28. package/src/types/ts-ast.d.ts +24 -0
  29. package/src/verax/cli/ci-summary.js +35 -0
  30. package/src/verax/cli/context-explanation.js +89 -0
  31. package/src/verax/cli/doctor.js +277 -0
  32. package/src/verax/cli/error-normalizer.js +154 -0
  33. package/src/verax/cli/explain-output.js +105 -0
  34. package/src/verax/cli/finding-explainer.js +130 -0
  35. package/src/verax/cli/init.js +237 -0
  36. package/src/verax/cli/run-overview.js +163 -0
  37. package/src/verax/cli/url-safety.js +111 -0
  38. package/src/verax/cli/wizard.js +109 -0
  39. package/src/verax/cli/zero-findings-explainer.js +57 -0
  40. package/src/verax/cli/zero-interaction-explainer.js +127 -0
  41. package/src/verax/core/action-classifier.js +86 -0
  42. package/src/verax/core/budget-engine.js +218 -0
  43. package/src/verax/core/canonical-outcomes.js +157 -0
  44. package/src/verax/core/decision-snapshot.js +335 -0
  45. package/src/verax/core/determinism-model.js +432 -0
  46. package/src/verax/core/incremental-store.js +245 -0
  47. package/src/verax/core/invariants.js +356 -0
  48. package/src/verax/core/promise-model.js +230 -0
  49. package/src/verax/core/replay-validator.js +350 -0
  50. package/src/verax/core/replay.js +222 -0
  51. package/src/verax/core/run-id.js +175 -0
  52. package/src/verax/core/run-manifest.js +99 -0
  53. package/src/verax/core/silence-impact.js +369 -0
  54. package/src/verax/core/silence-model.js +523 -0
  55. package/src/verax/detect/comparison.js +7 -34
  56. package/src/verax/detect/confidence-engine.js +764 -329
  57. package/src/verax/detect/detection-engine.js +293 -0
  58. package/src/verax/detect/evidence-index.js +127 -0
  59. package/src/verax/detect/expectation-model.js +241 -168
  60. package/src/verax/detect/explanation-helpers.js +187 -0
  61. package/src/verax/detect/finding-detector.js +450 -0
  62. package/src/verax/detect/findings-writer.js +41 -12
  63. package/src/verax/detect/flow-detector.js +366 -0
  64. package/src/verax/detect/index.js +200 -288
  65. package/src/verax/detect/interactive-findings.js +612 -0
  66. package/src/verax/detect/signal-mapper.js +308 -0
  67. package/src/verax/detect/skip-classifier.js +4 -4
  68. package/src/verax/detect/verdict-engine.js +561 -0
  69. package/src/verax/evidence-index-writer.js +61 -0
  70. package/src/verax/flow/flow-engine.js +3 -2
  71. package/src/verax/flow/flow-spec.js +1 -2
  72. package/src/verax/index.js +103 -15
  73. package/src/verax/intel/effect-detector.js +368 -0
  74. package/src/verax/intel/handler-mapper.js +249 -0
  75. package/src/verax/intel/index.js +281 -0
  76. package/src/verax/intel/route-extractor.js +280 -0
  77. package/src/verax/intel/ts-program.js +256 -0
  78. package/src/verax/intel/vue-navigation-extractor.js +642 -0
  79. package/src/verax/intel/vue-router-extractor.js +325 -0
  80. package/src/verax/learn/action-contract-extractor.js +338 -104
  81. package/src/verax/learn/ast-contract-extractor.js +148 -6
  82. package/src/verax/learn/flow-extractor.js +172 -0
  83. package/src/verax/learn/index.js +36 -2
  84. package/src/verax/learn/manifest-writer.js +122 -58
  85. package/src/verax/learn/project-detector.js +40 -0
  86. package/src/verax/learn/route-extractor.js +28 -97
  87. package/src/verax/learn/route-validator.js +8 -7
  88. package/src/verax/learn/state-extractor.js +212 -0
  89. package/src/verax/learn/static-extractor-navigation.js +114 -0
  90. package/src/verax/learn/static-extractor-validation.js +88 -0
  91. package/src/verax/learn/static-extractor.js +119 -10
  92. package/src/verax/learn/truth-assessor.js +24 -21
  93. package/src/verax/learn/ts-contract-resolver.js +14 -12
  94. package/src/verax/observe/aria-sensor.js +211 -0
  95. package/src/verax/observe/browser.js +30 -6
  96. package/src/verax/observe/console-sensor.js +2 -18
  97. package/src/verax/observe/domain-boundary.js +10 -1
  98. package/src/verax/observe/expectation-executor.js +513 -0
  99. package/src/verax/observe/flow-matcher.js +143 -0
  100. package/src/verax/observe/focus-sensor.js +196 -0
  101. package/src/verax/observe/human-driver.js +660 -273
  102. package/src/verax/observe/index.js +910 -26
  103. package/src/verax/observe/interaction-discovery.js +378 -15
  104. package/src/verax/observe/interaction-runner.js +562 -197
  105. package/src/verax/observe/loading-sensor.js +145 -0
  106. package/src/verax/observe/navigation-sensor.js +255 -0
  107. package/src/verax/observe/network-sensor.js +55 -7
  108. package/src/verax/observe/observed-expectation-deriver.js +186 -0
  109. package/src/verax/observe/observed-expectation.js +305 -0
  110. package/src/verax/observe/page-frontier.js +234 -0
  111. package/src/verax/observe/settle.js +38 -17
  112. package/src/verax/observe/state-sensor.js +393 -0
  113. package/src/verax/observe/state-ui-sensor.js +7 -1
  114. package/src/verax/observe/timing-sensor.js +228 -0
  115. package/src/verax/observe/traces-writer.js +73 -21
  116. package/src/verax/observe/ui-signal-sensor.js +143 -17
  117. package/src/verax/scan-summary-writer.js +80 -15
  118. package/src/verax/shared/artifact-manager.js +111 -9
  119. package/src/verax/shared/budget-profiles.js +136 -0
  120. package/src/verax/shared/caching.js +1 -1
  121. package/src/verax/shared/ci-detection.js +39 -0
  122. package/src/verax/shared/config-loader.js +169 -0
  123. package/src/verax/shared/dynamic-route-utils.js +224 -0
  124. package/src/verax/shared/expectation-coverage.js +44 -0
  125. package/src/verax/shared/expectation-prover.js +81 -0
  126. package/src/verax/shared/expectation-tracker.js +201 -0
  127. package/src/verax/shared/expectations-writer.js +60 -0
  128. package/src/verax/shared/first-run.js +44 -0
  129. package/src/verax/shared/progress-reporter.js +171 -0
  130. package/src/verax/shared/retry-policy.js +9 -1
  131. package/src/verax/shared/root-artifacts.js +49 -0
  132. package/src/verax/shared/scan-budget.js +86 -0
  133. package/src/verax/shared/url-normalizer.js +162 -0
  134. package/src/verax/shared/zip-artifacts.js +66 -0
  135. package/src/verax/validate/context-validator.js +244 -0
@@ -0,0 +1,175 @@
1
+ /**
2
+ * PHASE 5: DETERMINISTIC RUN IDENTIFICATION
3
+ *
4
+ * Generates stable, deterministic runId based on run parameters (NOT timestamps).
5
+ * Provides utilities for artifact path resolution.
6
+ */
7
+
8
+ import { createHash } from 'crypto';
9
+ import { join } from 'path';
10
+ import { readFileSync } from 'fs';
11
+ import { fileURLToPath } from 'url';
12
+ import { dirname } from 'path';
13
+
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const __dirname = dirname(__filename);
16
+
17
+ /**
18
+ * Get VERAX version from package.json
19
+ */
20
+ export function getVeraxVersion() {
21
+ try {
22
+ const packagePath = join(__dirname, '..', '..', '..', 'package.json');
23
+ const pkg = JSON.parse(readFileSync(packagePath, 'utf-8'));
24
+ return pkg.version || '0.1.0';
25
+ } catch (error) {
26
+ return '0.1.0';
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Generate deterministic runId from run parameters
32
+ *
33
+ * CRITICAL: NO timestamps, NO random values
34
+ * Hash must be identical for identical inputs
35
+ *
36
+ * @param {Object} params - Run parameters
37
+ * @param {string} params.url - Target URL
38
+ * @param {Object} params.safetyFlags - Safety flags (allowWrites, allowRiskyActions, allowCrossOrigin)
39
+ * @param {string} params.baseOrigin - Base origin
40
+ * @param {Object} params.scanBudget - Scan budget configuration
41
+ * @param {string} params.manifestPath - Optional manifest path
42
+ * @returns {string} Deterministic runId (hex hash)
43
+ */
44
+ export function generateRunId(params) {
45
+ const { url, safetyFlags = {}, baseOrigin, scanBudget, manifestPath = null } = params;
46
+
47
+ // Sort flags deterministically
48
+ const sortedFlags = {
49
+ allowCrossOrigin: safetyFlags.allowCrossOrigin || false,
50
+ allowRiskyActions: safetyFlags.allowRiskyActions || false,
51
+ allowWrites: safetyFlags.allowWrites || false
52
+ };
53
+
54
+ // Create deterministic representation
55
+ const runConfig = {
56
+ url,
57
+ flags: sortedFlags,
58
+ baseOrigin,
59
+ budget: {
60
+ maxScanDurationMs: scanBudget.maxScanDurationMs,
61
+ maxInteractionsPerPage: scanBudget.maxInteractionsPerPage,
62
+ maxUniqueUrls: scanBudget.maxUniqueUrls,
63
+ interactionTimeoutMs: scanBudget.interactionTimeoutMs,
64
+ navigationTimeoutMs: scanBudget.navigationTimeoutMs
65
+ },
66
+ manifestPath,
67
+ veraxVersion: getVeraxVersion()
68
+ };
69
+
70
+ // Generate stable hash
71
+ // Sort keys at all levels for deterministic serialization
72
+ const configString = JSON.stringify(runConfig, (key, value) => {
73
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
74
+ return Object.keys(value).sort().reduce((sorted, k) => {
75
+ sorted[k] = value[k];
76
+ return sorted;
77
+ }, {});
78
+ }
79
+ return value;
80
+ });
81
+ const hash = createHash('sha256').update(configString).digest('hex');
82
+
83
+ // Return first 16 chars for readability (still collision-resistant)
84
+ return hash.substring(0, 16);
85
+ }
86
+
87
+ /**
88
+ * Get artifact directory for a run
89
+ *
90
+ * @param {string} projectDir - Project directory
91
+ * @param {string} runId - Run identifier
92
+ * @returns {string} Absolute path to run artifact directory
93
+ */
94
+ export function getRunArtifactDir(projectDir, runId) {
95
+ return join(projectDir, '.verax', 'runs', runId);
96
+ }
97
+
98
+ /**
99
+ * Get artifact file path
100
+ *
101
+ * @param {string} projectDir - Project directory
102
+ * @param {string} runId - Run identifier
103
+ * @param {string} artifactName - Artifact filename (e.g., 'traces.json', 'findings.json')
104
+ * @returns {string} Absolute path to artifact file
105
+ */
106
+ export function getArtifactPath(projectDir, runId, artifactName) {
107
+ return join(getRunArtifactDir(projectDir, runId), artifactName);
108
+ }
109
+
110
+ /**
111
+ * Get screenshot directory path
112
+ *
113
+ * @param {string} projectDir - Project directory
114
+ * @param {string} runId - Run identifier
115
+ * @returns {string} Absolute path to screenshots directory
116
+ */
117
+ export function getScreenshotDir(projectDir, runId) {
118
+ return join(getRunArtifactDir(projectDir, runId), 'evidence', 'screenshots');
119
+ }
120
+
121
+ /**
122
+ * Get all expected artifact paths for a run
123
+ *
124
+ * @param {string} projectDir - Project directory
125
+ * @param {string} runId - Run identifier
126
+ * @returns {Object} Map of artifact names to paths
127
+ */
128
+ export function getExpectedArtifacts(projectDir, runId) {
129
+ const runDir = getRunArtifactDir(projectDir, runId);
130
+ return {
131
+ runManifest: join(runDir, 'run-manifest.json'),
132
+ manifest: join(runDir, 'manifest.json'),
133
+ traces: join(runDir, 'traces.json'),
134
+ findings: join(runDir, 'findings.json'),
135
+ silences: join(runDir, 'silences.json'),
136
+ screenshotDir: join(runDir, 'evidence', 'screenshots')
137
+ };
138
+ }
139
+
140
+ /**
141
+ * Compute SHA256 hash of a file
142
+ *
143
+ * @param {string} filePath - Path to file
144
+ * @returns {string} Hex hash
145
+ */
146
+ export function computeFileHash(filePath) {
147
+ try {
148
+ const content = readFileSync(filePath);
149
+ return createHash('sha256').update(content).digest('hex');
150
+ } catch (error) {
151
+ return null;
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Compute hashes for all artifacts in a run
157
+ *
158
+ * @param {string} projectDir - Project directory
159
+ * @param {string} runId - Run identifier
160
+ * @returns {Object} Map of artifact names to hashes
161
+ */
162
+ export function computeArtifactHashes(projectDir, runId) {
163
+ const artifacts = getExpectedArtifacts(projectDir, runId);
164
+ const hashes = {};
165
+
166
+ for (const [name, path] of Object.entries(artifacts)) {
167
+ if (name === 'screenshotDir') continue; // Directory, not file
168
+ const hash = computeFileHash(path);
169
+ if (hash) {
170
+ hashes[name] = hash;
171
+ }
172
+ }
173
+
174
+ return hashes;
175
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * PHASE 5: RUN MANIFEST
3
+ *
4
+ * Single source of truth for run metadata.
5
+ * Written FIRST, referenced by all later stages.
6
+ */
7
+
8
+ import { writeFileSync, mkdirSync, readFileSync } from 'fs';
9
+ import { getRunArtifactDir, getVeraxVersion } from './run-id.js';
10
+
11
+ /**
12
+ * Create run manifest at start of execution
13
+ *
14
+ * @param {string} projectDir - Project directory
15
+ * @param {string} runId - Run identifier (deterministic)
16
+ * @param {Object} params - Run parameters
17
+ * @param {string} params.url - Target URL
18
+ * @param {Object} params.safetyFlags - Safety flags
19
+ * @param {string} params.baseOrigin - Base origin
20
+ * @param {Object} params.scanBudget - Scan budget
21
+ * @param {string} params.manifestPath - Optional manifest path
22
+ * @param {Array<string>} params.argv - Command line arguments
23
+ * @returns {Object} Run manifest data
24
+ */
25
+ export function createRunManifest(projectDir, runId, params) {
26
+ const { url, safetyFlags, baseOrigin, scanBudget, manifestPath, argv = [] } = params;
27
+
28
+ const runManifest = {
29
+ runId,
30
+ veraxVersion: getVeraxVersion(),
31
+ nodeVersion: process.version,
32
+ // Playwright version would be detected from node_modules if needed
33
+ playwrightVersion: 'latest',
34
+ url,
35
+ baseOrigin,
36
+ flags: {
37
+ allowWrites: safetyFlags.allowWrites || false,
38
+ allowRiskyActions: safetyFlags.allowRiskyActions || false,
39
+ allowCrossOrigin: safetyFlags.allowCrossOrigin || false
40
+ },
41
+ safeMode: {
42
+ enabled: !safetyFlags.allowWrites || !safetyFlags.allowRiskyActions || !safetyFlags.allowCrossOrigin,
43
+ writesBlocked: !safetyFlags.allowWrites,
44
+ riskyActionsBlocked: !safetyFlags.allowRiskyActions,
45
+ crossOriginBlocked: !safetyFlags.allowCrossOrigin
46
+ },
47
+ budget: {
48
+ maxScanDurationMs: scanBudget.maxScanDurationMs,
49
+ maxInteractionsPerPage: scanBudget.maxInteractionsPerPage,
50
+ maxUniqueUrls: scanBudget.maxUniqueUrls,
51
+ interactionTimeoutMs: scanBudget.interactionTimeoutMs,
52
+ navigationTimeoutMs: scanBudget.navigationTimeoutMs,
53
+ stabilizationWindowMs: scanBudget.stabilizationWindowMs,
54
+ stabilizationSampleMidMs: scanBudget.stabilizationSampleMidMs,
55
+ stabilizationSampleEndMs: scanBudget.stabilizationSampleEndMs,
56
+ networkWaitMs: scanBudget.networkWaitMs,
57
+ settleTimeoutMs: scanBudget.settleTimeoutMs,
58
+ settleIdleMs: scanBudget.settleIdleMs,
59
+ settleDomStableMs: scanBudget.settleDomStableMs,
60
+ navigationStableWaitMs: scanBudget.navigationStableWaitMs
61
+ },
62
+ manifestPath,
63
+ command: argv.join(' '),
64
+ argv,
65
+ startTime: new Date().toISOString(),
66
+ artifactHashes: {} // Will be populated after artifacts are written
67
+ };
68
+
69
+ // Ensure run directory exists
70
+ const runDir = getRunArtifactDir(projectDir, runId);
71
+ mkdirSync(runDir, { recursive: true });
72
+
73
+ // Write run manifest
74
+ const runManifestPath = `${runDir}/run-manifest.json`;
75
+ writeFileSync(runManifestPath, JSON.stringify(runManifest, null, 2));
76
+
77
+ return runManifest;
78
+ }
79
+
80
+ /**
81
+ * Update run manifest with artifact hashes
82
+ *
83
+ * @param {string} projectDir - Project directory
84
+ * @param {string} runId - Run identifier
85
+ * @param {Object} hashes - Map of artifact names to hashes
86
+ */
87
+ export function updateRunManifestHashes(projectDir, runId, hashes) {
88
+ const runDir = getRunArtifactDir(projectDir, runId);
89
+ const runManifestPath = `${runDir}/run-manifest.json`;
90
+
91
+ try {
92
+ const runManifest = JSON.parse(readFileSync(runManifestPath, 'utf-8'));
93
+ runManifest.artifactHashes = hashes;
94
+ runManifest.endTime = new Date().toISOString();
95
+ writeFileSync(runManifestPath, JSON.stringify(runManifest, null, 2));
96
+ } catch (error) {
97
+ // Run manifest not found or invalid - continue without update
98
+ }
99
+ }
@@ -0,0 +1,369 @@
1
+ /**
2
+ * SILENCE IMPACT ACCOUNTING
3
+ *
4
+ * PHASE 4: Quantify how silence (unobserved/unevaluated states) affects confidence in our observations.
5
+ *
6
+ * PRINCIPLES:
7
+ * 1. Silence is never neutral - it reduces confidence in what we claim to observe
8
+ * 2. Different types of silence have different confidence impacts
9
+ * 3. Aggregate impacts show what confidence metric is weakened and by how much
10
+ * 4. Confidence impact is ALWAYS negative (silence cannot increase confidence)
11
+ * 5. Impacts are factual: based on type/scope, not on hypothetical outcomes
12
+ *
13
+ * Three confidence metrics affected by silence:
14
+ * - Coverage Confidence: How much of the codebase did we actually observe?
15
+ * - Promise Verification Confidence: How certain are we about promise verification?
16
+ * - Overall Observation Confidence: General confidence in observation completeness
17
+ */
18
+
19
+ import { SILENCE_TYPES, EVALUATION_STATUS } from './silence-model.js';
20
+
21
+ /**
22
+ * SILENCE_IMPACT_PROFILES - How different silence types affect confidence
23
+ *
24
+ * Each profile defines the impact on three confidence dimensions.
25
+ * All values are NEGATIVE (silence reduces confidence).
26
+ */
27
+ export const SILENCE_IMPACT_PROFILES = {
28
+ // Observation Infrastructure Failures - CRITICAL
29
+ // Sensor failure means we lost observability entirely for that area
30
+ [SILENCE_TYPES.SENSOR_FAILURE]: {
31
+ name: 'Sensor Failure',
32
+ severity: 'critical',
33
+ coverage: -20, // Major reduction: lost observability
34
+ promise_verification: -15, // Cannot verify promises without observation
35
+ overall: -18, // Very high impact
36
+ reasoning: 'Observation infrastructure failure - no data collected for affected area'
37
+ },
38
+
39
+ [SILENCE_TYPES.DISCOVERY_FAILURE]: {
40
+ name: 'Discovery Failure',
41
+ severity: 'critical',
42
+ coverage: -15, // Major reduction: didn't find items to evaluate
43
+ promise_verification: -10, // Unknown what promises existed
44
+ overall: -13, // Very high impact
45
+ reasoning: 'Could not discover items - evaluation incomplete for unknown scope'
46
+ },
47
+
48
+ // Timing Failures - HIGH
49
+ // Timeouts mean interaction couldn't complete, promise unverified
50
+ [SILENCE_TYPES.NAVIGATION_TIMEOUT]: {
51
+ name: 'Navigation Timeout',
52
+ severity: 'high',
53
+ coverage: -10, // Moderate reduction: page not reachable
54
+ promise_verification: -20, // Complete failure for navigation promise
55
+ overall: -15, // High impact
56
+ reasoning: 'Navigation could not complete - route unreachable or page unresponsive'
57
+ },
58
+
59
+ [SILENCE_TYPES.INTERACTION_TIMEOUT]: {
60
+ name: 'Interaction Timeout',
61
+ severity: 'high',
62
+ coverage: -8, // Moderate reduction
63
+ promise_verification: -18, // Interaction promise verification failed
64
+ overall: -13, // High impact
65
+ reasoning: 'Interaction did not complete within time budget - outcome unknown'
66
+ },
67
+
68
+ // Policy-Blocked Evaluation - MEDIUM
69
+ // Safety blocks prevent evaluation, but promise is known to exist
70
+ [SILENCE_TYPES.SAFETY_POLICY_BLOCK]: {
71
+ name: 'Safety Policy Block',
72
+ severity: 'medium',
73
+ coverage: -3, // Low reduction: coverage by skipping is intentional
74
+ promise_verification: -25, // Critical: promise cannot be verified due to safety policy
75
+ overall: -12, // Medium-high impact
76
+ reasoning: 'Promise verification blocked by safety policy (logout, destructive action) - cannot assert promise due to risk'
77
+ },
78
+
79
+ [SILENCE_TYPES.PROMISE_VERIFICATION_BLOCKED]: {
80
+ name: 'Promise Verification Blocked',
81
+ severity: 'medium',
82
+ coverage: -2, // Very low reduction: blocked verification only
83
+ promise_verification: -22, // Cannot verify due to external navigation or origin mismatch
84
+ overall: -10, // Medium impact
85
+ reasoning: 'Promise verification blocked - navigation leaves origin or enters external site'
86
+ },
87
+
88
+ // Resource Constraints - MEDIUM
89
+ // Budget limits mean we didn't finish evaluation
90
+ [SILENCE_TYPES.BUDGET_LIMIT_EXCEEDED]: {
91
+ name: 'Budget Limit Exceeded',
92
+ severity: 'medium',
93
+ coverage: -12, // Moderate reduction: didn't evaluate all items
94
+ promise_verification: -8, // Some promises verified, others not
95
+ overall: -10, // Medium impact
96
+ reasoning: 'Evaluation terminated due to time/interaction budget - remaining items not evaluated'
97
+ },
98
+
99
+ // Data Reuse - LOW
100
+ // Incremental reuse is safe by design (previous run passed)
101
+ [SILENCE_TYPES.INCREMENTAL_REUSE]: {
102
+ name: 'Incremental Reuse',
103
+ severity: 'low',
104
+ coverage: 0, // No impact: explicitly reusing validated baseline
105
+ promise_verification: 0, // Verified in previous run
106
+ overall: 0, // Intentional optimization
107
+ reasoning: 'Data from previous run reused - baseline still valid'
108
+ },
109
+
110
+ // Ambiguous/Incomplete - LOW
111
+ // No expectation defined = no promise to verify anyway
112
+ [SILENCE_TYPES.PROMISE_NOT_EVALUATED]: {
113
+ name: 'Promise Not Evaluated',
114
+ severity: 'low',
115
+ coverage: 0, // No impact: covered but no expectation
116
+ promise_verification: -2, // Minor: cannot assert promise without expectation
117
+ overall: -1, // Minimal impact
118
+ reasoning: 'Interaction found but no expectation defined - cannot verify promise'
119
+ },
120
+ };
121
+
122
+ /**
123
+ * Compute confidence impact for a single silence entry
124
+ *
125
+ * @param {Object} silence - SilenceEntry with silence_type, evaluation_status, context
126
+ * @returns {Object} Impact: { coverage: number, promise_verification: number, overall: number }
127
+ */
128
+ export function computeSilenceImpact(silence) {
129
+ if (!silence) {
130
+ return { coverage: 0, promise_verification: 0, overall: 0 };
131
+ }
132
+
133
+ const profile = SILENCE_IMPACT_PROFILES[silence.silence_type];
134
+
135
+ if (!profile) {
136
+ // Unknown silence type - be conservative
137
+ return {
138
+ coverage: -5,
139
+ promise_verification: -5,
140
+ overall: -5,
141
+ unknown_type: silence.silence_type
142
+ };
143
+ }
144
+
145
+ // Start with base profile
146
+ let impact = {
147
+ coverage: profile.coverage,
148
+ promise_verification: profile.promise_verification,
149
+ overall: profile.overall
150
+ };
151
+
152
+ // Adjust for evaluation status (some statuses are worse than others)
153
+ if (silence.evaluation_status === EVALUATION_STATUS.BLOCKED) {
154
+ // Blocked state: intentional, but still reduces confidence
155
+ // Slightly less impact than timeout
156
+ impact.promise_verification = Math.max(-20, impact.promise_verification + 2);
157
+ } else if (silence.evaluation_status === EVALUATION_STATUS.TIMED_OUT) {
158
+ // Timed out: worse than blocked (unintentional failure)
159
+ impact.promise_verification = Math.min(-25, impact.promise_verification - 2);
160
+ } else if (silence.evaluation_status === EVALUATION_STATUS.AMBIGUOUS) {
161
+ // Ambiguous: cannot assert but also didn't try
162
+ impact.promise_verification = Math.max(-5, impact.promise_verification + 3);
163
+ }
164
+
165
+ // Clamp to reasonable ranges
166
+ impact.coverage = Math.max(-100, impact.coverage);
167
+ impact.promise_verification = Math.max(-100, impact.promise_verification);
168
+ impact.overall = Math.max(-100, impact.overall);
169
+
170
+ return impact;
171
+ }
172
+
173
+ /**
174
+ * Aggregate impacts from multiple silences
175
+ *
176
+ * RULE: Impacts are cumulative but clamped at -100 (cannot be worse than complete loss)
177
+ *
178
+ * @param {Array} silences - Array of SilenceEntry objects
179
+ * @returns {Object} Aggregated impact with clamping
180
+ */
181
+ export function aggregateSilenceImpacts(silences) {
182
+ const total = {
183
+ coverage: 0,
184
+ promise_verification: 0,
185
+ overall: 0
186
+ };
187
+
188
+ if (!silences || silences.length === 0) {
189
+ return total;
190
+ }
191
+
192
+ for (const silence of silences) {
193
+ const impact = computeSilenceImpact(silence);
194
+ total.coverage += impact.coverage;
195
+ total.promise_verification += impact.promise_verification;
196
+ total.overall += impact.overall;
197
+ }
198
+
199
+ // Clamp to -100 to 0 range
200
+ return {
201
+ coverage: Math.max(-100, total.coverage),
202
+ promise_verification: Math.max(-100, total.promise_verification),
203
+ overall: Math.max(-100, total.overall)
204
+ };
205
+ }
206
+
207
+ /**
208
+ * Categorize impacts by severity
209
+ *
210
+ * @param {Array} silences - Array of SilenceEntry objects
211
+ * @returns {Object} Impacts organized by severity
212
+ */
213
+ export function categorizeSilencesByImpactSeverity(silences) {
214
+ const critical = [];
215
+ const high = [];
216
+ const medium = [];
217
+ const low = [];
218
+
219
+ if (!silences) return { critical, high, medium, low };
220
+
221
+ for (const silence of silences) {
222
+ const profile = SILENCE_IMPACT_PROFILES[silence.silence_type];
223
+ if (!profile) {
224
+ high.push(silence); // Default to high for unknown types
225
+ continue;
226
+ }
227
+
228
+ switch (profile.severity) {
229
+ case 'critical':
230
+ critical.push(silence);
231
+ break;
232
+ case 'high':
233
+ high.push(silence);
234
+ break;
235
+ case 'medium':
236
+ medium.push(silence);
237
+ break;
238
+ case 'low':
239
+ low.push(silence);
240
+ break;
241
+ }
242
+ }
243
+
244
+ return { critical, high, medium, low };
245
+ }
246
+
247
+ /**
248
+ * Compute impact summary for output
249
+ *
250
+ * Shows:
251
+ * - Total impact by dimension
252
+ * - Most impactful silence types
253
+ * - Silences affecting each dimension
254
+ *
255
+ * @param {Array} silences - Array of SilenceEntry objects
256
+ * @returns {Object} Detailed impact summary
257
+ */
258
+ export function createImpactSummary(silences) {
259
+ if (!silences || silences.length === 0) {
260
+ return {
261
+ total_silences: 0,
262
+ aggregated_impact: { coverage: 0, promise_verification: 0, overall: 0 },
263
+ by_severity: { critical: 0, high: 0, medium: 0, low: 0 },
264
+ most_impactful_types: [],
265
+ affected_dimensions: {
266
+ coverage: [],
267
+ promise_verification: [],
268
+ overall: []
269
+ }
270
+ };
271
+ }
272
+
273
+ const aggregated = aggregateSilenceImpacts(silences);
274
+ const bySeverity = categorizeSilencesByImpactSeverity(silences);
275
+
276
+ // Find most impactful types
277
+ const typeImpacts = {};
278
+ for (const silence of silences) {
279
+ const impact = computeSilenceImpact(silence);
280
+ if (!typeImpacts[silence.silence_type]) {
281
+ typeImpacts[silence.silence_type] = { count: 0, total_impact: 0 };
282
+ }
283
+ typeImpacts[silence.silence_type].count++;
284
+ typeImpacts[silence.silence_type].total_impact += impact.overall;
285
+ }
286
+
287
+ const mostImpactful = Object.entries(typeImpacts)
288
+ .map(([type, data]) => ({
289
+ type,
290
+ count: data.count,
291
+ average_impact: Math.round(data.total_impact / data.count),
292
+ total_impact: data.total_impact
293
+ }))
294
+ .sort((a, b) => a.total_impact - b.total_impact)
295
+ .slice(0, 5);
296
+
297
+ // Find silences affecting each dimension most
298
+ const affectedDimensions = {
299
+ coverage: silences
300
+ .filter(s => computeSilenceImpact(s).coverage < 0)
301
+ .sort((a, b) => computeSilenceImpact(a).coverage - computeSilenceImpact(b).coverage)
302
+ .slice(0, 3)
303
+ .map(s => s.silence_type),
304
+ promise_verification: silences
305
+ .filter(s => computeSilenceImpact(s).promise_verification < 0)
306
+ .sort((a, b) => computeSilenceImpact(a).promise_verification - computeSilenceImpact(b).promise_verification)
307
+ .slice(0, 3)
308
+ .map(s => s.silence_type),
309
+ overall: silences
310
+ .filter(s => computeSilenceImpact(s).overall < 0)
311
+ .sort((a, b) => computeSilenceImpact(a).overall - computeSilenceImpact(b).overall)
312
+ .slice(0, 3)
313
+ .map(s => s.silence_type)
314
+ };
315
+
316
+ return {
317
+ total_silences: silences.length,
318
+ aggregated_impact: aggregated,
319
+ by_severity: {
320
+ critical: bySeverity.critical.length,
321
+ high: bySeverity.high.length,
322
+ medium: bySeverity.medium.length,
323
+ low: bySeverity.low.length
324
+ },
325
+ most_impactful_types: mostImpactful,
326
+ affected_dimensions: affectedDimensions,
327
+ confidence_interpretation: generateConfidenceInterpretation(aggregated)
328
+ };
329
+ }
330
+
331
+ /**
332
+ * Generate human-readable interpretation of confidence impacts
333
+ *
334
+ * @param {Object} aggregated - Aggregated impact object
335
+ * @returns {string} Human-readable interpretation
336
+ */
337
+ function generateConfidenceInterpretation(aggregated) {
338
+ const { coverage: _coverage, promise_verification: _promise_verification, overall } = aggregated;
339
+
340
+ if (overall === 0) {
341
+ return 'No silence events - observation confidence is complete within evaluated scope';
342
+ }
343
+
344
+ if (overall <= -80) {
345
+ return 'CRITICAL: Observation significantly incomplete - major silence events limit what we can assert';
346
+ }
347
+
348
+ if (overall <= -50) {
349
+ return 'SIGNIFICANT: Multiple silence events reduce observation confidence - substantial unknowns remain';
350
+ }
351
+
352
+ if (overall <= -25) {
353
+ return 'MODERATE: Some silence events reduce observation completeness - some unknowns remain';
354
+ }
355
+
356
+ if (overall <= -10) {
357
+ return 'MINOR: Few silence events slightly reduce confidence - observation mostly complete';
358
+ }
359
+
360
+ return 'Very minor impact from silence events';
361
+ }
362
+
363
+ export default {
364
+ SILENCE_IMPACT_PROFILES,
365
+ computeSilenceImpact,
366
+ aggregateSilenceImpacts,
367
+ categorizeSilencesByImpactSeverity,
368
+ createImpactSummary
369
+ };