@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.
- package/README.md +10 -6
- package/bin/verax.js +11 -11
- package/package.json +29 -8
- package/src/cli/commands/baseline.js +103 -0
- package/src/cli/commands/default.js +51 -6
- package/src/cli/commands/doctor.js +29 -0
- package/src/cli/commands/ga.js +246 -0
- package/src/cli/commands/gates.js +95 -0
- package/src/cli/commands/inspect.js +4 -2
- package/src/cli/commands/release-check.js +215 -0
- package/src/cli/commands/run.js +45 -6
- package/src/cli/commands/security-check.js +212 -0
- package/src/cli/commands/truth.js +113 -0
- package/src/cli/entry.js +30 -20
- package/src/cli/util/angular-component-extractor.js +179 -0
- package/src/cli/util/angular-navigation-detector.js +141 -0
- package/src/cli/util/angular-network-detector.js +161 -0
- package/src/cli/util/angular-state-detector.js +162 -0
- package/src/cli/util/ast-interactive-detector.js +544 -0
- package/src/cli/util/ast-network-detector.js +603 -0
- package/src/cli/util/ast-promise-extractor.js +581 -0
- package/src/cli/util/ast-usestate-detector.js +602 -0
- package/src/cli/util/atomic-write.js +12 -1
- package/src/cli/util/bootstrap-guard.js +86 -0
- package/src/cli/util/console-reporter.js +72 -0
- package/src/cli/util/detection-engine.js +105 -41
- package/src/cli/util/determinism-runner.js +124 -0
- package/src/cli/util/determinism-writer.js +129 -0
- package/src/cli/util/digest-engine.js +359 -0
- package/src/cli/util/dom-diff.js +226 -0
- package/src/cli/util/evidence-engine.js +287 -0
- package/src/cli/util/expectation-extractor.js +151 -5
- package/src/cli/util/findings-writer.js +3 -0
- package/src/cli/util/framework-detector.js +572 -0
- package/src/cli/util/idgen.js +1 -1
- package/src/cli/util/interaction-planner.js +529 -0
- package/src/cli/util/learn-writer.js +2 -0
- package/src/cli/util/ledger-writer.js +110 -0
- package/src/cli/util/monorepo-resolver.js +162 -0
- package/src/cli/util/observation-engine.js +127 -278
- package/src/cli/util/observe-writer.js +2 -0
- package/src/cli/util/project-discovery.js +284 -0
- package/src/cli/util/project-writer.js +2 -0
- package/src/cli/util/run-id.js +23 -27
- package/src/cli/util/run-resolver.js +64 -0
- package/src/cli/util/run-result.js +778 -0
- package/src/cli/util/selector-resolver.js +235 -0
- package/src/cli/util/source-requirement.js +55 -0
- package/src/cli/util/summary-writer.js +2 -0
- package/src/cli/util/svelte-navigation-detector.js +163 -0
- package/src/cli/util/svelte-network-detector.js +80 -0
- package/src/cli/util/svelte-sfc-extractor.js +146 -0
- package/src/cli/util/svelte-state-detector.js +242 -0
- package/src/cli/util/trust-activation-integration.js +496 -0
- package/src/cli/util/trust-activation-wrapper.js +85 -0
- package/src/cli/util/trust-integration-hooks.js +164 -0
- package/src/cli/util/types.js +153 -0
- package/src/cli/util/url-validation.js +40 -0
- package/src/cli/util/vue-navigation-detector.js +178 -0
- package/src/cli/util/vue-sfc-extractor.js +161 -0
- package/src/cli/util/vue-state-detector.js +215 -0
- package/src/types/fs-augment.d.ts +23 -0
- package/src/types/global.d.ts +137 -0
- package/src/types/internal-types.d.ts +35 -0
- package/src/verax/cli/init.js +4 -18
- package/src/verax/core/action-classifier.js +4 -3
- package/src/verax/core/artifacts/registry.js +139 -0
- package/src/verax/core/artifacts/verifier.js +990 -0
- package/src/verax/core/baseline/baseline.enforcer.js +137 -0
- package/src/verax/core/baseline/baseline.snapshot.js +233 -0
- package/src/verax/core/capabilities/gates.js +505 -0
- package/src/verax/core/capabilities/registry.js +475 -0
- package/src/verax/core/confidence/confidence-compute.js +144 -0
- package/src/verax/core/confidence/confidence-invariants.js +234 -0
- package/src/verax/core/confidence/confidence-report-writer.js +112 -0
- package/src/verax/core/confidence/confidence-weights.js +44 -0
- package/src/verax/core/confidence/confidence.defaults.js +65 -0
- package/src/verax/core/confidence/confidence.loader.js +80 -0
- package/src/verax/core/confidence/confidence.schema.js +94 -0
- package/src/verax/core/confidence-engine-refactor.js +489 -0
- package/src/verax/core/confidence-engine.js +625 -0
- package/src/verax/core/contracts/index.js +29 -0
- package/src/verax/core/contracts/types.js +186 -0
- package/src/verax/core/contracts/validators.js +456 -0
- package/src/verax/core/decisions/decision.trace.js +278 -0
- package/src/verax/core/determinism/contract-writer.js +89 -0
- package/src/verax/core/determinism/contract.js +139 -0
- package/src/verax/core/determinism/diff.js +405 -0
- package/src/verax/core/determinism/engine.js +222 -0
- package/src/verax/core/determinism/finding-identity.js +149 -0
- package/src/verax/core/determinism/normalize.js +466 -0
- package/src/verax/core/determinism/report-writer.js +93 -0
- package/src/verax/core/determinism/run-fingerprint.js +123 -0
- package/src/verax/core/dynamic-route-intelligence.js +529 -0
- package/src/verax/core/evidence/evidence-capture-service.js +308 -0
- package/src/verax/core/evidence/evidence-intent-ledger.js +166 -0
- package/src/verax/core/evidence-builder.js +487 -0
- package/src/verax/core/execution-mode-context.js +77 -0
- package/src/verax/core/execution-mode-detector.js +192 -0
- package/src/verax/core/failures/exit-codes.js +88 -0
- package/src/verax/core/failures/failure-summary.js +76 -0
- package/src/verax/core/failures/failure.factory.js +225 -0
- package/src/verax/core/failures/failure.ledger.js +133 -0
- package/src/verax/core/failures/failure.types.js +196 -0
- package/src/verax/core/failures/index.js +10 -0
- package/src/verax/core/ga/ga-report-writer.js +43 -0
- package/src/verax/core/ga/ga.artifact.js +49 -0
- package/src/verax/core/ga/ga.contract.js +435 -0
- package/src/verax/core/ga/ga.enforcer.js +87 -0
- package/src/verax/core/guardrails/guardrails-report-writer.js +109 -0
- package/src/verax/core/guardrails/policy.defaults.js +210 -0
- package/src/verax/core/guardrails/policy.loader.js +84 -0
- package/src/verax/core/guardrails/policy.schema.js +110 -0
- package/src/verax/core/guardrails/truth-reconciliation.js +136 -0
- package/src/verax/core/guardrails-engine.js +505 -0
- package/src/verax/core/incremental-store.js +1 -0
- package/src/verax/core/integrity/budget.js +138 -0
- package/src/verax/core/integrity/determinism.js +342 -0
- package/src/verax/core/integrity/integrity.js +208 -0
- package/src/verax/core/integrity/poisoning.js +108 -0
- package/src/verax/core/integrity/transaction.js +140 -0
- package/src/verax/core/observe/run-timeline.js +318 -0
- package/src/verax/core/perf/perf.contract.js +186 -0
- package/src/verax/core/perf/perf.display.js +65 -0
- package/src/verax/core/perf/perf.enforcer.js +91 -0
- package/src/verax/core/perf/perf.monitor.js +209 -0
- package/src/verax/core/perf/perf.report.js +200 -0
- package/src/verax/core/pipeline-tracker.js +243 -0
- package/src/verax/core/product-definition.js +127 -0
- package/src/verax/core/release/provenance.builder.js +130 -0
- package/src/verax/core/release/release-report-writer.js +40 -0
- package/src/verax/core/release/release.enforcer.js +164 -0
- package/src/verax/core/release/reproducibility.check.js +222 -0
- package/src/verax/core/release/sbom.builder.js +292 -0
- package/src/verax/core/replay-validator.js +2 -0
- package/src/verax/core/replay.js +4 -0
- package/src/verax/core/report/cross-index.js +195 -0
- package/src/verax/core/report/human-summary.js +362 -0
- package/src/verax/core/route-intelligence.js +420 -0
- package/src/verax/core/run-id.js +6 -3
- package/src/verax/core/run-manifest.js +4 -3
- package/src/verax/core/security/secrets.scan.js +329 -0
- package/src/verax/core/security/security-report.js +50 -0
- package/src/verax/core/security/security.enforcer.js +128 -0
- package/src/verax/core/security/supplychain.defaults.json +38 -0
- package/src/verax/core/security/supplychain.policy.js +334 -0
- package/src/verax/core/security/vuln.scan.js +265 -0
- package/src/verax/core/truth/truth.certificate.js +252 -0
- package/src/verax/core/ui-feedback-intelligence.js +481 -0
- package/src/verax/detect/conditional-ui-silent-failure.js +84 -0
- package/src/verax/detect/confidence-engine.js +62 -34
- package/src/verax/detect/confidence-helper.js +34 -0
- package/src/verax/detect/dynamic-route-findings.js +338 -0
- package/src/verax/detect/expectation-chain-detector.js +417 -0
- package/src/verax/detect/expectation-model.js +2 -2
- package/src/verax/detect/failure-cause-inference.js +293 -0
- package/src/verax/detect/findings-writer.js +131 -35
- package/src/verax/detect/flow-detector.js +2 -2
- package/src/verax/detect/form-silent-failure.js +98 -0
- package/src/verax/detect/index.js +46 -5
- package/src/verax/detect/invariants-enforcer.js +147 -0
- package/src/verax/detect/journey-stall-detector.js +558 -0
- package/src/verax/detect/navigation-silent-failure.js +82 -0
- package/src/verax/detect/problem-aggregator.js +361 -0
- package/src/verax/detect/route-findings.js +219 -0
- package/src/verax/detect/summary-writer.js +477 -0
- package/src/verax/detect/test-failure-cause-inference.js +314 -0
- package/src/verax/detect/ui-feedback-findings.js +207 -0
- package/src/verax/detect/view-switch-correlator.js +242 -0
- package/src/verax/flow/flow-engine.js +2 -1
- package/src/verax/flow/flow-spec.js +0 -6
- package/src/verax/index.js +4 -0
- package/src/verax/intel/ts-program.js +1 -0
- package/src/verax/intel/vue-navigation-extractor.js +3 -0
- package/src/verax/learn/action-contract-extractor.js +3 -0
- package/src/verax/learn/ast-contract-extractor.js +1 -1
- package/src/verax/learn/flow-extractor.js +1 -0
- package/src/verax/learn/project-detector.js +5 -0
- package/src/verax/learn/react-router-extractor.js +2 -0
- package/src/verax/learn/source-instrumenter.js +1 -0
- package/src/verax/learn/state-extractor.js +2 -1
- package/src/verax/learn/static-extractor.js +1 -0
- package/src/verax/observe/coverage-gaps.js +132 -0
- package/src/verax/observe/expectation-handler.js +126 -0
- package/src/verax/observe/incremental-skip.js +46 -0
- package/src/verax/observe/index.js +51 -155
- package/src/verax/observe/interaction-executor.js +192 -0
- package/src/verax/observe/interaction-runner.js +782 -513
- package/src/verax/observe/network-firewall.js +86 -0
- package/src/verax/observe/observation-builder.js +169 -0
- package/src/verax/observe/observe-context.js +205 -0
- package/src/verax/observe/observe-helpers.js +192 -0
- package/src/verax/observe/observe-runner.js +230 -0
- package/src/verax/observe/observers/budget-observer.js +185 -0
- package/src/verax/observe/observers/console-observer.js +102 -0
- package/src/verax/observe/observers/coverage-observer.js +107 -0
- package/src/verax/observe/observers/interaction-observer.js +471 -0
- package/src/verax/observe/observers/navigation-observer.js +132 -0
- package/src/verax/observe/observers/network-observer.js +87 -0
- package/src/verax/observe/observers/safety-observer.js +82 -0
- package/src/verax/observe/observers/ui-feedback-observer.js +99 -0
- package/src/verax/observe/page-traversal.js +138 -0
- package/src/verax/observe/snapshot-ops.js +94 -0
- package/src/verax/observe/ui-feedback-detector.js +742 -0
- package/src/verax/scan-summary-writer.js +2 -0
- package/src/verax/shared/artifact-manager.js +25 -5
- package/src/verax/shared/caching.js +1 -0
- package/src/verax/shared/css-spinner-rules.js +204 -0
- package/src/verax/shared/expectation-tracker.js +1 -0
- package/src/verax/shared/view-switch-rules.js +208 -0
- package/src/verax/shared/zip-artifacts.js +6 -0
- package/src/verax/shared/config-loader.js +0 -169
- /package/src/verax/shared/{expectation-proof.js → expectation-validation.js} +0 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VERAX Core Contracts - Canonical Type Definitions
|
|
3
|
+
*
|
|
4
|
+
* This module defines the single source of truth for all core VERAX types:
|
|
5
|
+
* Finding, Evidence, Observation, Confidence, and associated enums.
|
|
6
|
+
*
|
|
7
|
+
* These definitions enforce strict runtime contracts and form the foundation
|
|
8
|
+
* for the Evidence Law: findings without sufficient evidence cannot be Confirmed.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Confidence Levels - measure of certainty in a finding
|
|
13
|
+
*/
|
|
14
|
+
export const CONFIDENCE_LEVEL = {
|
|
15
|
+
HIGH: 'HIGH',
|
|
16
|
+
MEDIUM: 'MEDIUM',
|
|
17
|
+
LOW: 'LOW',
|
|
18
|
+
UNPROVEN: 'UNPROVEN'
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Finding Status - the evaluation outcome
|
|
23
|
+
*/
|
|
24
|
+
export const FINDING_STATUS = {
|
|
25
|
+
CONFIRMED: 'CONFIRMED', // Evidence law satisfied: sufficient evidence exists
|
|
26
|
+
SUSPECTED: 'SUSPECTED', // Needs evidence: signal observed but evidence incomplete
|
|
27
|
+
INFORMATIONAL: 'INFORMATIONAL', // Observation recorded, no claim of failure
|
|
28
|
+
UNPROVEN: 'UNPROVEN', // Evidence Law v1: insufficient evidence to support claim
|
|
29
|
+
DROPPED: 'DROPPED' // Violated contracts, removed from report
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Finding Type - category of silent failure
|
|
34
|
+
*/
|
|
35
|
+
export const FINDING_TYPE = {
|
|
36
|
+
NAVIGATION_SILENT_FAILURE: 'navigation_silent_failure',
|
|
37
|
+
NETWORK_SILENT_FAILURE: 'network_silent_failure',
|
|
38
|
+
STATE_SILENT_FAILURE: 'state_silent_failure',
|
|
39
|
+
OBSERVED_BREAK: 'observed_break',
|
|
40
|
+
SILENT_FAILURE: 'silent_failure',
|
|
41
|
+
FLOW_SILENT_FAILURE: 'flow_silent_failure'
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Impact Level - severity of the silence
|
|
46
|
+
*/
|
|
47
|
+
export const IMPACT = {
|
|
48
|
+
HIGH: 'HIGH',
|
|
49
|
+
MEDIUM: 'MEDIUM',
|
|
50
|
+
LOW: 'LOW'
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* User Risk - how the silence affects the user
|
|
55
|
+
*/
|
|
56
|
+
export const USER_RISK = {
|
|
57
|
+
BLOCKS: 'BLOCKS', // User action is blocked from completion
|
|
58
|
+
CONFUSES: 'CONFUSES', // User is confused about what happened
|
|
59
|
+
DEGRADES: 'DEGRADES' // User experience is degraded
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Ownership - which layer failed
|
|
64
|
+
*/
|
|
65
|
+
export const OWNERSHIP = {
|
|
66
|
+
FRONTEND: 'FRONTEND',
|
|
67
|
+
BACKEND: 'BACKEND',
|
|
68
|
+
INTEGRATION: 'INTEGRATION',
|
|
69
|
+
ACCESSIBILITY: 'ACCESSIBILITY',
|
|
70
|
+
PERFORMANCE: 'PERFORMANCE'
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Evidence Type - what provides proof
|
|
75
|
+
*/
|
|
76
|
+
export const EVIDENCE_TYPE = {
|
|
77
|
+
NETWORK_ACTIVITY: 'network_activity',
|
|
78
|
+
DOM_CHANGE: 'dom_change',
|
|
79
|
+
STATE_CHANGE: 'state_change',
|
|
80
|
+
URL_CHANGE: 'url_change',
|
|
81
|
+
SCREENSHOT: 'screenshot',
|
|
82
|
+
CONSOLE_OUTPUT: 'console_output',
|
|
83
|
+
SENSOR_DATA: 'sensor_data'
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Canonical Finding Interface
|
|
88
|
+
*
|
|
89
|
+
* @typedef {Object} Finding
|
|
90
|
+
* @property {string} type - One of FINDING_TYPE
|
|
91
|
+
* @property {string} [status] - One of FINDING_STATUS (defaults to SUSPECTED if evidence insufficient)
|
|
92
|
+
* @property {Object} interaction - The user interaction that triggered analysis
|
|
93
|
+
* @property {Object} evidence - Proof of the gap (REQUIRED for CONFIRMED status)
|
|
94
|
+
* @property {Object} confidence - Certainty assessment
|
|
95
|
+
* @property {Object} signals - Impact classification (impact, userRisk, ownership, grouping)
|
|
96
|
+
* @property {string} what_happened - Factual description of what occurred
|
|
97
|
+
* @property {string} what_was_expected - Factual description of code promise
|
|
98
|
+
* @property {string} what_was_observed - Factual description of observed outcome
|
|
99
|
+
* @property {string} why_it_matters - Human explanation of the gap
|
|
100
|
+
* @property {string} [humanSummary] - Human-readable summary
|
|
101
|
+
* @property {string} [actionHint] - Recommended next step
|
|
102
|
+
* @property {Object} [promise] - Promise descriptor from source code
|
|
103
|
+
* @property {string} [id] - Optional unique identifier
|
|
104
|
+
* @property {string} [findingId] - Optional deterministic ID based on expectation
|
|
105
|
+
* @property {string} [expectationId] - Optional reference to matched expectation
|
|
106
|
+
*/
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Canonical Evidence Interface
|
|
110
|
+
*
|
|
111
|
+
* @typedef {Object} Evidence
|
|
112
|
+
* @property {string} [type] - One of EVIDENCE_TYPE
|
|
113
|
+
* @property {boolean} [hasDomChange] - Whether DOM structure changed
|
|
114
|
+
* @property {boolean} [hasUrlChange] - Whether URL changed
|
|
115
|
+
* @property {boolean} [hasNetworkActivity] - Whether network requests occurred
|
|
116
|
+
* @property {boolean} [hasStateChange] - Whether application state changed
|
|
117
|
+
* @property {string} [beforeUrl] - URL before interaction
|
|
118
|
+
* @property {string} [afterUrl] - URL after interaction
|
|
119
|
+
* @property {string} [before] - Before state (screenshot path or data)
|
|
120
|
+
* @property {string} [after] - After state (screenshot path or data)
|
|
121
|
+
* @property {Object} [beforeDom] - DOM structure before
|
|
122
|
+
* @property {Object} [afterDom] - DOM structure after
|
|
123
|
+
* @property {Array} [networkRequests] - Network activity captured
|
|
124
|
+
* @property {Array} [consoleLogs] - Console messages
|
|
125
|
+
* @property {Object} [sensors] - Sensor data (navigation, uiSignals, etc.)
|
|
126
|
+
* @property {string} [source] - Source file reference
|
|
127
|
+
* @property {string} [expectedTarget] - Expected target for navigation/network
|
|
128
|
+
* @property {boolean} [targetReached] - Whether expected target was reached
|
|
129
|
+
*/
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Canonical Confidence Interface
|
|
133
|
+
*
|
|
134
|
+
* @typedef {Object} Confidence
|
|
135
|
+
* @property {string} level - One of CONFIDENCE_LEVEL
|
|
136
|
+
* @property {number} score - 0-100 confidence percentage
|
|
137
|
+
* @property {Array} [factors] - List of confidence factors
|
|
138
|
+
* @property {string} [explanation] - Detailed explanation of confidence level
|
|
139
|
+
*/
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Canonical Observation Interface
|
|
143
|
+
*
|
|
144
|
+
* @typedef {Object} Observation
|
|
145
|
+
* @property {string} type - Type of observation
|
|
146
|
+
* @property {string} selector - Element selector
|
|
147
|
+
* @property {string} label - Human-readable label
|
|
148
|
+
* @property {Object} sensors - Sensor readings before and after
|
|
149
|
+
* @property {Array} [evidence] - Array of evidence items from observation
|
|
150
|
+
*/
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Canonical Signals Interface
|
|
154
|
+
*
|
|
155
|
+
* @typedef {Object} Signals
|
|
156
|
+
* @property {string} impact - One of IMPACT
|
|
157
|
+
* @property {string} userRisk - One of USER_RISK
|
|
158
|
+
* @property {string} ownership - One of OWNERSHIP
|
|
159
|
+
* @property {Object} grouping - Grouping metadata
|
|
160
|
+
* @property {string} [grouping.groupByRoute] - Route pattern
|
|
161
|
+
* @property {string} [grouping.groupByFailureType] - Type of failure
|
|
162
|
+
* @property {string} [grouping.groupByFeature] - Feature area
|
|
163
|
+
*/
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Verify all enum exports are available
|
|
167
|
+
*/
|
|
168
|
+
export const ALL_ENUMS = {
|
|
169
|
+
CONFIDENCE_LEVEL,
|
|
170
|
+
FINDING_STATUS,
|
|
171
|
+
FINDING_TYPE,
|
|
172
|
+
IMPACT,
|
|
173
|
+
USER_RISK,
|
|
174
|
+
OWNERSHIP,
|
|
175
|
+
EVIDENCE_TYPE
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
export default {
|
|
179
|
+
CONFIDENCE_LEVEL,
|
|
180
|
+
FINDING_STATUS,
|
|
181
|
+
FINDING_TYPE,
|
|
182
|
+
IMPACT,
|
|
183
|
+
USER_RISK,
|
|
184
|
+
OWNERSHIP,
|
|
185
|
+
EVIDENCE_TYPE
|
|
186
|
+
};
|
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VERAX Validators - Runtime contract enforcement
|
|
3
|
+
*
|
|
4
|
+
* Implements the Evidence Law:
|
|
5
|
+
* "A finding cannot be marked CONFIRMED without sufficient evidence."
|
|
6
|
+
*
|
|
7
|
+
* All validators follow the pattern:
|
|
8
|
+
* @returns {Object} { ok: boolean, errors: string[], downgrade?: string }
|
|
9
|
+
*
|
|
10
|
+
* If a finding violates contracts, it should be downgraded to SUSPECTED
|
|
11
|
+
* or dropped from the report if it's critical.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
CONFIDENCE_LEVEL,
|
|
16
|
+
FINDING_STATUS,
|
|
17
|
+
FINDING_TYPE,
|
|
18
|
+
IMPACT,
|
|
19
|
+
USER_RISK,
|
|
20
|
+
OWNERSHIP,
|
|
21
|
+
EVIDENCE_TYPE
|
|
22
|
+
} from './types.js';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Validate a Finding object against contracts
|
|
26
|
+
*
|
|
27
|
+
* Evidence Law Enforcement:
|
|
28
|
+
* - If confidence.level === CONFIRMED, evidence must be substantive
|
|
29
|
+
* - If evidence is missing or empty, finding cannot be CONFIRMED
|
|
30
|
+
* - Returns { ok, errors, shouldDowngrade, suggestedStatus }
|
|
31
|
+
*
|
|
32
|
+
* @param {Object} finding - Finding object to validate
|
|
33
|
+
* @returns {Object} Validation result with enforcement recommendation
|
|
34
|
+
*/
|
|
35
|
+
export function validateFinding(finding) {
|
|
36
|
+
const errors = [];
|
|
37
|
+
let shouldDowngrade = false;
|
|
38
|
+
let suggestedStatus = null;
|
|
39
|
+
|
|
40
|
+
// Contract 1: Required top-level fields
|
|
41
|
+
if (!finding) {
|
|
42
|
+
return {
|
|
43
|
+
ok: false,
|
|
44
|
+
errors: ['Finding is null or undefined'],
|
|
45
|
+
shouldDowngrade: false
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!finding.type) {
|
|
50
|
+
errors.push('Missing required field: type');
|
|
51
|
+
} else if (!Object.values(FINDING_TYPE).includes(finding.type)) {
|
|
52
|
+
errors.push(`Invalid type: ${finding.type}. Must be one of: ${Object.values(FINDING_TYPE).join(', ')}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!finding.interaction || typeof finding.interaction !== 'object') {
|
|
56
|
+
errors.push('Missing or invalid required field: interaction (must be object)');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!finding.what_happened || typeof finding.what_happened !== 'string') {
|
|
60
|
+
errors.push('Missing or invalid required field: what_happened (must be non-empty string)');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!finding.what_was_expected || typeof finding.what_was_expected !== 'string') {
|
|
64
|
+
errors.push('Missing or invalid required field: what_was_expected (must be non-empty string)');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!finding.what_was_observed || typeof finding.what_was_observed !== 'string') {
|
|
68
|
+
errors.push('Missing or invalid required field: what_was_observed (must be non-empty string)');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Contract 2: Evidence validation (CRITICAL for Evidence Law)
|
|
72
|
+
const evidenceValidation = validateEvidence(finding.evidence);
|
|
73
|
+
if (!evidenceValidation.ok) {
|
|
74
|
+
errors.push(`Invalid evidence: ${evidenceValidation.errors.join('; ')}`);
|
|
75
|
+
shouldDowngrade = true;
|
|
76
|
+
suggestedStatus = FINDING_STATUS.SUSPECTED;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Contract 3: Confidence validation
|
|
80
|
+
const confidenceValidation = validateConfidence(finding.confidence);
|
|
81
|
+
if (!confidenceValidation.ok) {
|
|
82
|
+
errors.push(`Invalid confidence: ${confidenceValidation.errors.join('; ')}`);
|
|
83
|
+
shouldDowngrade = true;
|
|
84
|
+
suggestedStatus = FINDING_STATUS.SUSPECTED;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Contract 4: Signals validation
|
|
88
|
+
if (!finding.signals || typeof finding.signals !== 'object') {
|
|
89
|
+
errors.push('Missing or invalid required field: signals (must be object)');
|
|
90
|
+
} else {
|
|
91
|
+
const signalsValidation = validateSignals(finding.signals);
|
|
92
|
+
if (!signalsValidation.ok) {
|
|
93
|
+
errors.push(`Invalid signals: ${signalsValidation.errors.join('; ')}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// *** EVIDENCE LAW ENFORCEMENT ***
|
|
98
|
+
// PHASE 16: Check evidencePackage completeness for CONFIRMED findings
|
|
99
|
+
// PHASE 21.1: HARD LOCK - CONFIRMED without complete evidencePackage is IMPOSSIBLE
|
|
100
|
+
// PHASE 21: Evidence Law validation for CONFIRMED findings
|
|
101
|
+
if (finding.status === FINDING_STATUS.CONFIRMED || finding.severity === 'CONFIRMED') {
|
|
102
|
+
// EVIDENCE LAW v1: Check evidence structure first (context anchor + effect evidence)
|
|
103
|
+
const evidenceLawResult = enforceEvidenceLawV1(finding.evidence);
|
|
104
|
+
if (!evidenceLawResult.ok && evidenceLawResult.downgrade) {
|
|
105
|
+
console.log(
|
|
106
|
+
`EVIDENCE_LAW v1: downgraded CONFIRMED -> ${evidenceLawResult.downgrade} ` +
|
|
107
|
+
`(missing: ${evidenceLawResult.missing.join(', ')})`
|
|
108
|
+
);
|
|
109
|
+
return {
|
|
110
|
+
ok: true,
|
|
111
|
+
errors: [],
|
|
112
|
+
shouldDowngrade: true,
|
|
113
|
+
suggestedStatus: evidenceLawResult.downgrade
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// PHASE 21.1: Strict invariant - CONFIRMED findings MUST have complete evidencePackage
|
|
118
|
+
if (finding.evidencePackage) {
|
|
119
|
+
const missingFields = finding.evidencePackage.missingEvidence || [];
|
|
120
|
+
const isComplete = finding.evidencePackage.isComplete === true;
|
|
121
|
+
|
|
122
|
+
if (!isComplete || missingFields.length > 0) {
|
|
123
|
+
// PHASE 21.1: HARD FAILURE - do not downgrade, fail validation
|
|
124
|
+
errors.push(
|
|
125
|
+
`Evidence Law Violation (CRITICAL): Finding marked CONFIRMED but evidencePackage is incomplete. ` +
|
|
126
|
+
`Missing fields: ${missingFields.join(', ')}. ` +
|
|
127
|
+
`evidencePackage.isComplete=${isComplete}. ` +
|
|
128
|
+
`This finding MUST be dropped, not downgraded.`
|
|
129
|
+
);
|
|
130
|
+
// Do not set shouldDowngrade - this is a critical failure that should drop the finding
|
|
131
|
+
return {
|
|
132
|
+
ok: false,
|
|
133
|
+
errors,
|
|
134
|
+
shouldDowngrade: false, // Fail closed - drop, don't downgrade
|
|
135
|
+
suggestedStatus: null
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
} else if (!isEvidenceSubstantive(finding.evidence)) {
|
|
139
|
+
// PHASE 21.1: CONFIRMED finding without evidencePackage and without substantive evidence → CRITICAL FAILURE
|
|
140
|
+
errors.push(
|
|
141
|
+
`Evidence Law Violation (CRITICAL): Finding marked CONFIRMED but lacks evidencePackage and evidence is insufficient. ` +
|
|
142
|
+
`This finding MUST be dropped, not downgraded.`
|
|
143
|
+
);
|
|
144
|
+
// Do not set shouldDowngrade - this is a critical failure that should drop the finding
|
|
145
|
+
return {
|
|
146
|
+
ok: false,
|
|
147
|
+
errors,
|
|
148
|
+
shouldDowngrade: false, // Fail closed - drop, don't downgrade
|
|
149
|
+
suggestedStatus: null
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
ok: errors.length === 0,
|
|
156
|
+
errors,
|
|
157
|
+
shouldDowngrade,
|
|
158
|
+
suggestedStatus
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Validate an Evidence object
|
|
164
|
+
* Evidence is REQUIRED for any CONFIRMED finding.
|
|
165
|
+
*
|
|
166
|
+
* @param {Object} evidence - Evidence object to validate
|
|
167
|
+
* @returns {Object} { ok: boolean, errors: string[] }
|
|
168
|
+
*/
|
|
169
|
+
export function validateEvidence(evidence) {
|
|
170
|
+
const errors = [];
|
|
171
|
+
|
|
172
|
+
if (!evidence) {
|
|
173
|
+
errors.push('Evidence object is missing');
|
|
174
|
+
return { ok: false, errors };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (typeof evidence !== 'object') {
|
|
178
|
+
errors.push('Evidence must be an object');
|
|
179
|
+
return { ok: false, errors };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Evidence should contain at least one substantive field
|
|
183
|
+
const substantiveFields = [
|
|
184
|
+
'hasDomChange',
|
|
185
|
+
'hasUrlChange',
|
|
186
|
+
'hasNetworkActivity',
|
|
187
|
+
'hasStateChange',
|
|
188
|
+
'networkRequests',
|
|
189
|
+
'consoleLogs',
|
|
190
|
+
'before',
|
|
191
|
+
'after',
|
|
192
|
+
'beforeDom',
|
|
193
|
+
'afterDom'
|
|
194
|
+
];
|
|
195
|
+
|
|
196
|
+
const hasAtLeastOneField = substantiveFields.some(
|
|
197
|
+
field => evidence[field] !== undefined && evidence[field] !== null
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
if (!hasAtLeastOneField && !evidence.sensors) {
|
|
201
|
+
errors.push(
|
|
202
|
+
'Evidence object is empty. Must contain at least one of: ' +
|
|
203
|
+
substantiveFields.join(', ') + ', or sensors data'
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Optional: validate specific evidence fields if present
|
|
208
|
+
if (evidence.type && !Object.values(EVIDENCE_TYPE).includes(evidence.type)) {
|
|
209
|
+
errors.push(`Invalid evidence type: ${evidence.type}`);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
ok: errors.length === 0,
|
|
214
|
+
errors
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Validate a Confidence object
|
|
220
|
+
*
|
|
221
|
+
* @param {Object} confidence - Confidence object to validate
|
|
222
|
+
* @returns {Object} { ok: boolean, errors: string[] }
|
|
223
|
+
*/
|
|
224
|
+
export function validateConfidence(confidence) {
|
|
225
|
+
const errors = [];
|
|
226
|
+
|
|
227
|
+
if (!confidence || typeof confidence !== 'object') {
|
|
228
|
+
errors.push('Confidence must be a non-empty object');
|
|
229
|
+
return { ok: false, errors };
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (!confidence.level) {
|
|
233
|
+
errors.push('Missing required field: confidence.level');
|
|
234
|
+
} else if (!Object.values(CONFIDENCE_LEVEL).includes(confidence.level)) {
|
|
235
|
+
errors.push(
|
|
236
|
+
`Invalid confidence level: ${confidence.level}. ` +
|
|
237
|
+
`Must be one of: ${Object.values(CONFIDENCE_LEVEL).join(', ')}`
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Contract v1: Accept both score01 (0-1) and legacy score (0-100) for backward compat
|
|
242
|
+
if (confidence.score01 !== undefined) {
|
|
243
|
+
if (typeof confidence.score01 !== 'number' || confidence.score01 < 0 || confidence.score01 > 1) {
|
|
244
|
+
errors.push(`Invalid confidence.score01: ${confidence.score01}. Must be a number 0-1`);
|
|
245
|
+
}
|
|
246
|
+
} else if (confidence.score !== undefined) {
|
|
247
|
+
if (typeof confidence.score !== 'number' || confidence.score < 0 || confidence.score > 100) {
|
|
248
|
+
errors.push(`Invalid confidence.score: ${confidence.score}. Must be a number 0-100`);
|
|
249
|
+
}
|
|
250
|
+
} else {
|
|
251
|
+
errors.push('Missing required field: confidence.score01 or confidence.score');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
ok: errors.length === 0,
|
|
256
|
+
errors
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Validate a Signals object
|
|
262
|
+
*
|
|
263
|
+
* @param {Object} signals - Signals object to validate
|
|
264
|
+
* @returns {Object} { ok: boolean, errors: string[] }
|
|
265
|
+
*/
|
|
266
|
+
export function validateSignals(signals) {
|
|
267
|
+
const errors = [];
|
|
268
|
+
|
|
269
|
+
if (!signals || typeof signals !== 'object') {
|
|
270
|
+
errors.push('Signals must be a non-empty object');
|
|
271
|
+
return { ok: false, errors };
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (!signals.impact) {
|
|
275
|
+
errors.push('Missing required field: signals.impact');
|
|
276
|
+
} else if (!Object.values(IMPACT).includes(signals.impact)) {
|
|
277
|
+
errors.push(
|
|
278
|
+
`Invalid impact: ${signals.impact}. ` +
|
|
279
|
+
`Must be one of: ${Object.values(IMPACT).join(', ')}`
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (!signals.userRisk) {
|
|
284
|
+
errors.push('Missing required field: signals.userRisk');
|
|
285
|
+
} else if (!Object.values(USER_RISK).includes(signals.userRisk)) {
|
|
286
|
+
errors.push(
|
|
287
|
+
`Invalid userRisk: ${signals.userRisk}. ` +
|
|
288
|
+
`Must be one of: ${Object.values(USER_RISK).join(', ')}`
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (!signals.ownership) {
|
|
293
|
+
errors.push('Missing required field: signals.ownership');
|
|
294
|
+
} else if (!Object.values(OWNERSHIP).includes(signals.ownership)) {
|
|
295
|
+
errors.push(
|
|
296
|
+
`Invalid ownership: ${signals.ownership}. ` +
|
|
297
|
+
`Must be one of: ${Object.values(OWNERSHIP).join(', ')}`
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (!signals.grouping || typeof signals.grouping !== 'object') {
|
|
302
|
+
errors.push('Missing or invalid required field: signals.grouping (must be object)');
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return {
|
|
306
|
+
ok: errors.length === 0,
|
|
307
|
+
errors
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* EVIDENCE LAW v1: Check if CONFIRMED finding has complete evidence structure
|
|
313
|
+
*
|
|
314
|
+
* Rule A (Context Anchor): Must have beforeUrl OR beforeScreenshot OR before
|
|
315
|
+
* Rule B (Effect Evidence): Must have afterUrl OR after OR flags OR quantitative indicators
|
|
316
|
+
*
|
|
317
|
+
* @param {Object} evidence - Evidence object to validate
|
|
318
|
+
* @returns {Object} { ok: boolean, downgrade: 'UNPROVEN'|'SUSPECTED'|null, missing: string[] }
|
|
319
|
+
*/
|
|
320
|
+
export function enforceEvidenceLawV1(evidence) {
|
|
321
|
+
if (!evidence || typeof evidence !== 'object' || Object.keys(evidence).length === 0) {
|
|
322
|
+
return { ok: false, downgrade: 'UNPROVEN', missing: ['evidence object'] };
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const missing = [];
|
|
326
|
+
|
|
327
|
+
// Rule A: Context anchor (before state)
|
|
328
|
+
const hasContextAnchor = evidence.beforeUrl || evidence.beforeScreenshot || evidence.before;
|
|
329
|
+
if (!hasContextAnchor) {
|
|
330
|
+
missing.push('context anchor (beforeUrl/beforeScreenshot/before)');
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Rule B: Effect evidence (after state or change indicators)
|
|
334
|
+
const hasEffectEvidence =
|
|
335
|
+
evidence.afterUrl ||
|
|
336
|
+
evidence.afterScreenshot ||
|
|
337
|
+
evidence.after ||
|
|
338
|
+
evidence.urlChanged === true ||
|
|
339
|
+
evidence.domChanged === true ||
|
|
340
|
+
evidence.uiChanged === true ||
|
|
341
|
+
(typeof evidence.networkRequests === 'number' && evidence.networkRequests > 0) ||
|
|
342
|
+
(Array.isArray(evidence.networkRequests) && evidence.networkRequests.length > 0) ||
|
|
343
|
+
(typeof evidence.consoleErrors === 'number' && evidence.consoleErrors > 0) ||
|
|
344
|
+
(Array.isArray(evidence.consoleErrors) && evidence.consoleErrors.length > 0) ||
|
|
345
|
+
evidence.timingBreakdown;
|
|
346
|
+
|
|
347
|
+
if (!hasEffectEvidence) {
|
|
348
|
+
missing.push('effect evidence (after/flags/quantitative)');
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Downgrade logic
|
|
352
|
+
if (!hasContextAnchor && !hasEffectEvidence) {
|
|
353
|
+
return { ok: false, downgrade: 'UNPROVEN', missing };
|
|
354
|
+
}
|
|
355
|
+
if (!hasContextAnchor || !hasEffectEvidence) {
|
|
356
|
+
return { ok: false, downgrade: 'SUSPECTED', missing };
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return { ok: true, downgrade: null, missing: [] };
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* EVIDENCE LAW: Determine if evidence is sufficient for CONFIRMED status
|
|
364
|
+
*
|
|
365
|
+
* Substantive evidence means:
|
|
366
|
+
* - At least one positive signal (dom change, url change, network activity, etc.)
|
|
367
|
+
* - OR concrete sensor data from interaction
|
|
368
|
+
* - NOT just empty object or missing evidence
|
|
369
|
+
*
|
|
370
|
+
* @param {Object} evidence - Evidence object to evaluate
|
|
371
|
+
* @returns {boolean} True if evidence is substantive enough for CONFIRMED status
|
|
372
|
+
*/
|
|
373
|
+
export function isEvidenceSubstantive(evidence) {
|
|
374
|
+
if (!evidence || typeof evidence !== 'object') {
|
|
375
|
+
return false;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Check for positive signal indicators
|
|
379
|
+
const hasPositiveSignal =
|
|
380
|
+
evidence.hasDomChange === true ||
|
|
381
|
+
evidence.hasUrlChange === true ||
|
|
382
|
+
evidence.hasNetworkActivity === true ||
|
|
383
|
+
evidence.hasStateChange === true ||
|
|
384
|
+
(Array.isArray(evidence.networkRequests) && evidence.networkRequests.length > 0) ||
|
|
385
|
+
(Array.isArray(evidence.consoleLogs) && evidence.consoleLogs.length > 0) ||
|
|
386
|
+
(evidence.before && evidence.after);
|
|
387
|
+
|
|
388
|
+
if (hasPositiveSignal) {
|
|
389
|
+
return true;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Check for sensor data
|
|
393
|
+
if (evidence.sensors && typeof evidence.sensors === 'object' && Object.keys(evidence.sensors).length > 0) {
|
|
394
|
+
return true;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return false;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Enforce contracts on a finding array
|
|
402
|
+
*
|
|
403
|
+
* Returns findings with status downgraded if necessary, filters out
|
|
404
|
+
* findings that violate critical contracts.
|
|
405
|
+
*
|
|
406
|
+
* @param {Array} findings - Array of findings to validate
|
|
407
|
+
* @returns {Object} { valid: Array, dropped: Array, downgrades: Array }
|
|
408
|
+
*/
|
|
409
|
+
export function enforceContractsOnFindings(findings) {
|
|
410
|
+
if (!Array.isArray(findings)) {
|
|
411
|
+
return { valid: [], dropped: [], downgrades: [] };
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const valid = [];
|
|
415
|
+
const dropped = [];
|
|
416
|
+
const downgrades = [];
|
|
417
|
+
|
|
418
|
+
for (const finding of findings) {
|
|
419
|
+
const validation = validateFinding(finding);
|
|
420
|
+
|
|
421
|
+
if (!validation.ok && !validation.shouldDowngrade) {
|
|
422
|
+
// Critical contract violation - drop
|
|
423
|
+
dropped.push({
|
|
424
|
+
finding,
|
|
425
|
+
reason: validation.errors.join('; ')
|
|
426
|
+
});
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (validation.shouldDowngrade && validation.suggestedStatus) {
|
|
431
|
+
// Downgrade the finding
|
|
432
|
+
const downgraded = { ...finding, status: validation.suggestedStatus };
|
|
433
|
+
downgrades.push({
|
|
434
|
+
original: finding,
|
|
435
|
+
downgraded,
|
|
436
|
+
reason: validation.errors.join('; ')
|
|
437
|
+
});
|
|
438
|
+
valid.push(downgraded);
|
|
439
|
+
} else {
|
|
440
|
+
// Valid finding
|
|
441
|
+
valid.push(finding);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
return { valid, dropped, downgrades };
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
export default {
|
|
449
|
+
validateFinding,
|
|
450
|
+
validateEvidence,
|
|
451
|
+
validateConfidence,
|
|
452
|
+
validateSignals,
|
|
453
|
+
isEvidenceSubstantive,
|
|
454
|
+
enforceEvidenceLawV1,
|
|
455
|
+
enforceContractsOnFindings
|
|
456
|
+
};
|