@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.
- package/README.md +14 -18
- package/bin/verax.js +7 -0
- package/package.json +3 -3
- package/src/cli/commands/baseline.js +104 -0
- package/src/cli/commands/default.js +79 -25
- package/src/cli/commands/ga.js +243 -0
- package/src/cli/commands/gates.js +95 -0
- package/src/cli/commands/inspect.js +131 -2
- package/src/cli/commands/release-check.js +213 -0
- package/src/cli/commands/run.js +246 -35
- package/src/cli/commands/security-check.js +211 -0
- package/src/cli/commands/truth.js +114 -0
- package/src/cli/entry.js +304 -67
- 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 +546 -0
- package/src/cli/util/ast-network-detector.js +603 -0
- package/src/cli/util/ast-usestate-detector.js +602 -0
- package/src/cli/util/bootstrap-guard.js +86 -0
- package/src/cli/util/determinism-runner.js +123 -0
- package/src/cli/util/determinism-writer.js +129 -0
- package/src/cli/util/env-url.js +4 -0
- package/src/cli/util/expectation-extractor.js +369 -73
- package/src/cli/util/findings-writer.js +126 -16
- package/src/cli/util/learn-writer.js +3 -1
- package/src/cli/util/observe-writer.js +3 -1
- package/src/cli/util/paths.js +3 -12
- package/src/cli/util/project-discovery.js +3 -0
- package/src/cli/util/project-writer.js +3 -1
- package/src/cli/util/run-resolver.js +64 -0
- package/src/cli/util/source-requirement.js +55 -0
- package/src/cli/util/summary-writer.js +1 -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 +147 -0
- package/src/cli/util/svelte-state-detector.js +243 -0
- package/src/cli/util/vue-navigation-detector.js +177 -0
- package/src/cli/util/vue-sfc-extractor.js +162 -0
- package/src/cli/util/vue-state-detector.js +215 -0
- package/src/verax/cli/finding-explainer.js +56 -3
- package/src/verax/core/artifacts/registry.js +154 -0
- package/src/verax/core/artifacts/verifier.js +980 -0
- package/src/verax/core/baseline/baseline.enforcer.js +137 -0
- package/src/verax/core/baseline/baseline.snapshot.js +231 -0
- package/src/verax/core/capabilities/gates.js +499 -0
- package/src/verax/core/capabilities/registry.js +475 -0
- package/src/verax/core/confidence/confidence-compute.js +137 -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 +79 -0
- package/src/verax/core/confidence/confidence.schema.js +94 -0
- package/src/verax/core/confidence-engine-refactor.js +484 -0
- package/src/verax/core/confidence-engine.js +486 -0
- package/src/verax/core/confidence-engine.js.backup +471 -0
- package/src/verax/core/contracts/index.js +29 -0
- package/src/verax/core/contracts/types.js +185 -0
- package/src/verax/core/contracts/validators.js +381 -0
- package/src/verax/core/decision-snapshot.js +30 -3
- package/src/verax/core/decisions/decision.trace.js +276 -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 +364 -0
- package/src/verax/core/determinism/engine.js +221 -0
- package/src/verax/core/determinism/finding-identity.js +148 -0
- package/src/verax/core/determinism/normalize.js +438 -0
- package/src/verax/core/determinism/report-writer.js +92 -0
- package/src/verax/core/determinism/run-fingerprint.js +118 -0
- package/src/verax/core/dynamic-route-intelligence.js +528 -0
- package/src/verax/core/evidence/evidence-capture-service.js +307 -0
- package/src/verax/core/evidence/evidence-intent-ledger.js +165 -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 +190 -0
- package/src/verax/core/failures/exit-codes.js +86 -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 +132 -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 +434 -0
- package/src/verax/core/ga/ga.enforcer.js +86 -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 +83 -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/observe/run-timeline.js +316 -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 +198 -0
- package/src/verax/core/pipeline-tracker.js +238 -0
- package/src/verax/core/product-definition.js +127 -0
- package/src/verax/core/release/provenance.builder.js +271 -0
- package/src/verax/core/release/release-report-writer.js +40 -0
- package/src/verax/core/release/release.enforcer.js +159 -0
- package/src/verax/core/release/reproducibility.check.js +221 -0
- package/src/verax/core/release/sbom.builder.js +283 -0
- package/src/verax/core/report/cross-index.js +192 -0
- package/src/verax/core/report/human-summary.js +222 -0
- package/src/verax/core/route-intelligence.js +419 -0
- package/src/verax/core/security/secrets.scan.js +326 -0
- package/src/verax/core/security/security-report.js +50 -0
- package/src/verax/core/security/security.enforcer.js +124 -0
- package/src/verax/core/security/supplychain.defaults.json +38 -0
- package/src/verax/core/security/supplychain.policy.js +326 -0
- package/src/verax/core/security/vuln.scan.js +265 -0
- package/src/verax/core/truth/truth.certificate.js +250 -0
- package/src/verax/core/ui-feedback-intelligence.js +515 -0
- package/src/verax/detect/confidence-engine.js +628 -40
- package/src/verax/detect/confidence-helper.js +33 -0
- package/src/verax/detect/detection-engine.js +18 -1
- package/src/verax/detect/dynamic-route-findings.js +335 -0
- package/src/verax/detect/expectation-chain-detector.js +417 -0
- package/src/verax/detect/expectation-model.js +3 -1
- package/src/verax/detect/findings-writer.js +141 -5
- package/src/verax/detect/index.js +229 -5
- package/src/verax/detect/journey-stall-detector.js +558 -0
- package/src/verax/detect/route-findings.js +218 -0
- package/src/verax/detect/ui-feedback-findings.js +207 -0
- package/src/verax/detect/verdict-engine.js +57 -3
- package/src/verax/detect/view-switch-correlator.js +242 -0
- package/src/verax/index.js +413 -45
- package/src/verax/learn/action-contract-extractor.js +682 -64
- package/src/verax/learn/route-validator.js +4 -1
- package/src/verax/observe/index.js +88 -843
- package/src/verax/observe/interaction-runner.js +25 -8
- package/src/verax/observe/observe-context.js +205 -0
- package/src/verax/observe/observe-helpers.js +191 -0
- package/src/verax/observe/observe-runner.js +226 -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/ui-feedback-detector.js +742 -0
- package/src/verax/observe/ui-signal-sensor.js +148 -2
- package/src/verax/scan-summary-writer.js +42 -8
- package/src/verax/shared/artifact-manager.js +8 -5
- package/src/verax/shared/css-spinner-rules.js +204 -0
- package/src/verax/shared/view-switch-rules.js +208 -0
package/src/verax/index.js
CHANGED
|
@@ -9,8 +9,29 @@ import SilenceTracker from './core/silence-model.js';
|
|
|
9
9
|
import { generateRunId, getRunArtifactDir, getArtifactPath } from './core/run-id.js';
|
|
10
10
|
import { createRunManifest, updateRunManifestHashes } from './core/run-manifest.js';
|
|
11
11
|
import { computeArtifactHashes } from './core/run-id.js';
|
|
12
|
+
import { FailureLedger } from './core/failures/failure.ledger.js';
|
|
13
|
+
import { errorToFailure, createIOFailure, createInternalFailure } from './core/failures/failure.factory.js';
|
|
14
|
+
import { FAILURE_CODE, EXECUTION_PHASE } from './core/failures/failure.types.js';
|
|
15
|
+
import { createPerformanceMonitor } from './core/perf/perf.monitor.js';
|
|
16
|
+
import { generatePerformanceReport, writePerformanceReport } from './core/perf/perf.report.js';
|
|
17
|
+
import { recordPerformanceViolations } from './core/perf/perf.enforcer.js';
|
|
18
|
+
import { PipelineTracker, PIPELINE_STAGES } from './core/pipeline-tracker.js';
|
|
19
|
+
import { verifyRun } from './core/artifacts/verifier.js';
|
|
20
|
+
import { getArtifactVersions } from './core/artifacts/registry.js';
|
|
12
21
|
|
|
13
22
|
export async function scan(projectDir, url, manifestPath = null, scanBudgetOverride = null, safetyFlags = {}) {
|
|
23
|
+
// PHASE 21.5: Initialize failure ledger (runId will be set after learn)
|
|
24
|
+
const scanBudget = scanBudgetOverride || createScanBudgetWithProfile();
|
|
25
|
+
const failureLedger = new FailureLedger(null, projectDir);
|
|
26
|
+
|
|
27
|
+
// PHASE 21.9: Initialize performance monitor
|
|
28
|
+
const perfMonitor = createPerformanceMonitor();
|
|
29
|
+
perfMonitor.startPhase('LEARN');
|
|
30
|
+
|
|
31
|
+
// ARCHITECTURAL HARDENING: Initialize pipeline tracker early (before runId is known)
|
|
32
|
+
// We'll set runId on the tracker once it's generated
|
|
33
|
+
let pipelineTracker = null;
|
|
34
|
+
|
|
14
35
|
// If manifestPath is provided, read it first before learn() overwrites it
|
|
15
36
|
let loadedManifest = null;
|
|
16
37
|
if (manifestPath) {
|
|
@@ -19,26 +40,62 @@ export async function scan(projectDir, url, manifestPath = null, scanBudgetOverr
|
|
|
19
40
|
const manifestContent = JSON.parse(readFileSync(manifestPath, 'utf-8'));
|
|
20
41
|
loadedManifest = manifestContent;
|
|
21
42
|
} catch (e) {
|
|
22
|
-
//
|
|
43
|
+
// PHASE 21.5: Record I/O failure
|
|
44
|
+
const failure = createIOFailure(
|
|
45
|
+
FAILURE_CODE.IO_READ_FAILED,
|
|
46
|
+
`Failed to read manifest: ${e.message}`,
|
|
47
|
+
'scan',
|
|
48
|
+
{ manifestPath, error: e.message },
|
|
49
|
+
true // Recoverable - will fall through to learn
|
|
50
|
+
);
|
|
51
|
+
failureLedger.record(failure);
|
|
23
52
|
}
|
|
24
53
|
}
|
|
25
54
|
|
|
55
|
+
// ARCHITECTURAL HARDENING: Track LEARN stage
|
|
56
|
+
// Note: We can't track LEARN yet because we don't have runId, but we'll track it retroactively
|
|
57
|
+
const learnStartTime = Date.now();
|
|
26
58
|
const learnedManifest = await learn(projectDir);
|
|
59
|
+
const learnEndTime = Date.now();
|
|
60
|
+
perfMonitor.endPhase('LEARN');
|
|
27
61
|
|
|
28
|
-
//
|
|
62
|
+
// ARCHITECTURAL HARDENING: Explicit manifest merging with deterministic precedence
|
|
63
|
+
// Rule: Loaded manifest routes take precedence, but learned manifest provides truth and project type
|
|
29
64
|
let manifest;
|
|
30
65
|
if (loadedManifest) {
|
|
66
|
+
// Explicit merge: loaded manifest routes override learned, but learned provides truth
|
|
31
67
|
manifest = {
|
|
32
68
|
...learnedManifest,
|
|
69
|
+
// Routes from loaded manifest (if present) override learned routes
|
|
70
|
+
publicRoutes: loadedManifest.publicRoutes || learnedManifest.publicRoutes || [],
|
|
71
|
+
routes: loadedManifest.routes || learnedManifest.routes || [],
|
|
72
|
+
internalRoutes: loadedManifest.internalRoutes || learnedManifest.internalRoutes || [],
|
|
73
|
+
staticExpectations: loadedManifest.staticExpectations || learnedManifest.staticExpectations || [],
|
|
74
|
+
// Project type: prefer loaded, fallback to learned
|
|
33
75
|
projectType: loadedManifest.projectType || learnedManifest.projectType,
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
manifestPath: manifestPath
|
|
76
|
+
// Always preserve learned truth (this is the source of truth)
|
|
77
|
+
learnTruth: learnedManifest.learnTruth || {},
|
|
78
|
+
// Track manifest source for auditability
|
|
79
|
+
manifestSource: 'merged',
|
|
80
|
+
manifestPath: manifestPath,
|
|
81
|
+
mergeMetadata: {
|
|
82
|
+
loadedManifestProvided: true,
|
|
83
|
+
learnedManifestProvided: true,
|
|
84
|
+
routesSource: loadedManifest.routes ? 'loaded' : 'learned',
|
|
85
|
+
projectTypeSource: loadedManifest.projectType ? 'loaded' : 'learned'
|
|
86
|
+
}
|
|
39
87
|
};
|
|
40
88
|
} else {
|
|
41
|
-
manifest =
|
|
89
|
+
manifest = {
|
|
90
|
+
...learnedManifest,
|
|
91
|
+
manifestSource: 'learned',
|
|
92
|
+
mergeMetadata: {
|
|
93
|
+
loadedManifestProvided: false,
|
|
94
|
+
learnedManifestProvided: true,
|
|
95
|
+
routesSource: 'learned',
|
|
96
|
+
projectTypeSource: 'learned'
|
|
97
|
+
}
|
|
98
|
+
};
|
|
42
99
|
}
|
|
43
100
|
|
|
44
101
|
if (!url) {
|
|
@@ -53,18 +110,8 @@ export async function scan(projectDir, url, manifestPath = null, scanBudgetOverr
|
|
|
53
110
|
manifestPath: manifest.manifestPath
|
|
54
111
|
};
|
|
55
112
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if (!validation) {
|
|
59
|
-
// validateRoutes might return null if routes cannot be validated
|
|
60
|
-
validation = {
|
|
61
|
-
routesValidated: 0,
|
|
62
|
-
routesReachable: 0,
|
|
63
|
-
routesUnreachable: 0,
|
|
64
|
-
details: [],
|
|
65
|
-
warnings: []
|
|
66
|
-
};
|
|
67
|
-
}
|
|
113
|
+
// ARCHITECTURAL HARDENING: validateRoutes now always returns explicit validation object
|
|
114
|
+
const validation = await validateRoutes(manifestForValidation, url);
|
|
68
115
|
if (validation.warnings && validation.warnings.length > 0) {
|
|
69
116
|
if (!manifest.learnTruth.warnings) {
|
|
70
117
|
manifest.learnTruth.warnings = [];
|
|
@@ -72,9 +119,6 @@ export async function scan(projectDir, url, manifestPath = null, scanBudgetOverr
|
|
|
72
119
|
manifest.learnTruth.warnings.push(...validation.warnings);
|
|
73
120
|
}
|
|
74
121
|
|
|
75
|
-
// Use budget profile if no override provided
|
|
76
|
-
const scanBudget = scanBudgetOverride || createScanBudgetWithProfile();
|
|
77
|
-
|
|
78
122
|
// PHASE 5: Generate deterministic runId and create run manifest
|
|
79
123
|
const { getBaseOrigin } = await import('./observe/domain-boundary.js');
|
|
80
124
|
const baseOrigin = getBaseOrigin(url);
|
|
@@ -86,6 +130,41 @@ export async function scan(projectDir, url, manifestPath = null, scanBudgetOverr
|
|
|
86
130
|
manifestPath
|
|
87
131
|
});
|
|
88
132
|
|
|
133
|
+
// PHASE 21.5: Set runId in failure ledger
|
|
134
|
+
failureLedger.runId = runId;
|
|
135
|
+
|
|
136
|
+
// ARCHITECTURAL HARDENING: Initialize pipeline tracker now that we have runId
|
|
137
|
+
// PHASE 25: Pass runFingerprint params to PipelineTracker
|
|
138
|
+
const runFingerprintParams = url ? {
|
|
139
|
+
url,
|
|
140
|
+
projectDir,
|
|
141
|
+
manifestPath: null,
|
|
142
|
+
fixtureId: null
|
|
143
|
+
} : null;
|
|
144
|
+
pipelineTracker = new PipelineTracker(projectDir, runId, runFingerprintParams);
|
|
145
|
+
|
|
146
|
+
// Record LEARN stage retroactively (it completed before we had runId)
|
|
147
|
+
try {
|
|
148
|
+
pipelineTracker.stages[PIPELINE_STAGES.LEARN] = {
|
|
149
|
+
name: PIPELINE_STAGES.LEARN,
|
|
150
|
+
startedAt: new Date(learnStartTime).toISOString(),
|
|
151
|
+
completedAt: new Date(learnEndTime).toISOString(),
|
|
152
|
+
durationMs: learnEndTime - learnStartTime,
|
|
153
|
+
status: 'COMPLETE'
|
|
154
|
+
};
|
|
155
|
+
pipelineTracker.completedStages.push(PIPELINE_STAGES.LEARN);
|
|
156
|
+
pipelineTracker._writeMeta();
|
|
157
|
+
} catch (error) {
|
|
158
|
+
const failure = createInternalFailure(
|
|
159
|
+
FAILURE_CODE.INTERNAL_UNEXPECTED_ERROR,
|
|
160
|
+
`Failed to record LEARN stage: ${error.message}`,
|
|
161
|
+
'scan',
|
|
162
|
+
{ error: error.message },
|
|
163
|
+
error.stack
|
|
164
|
+
);
|
|
165
|
+
failureLedger.record(failure);
|
|
166
|
+
}
|
|
167
|
+
|
|
89
168
|
// Create run manifest at start of execution
|
|
90
169
|
const runManifest = createRunManifest(projectDir, runId, {
|
|
91
170
|
url,
|
|
@@ -97,15 +176,61 @@ export async function scan(projectDir, url, manifestPath = null, scanBudgetOverr
|
|
|
97
176
|
});
|
|
98
177
|
|
|
99
178
|
const usedManifestPath = manifestPath || manifest.manifestPath;
|
|
100
|
-
|
|
179
|
+
|
|
180
|
+
// ARCHITECTURAL HARDENING: Track OBSERVE stage
|
|
181
|
+
pipelineTracker.startStage(PIPELINE_STAGES.OBSERVE);
|
|
182
|
+
perfMonitor.startPhase('OBSERVE');
|
|
183
|
+
let observation;
|
|
184
|
+
try {
|
|
185
|
+
observation = await observe(url, usedManifestPath, scanBudget, safetyFlags, projectDir, runId);
|
|
186
|
+
perfMonitor.endPhase('OBSERVE');
|
|
187
|
+
pipelineTracker.completeStage(PIPELINE_STAGES.OBSERVE, {
|
|
188
|
+
pagesVisited: observation.observeTruth?.pagesVisited || 0,
|
|
189
|
+
interactionsExecuted: observation.observeTruth?.interactionsExecuted || 0
|
|
190
|
+
});
|
|
191
|
+
} catch (error) {
|
|
192
|
+
perfMonitor.endPhase('OBSERVE');
|
|
193
|
+
pipelineTracker.failStage(PIPELINE_STAGES.OBSERVE, error);
|
|
194
|
+
const failure = errorToFailure(
|
|
195
|
+
error,
|
|
196
|
+
FAILURE_CODE.OBSERVE_EXECUTION_FAILED,
|
|
197
|
+
'OBSERVE',
|
|
198
|
+
EXECUTION_PHASE.OBSERVE,
|
|
199
|
+
'observe',
|
|
200
|
+
{ manifestPath: usedManifestPath }
|
|
201
|
+
);
|
|
202
|
+
failureLedger.record(failure);
|
|
203
|
+
throw error;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Update performance monitor with observed metrics
|
|
207
|
+
if (observation.observeTruth) {
|
|
208
|
+
const pagesVisited = observation.observeTruth.pagesVisited || 0;
|
|
209
|
+
const interactionsExecuted = observation.observeTruth.interactionsExecuted || observation.observeTruth.candidatesSelected || 0;
|
|
210
|
+
|
|
211
|
+
for (let i = 0; i < pagesVisited; i++) {
|
|
212
|
+
perfMonitor.incrementPages();
|
|
213
|
+
}
|
|
214
|
+
for (let i = 0; i < interactionsExecuted; i++) {
|
|
215
|
+
perfMonitor.incrementInteractions();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
101
218
|
|
|
102
219
|
// Write a copy of the manifest into canonical run directory for replay integrity
|
|
103
220
|
try {
|
|
104
221
|
const { writeFileSync } = await import('fs');
|
|
105
222
|
const manifestCopyPath = getArtifactPath(projectDir, runId, 'manifest.json');
|
|
106
223
|
writeFileSync(manifestCopyPath, JSON.stringify(manifest, null, 2));
|
|
107
|
-
} catch {
|
|
108
|
-
//
|
|
224
|
+
} catch (error) {
|
|
225
|
+
// PHASE 21.5: Record I/O failure (WARNING - recoverable)
|
|
226
|
+
const failure = createIOFailure(
|
|
227
|
+
FAILURE_CODE.IO_WRITE_FAILED,
|
|
228
|
+
`Failed to write manifest copy: ${error.message}`,
|
|
229
|
+
'scan',
|
|
230
|
+
{ manifestPath, error: error.message },
|
|
231
|
+
true
|
|
232
|
+
);
|
|
233
|
+
failureLedger.record(failure);
|
|
109
234
|
}
|
|
110
235
|
|
|
111
236
|
// Create silence tracker from observation silences
|
|
@@ -114,27 +239,69 @@ export async function scan(projectDir, url, manifestPath = null, scanBudgetOverr
|
|
|
114
239
|
silenceTracker.recordBatch(observation.silences.entries);
|
|
115
240
|
}
|
|
116
241
|
|
|
117
|
-
|
|
242
|
+
// ARCHITECTURAL HARDENING: Track DETECT stage
|
|
243
|
+
pipelineTracker.startStage(PIPELINE_STAGES.DETECT);
|
|
244
|
+
perfMonitor.startPhase('DETECT');
|
|
245
|
+
let findings;
|
|
246
|
+
try {
|
|
247
|
+
findings = await detect(usedManifestPath, observation.tracesPath, validation, observation.expectationCoverageGaps || [], silenceTracker);
|
|
248
|
+
perfMonitor.endPhase('DETECT');
|
|
249
|
+
pipelineTracker.completeStage(PIPELINE_STAGES.DETECT, {
|
|
250
|
+
findingsCount: findings.findings?.length || 0,
|
|
251
|
+
coverageGapsCount: findings.coverageGaps?.length || 0
|
|
252
|
+
});
|
|
253
|
+
} catch (error) {
|
|
254
|
+
perfMonitor.endPhase('DETECT');
|
|
255
|
+
pipelineTracker.failStage(PIPELINE_STAGES.DETECT, error);
|
|
256
|
+
const failure = errorToFailure(
|
|
257
|
+
error,
|
|
258
|
+
FAILURE_CODE.DETECT_FINDING_PROCESSING_FAILED,
|
|
259
|
+
'DETECT',
|
|
260
|
+
EXECUTION_PHASE.DETECT,
|
|
261
|
+
'detect',
|
|
262
|
+
{ manifestPath: usedManifestPath }
|
|
263
|
+
);
|
|
264
|
+
failureLedger.record(failure);
|
|
265
|
+
throw error; // Re-throw BLOCKING failures
|
|
266
|
+
}
|
|
118
267
|
|
|
119
268
|
const learnTruthWithValidation = {
|
|
120
269
|
...manifest.learnTruth,
|
|
121
270
|
validation: validation
|
|
122
271
|
};
|
|
123
272
|
|
|
273
|
+
// ARCHITECTURAL HARDENING: Track WRITE stage
|
|
274
|
+
pipelineTracker.startStage(PIPELINE_STAGES.WRITE);
|
|
124
275
|
const runDir = getRunArtifactDir(projectDir, runId);
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
276
|
+
let scanSummary;
|
|
277
|
+
try {
|
|
278
|
+
scanSummary = await writeScanSummary(
|
|
279
|
+
projectDir,
|
|
280
|
+
url,
|
|
281
|
+
manifest.projectType,
|
|
282
|
+
learnTruthWithValidation,
|
|
283
|
+
observation.observeTruth,
|
|
284
|
+
findings.detectTruth,
|
|
285
|
+
manifest.manifestPath,
|
|
286
|
+
observation.tracesPath,
|
|
287
|
+
findings.findingsPath,
|
|
288
|
+
runDir,
|
|
289
|
+
findings.findings // PHASE 7: Pass findings array for decision snapshot
|
|
290
|
+
);
|
|
291
|
+
pipelineTracker.completeStage(PIPELINE_STAGES.WRITE);
|
|
292
|
+
} catch (error) {
|
|
293
|
+
pipelineTracker.failStage(PIPELINE_STAGES.WRITE, error);
|
|
294
|
+
const failure = errorToFailure(
|
|
295
|
+
error,
|
|
296
|
+
FAILURE_CODE.IO_WRITE_FAILED,
|
|
297
|
+
'WRITE',
|
|
298
|
+
EXECUTION_PHASE.WRITE,
|
|
299
|
+
'writeScanSummary',
|
|
300
|
+
{ runDir }
|
|
301
|
+
);
|
|
302
|
+
failureLedger.record(failure);
|
|
303
|
+
throw error;
|
|
304
|
+
}
|
|
138
305
|
|
|
139
306
|
// Compute observation summary from scan results (not a verdict)
|
|
140
307
|
// Pass observation object (which includes traces) to observation engine
|
|
@@ -163,9 +330,204 @@ export async function scan(projectDir, url, manifestPath = null, scanBudgetOverr
|
|
|
163
330
|
);
|
|
164
331
|
observationSummary.evidenceIndexPath = evidenceIndexPath;
|
|
165
332
|
|
|
166
|
-
// PHASE
|
|
167
|
-
|
|
168
|
-
|
|
333
|
+
// PHASE 21.4: Write policy artifacts
|
|
334
|
+
try {
|
|
335
|
+
const { writeFileSync } = await import('fs');
|
|
336
|
+
const { loadGuardrailsPolicy, getPolicyReport: getGuardrailsPolicyReport } = await import('./core/guardrails/policy.loader.js');
|
|
337
|
+
const { loadConfidencePolicy, getPolicyReport: getConfidencePolicyReport } = await import('./core/confidence/confidence.loader.js');
|
|
338
|
+
|
|
339
|
+
const guardrailsPolicy = loadGuardrailsPolicy();
|
|
340
|
+
const confidencePolicy = loadConfidencePolicy();
|
|
341
|
+
|
|
342
|
+
const guardrailsPolicyPath = getArtifactPath(projectDir, failureLedger.runId, 'guardrails.policy.json');
|
|
343
|
+
const confidencePolicyPath = getArtifactPath(projectDir, failureLedger.runId, 'confidence.policy.json');
|
|
344
|
+
|
|
345
|
+
writeFileSync(guardrailsPolicyPath, JSON.stringify({
|
|
346
|
+
...getGuardrailsPolicyReport(guardrailsPolicy),
|
|
347
|
+
rules: guardrailsPolicy.rules.map(r => ({
|
|
348
|
+
id: r.id,
|
|
349
|
+
category: r.category,
|
|
350
|
+
action: r.action,
|
|
351
|
+
mandatory: r.mandatory
|
|
352
|
+
}))
|
|
353
|
+
}, null, 2));
|
|
354
|
+
|
|
355
|
+
writeFileSync(confidencePolicyPath, JSON.stringify(getConfidencePolicyReport(confidencePolicy), null, 2));
|
|
356
|
+
} catch (error) {
|
|
357
|
+
// PHASE 21.5: Record I/O failure (WARNING - recoverable)
|
|
358
|
+
const failure = createIOFailure(
|
|
359
|
+
FAILURE_CODE.IO_WRITE_FAILED,
|
|
360
|
+
`Failed to write policy artifacts: ${error.message}`,
|
|
361
|
+
'scan',
|
|
362
|
+
{ error: error.message },
|
|
363
|
+
true
|
|
364
|
+
);
|
|
365
|
+
failureLedger.record(failure);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
// ARCHITECTURAL HARDENING: Write failure ledger with fallback to run.status.json
|
|
370
|
+
let ledgerPath;
|
|
371
|
+
try {
|
|
372
|
+
ledgerPath = failureLedger.write();
|
|
373
|
+
} catch (error) {
|
|
374
|
+
// If ledger write fails, write fallback entry to run.status.json
|
|
375
|
+
const failure = createInternalFailure(
|
|
376
|
+
FAILURE_CODE.INTERNAL_UNEXPECTED_ERROR,
|
|
377
|
+
`Failed to write failure ledger: ${error.message}`,
|
|
378
|
+
'scan',
|
|
379
|
+
{ error: error.message },
|
|
380
|
+
error.stack
|
|
381
|
+
);
|
|
382
|
+
failureLedger.record(failure);
|
|
383
|
+
|
|
384
|
+
// Write fallback failure entry to run.status.json
|
|
385
|
+
try {
|
|
386
|
+
const { writeFileSync, readFileSync } = await import('fs');
|
|
387
|
+
const runStatusPath = getArtifactPath(projectDir, runId, 'run.status.json');
|
|
388
|
+
let runStatus = { status: 'FAILED', failures: [] };
|
|
389
|
+
try {
|
|
390
|
+
const existing = readFileSync(runStatusPath, 'utf-8');
|
|
391
|
+
runStatus = JSON.parse(existing);
|
|
392
|
+
} catch {
|
|
393
|
+
// File doesn't exist or is invalid, use defaults
|
|
394
|
+
}
|
|
395
|
+
runStatus.failures = runStatus.failures || [];
|
|
396
|
+
runStatus.failures.push({
|
|
397
|
+
code: FAILURE_CODE.INTERNAL_UNEXPECTED_ERROR,
|
|
398
|
+
message: `Failed to write failure ledger: ${error.message}`,
|
|
399
|
+
timestamp: new Date().toISOString(),
|
|
400
|
+
recoverable: false
|
|
401
|
+
});
|
|
402
|
+
runStatus.ledgerWriteFailed = true;
|
|
403
|
+
runStatus.ledgerWriteError = error.message;
|
|
404
|
+
writeFileSync(runStatusPath, JSON.stringify(runStatus, null, 2) + '\n', 'utf-8');
|
|
405
|
+
} catch (fallbackError) {
|
|
406
|
+
// Even fallback write failed - log only
|
|
407
|
+
console.error('CRITICAL: Failed to write failure ledger and fallback:', error.message, fallbackError.message);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// PHASE 21.9: Generate and write performance report
|
|
412
|
+
try {
|
|
413
|
+
perfMonitor.stop();
|
|
414
|
+
const monitorReport = perfMonitor.getFinalReport();
|
|
415
|
+
const profileName = process.env.VERAX_BUDGET_PROFILE || 'STANDARD';
|
|
416
|
+
const perfReport = generatePerformanceReport(projectDir, runId, monitorReport, profileName);
|
|
417
|
+
writePerformanceReport(projectDir, runId, perfReport);
|
|
418
|
+
|
|
419
|
+
// Record performance violations in failure ledger
|
|
420
|
+
recordPerformanceViolations(projectDir, runId, failureLedger);
|
|
421
|
+
} catch (error) {
|
|
422
|
+
// Record performance report failure (WARNING - recoverable)
|
|
423
|
+
const failure = createIOFailure(
|
|
424
|
+
FAILURE_CODE.IO_WRITE_FAILED,
|
|
425
|
+
`Failed to write performance report: ${error.message}`,
|
|
426
|
+
'scan',
|
|
427
|
+
{ error: error.message },
|
|
428
|
+
true
|
|
429
|
+
);
|
|
430
|
+
failureLedger.record(failure);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// ARCHITECTURAL HARDENING: Track VERIFY stage
|
|
434
|
+
pipelineTracker.startStage(PIPELINE_STAGES.VERIFY);
|
|
435
|
+
let verification = null;
|
|
436
|
+
try {
|
|
437
|
+
const artifactVersions = getArtifactVersions();
|
|
438
|
+
verification = verifyRun(runDir, artifactVersions);
|
|
439
|
+
pipelineTracker.completeStage(PIPELINE_STAGES.VERIFY, {
|
|
440
|
+
ok: verification.ok,
|
|
441
|
+
errorsCount: verification.errors.length,
|
|
442
|
+
warningsCount: verification.warnings.length
|
|
443
|
+
});
|
|
444
|
+
} catch (error) {
|
|
445
|
+
pipelineTracker.failStage(PIPELINE_STAGES.VERIFY, error);
|
|
446
|
+
const failure = errorToFailure(
|
|
447
|
+
error,
|
|
448
|
+
FAILURE_CODE.VERIFICATION_FAILED,
|
|
449
|
+
'VERIFY',
|
|
450
|
+
EXECUTION_PHASE.VERIFY,
|
|
451
|
+
'verifyRun',
|
|
452
|
+
{ runDir }
|
|
453
|
+
);
|
|
454
|
+
failureLedger.record(failure);
|
|
455
|
+
// Verification failure is not blocking, but we record it
|
|
456
|
+
verification = {
|
|
457
|
+
ok: false,
|
|
458
|
+
errors: [error.message],
|
|
459
|
+
warnings: [],
|
|
460
|
+
verifiedAt: new Date().toISOString()
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// ARCHITECTURAL HARDENING: Track VERDICT stage (only after verification completes)
|
|
465
|
+
pipelineTracker.startStage(PIPELINE_STAGES.VERDICT);
|
|
466
|
+
try {
|
|
467
|
+
// PHASE 5: Compute artifact hashes and update run manifest
|
|
468
|
+
const artifactHashes = computeArtifactHashes(projectDir, runId);
|
|
469
|
+
updateRunManifestHashes(projectDir, runId, artifactHashes);
|
|
470
|
+
|
|
471
|
+
// Verdict computation (determinism and evidence law checks)
|
|
472
|
+
let determinismVerdict = null;
|
|
473
|
+
let evidenceLawViolated = false;
|
|
474
|
+
try {
|
|
475
|
+
const decisionsPath = getArtifactPath(projectDir, runId, 'decisions.json');
|
|
476
|
+
const { readFileSync, existsSync } = await import('fs');
|
|
477
|
+
if (existsSync(decisionsPath)) {
|
|
478
|
+
const decisions = JSON.parse(readFileSync(decisionsPath, 'utf-8'));
|
|
479
|
+
const { DecisionRecorder } = await import('./core/determinism-model.js');
|
|
480
|
+
const recorder = DecisionRecorder.fromExport(decisions);
|
|
481
|
+
const { computeDeterminismVerdict } = await import('./core/determinism/contract.js');
|
|
482
|
+
const verdict = computeDeterminismVerdict(recorder);
|
|
483
|
+
determinismVerdict = verdict.verdict;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Check for Evidence Law violations (incomplete evidence with CONFIRMED findings)
|
|
487
|
+
if (findings.findings) {
|
|
488
|
+
for (const finding of findings.findings) {
|
|
489
|
+
if ((finding.severity === 'CONFIRMED' || finding.status === 'CONFIRMED') &&
|
|
490
|
+
finding.evidencePackage && !finding.evidencePackage.isComplete) {
|
|
491
|
+
evidenceLawViolated = true;
|
|
492
|
+
break;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
} catch (error) {
|
|
497
|
+
// Record failure but don't block
|
|
498
|
+
const failure = createInternalFailure(
|
|
499
|
+
FAILURE_CODE.INTERNAL_UNEXPECTED_ERROR,
|
|
500
|
+
`Failed to compute determinism verdict: ${error.message}`,
|
|
501
|
+
'scan',
|
|
502
|
+
{ error: error.message },
|
|
503
|
+
error.stack
|
|
504
|
+
);
|
|
505
|
+
failureLedger.record(failure);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
pipelineTracker.completeStage(PIPELINE_STAGES.VERDICT, {
|
|
509
|
+
determinismVerdict,
|
|
510
|
+
evidenceLawViolated,
|
|
511
|
+
verificationOk: verification?.ok || false
|
|
512
|
+
});
|
|
513
|
+
} catch (error) {
|
|
514
|
+
pipelineTracker.failStage(PIPELINE_STAGES.VERDICT, error);
|
|
515
|
+
const failure = errorToFailure(
|
|
516
|
+
error,
|
|
517
|
+
FAILURE_CODE.VERDICT_COMPUTATION_FAILED,
|
|
518
|
+
'VERDICT',
|
|
519
|
+
EXECUTION_PHASE.VERDICT,
|
|
520
|
+
'computeVerdict',
|
|
521
|
+
{}
|
|
522
|
+
);
|
|
523
|
+
failureLedger.record(failure);
|
|
524
|
+
throw error;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Extract verdict data from pipeline tracker
|
|
528
|
+
const verdictStage = pipelineTracker.getStage(PIPELINE_STAGES.VERDICT);
|
|
529
|
+
const determinismVerdict = verdictStage?.determinismVerdict || null;
|
|
530
|
+
const evidenceLawViolated = verdictStage?.evidenceLawViolated || false;
|
|
169
531
|
|
|
170
532
|
return {
|
|
171
533
|
manifest,
|
|
@@ -175,8 +537,14 @@ export async function scan(projectDir, url, manifestPath = null, scanBudgetOverr
|
|
|
175
537
|
validation,
|
|
176
538
|
coverageGaps: findings.coverageGaps || [],
|
|
177
539
|
observationSummary,
|
|
178
|
-
runId,
|
|
179
|
-
runManifest
|
|
540
|
+
runId: failureLedger.runId,
|
|
541
|
+
runManifest,
|
|
542
|
+
failureLedger: failureLedger.export(),
|
|
543
|
+
ledgerPath,
|
|
544
|
+
determinismVerdict,
|
|
545
|
+
evidenceLawViolated,
|
|
546
|
+
verification,
|
|
547
|
+
pipelineStages: pipelineTracker.getAllStages()
|
|
180
548
|
};
|
|
181
549
|
}
|
|
182
550
|
|