@veraxhq/verax 0.3.0 → 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 +28 -20
- package/bin/verax.js +11 -18
- package/package.json +28 -7
- package/src/cli/commands/baseline.js +1 -2
- package/src/cli/commands/default.js +72 -81
- package/src/cli/commands/doctor.js +29 -0
- package/src/cli/commands/ga.js +3 -0
- package/src/cli/commands/gates.js +1 -1
- package/src/cli/commands/inspect.js +6 -133
- package/src/cli/commands/release-check.js +2 -0
- package/src/cli/commands/run.js +74 -246
- package/src/cli/commands/security-check.js +2 -1
- package/src/cli/commands/truth.js +0 -1
- package/src/cli/entry.js +82 -309
- package/src/cli/util/angular-component-extractor.js +2 -2
- package/src/cli/util/angular-navigation-detector.js +2 -2
- package/src/cli/util/ast-interactive-detector.js +4 -6
- package/src/cli/util/ast-network-detector.js +3 -3
- package/src/cli/util/ast-promise-extractor.js +581 -0
- package/src/cli/util/ast-usestate-detector.js +3 -3
- package/src/cli/util/atomic-write.js +12 -1
- 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 +2 -1
- package/src/cli/util/determinism-writer.js +1 -1
- package/src/cli/util/digest-engine.js +359 -0
- package/src/cli/util/dom-diff.js +226 -0
- package/src/cli/util/env-url.js +0 -4
- package/src/cli/util/evidence-engine.js +287 -0
- package/src/cli/util/expectation-extractor.js +217 -367
- package/src/cli/util/findings-writer.js +19 -126
- 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 -2
- 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 -2
- package/src/cli/util/paths.js +12 -3
- package/src/cli/util/project-discovery.js +284 -3
- package/src/cli/util/project-writer.js +2 -2
- package/src/cli/util/run-id.js +23 -27
- package/src/cli/util/run-result.js +778 -0
- package/src/cli/util/selector-resolver.js +235 -0
- package/src/cli/util/summary-writer.js +2 -1
- package/src/cli/util/svelte-navigation-detector.js +3 -3
- package/src/cli/util/svelte-sfc-extractor.js +0 -1
- package/src/cli/util/svelte-state-detector.js +1 -2
- 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 +4 -3
- package/src/cli/util/vue-sfc-extractor.js +1 -2
- package/src/cli/util/vue-state-detector.js +1 -1
- 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/finding-explainer.js +3 -56
- package/src/verax/cli/init.js +4 -18
- package/src/verax/core/action-classifier.js +4 -3
- package/src/verax/core/artifacts/registry.js +0 -15
- package/src/verax/core/artifacts/verifier.js +18 -8
- package/src/verax/core/baseline/baseline.snapshot.js +2 -0
- package/src/verax/core/capabilities/gates.js +7 -1
- package/src/verax/core/confidence/confidence-compute.js +14 -7
- package/src/verax/core/confidence/confidence.loader.js +1 -0
- package/src/verax/core/confidence-engine-refactor.js +8 -3
- package/src/verax/core/confidence-engine.js +162 -23
- package/src/verax/core/contracts/types.js +1 -0
- package/src/verax/core/contracts/validators.js +79 -4
- package/src/verax/core/decision-snapshot.js +3 -30
- package/src/verax/core/decisions/decision.trace.js +2 -0
- package/src/verax/core/determinism/contract-writer.js +2 -2
- package/src/verax/core/determinism/contract.js +1 -1
- package/src/verax/core/determinism/diff.js +42 -1
- package/src/verax/core/determinism/engine.js +7 -6
- package/src/verax/core/determinism/finding-identity.js +3 -2
- package/src/verax/core/determinism/normalize.js +32 -4
- package/src/verax/core/determinism/report-writer.js +1 -0
- package/src/verax/core/determinism/run-fingerprint.js +7 -2
- package/src/verax/core/dynamic-route-intelligence.js +8 -7
- package/src/verax/core/evidence/evidence-capture-service.js +1 -0
- package/src/verax/core/evidence/evidence-intent-ledger.js +2 -1
- package/src/verax/core/evidence-builder.js +2 -2
- package/src/verax/core/execution-mode-context.js +1 -1
- package/src/verax/core/execution-mode-detector.js +5 -3
- package/src/verax/core/failures/exit-codes.js +39 -37
- package/src/verax/core/failures/failure-summary.js +1 -1
- package/src/verax/core/failures/failure.factory.js +3 -3
- package/src/verax/core/failures/failure.ledger.js +3 -2
- package/src/verax/core/ga/ga.artifact.js +1 -1
- package/src/verax/core/ga/ga.contract.js +3 -2
- package/src/verax/core/ga/ga.enforcer.js +1 -0
- package/src/verax/core/guardrails/policy.loader.js +1 -0
- package/src/verax/core/guardrails/truth-reconciliation.js +1 -1
- package/src/verax/core/guardrails-engine.js +2 -2
- 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 +2 -0
- package/src/verax/core/perf/perf.report.js +2 -0
- package/src/verax/core/pipeline-tracker.js +5 -0
- package/src/verax/core/release/provenance.builder.js +73 -214
- package/src/verax/core/release/release.enforcer.js +14 -9
- package/src/verax/core/release/reproducibility.check.js +1 -0
- package/src/verax/core/release/sbom.builder.js +32 -23
- 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 +6 -3
- package/src/verax/core/report/human-summary.js +141 -1
- package/src/verax/core/route-intelligence.js +4 -3
- 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 +10 -7
- package/src/verax/core/security/security.enforcer.js +4 -0
- package/src/verax/core/security/supplychain.policy.js +9 -1
- package/src/verax/core/security/vuln.scan.js +2 -2
- package/src/verax/core/truth/truth.certificate.js +3 -1
- package/src/verax/core/ui-feedback-intelligence.js +12 -46
- package/src/verax/detect/conditional-ui-silent-failure.js +84 -0
- package/src/verax/detect/confidence-engine.js +100 -660
- package/src/verax/detect/confidence-helper.js +1 -0
- package/src/verax/detect/detection-engine.js +1 -18
- package/src/verax/detect/dynamic-route-findings.js +17 -14
- package/src/verax/detect/expectation-chain-detector.js +1 -1
- package/src/verax/detect/expectation-model.js +3 -5
- package/src/verax/detect/failure-cause-inference.js +293 -0
- package/src/verax/detect/findings-writer.js +126 -166
- 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 +51 -234
- package/src/verax/detect/invariants-enforcer.js +147 -0
- package/src/verax/detect/journey-stall-detector.js +4 -4
- 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 +7 -6
- 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 +18 -18
- package/src/verax/detect/verdict-engine.js +3 -57
- package/src/verax/detect/view-switch-correlator.js +2 -2
- package/src/verax/flow/flow-engine.js +2 -1
- package/src/verax/flow/flow-spec.js +0 -6
- package/src/verax/index.js +48 -412
- 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 +67 -682
- 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/route-validator.js +1 -4
- 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 +735 -84
- package/src/verax/observe/interaction-executor.js +192 -0
- package/src/verax/observe/interaction-runner.js +782 -530
- 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 +1 -1
- package/src/verax/observe/observe-helpers.js +2 -1
- package/src/verax/observe/observe-runner.js +28 -24
- package/src/verax/observe/observers/budget-observer.js +3 -3
- package/src/verax/observe/observers/console-observer.js +4 -4
- package/src/verax/observe/observers/coverage-observer.js +4 -4
- package/src/verax/observe/observers/interaction-observer.js +3 -3
- package/src/verax/observe/observers/navigation-observer.js +4 -4
- package/src/verax/observe/observers/network-observer.js +4 -4
- package/src/verax/observe/observers/safety-observer.js +1 -1
- package/src/verax/observe/observers/ui-feedback-observer.js +4 -4
- package/src/verax/observe/page-traversal.js +138 -0
- package/src/verax/observe/snapshot-ops.js +94 -0
- package/src/verax/observe/ui-signal-sensor.js +2 -148
- package/src/verax/scan-summary-writer.js +10 -42
- package/src/verax/shared/artifact-manager.js +30 -13
- package/src/verax/shared/caching.js +1 -0
- package/src/verax/shared/expectation-tracker.js +1 -0
- package/src/verax/shared/zip-artifacts.js +6 -0
- package/src/verax/core/confidence-engine.js.backup +0 -471
- package/src/verax/shared/config-loader.js +0 -169
- /package/src/verax/shared/{expectation-proof.js → expectation-validation.js} +0 -0
|
@@ -1,196 +1,156 @@
|
|
|
1
1
|
import { resolve } from 'path';
|
|
2
2
|
import { mkdirSync, writeFileSync } from 'fs';
|
|
3
3
|
import { CANONICAL_OUTCOMES } from '../core/canonical-outcomes.js';
|
|
4
|
-
import {
|
|
5
|
-
import { ARTIFACT_REGISTRY, getArtifactVersions } from '../core/artifacts/registry.js';
|
|
6
|
-
import { buildAndEnforceEvidencePackage, EvidenceBuildError, validateEvidencePackageStrict } from '../core/evidence-builder.js';
|
|
7
|
-
import { writeEvidenceIntentLedger } from '../core/evidence/evidence-intent-ledger.js';
|
|
4
|
+
import { ARTIFACT_REGISTRY } from '../core/artifacts/registry.js';
|
|
8
5
|
|
|
9
|
-
|
|
10
|
-
* Write findings to canonical artifact root.
|
|
11
|
-
* Writes to .verax/runs/<runId>/findings.json.
|
|
12
|
-
*
|
|
13
|
-
* PHASE 0 ENFORCEMENT: Applies contracts enforcement
|
|
14
|
-
* - Downgrades findings without evidence from CONFIRMED to SUSPECTED
|
|
15
|
-
* - Drops findings that violate critical contracts
|
|
16
|
-
*
|
|
17
|
-
* PHASE 2: Includes outcome classification summary.
|
|
18
|
-
* PHASE 3: Includes promise type summary.
|
|
19
|
-
*
|
|
20
|
-
* @param {string} projectDir
|
|
21
|
-
* @param {string} url
|
|
22
|
-
* @param {Array} findings
|
|
23
|
-
* @param {Array} coverageGaps
|
|
24
|
-
* @param {string} runDirOpt - Required absolute run directory path
|
|
25
|
-
* @returns {Object} findings report with enforcement metadata
|
|
26
|
-
*/
|
|
27
|
-
export function writeFindings(projectDir, url, findings, coverageGaps = [], runDirOpt) {
|
|
28
|
-
if (!runDirOpt) {
|
|
29
|
-
throw new Error('runDirOpt is required');
|
|
30
|
-
}
|
|
31
|
-
mkdirSync(runDirOpt, { recursive: true });
|
|
32
|
-
const findingsPath = resolve(runDirOpt, ARTIFACT_REGISTRY.findings.filename);
|
|
6
|
+
const DEFAULT_CLOCK = () => new Date().toISOString();
|
|
33
7
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
evidenceBuildFailures.push({
|
|
61
|
-
finding: { type: finding.type || 'unknown', id: finding.findingId || finding.id },
|
|
62
|
-
reason: `EVIDENCE_VALIDATION_FAILED: ${error.message}`,
|
|
63
|
-
errorCode: error.code || 'EVIDENCE_VALIDATION_FAILED',
|
|
64
|
-
action: 'DOWNGRADED'
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
} else {
|
|
68
|
-
findingsWithEvidence.push(finding);
|
|
69
|
-
}
|
|
8
|
+
// Pure: builds deterministic report object from provided data and timestamp
|
|
9
|
+
export function buildFindingsReport({ url, findings = [], coverageGaps = [], detectedAt }) {
|
|
10
|
+
const outcomeSummary = {};
|
|
11
|
+
Object.values(CANONICAL_OUTCOMES).forEach(outcome => {
|
|
12
|
+
outcomeSummary[outcome] = 0;
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const promiseSummary = {};
|
|
16
|
+
|
|
17
|
+
// Contract enforcement: separate findings into valid, downgradable, and droppable
|
|
18
|
+
const downgrades = [];
|
|
19
|
+
const droppedFindingIds = [];
|
|
20
|
+
const enforcedFindings = [];
|
|
21
|
+
|
|
22
|
+
for (const finding of findings) {
|
|
23
|
+
// Check for critical missing fields (drop these)
|
|
24
|
+
const hasCriticalNarrativeFields =
|
|
25
|
+
finding.what_happened &&
|
|
26
|
+
finding.what_was_expected &&
|
|
27
|
+
finding.what_was_observed &&
|
|
28
|
+
finding.why_it_matters;
|
|
29
|
+
|
|
30
|
+
const hasRequiredType = finding.type;
|
|
31
|
+
|
|
32
|
+
if (!hasRequiredType || !hasCriticalNarrativeFields) {
|
|
33
|
+
droppedFindingIds.push(finding.id || 'unknown');
|
|
70
34
|
continue;
|
|
71
35
|
}
|
|
72
|
-
|
|
73
|
-
//
|
|
74
|
-
|
|
75
|
-
try {
|
|
76
|
-
const findingWithEvidence = buildAndEnforceEvidencePackage(finding, {
|
|
77
|
-
expectation: finding.expectation || null,
|
|
78
|
-
trace: {
|
|
79
|
-
interaction: finding.interaction || {},
|
|
80
|
-
before: finding.evidence?.before ? { screenshot: finding.evidence.before, url: finding.evidence.beforeUrl } : null,
|
|
81
|
-
after: finding.evidence?.after ? { screenshot: finding.evidence.after, url: finding.evidence.afterUrl } : null,
|
|
82
|
-
sensors: finding.evidence?.sensors || {},
|
|
83
|
-
dom: finding.evidence?.dom || {},
|
|
84
|
-
},
|
|
85
|
-
evidence: finding.evidence || {},
|
|
86
|
-
confidence: finding.confidenceLevel ? {
|
|
87
|
-
level: finding.confidenceLevel,
|
|
88
|
-
score: finding.confidence,
|
|
89
|
-
reasons: finding.confidenceReasons || [],
|
|
90
|
-
} : null,
|
|
91
|
-
});
|
|
92
|
-
findingsWithEvidence.push(findingWithEvidence);
|
|
93
|
-
} catch (error) {
|
|
94
|
-
// Evidence building failure → DOWNGRADE finding (never drop)
|
|
95
|
-
const originalSeverity = finding.severity || finding.status || 'SUSPECTED';
|
|
36
|
+
|
|
37
|
+
// Check for Evidence Law violation (downgrade)
|
|
38
|
+
if (finding.status === 'CONFIRMED' && (!finding.evidence || Object.keys(finding.evidence).length === 0)) {
|
|
96
39
|
const downgradedFinding = {
|
|
97
40
|
...finding,
|
|
98
|
-
severity: 'SUSPECTED',
|
|
99
41
|
status: 'SUSPECTED',
|
|
100
|
-
|
|
101
|
-
downgraded: true,
|
|
102
|
-
reason: `Evidence capture failed: ${error.message}`,
|
|
103
|
-
originalSeverity: originalSeverity,
|
|
104
|
-
errorCode: error.code || 'EVIDENCE_BUILD_FAILED',
|
|
105
|
-
missingFields: error.missingFields || []
|
|
106
|
-
}
|
|
42
|
+
reason: (finding.reason || '') + ' (Evidence Law enforced - no evidence exists for CONFIRMED status)'
|
|
107
43
|
};
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
} else {
|
|
119
|
-
evidenceBuildFailures.push({
|
|
120
|
-
finding: { type: finding.type || 'unknown', id: finding.findingId || finding.id },
|
|
121
|
-
reason: `EVIDENCE_BUILD_FAILED: Unexpected error: ${error.message}`,
|
|
122
|
-
errorCode: 'EVIDENCE_BUILD_FAILED',
|
|
123
|
-
action: 'DOWNGRADED'
|
|
124
|
-
});
|
|
125
|
-
}
|
|
44
|
+
downgrades.push({
|
|
45
|
+
id: finding.id || 'unknown',
|
|
46
|
+
originalStatus: 'CONFIRMED',
|
|
47
|
+
downgradeToStatus: 'SUSPECTED',
|
|
48
|
+
reason: 'Evidence Law enforced - no evidence exists for CONFIRMED status'
|
|
49
|
+
});
|
|
50
|
+
enforcedFindings.push(downgradedFinding);
|
|
51
|
+
} else {
|
|
52
|
+
// Valid finding
|
|
53
|
+
enforcedFindings.push(finding);
|
|
126
54
|
}
|
|
127
55
|
}
|
|
128
|
-
|
|
129
|
-
//
|
|
130
|
-
const { valid: enforcedFindings, dropped, downgrades } = enforceContractsOnFindings(findingsWithEvidence);
|
|
131
|
-
|
|
132
|
-
// ARCHITECTURAL HARDENING: Track evidence build failures separately (they result in downgrades, not drops)
|
|
133
|
-
// Only contract violations result in drops
|
|
134
|
-
const allDropped = dropped;
|
|
135
|
-
|
|
136
|
-
// PHASE 2: Compute outcome summary (using enforced findings)
|
|
137
|
-
const outcomeSummary = {};
|
|
138
|
-
Object.values(CANONICAL_OUTCOMES).forEach(outcome => {
|
|
139
|
-
outcomeSummary[outcome] = 0;
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
// PHASE 3: Compute promise summary
|
|
143
|
-
const promiseSummary = {};
|
|
144
|
-
|
|
56
|
+
|
|
57
|
+
// Build outcome and promise summary from enforced findings
|
|
145
58
|
for (const finding of enforcedFindings) {
|
|
146
59
|
const outcome = finding.outcome || CANONICAL_OUTCOMES.SILENT_FAILURE;
|
|
147
60
|
outcomeSummary[outcome] = (outcomeSummary[outcome] || 0) + 1;
|
|
148
|
-
|
|
61
|
+
|
|
149
62
|
const promiseType = finding.promise?.type || 'UNKNOWN_PROMISE';
|
|
150
63
|
promiseSummary[promiseType] = (promiseSummary[promiseType] || 0) + 1;
|
|
151
64
|
}
|
|
152
65
|
|
|
153
|
-
|
|
66
|
+
// Sort findings deterministically by id
|
|
67
|
+
const sortedFindings = enforcedFindings.sort((a, b) => (a.id || '').localeCompare(b.id || ''));
|
|
68
|
+
|
|
69
|
+
// Production report: exclude enforcement metadata (Trust Lock)
|
|
70
|
+
return {
|
|
154
71
|
version: 1,
|
|
155
|
-
contractVersion:
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
coverageGaps: coverageGaps,
|
|
163
|
-
// PHASE 0: Enforcement metadata
|
|
164
|
-
// PHASE 21.1: Include evidence build failures in dropped count
|
|
165
|
-
enforcement: {
|
|
166
|
-
droppedCount: allDropped.length,
|
|
167
|
-
downgradedCount: downgrades.length,
|
|
168
|
-
downgrades: downgrades.map(d => ({
|
|
169
|
-
reason: d.reason,
|
|
170
|
-
originalStatus: d.original.status,
|
|
171
|
-
downgradeToStatus: d.downgraded.status
|
|
172
|
-
})),
|
|
173
|
-
evidenceBuildFailures: evidenceBuildFailures.map(f => ({
|
|
174
|
-
reason: f.reason,
|
|
175
|
-
errorCode: f.errorCode,
|
|
176
|
-
findingType: f.finding.type || 'unknown',
|
|
177
|
-
findingId: f.finding.id || null,
|
|
178
|
-
missingFields: f.missingFields || [],
|
|
179
|
-
action: f.action || 'DOWNGRADED'
|
|
180
|
-
}))
|
|
181
|
-
},
|
|
72
|
+
contractVersion: ARTIFACT_REGISTRY.findings.contractVersion,
|
|
73
|
+
detectedAt: detectedAt,
|
|
74
|
+
url,
|
|
75
|
+
outcomeSummary, // PHASE 2
|
|
76
|
+
promiseSummary, // PHASE 3
|
|
77
|
+
findings: sortedFindings,
|
|
78
|
+
coverageGaps,
|
|
182
79
|
notes: []
|
|
183
80
|
};
|
|
81
|
+
}
|
|
184
82
|
|
|
185
|
-
|
|
83
|
+
// Internal helper: builds report with enforcement metadata for disk persistence
|
|
84
|
+
function buildFindingsReportWithEnforcement({ url, findings = [], coverageGaps = [], detectedAt }) {
|
|
85
|
+
const report = buildFindingsReport({ url, findings, coverageGaps, detectedAt });
|
|
86
|
+
|
|
87
|
+
const downgrades = [];
|
|
88
|
+
const droppedFindingIds = [];
|
|
186
89
|
|
|
187
|
-
|
|
188
|
-
|
|
90
|
+
for (const finding of findings) {
|
|
91
|
+
const hasCriticalNarrativeFields =
|
|
92
|
+
finding.what_happened &&
|
|
93
|
+
finding.what_was_expected &&
|
|
94
|
+
finding.what_was_observed &&
|
|
95
|
+
finding.why_it_matters;
|
|
96
|
+
|
|
97
|
+
const hasRequiredType = finding.type;
|
|
98
|
+
|
|
99
|
+
if (!hasRequiredType || !hasCriticalNarrativeFields) {
|
|
100
|
+
droppedFindingIds.push(finding.id || 'unknown');
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (finding.status === 'CONFIRMED' && (!finding.evidence || Object.keys(finding.evidence).length === 0)) {
|
|
105
|
+
downgrades.push({
|
|
106
|
+
id: finding.id || 'unknown',
|
|
107
|
+
originalStatus: 'CONFIRMED',
|
|
108
|
+
downgradeToStatus: 'SUSPECTED',
|
|
109
|
+
reason: 'Evidence Law enforced - no evidence exists for CONFIRMED status'
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
189
113
|
|
|
190
114
|
return {
|
|
191
|
-
...
|
|
192
|
-
|
|
193
|
-
|
|
115
|
+
...report,
|
|
116
|
+
enforcement: {
|
|
117
|
+
evidenceLawEnforced: true,
|
|
118
|
+
contractVersion: 1,
|
|
119
|
+
timestamp: detectedAt,
|
|
120
|
+
droppedCount: droppedFindingIds.length,
|
|
121
|
+
downgradedCount: downgrades.length,
|
|
122
|
+
downgrades: downgrades
|
|
123
|
+
}
|
|
194
124
|
};
|
|
195
125
|
}
|
|
196
126
|
|
|
127
|
+
// Side-effectful: persists a fully built report to disk
|
|
128
|
+
export function persistFindingsReport(runDir, report) {
|
|
129
|
+
if (!runDir) {
|
|
130
|
+
throw new Error('runDirOpt is required');
|
|
131
|
+
}
|
|
132
|
+
mkdirSync(runDir, { recursive: true });
|
|
133
|
+
const findingsPath = resolve(runDir, 'findings.json');
|
|
134
|
+
writeFileSync(findingsPath, JSON.stringify(report, null, 2) + '\n');
|
|
135
|
+
return { ...report, findingsPath };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Write findings to canonical artifact root.
|
|
140
|
+
* Writes to .verax/runs/<runId>/findings.json.
|
|
141
|
+
*
|
|
142
|
+
* PHASE 2: Includes outcome classification summary.
|
|
143
|
+
* PHASE 3: Includes promise type summary.
|
|
144
|
+
*
|
|
145
|
+
* @param {string} projectDir
|
|
146
|
+
* @param {string} url
|
|
147
|
+
* @param {Array} findings
|
|
148
|
+
* @param {Array} coverageGaps
|
|
149
|
+
* @param {string} runDirOpt - Required absolute run directory path
|
|
150
|
+
*/
|
|
151
|
+
export function writeFindings(projectDir, url, findings, coverageGaps = [], runDirOpt) {
|
|
152
|
+
const detectedAt = DEFAULT_CLOCK();
|
|
153
|
+
const report = buildFindingsReportWithEnforcement({ url, findings: findings || [], coverageGaps: coverageGaps || [], detectedAt });
|
|
154
|
+
return persistFindingsReport(runDirOpt, report);
|
|
155
|
+
}
|
|
156
|
+
|
|
@@ -153,7 +153,7 @@ export function detectFlowSilentFailures(traces, manifest, findings, coverageGap
|
|
|
153
153
|
// Check navigation expectation
|
|
154
154
|
if (expectsNavigation(manifest, interaction, beforeUrl)) {
|
|
155
155
|
const navExp = manifest.staticExpectations?.find(e =>
|
|
156
|
-
e.type === 'navigation' &&
|
|
156
|
+
(e.type === 'navigation' || e.type === 'spa_navigation') &&
|
|
157
157
|
isProvenExpectation(e) &&
|
|
158
158
|
e.fromPath && getUrlPath(beforeUrl) &&
|
|
159
159
|
e.fromPath.replace(/\/$/, '') === getUrlPath(beforeUrl).replace(/\/$/, '') &&
|
|
@@ -223,7 +223,7 @@ export function detectFlowSilentFailures(traces, manifest, findings, coverageGap
|
|
|
223
223
|
}
|
|
224
224
|
|
|
225
225
|
// Also check network expectation even if navigation was matched (for step 2 of flow)
|
|
226
|
-
if (matchedExpectation && matchedExpectation.type === 'navigation' && manifest.staticExpectations) {
|
|
226
|
+
if (matchedExpectation && (matchedExpectation.type === 'navigation' || matchedExpectation.type === 'spa_navigation') && manifest.staticExpectations) {
|
|
227
227
|
const networkExp = manifest.staticExpectations.find(e =>
|
|
228
228
|
e.type === 'network_action' &&
|
|
229
229
|
isProvenExpectation(e) &&
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Form Silent Failure Detection
|
|
3
|
+
*
|
|
4
|
+
* Detects form submissions where:
|
|
5
|
+
* - Form submit handler executes
|
|
6
|
+
* - Network request completes with 2xx status
|
|
7
|
+
* - AND no success UI feedback appears (no toast, modal, or DOM change)
|
|
8
|
+
* - AND no navigation occurs
|
|
9
|
+
*
|
|
10
|
+
* CONFIDENCE: HIGH (network + UI evidence)
|
|
11
|
+
* Note: Does NOT attempt to parse response content (unsupported)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { hasMeaningfulUrlChange, hasDomChange } from './comparison.js';
|
|
15
|
+
import { enrichFindingWithExplanations } from './finding-detector.js';
|
|
16
|
+
|
|
17
|
+
export function detectFormSilentFailures(traces, manifest, findings) {
|
|
18
|
+
// Parameters:
|
|
19
|
+
// traces - array of interaction traces from observation
|
|
20
|
+
// manifest - project manifest (contains expectations)
|
|
21
|
+
// findings - array to append new findings to (mutated in-place)
|
|
22
|
+
|
|
23
|
+
for (const trace of traces) {
|
|
24
|
+
const interaction = trace.interaction || {};
|
|
25
|
+
|
|
26
|
+
// Only analyze form interactions
|
|
27
|
+
if (interaction.type !== 'form' && interaction.category !== 'form') {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const beforeUrl = trace.before?.url || trace.beforeUrl || '';
|
|
32
|
+
const afterUrl = trace.after?.url || trace.afterUrl || '';
|
|
33
|
+
const sensors = trace.sensors || {};
|
|
34
|
+
const network = sensors.network || {};
|
|
35
|
+
const uiSignals = sensors.uiSignals || {};
|
|
36
|
+
const uiDiff = uiSignals.diff || {};
|
|
37
|
+
|
|
38
|
+
const urlChanged = hasMeaningfulUrlChange(beforeUrl, afterUrl);
|
|
39
|
+
const domChanged = hasDomChange(trace);
|
|
40
|
+
const uiChanged = uiDiff.changed === true;
|
|
41
|
+
|
|
42
|
+
// Check for successful network requests (2xx)
|
|
43
|
+
const successNetworkRequest = (network.requests || []).some(req => {
|
|
44
|
+
const status = req.status || req.statusCode || 0;
|
|
45
|
+
return status >= 200 && status < 300;
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Detection logic:
|
|
49
|
+
// Form submitted with successful network response but:
|
|
50
|
+
// 1. No navigation (URL unchanged)
|
|
51
|
+
// 2. No DOM change (no new content loaded)
|
|
52
|
+
// 3. No UI feedback (no toast, modal, highlight, etc.)
|
|
53
|
+
if (successNetworkRequest && !urlChanged && !domChanged && !uiChanged) {
|
|
54
|
+
const evidence = {
|
|
55
|
+
before: trace.before?.screenshot || trace.beforeScreenshot || '',
|
|
56
|
+
after: trace.after?.screenshot || trace.afterScreenshot || '',
|
|
57
|
+
beforeUrl,
|
|
58
|
+
afterUrl,
|
|
59
|
+
networkRequests: network.requests || [],
|
|
60
|
+
successRequests: (network.requests || []).filter(r => {
|
|
61
|
+
const status = r.status || r.statusCode || 0;
|
|
62
|
+
return status >= 200 && status < 300;
|
|
63
|
+
}).length,
|
|
64
|
+
uiChanged,
|
|
65
|
+
domChanged,
|
|
66
|
+
urlChanged,
|
|
67
|
+
reason: 'Form submitted successfully but provided no visual feedback'
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const finding = {
|
|
71
|
+
type: 'form_silent_failure',
|
|
72
|
+
description: `Form submission succeeded with no success feedback to user`,
|
|
73
|
+
summary: `Form submitted successfully (2xx response) but no UI feedback (no toast, message, or redirect)`,
|
|
74
|
+
explanation: `The form submission was completed by the server (2xx status code received), but the application provided no visual feedback to the user. The page remained unchanged, with no success message, toast notification, or redirect.`,
|
|
75
|
+
evidence,
|
|
76
|
+
confidence: {
|
|
77
|
+
level: 0.90, // HIGH - network + UI evidence
|
|
78
|
+
reasons: [
|
|
79
|
+
'Form submitted and network request returned 2xx (success)',
|
|
80
|
+
'No UI feedback appeared (no toast, modal, or message)',
|
|
81
|
+
'No page navigation (user stayed on same page)',
|
|
82
|
+
'No DOM change (no new content loaded)'
|
|
83
|
+
]
|
|
84
|
+
},
|
|
85
|
+
promise: {
|
|
86
|
+
type: 'form_submission',
|
|
87
|
+
expected: 'Submit form and display success feedback',
|
|
88
|
+
actual: 'Form submitted successfully but no feedback provided'
|
|
89
|
+
},
|
|
90
|
+
capabilityNote: 'Detection based on network status and visual feedback only. Does not parse response body or validate success semantics (UNSUPPORTED).'
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Enrich with explanations
|
|
94
|
+
enrichFindingWithExplanations(finding, trace);
|
|
95
|
+
findings.push(finding);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|