@veraxhq/verax 0.2.1 → 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 (152) hide show
  1. package/README.md +14 -18
  2. package/bin/verax.js +7 -0
  3. package/package.json +3 -3
  4. package/src/cli/commands/baseline.js +104 -0
  5. package/src/cli/commands/default.js +79 -25
  6. package/src/cli/commands/ga.js +243 -0
  7. package/src/cli/commands/gates.js +95 -0
  8. package/src/cli/commands/inspect.js +131 -2
  9. package/src/cli/commands/release-check.js +213 -0
  10. package/src/cli/commands/run.js +246 -35
  11. package/src/cli/commands/security-check.js +211 -0
  12. package/src/cli/commands/truth.js +114 -0
  13. package/src/cli/entry.js +304 -67
  14. package/src/cli/util/angular-component-extractor.js +179 -0
  15. package/src/cli/util/angular-navigation-detector.js +141 -0
  16. package/src/cli/util/angular-network-detector.js +161 -0
  17. package/src/cli/util/angular-state-detector.js +162 -0
  18. package/src/cli/util/ast-interactive-detector.js +546 -0
  19. package/src/cli/util/ast-network-detector.js +603 -0
  20. package/src/cli/util/ast-usestate-detector.js +602 -0
  21. package/src/cli/util/bootstrap-guard.js +86 -0
  22. package/src/cli/util/determinism-runner.js +123 -0
  23. package/src/cli/util/determinism-writer.js +129 -0
  24. package/src/cli/util/env-url.js +4 -0
  25. package/src/cli/util/expectation-extractor.js +369 -73
  26. package/src/cli/util/findings-writer.js +126 -16
  27. package/src/cli/util/learn-writer.js +3 -1
  28. package/src/cli/util/observe-writer.js +3 -1
  29. package/src/cli/util/paths.js +3 -12
  30. package/src/cli/util/project-discovery.js +3 -0
  31. package/src/cli/util/project-writer.js +3 -1
  32. package/src/cli/util/run-resolver.js +64 -0
  33. package/src/cli/util/source-requirement.js +55 -0
  34. package/src/cli/util/summary-writer.js +1 -0
  35. package/src/cli/util/svelte-navigation-detector.js +163 -0
  36. package/src/cli/util/svelte-network-detector.js +80 -0
  37. package/src/cli/util/svelte-sfc-extractor.js +147 -0
  38. package/src/cli/util/svelte-state-detector.js +243 -0
  39. package/src/cli/util/vue-navigation-detector.js +177 -0
  40. package/src/cli/util/vue-sfc-extractor.js +162 -0
  41. package/src/cli/util/vue-state-detector.js +215 -0
  42. package/src/verax/cli/finding-explainer.js +56 -3
  43. package/src/verax/core/artifacts/registry.js +154 -0
  44. package/src/verax/core/artifacts/verifier.js +980 -0
  45. package/src/verax/core/baseline/baseline.enforcer.js +137 -0
  46. package/src/verax/core/baseline/baseline.snapshot.js +231 -0
  47. package/src/verax/core/capabilities/gates.js +499 -0
  48. package/src/verax/core/capabilities/registry.js +475 -0
  49. package/src/verax/core/confidence/confidence-compute.js +137 -0
  50. package/src/verax/core/confidence/confidence-invariants.js +234 -0
  51. package/src/verax/core/confidence/confidence-report-writer.js +112 -0
  52. package/src/verax/core/confidence/confidence-weights.js +44 -0
  53. package/src/verax/core/confidence/confidence.defaults.js +65 -0
  54. package/src/verax/core/confidence/confidence.loader.js +79 -0
  55. package/src/verax/core/confidence/confidence.schema.js +94 -0
  56. package/src/verax/core/confidence-engine-refactor.js +484 -0
  57. package/src/verax/core/confidence-engine.js +486 -0
  58. package/src/verax/core/confidence-engine.js.backup +471 -0
  59. package/src/verax/core/contracts/index.js +29 -0
  60. package/src/verax/core/contracts/types.js +185 -0
  61. package/src/verax/core/contracts/validators.js +381 -0
  62. package/src/verax/core/decision-snapshot.js +30 -3
  63. package/src/verax/core/decisions/decision.trace.js +276 -0
  64. package/src/verax/core/determinism/contract-writer.js +89 -0
  65. package/src/verax/core/determinism/contract.js +139 -0
  66. package/src/verax/core/determinism/diff.js +364 -0
  67. package/src/verax/core/determinism/engine.js +221 -0
  68. package/src/verax/core/determinism/finding-identity.js +148 -0
  69. package/src/verax/core/determinism/normalize.js +438 -0
  70. package/src/verax/core/determinism/report-writer.js +92 -0
  71. package/src/verax/core/determinism/run-fingerprint.js +118 -0
  72. package/src/verax/core/dynamic-route-intelligence.js +528 -0
  73. package/src/verax/core/evidence/evidence-capture-service.js +307 -0
  74. package/src/verax/core/evidence/evidence-intent-ledger.js +165 -0
  75. package/src/verax/core/evidence-builder.js +487 -0
  76. package/src/verax/core/execution-mode-context.js +77 -0
  77. package/src/verax/core/execution-mode-detector.js +190 -0
  78. package/src/verax/core/failures/exit-codes.js +86 -0
  79. package/src/verax/core/failures/failure-summary.js +76 -0
  80. package/src/verax/core/failures/failure.factory.js +225 -0
  81. package/src/verax/core/failures/failure.ledger.js +132 -0
  82. package/src/verax/core/failures/failure.types.js +196 -0
  83. package/src/verax/core/failures/index.js +10 -0
  84. package/src/verax/core/ga/ga-report-writer.js +43 -0
  85. package/src/verax/core/ga/ga.artifact.js +49 -0
  86. package/src/verax/core/ga/ga.contract.js +434 -0
  87. package/src/verax/core/ga/ga.enforcer.js +86 -0
  88. package/src/verax/core/guardrails/guardrails-report-writer.js +109 -0
  89. package/src/verax/core/guardrails/policy.defaults.js +210 -0
  90. package/src/verax/core/guardrails/policy.loader.js +83 -0
  91. package/src/verax/core/guardrails/policy.schema.js +110 -0
  92. package/src/verax/core/guardrails/truth-reconciliation.js +136 -0
  93. package/src/verax/core/guardrails-engine.js +505 -0
  94. package/src/verax/core/observe/run-timeline.js +316 -0
  95. package/src/verax/core/perf/perf.contract.js +186 -0
  96. package/src/verax/core/perf/perf.display.js +65 -0
  97. package/src/verax/core/perf/perf.enforcer.js +91 -0
  98. package/src/verax/core/perf/perf.monitor.js +209 -0
  99. package/src/verax/core/perf/perf.report.js +198 -0
  100. package/src/verax/core/pipeline-tracker.js +238 -0
  101. package/src/verax/core/product-definition.js +127 -0
  102. package/src/verax/core/release/provenance.builder.js +271 -0
  103. package/src/verax/core/release/release-report-writer.js +40 -0
  104. package/src/verax/core/release/release.enforcer.js +159 -0
  105. package/src/verax/core/release/reproducibility.check.js +221 -0
  106. package/src/verax/core/release/sbom.builder.js +283 -0
  107. package/src/verax/core/report/cross-index.js +192 -0
  108. package/src/verax/core/report/human-summary.js +222 -0
  109. package/src/verax/core/route-intelligence.js +419 -0
  110. package/src/verax/core/security/secrets.scan.js +326 -0
  111. package/src/verax/core/security/security-report.js +50 -0
  112. package/src/verax/core/security/security.enforcer.js +124 -0
  113. package/src/verax/core/security/supplychain.defaults.json +38 -0
  114. package/src/verax/core/security/supplychain.policy.js +326 -0
  115. package/src/verax/core/security/vuln.scan.js +265 -0
  116. package/src/verax/core/truth/truth.certificate.js +250 -0
  117. package/src/verax/core/ui-feedback-intelligence.js +515 -0
  118. package/src/verax/detect/confidence-engine.js +628 -40
  119. package/src/verax/detect/confidence-helper.js +33 -0
  120. package/src/verax/detect/detection-engine.js +18 -1
  121. package/src/verax/detect/dynamic-route-findings.js +335 -0
  122. package/src/verax/detect/expectation-chain-detector.js +417 -0
  123. package/src/verax/detect/expectation-model.js +3 -1
  124. package/src/verax/detect/findings-writer.js +141 -5
  125. package/src/verax/detect/index.js +229 -5
  126. package/src/verax/detect/journey-stall-detector.js +558 -0
  127. package/src/verax/detect/route-findings.js +218 -0
  128. package/src/verax/detect/ui-feedback-findings.js +207 -0
  129. package/src/verax/detect/verdict-engine.js +57 -3
  130. package/src/verax/detect/view-switch-correlator.js +242 -0
  131. package/src/verax/index.js +413 -45
  132. package/src/verax/learn/action-contract-extractor.js +682 -64
  133. package/src/verax/learn/route-validator.js +4 -1
  134. package/src/verax/observe/index.js +88 -843
  135. package/src/verax/observe/interaction-runner.js +25 -8
  136. package/src/verax/observe/observe-context.js +205 -0
  137. package/src/verax/observe/observe-helpers.js +191 -0
  138. package/src/verax/observe/observe-runner.js +226 -0
  139. package/src/verax/observe/observers/budget-observer.js +185 -0
  140. package/src/verax/observe/observers/console-observer.js +102 -0
  141. package/src/verax/observe/observers/coverage-observer.js +107 -0
  142. package/src/verax/observe/observers/interaction-observer.js +471 -0
  143. package/src/verax/observe/observers/navigation-observer.js +132 -0
  144. package/src/verax/observe/observers/network-observer.js +87 -0
  145. package/src/verax/observe/observers/safety-observer.js +82 -0
  146. package/src/verax/observe/observers/ui-feedback-observer.js +99 -0
  147. package/src/verax/observe/ui-feedback-detector.js +742 -0
  148. package/src/verax/observe/ui-signal-sensor.js +148 -2
  149. package/src/verax/scan-summary-writer.js +42 -8
  150. package/src/verax/shared/artifact-manager.js +8 -5
  151. package/src/verax/shared/css-spinner-rules.js +204 -0
  152. package/src/verax/shared/view-switch-rules.js +208 -0
@@ -0,0 +1,210 @@
1
+ /**
2
+ * PHASE 21.4 — Guardrails Policy Defaults
3
+ *
4
+ * Default guardrails policy extracted from hardcoded rules.
5
+ * All rules are mandatory and cannot be disabled.
6
+ */
7
+
8
+ /**
9
+ * PHASE 17: Guardrails Rule Codes
10
+ */
11
+ export const GUARDRAILS_RULE = {
12
+ NET_SUCCESS_NO_UI: 'GUARD_NET_SUCCESS_NO_UI',
13
+ ANALYTICS_ONLY: 'GUARD_ANALYTICS_ONLY',
14
+ SHALLOW_ROUTING: 'GUARD_SHALLOW_ROUTING',
15
+ UI_FEEDBACK_PRESENT: 'GUARD_UI_FEEDBACK_PRESENT',
16
+ INTERACTION_BLOCKED: 'GUARD_INTERACTION_BLOCKED',
17
+ VALIDATION_PRESENT: 'GUARD_VALIDATION_PRESENT',
18
+ CONTRADICT_EVIDENCE: 'GUARD_CONTRADICT_EVIDENCE',
19
+ VIEW_SWITCH_MINOR_CHANGE: 'GUARD_VIEW_SWITCH_MINOR_CHANGE',
20
+ VIEW_SWITCH_ANALYTICS_ONLY: 'GUARD_VIEW_SWITCH_ANALYTICS_ONLY',
21
+ VIEW_SWITCH_AMBIGUOUS: 'GUARD_VIEW_SWITCH_AMBIGUOUS',
22
+ };
23
+
24
+ /**
25
+ * Default guardrails policy
26
+ *
27
+ * This policy matches the current hardcoded behavior exactly.
28
+ */
29
+ export const DEFAULT_GUARDRAILS_POLICY = {
30
+ version: '21.4.0',
31
+ source: 'default',
32
+ rules: [
33
+ {
34
+ id: GUARDRAILS_RULE.NET_SUCCESS_NO_UI,
35
+ category: 'network',
36
+ trigger: 'Network request succeeded but no UI change observed',
37
+ action: 'BLOCK',
38
+ confidenceDelta: -0.3,
39
+ appliesTo: ['silent_failure', 'network'],
40
+ mandatory: true,
41
+ evaluation: {
42
+ type: 'network_success_no_ui',
43
+ conditions: {
44
+ networkSuccess: true,
45
+ noUiChange: true,
46
+ noErrors: true,
47
+ isSilentFailure: true,
48
+ isConfirmed: true
49
+ }
50
+ }
51
+ },
52
+ {
53
+ id: GUARDRAILS_RULE.ANALYTICS_ONLY,
54
+ category: 'network',
55
+ trigger: 'Only analytics/beacon requests detected',
56
+ action: 'BLOCK',
57
+ confidenceDelta: -0.5,
58
+ appliesTo: ['network', 'silent_failure'],
59
+ mandatory: true,
60
+ evaluation: {
61
+ type: 'analytics_only',
62
+ conditions: {
63
+ isAnalyticsOnly: true,
64
+ isNetworkFinding: true,
65
+ isConfirmed: true,
66
+ singleRequest: true
67
+ }
68
+ }
69
+ },
70
+ {
71
+ id: GUARDRAILS_RULE.SHALLOW_ROUTING,
72
+ category: 'navigation',
73
+ trigger: 'Hash-only or shallow routing detected',
74
+ action: 'BLOCK',
75
+ confidenceDelta: -0.2,
76
+ appliesTo: ['navigation', 'route'],
77
+ mandatory: true,
78
+ evaluation: {
79
+ type: 'shallow_routing',
80
+ conditions: {
81
+ isHashOnly: true,
82
+ isShallowRouting: true,
83
+ isNavigationFinding: true,
84
+ isConfirmed: true
85
+ }
86
+ }
87
+ },
88
+ {
89
+ id: GUARDRAILS_RULE.UI_FEEDBACK_PRESENT,
90
+ category: 'ui-feedback',
91
+ trigger: 'UI feedback is present, contradicting silent failure claim',
92
+ action: 'BLOCK',
93
+ confidenceDelta: -0.4,
94
+ appliesTo: ['silent_failure', 'feedback_missing'],
95
+ mandatory: true,
96
+ evaluation: {
97
+ type: 'ui_feedback_present',
98
+ conditions: {
99
+ hasFeedback: true,
100
+ isSilentFailure: true,
101
+ isConfirmed: true
102
+ }
103
+ }
104
+ },
105
+ {
106
+ id: GUARDRAILS_RULE.INTERACTION_BLOCKED,
107
+ category: 'state',
108
+ trigger: 'Interaction was disabled/blocked',
109
+ action: 'INFO',
110
+ confidenceDelta: -0.5,
111
+ appliesTo: ['silent_failure'],
112
+ mandatory: true,
113
+ evaluation: {
114
+ type: 'interaction_blocked',
115
+ conditions: {
116
+ isDisabled: true,
117
+ isSilentFailure: true,
118
+ isConfirmed: true
119
+ }
120
+ }
121
+ },
122
+ {
123
+ id: GUARDRAILS_RULE.VALIDATION_PRESENT,
124
+ category: 'validation',
125
+ trigger: 'Validation feedback is present, contradicting validation failure claim',
126
+ action: 'BLOCK',
127
+ confidenceDelta: -0.3,
128
+ appliesTo: ['validation', 'form'],
129
+ mandatory: true,
130
+ evaluation: {
131
+ type: 'validation_present',
132
+ conditions: {
133
+ hasValidationFeedback: true,
134
+ isValidationFailure: true,
135
+ isConfirmed: true
136
+ }
137
+ }
138
+ },
139
+ {
140
+ id: GUARDRAILS_RULE.CONTRADICT_EVIDENCE,
141
+ category: 'state',
142
+ trigger: 'Evidence package is incomplete',
143
+ action: 'BLOCK',
144
+ confidenceDelta: -0.2,
145
+ appliesTo: ['*'], // Applies to all findings
146
+ mandatory: true,
147
+ evaluation: {
148
+ type: 'contradict_evidence',
149
+ conditions: {
150
+ isConfirmed: true,
151
+ evidenceIncomplete: true
152
+ }
153
+ }
154
+ },
155
+ {
156
+ id: GUARDRAILS_RULE.VIEW_SWITCH_MINOR_CHANGE,
157
+ category: 'view-switch',
158
+ trigger: 'URL unchanged and change is minor (e.g. button text only)',
159
+ action: 'BLOCK',
160
+ confidenceDelta: -0.4,
161
+ appliesTo: ['view_switch', 'state_action'],
162
+ mandatory: true,
163
+ evaluation: {
164
+ type: 'view_switch_minor_change',
165
+ conditions: {
166
+ isViewSwitch: true,
167
+ urlUnchanged: true,
168
+ isMinorChange: true,
169
+ isConfirmed: true
170
+ }
171
+ }
172
+ },
173
+ {
174
+ id: GUARDRAILS_RULE.VIEW_SWITCH_ANALYTICS_ONLY,
175
+ category: 'view-switch',
176
+ trigger: 'Only analytics fired, no UI change',
177
+ action: 'BLOCK',
178
+ confidenceDelta: -0.5,
179
+ appliesTo: ['view_switch', 'state_action'],
180
+ mandatory: true,
181
+ evaluation: {
182
+ type: 'view_switch_analytics_only',
183
+ conditions: {
184
+ isViewSwitch: true,
185
+ analyticsOnly: true,
186
+ isConfirmed: true
187
+ }
188
+ }
189
+ },
190
+ {
191
+ id: GUARDRAILS_RULE.VIEW_SWITCH_AMBIGUOUS,
192
+ category: 'view-switch',
193
+ trigger: 'State change promise exists but UI outcome ambiguous (one signal only)',
194
+ action: 'DOWNGRADE',
195
+ confidenceDelta: -0.3,
196
+ appliesTo: ['view_switch', 'state_action'],
197
+ mandatory: true,
198
+ evaluation: {
199
+ type: 'view_switch_ambiguous',
200
+ conditions: {
201
+ isViewSwitch: true,
202
+ hasPromise: true,
203
+ ambiguousOutcome: true,
204
+ isConfirmed: true
205
+ }
206
+ }
207
+ }
208
+ ]
209
+ };
210
+
@@ -0,0 +1,83 @@
1
+ /**
2
+ * PHASE 21.4 — Guardrails Policy Loader
3
+ *
4
+ * Loads guardrails policies from files or uses defaults.
5
+ * Validates policies and ensures all rules are mandatory.
6
+ */
7
+
8
+ import { readFileSync, existsSync } from 'fs';
9
+ import { resolve } from 'path';
10
+ import { validateGuardrailsPolicy } from './policy.schema.js';
11
+ import { DEFAULT_GUARDRAILS_POLICY } from './policy.defaults.js';
12
+
13
+ /**
14
+ * Load guardrails policy
15
+ *
16
+ * @param {string|null} policyPath - Path to custom policy file (optional)
17
+ * @param {string} projectDir - Project directory
18
+ * @returns {Object} Guardrails policy
19
+ * @throws {Error} If policy is invalid
20
+ */
21
+ export function loadGuardrailsPolicy(policyPath = null, projectDir = null) {
22
+ // If no custom policy path, use defaults
23
+ if (!policyPath) {
24
+ return DEFAULT_GUARDRAILS_POLICY;
25
+ }
26
+
27
+ // Resolve policy path
28
+ const resolvedPath = projectDir ? resolve(projectDir, policyPath) : resolve(policyPath);
29
+
30
+ // Check if file exists
31
+ if (!existsSync(resolvedPath)) {
32
+ throw new Error(`Guardrails policy file not found: ${resolvedPath}`);
33
+ }
34
+
35
+ // Read and parse policy
36
+ let policy;
37
+ try {
38
+ const policyContent = readFileSync(resolvedPath, 'utf-8');
39
+ policy = JSON.parse(policyContent);
40
+ } catch (error) {
41
+ throw new Error(`Failed to load guardrails policy: ${error.message}`);
42
+ }
43
+
44
+ // Validate policy
45
+ try {
46
+ validateGuardrailsPolicy(policy);
47
+ } catch (error) {
48
+ throw new Error(`Invalid guardrails policy: ${error.message}`);
49
+ }
50
+
51
+ // Mark as custom
52
+ policy.source = 'custom';
53
+
54
+ // HARD LOCK: Ensure all rules are mandatory
55
+ for (const rule of policy.rules) {
56
+ if (rule.mandatory !== true) {
57
+ throw new Error(`Guardrails rule ${rule.id} cannot be disabled (mandatory must be true)`);
58
+ }
59
+ }
60
+
61
+ return policy;
62
+ }
63
+
64
+ /**
65
+ * Get policy report metadata
66
+ *
67
+ * @param {Object} policy - Guardrails policy
68
+ * @returns {Object} Policy report metadata
69
+ */
70
+ export function getPolicyReport(policy) {
71
+ return {
72
+ version: policy.version,
73
+ source: policy.source,
74
+ ruleCount: policy.rules.length,
75
+ rules: policy.rules.map(rule => ({
76
+ id: rule.id,
77
+ category: rule.category,
78
+ action: rule.action,
79
+ mandatory: rule.mandatory
80
+ }))
81
+ };
82
+ }
83
+
@@ -0,0 +1,110 @@
1
+ /**
2
+ * PHASE 21.4 — Guardrails Policy Schema
3
+ *
4
+ * Defines the structure for guardrails policies.
5
+ * All rules are mandatory and cannot be disabled.
6
+ */
7
+
8
+ /**
9
+ * Guardrails Policy Schema
10
+ *
11
+ * @typedef {Object} GuardrailsPolicy
12
+ * @property {string} version - Policy version
13
+ * @property {string} source - 'default' | 'custom'
14
+ * @property {Array<GuardrailsRule>} rules - Array of guardrails rules
15
+ */
16
+
17
+ /**
18
+ * Guardrails Rule
19
+ *
20
+ * @typedef {Object} GuardrailsRule
21
+ * @property {string} id - Stable rule identifier
22
+ * @property {string} category - Rule category (network, navigation, ui-feedback, validation, state)
23
+ * @property {string} trigger - Trigger condition description
24
+ * @property {string} action - Action type (BLOCK / DOWNGRADE / INFO)
25
+ * @property {number} confidenceDelta - Confidence adjustment (must be ≤ 0)
26
+ * @property {Array<string>} appliesTo - Capabilities list this rule applies to
27
+ * @property {boolean} mandatory - Always true (cannot be disabled)
28
+ * @property {Object} evaluation - Evaluation function parameters
29
+ */
30
+
31
+ /**
32
+ * Validate guardrails policy
33
+ *
34
+ * @param {Object} policy - Policy to validate
35
+ * @throws {Error} If policy is invalid
36
+ */
37
+ export function validateGuardrailsPolicy(policy) {
38
+ if (!policy) {
39
+ throw new Error('Guardrails policy is required');
40
+ }
41
+
42
+ if (!policy.version || typeof policy.version !== 'string') {
43
+ throw new Error('Guardrails policy must have a version string');
44
+ }
45
+
46
+ if (!policy.rules || !Array.isArray(policy.rules)) {
47
+ throw new Error('Guardrails policy must have a rules array');
48
+ }
49
+
50
+ for (const rule of policy.rules) {
51
+ validateGuardrailsRule(rule);
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Validate a guardrails rule
57
+ *
58
+ * @param {Object} rule - Rule to validate
59
+ * @throws {Error} If rule is invalid
60
+ */
61
+ export function validateGuardrailsRule(rule) {
62
+ if (!rule.id || typeof rule.id !== 'string') {
63
+ throw new Error('Guardrails rule must have a stable id string');
64
+ }
65
+
66
+ if (!rule.category || typeof rule.category !== 'string') {
67
+ throw new Error('Guardrails rule must have a category string');
68
+ }
69
+
70
+ const validCategories = ['network', 'navigation', 'ui-feedback', 'validation', 'state'];
71
+ if (!validCategories.includes(rule.category)) {
72
+ throw new Error(`Guardrails rule category must be one of: ${validCategories.join(', ')}`);
73
+ }
74
+
75
+ if (!rule.action || typeof rule.action !== 'string') {
76
+ throw new Error('Guardrails rule must have an action string');
77
+ }
78
+
79
+ const validActions = ['BLOCK', 'DOWNGRADE', 'INFO'];
80
+ if (!validActions.includes(rule.action)) {
81
+ throw new Error(`Guardrails rule action must be one of: ${validActions.join(', ')}`);
82
+ }
83
+
84
+ if (typeof rule.confidenceDelta !== 'number') {
85
+ throw new Error('Guardrails rule must have a confidenceDelta number');
86
+ }
87
+
88
+ // HARD LOCK: Confidence delta must be ≤ 0 (can only decrease confidence)
89
+ if (rule.confidenceDelta > 0) {
90
+ throw new Error('Guardrails rule confidenceDelta must be ≤ 0 (cannot increase confidence)');
91
+ }
92
+
93
+ if (!Array.isArray(rule.appliesTo)) {
94
+ throw new Error('Guardrails rule appliesTo must be an array');
95
+ }
96
+
97
+ // HARD LOCK: All rules are mandatory
98
+ if (rule.mandatory !== true) {
99
+ throw new Error('Guardrails rule must have mandatory: true (cannot be disabled)');
100
+ }
101
+
102
+ if (!rule.trigger || typeof rule.trigger !== 'string') {
103
+ throw new Error('Guardrails rule must have a trigger description');
104
+ }
105
+
106
+ if (!rule.evaluation || typeof rule.evaluation !== 'object') {
107
+ throw new Error('Guardrails rule must have an evaluation object');
108
+ }
109
+ }
110
+
@@ -0,0 +1,136 @@
1
+ /**
2
+ * PHASE 23 — Guardrails Truth Reconciliation
3
+ *
4
+ * Reconciles confidence with guardrails outcome to ensure consistency.
5
+ * Guardrails outcome is the authoritative "final claim boundary".
6
+ */
7
+
8
+ import { CONFIDENCE_LEVEL } from '../confidence-engine.js';
9
+
10
+ /**
11
+ * Reconciliation reason codes
12
+ */
13
+ export const RECONCILIATION_REASON = {
14
+ GUARDRAILS_DOWNGRADE_CONFIRMED: 'RECON_GUARDRAILS_DOWNGRADE_CONFIRMED',
15
+ GUARDRAILS_DOWNGRADE_SUSPECTED: 'RECON_GUARDRAILS_DOWNGRADE_SUSPECTED',
16
+ GUARDRAILS_INFORMATIONAL: 'RECON_GUARDRAILS_INFORMATIONAL',
17
+ GUARDRAILS_IGNORED: 'RECON_GUARDRAILS_IGNORED',
18
+ CONFIDENCE_CAP_GUARDRAILS_DOWNGRADE: 'RECON_CONF_CAP_GUARDRAILS_DOWNGRADE',
19
+ CONFIDENCE_CAP_INFORMATIONAL: 'RECON_CONF_CAP_INFORMATIONAL',
20
+ CONFIDENCE_CAP_IGNORED: 'RECON_CONF_CAP_IGNORED',
21
+ NO_RECONCILIATION_NEEDED: 'RECON_NO_RECONCILIATION_NEEDED',
22
+ };
23
+
24
+ /**
25
+ * Finalize finding truth by reconciling confidence with guardrails outcome.
26
+ *
27
+ * @param {Object} finding - Finding object (may have been modified by guardrails)
28
+ * @param {Object} guardrailsResult - Result from applyGuardrails
29
+ * @param {Object} context - Context { initialConfidence, initialConfidenceLevel }
30
+ * @returns {Object} { finalFinding, truthDecision }
31
+ */
32
+ export function finalizeFindingTruth(finding, guardrailsResult, context = {}) {
33
+ const guardrails = guardrailsResult.guardrails || finding.guardrails || {};
34
+ const finalDecision = guardrails.finalDecision || guardrails.recommendedStatus || finding.severity || 'SUSPECTED';
35
+
36
+ // Capture initial confidence before guardrails
37
+ const confidenceBefore = context.initialConfidence !== undefined
38
+ ? context.initialConfidence
39
+ : (finding.confidence || 0);
40
+ const confidenceLevelBefore = context.initialConfidenceLevel
41
+ || finding.confidenceLevel
42
+ || (confidenceBefore >= 0.8 ? 'HIGH' : confidenceBefore >= 0.5 ? 'MEDIUM' : confidenceBefore >= 0.2 ? 'LOW' : 'UNPROVEN');
43
+
44
+ // Start with guardrails-adjusted confidence
45
+ let confidenceAfter = finding.confidence !== undefined ? finding.confidence : confidenceBefore;
46
+ let confidenceLevelAfter = finding.confidenceLevel || confidenceLevelBefore;
47
+
48
+ const reconciliationReasons = [];
49
+ const contradictionsResolved = [];
50
+
51
+ // Enforce truth boundaries based on final decision
52
+ if (finalDecision === 'CONFIRMED') {
53
+ // CONFIRMED must have guardrails finalDecision == CONFIRMED
54
+ if (guardrails.finalDecision !== 'CONFIRMED' && guardrails.recommendedStatus !== 'CONFIRMED') {
55
+ // This should not happen if guardrails are applied correctly, but enforce it
56
+ contradictionsResolved.push({
57
+ code: 'CONTRADICTION_CONFIRMED_WITHOUT_GUARDRAILS',
58
+ message: 'Finding marked CONFIRMED but guardrails did not approve CONFIRMED status'
59
+ });
60
+ }
61
+ // CONFIRMED can have any confidence level (no cap)
62
+ } else if (finalDecision === 'SUSPECTED') {
63
+ // SUSPECTED due to guardrails downgrade -> cap confidence at 0.69 (MEDIUM)
64
+ const wasDowngraded = guardrails.contradictions && guardrails.contradictions.length > 0;
65
+ const hasEvidenceIntentFailure = guardrails.appliedRules?.some(r =>
66
+ r.code?.includes('EVIDENCE') || r.message?.includes('evidence')
67
+ );
68
+
69
+ if (wasDowngraded || hasEvidenceIntentFailure) {
70
+ if (confidenceAfter > 0.69) {
71
+ confidenceAfter = 0.69;
72
+ confidenceLevelAfter = 'MEDIUM';
73
+ reconciliationReasons.push(RECONCILIATION_REASON.CONFIDENCE_CAP_GUARDRAILS_DOWNGRADE);
74
+ }
75
+ reconciliationReasons.push(RECONCILIATION_REASON.GUARDRAILS_DOWNGRADE_SUSPECTED);
76
+ }
77
+ } else if (finalDecision === 'INFORMATIONAL') {
78
+ // INFORMATIONAL -> confidence must be LOW or UNPROVEN
79
+ if (confidenceAfter > 0.2) {
80
+ confidenceAfter = 0.2;
81
+ confidenceLevelAfter = 'LOW';
82
+ reconciliationReasons.push(RECONCILIATION_REASON.CONFIDENCE_CAP_INFORMATIONAL);
83
+ }
84
+ reconciliationReasons.push(RECONCILIATION_REASON.GUARDRAILS_INFORMATIONAL);
85
+ } else if (finalDecision === 'IGNORED') {
86
+ // IGNORED -> confidence must be UNPROVEN
87
+ confidenceAfter = 0;
88
+ confidenceLevelAfter = 'UNPROVEN';
89
+ reconciliationReasons.push(RECONCILIATION_REASON.CONFIDENCE_CAP_IGNORED);
90
+ reconciliationReasons.push(RECONCILIATION_REASON.GUARDRAILS_IGNORED);
91
+ }
92
+
93
+ // If no reconciliation was needed, record it
94
+ if (reconciliationReasons.length === 0) {
95
+ reconciliationReasons.push(RECONCILIATION_REASON.NO_RECONCILIATION_NEEDED);
96
+ }
97
+
98
+ // Build final finding with reconciled confidence
99
+ const finalFinding = {
100
+ ...finding,
101
+ severity: finalDecision,
102
+ status: finalDecision,
103
+ confidence: confidenceAfter,
104
+ confidenceLevel: confidenceLevelAfter,
105
+ guardrails: {
106
+ ...guardrails,
107
+ reconciliation: {
108
+ confidenceBefore,
109
+ confidenceAfter,
110
+ confidenceLevelBefore,
111
+ confidenceLevelAfter,
112
+ reconciliationReasons,
113
+ contradictionsResolved
114
+ }
115
+ }
116
+ };
117
+
118
+ // Build truth decision
119
+ const truthDecision = {
120
+ finalStatus: finalDecision,
121
+ appliedGuardrails: guardrails.appliedRules?.map(r => r.code) || [],
122
+ confidenceBefore,
123
+ confidenceAfter,
124
+ confidenceLevelBefore,
125
+ confidenceLevelAfter,
126
+ reconciliationReasons,
127
+ contradictionsResolved,
128
+ confidenceDelta: confidenceAfter - confidenceBefore
129
+ };
130
+
131
+ return {
132
+ finalFinding,
133
+ truthDecision
134
+ };
135
+ }
136
+