@veraxhq/verax 0.2.0 → 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 +15 -5
- package/src/cli/commands/baseline.js +104 -0
- package/src/cli/commands/default.js +323 -111
- package/src/cli/commands/doctor.js +36 -4
- 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 +498 -103
- package/src/cli/commands/security-check.js +211 -0
- package/src/cli/commands/truth.js +114 -0
- package/src/cli/entry.js +305 -68
- 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/detection-engine.js +4 -3
- 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/events.js +76 -0
- package/src/cli/util/expectation-extractor.js +380 -74
- package/src/cli/util/findings-writer.js +126 -15
- package/src/cli/util/learn-writer.js +3 -1
- package/src/cli/util/observation-engine.js +69 -23
- package/src/cli/util/observe-writer.js +3 -1
- package/src/cli/util/paths.js +6 -14
- package/src/cli/util/project-discovery.js +23 -0
- package/src/cli/util/project-writer.js +3 -1
- package/src/cli/util/redact.js +2 -2
- package/src/cli/util/run-resolver.js +64 -0
- package/src/cli/util/runtime-budget.js +147 -0
- package/src/cli/util/source-requirement.js +55 -0
- package/src/cli/util/summary-writer.js +13 -1
- 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/types/global.d.ts +28 -0
- package/src/types/ts-ast.d.ts +24 -0
- package/src/verax/cli/doctor.js +2 -2
- package/src/verax/cli/finding-explainer.js +56 -3
- package/src/verax/cli/init.js +1 -1
- package/src/verax/cli/url-safety.js +12 -2
- package/src/verax/cli/wizard.js +13 -2
- 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/budget-engine.js +1 -1
- 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 +31 -4
- 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/determinism-model.js +35 -6
- 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/incremental-store.js +15 -7
- 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/replay-validator.js +4 -4
- package/src/verax/core/replay.js +1 -1
- 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/silence-impact.js +1 -1
- package/src/verax/core/silence-model.js +9 -7
- package/src/verax/core/truth/truth.certificate.js +250 -0
- package/src/verax/core/ui-feedback-intelligence.js +515 -0
- package/src/verax/detect/comparison.js +8 -3
- package/src/verax/detect/confidence-engine.js +645 -57
- package/src/verax/detect/confidence-helper.js +33 -0
- package/src/verax/detect/detection-engine.js +19 -2
- package/src/verax/detect/dynamic-route-findings.js +335 -0
- package/src/verax/detect/evidence-index.js +15 -65
- package/src/verax/detect/expectation-chain-detector.js +417 -0
- package/src/verax/detect/expectation-model.js +56 -3
- package/src/verax/detect/explanation-helpers.js +1 -1
- package/src/verax/detect/finding-detector.js +2 -2
- package/src/verax/detect/findings-writer.js +149 -20
- package/src/verax/detect/flow-detector.js +4 -4
- package/src/verax/detect/index.js +265 -15
- package/src/verax/detect/interactive-findings.js +3 -4
- package/src/verax/detect/journey-stall-detector.js +558 -0
- package/src/verax/detect/route-findings.js +218 -0
- package/src/verax/detect/signal-mapper.js +2 -2
- package/src/verax/detect/skip-classifier.js +4 -4
- package/src/verax/detect/ui-feedback-findings.js +207 -0
- package/src/verax/detect/verdict-engine.js +61 -9
- package/src/verax/detect/view-switch-correlator.js +242 -0
- package/src/verax/flow/flow-engine.js +3 -2
- package/src/verax/flow/flow-spec.js +1 -2
- package/src/verax/index.js +413 -33
- package/src/verax/intel/effect-detector.js +1 -1
- package/src/verax/intel/index.js +2 -2
- package/src/verax/intel/route-extractor.js +3 -3
- package/src/verax/intel/vue-navigation-extractor.js +81 -18
- package/src/verax/intel/vue-router-extractor.js +4 -2
- package/src/verax/learn/action-contract-extractor.js +684 -66
- package/src/verax/learn/ast-contract-extractor.js +53 -1
- package/src/verax/learn/index.js +36 -2
- package/src/verax/learn/manifest-writer.js +28 -14
- package/src/verax/learn/route-extractor.js +1 -1
- package/src/verax/learn/route-validator.js +12 -8
- package/src/verax/learn/state-extractor.js +1 -1
- package/src/verax/learn/static-extractor-navigation.js +1 -1
- package/src/verax/learn/static-extractor-validation.js +2 -2
- package/src/verax/learn/static-extractor.js +8 -7
- package/src/verax/learn/ts-contract-resolver.js +14 -12
- package/src/verax/observe/browser.js +22 -3
- package/src/verax/observe/console-sensor.js +2 -2
- package/src/verax/observe/expectation-executor.js +2 -1
- package/src/verax/observe/focus-sensor.js +1 -1
- package/src/verax/observe/human-driver.js +29 -10
- package/src/verax/observe/index.js +92 -844
- package/src/verax/observe/interaction-discovery.js +27 -15
- package/src/verax/observe/interaction-runner.js +31 -14
- package/src/verax/observe/loading-sensor.js +6 -0
- package/src/verax/observe/navigation-sensor.js +1 -1
- 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/settle.js +1 -0
- package/src/verax/observe/state-sensor.js +8 -4
- package/src/verax/observe/state-ui-sensor.js +7 -1
- package/src/verax/observe/traces-writer.js +27 -16
- package/src/verax/observe/ui-feedback-detector.js +742 -0
- package/src/verax/observe/ui-signal-sensor.js +155 -2
- package/src/verax/scan-summary-writer.js +46 -9
- package/src/verax/shared/artifact-manager.js +9 -6
- package/src/verax/shared/budget-profiles.js +2 -2
- package/src/verax/shared/caching.js +1 -1
- package/src/verax/shared/config-loader.js +1 -2
- package/src/verax/shared/css-spinner-rules.js +204 -0
- package/src/verax/shared/dynamic-route-utils.js +12 -6
- package/src/verax/shared/retry-policy.js +1 -6
- package/src/verax/shared/root-artifacts.js +1 -1
- package/src/verax/shared/view-switch-rules.js +208 -0
- package/src/verax/shared/zip-artifacts.js +1 -0
- package/src/verax/validate/context-validator.js +1 -1
- package/src/verax/observe/index.js.backup +0 -1
- package/src/verax/validate/context-validator.js.bak +0 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 25 — Determinism Contract Writer
|
|
3
|
+
*
|
|
4
|
+
* Writes determinism.contract.json artifact capturing adaptive events,
|
|
5
|
+
* retries, timing adjustments, and other non-deterministic behaviors.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { writeFileSync } from 'fs';
|
|
9
|
+
import { resolve } from 'path';
|
|
10
|
+
import { DecisionRecorder } from '../determinism-model.js';
|
|
11
|
+
import { ARTIFACT_REGISTRY } from '../artifacts/registry.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Write determinism contract artifact
|
|
15
|
+
*
|
|
16
|
+
* @param {string} runDir - Absolute run directory path
|
|
17
|
+
* @param {DecisionRecorder} decisionRecorder - Decision recorder instance
|
|
18
|
+
* @returns {string} Path to written contract
|
|
19
|
+
*/
|
|
20
|
+
export function writeDeterminismContract(runDir, decisionRecorder) {
|
|
21
|
+
const contractPath = resolve(runDir, ARTIFACT_REGISTRY.determinismContract.filename);
|
|
22
|
+
|
|
23
|
+
const adaptiveEvents = [];
|
|
24
|
+
const retryEvents = [];
|
|
25
|
+
const timingAdjustments = [];
|
|
26
|
+
|
|
27
|
+
if (decisionRecorder) {
|
|
28
|
+
// Extract adaptive stabilization events
|
|
29
|
+
const adaptiveStabilization = decisionRecorder.getByCategory('ADAPTIVE_STABILIZATION');
|
|
30
|
+
for (const decision of adaptiveStabilization) {
|
|
31
|
+
adaptiveEvents.push({
|
|
32
|
+
decision_id: decision.decision_id,
|
|
33
|
+
category: decision.category,
|
|
34
|
+
timestamp: decision.timestamp,
|
|
35
|
+
reason: decision.reason,
|
|
36
|
+
context: decision.context || null,
|
|
37
|
+
chosen_value: decision.chosen_value,
|
|
38
|
+
inputs: decision.inputs || {}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Extract retry events
|
|
43
|
+
const retries = decisionRecorder.getByCategory('RETRY');
|
|
44
|
+
for (const decision of retries) {
|
|
45
|
+
retryEvents.push({
|
|
46
|
+
decision_id: decision.decision_id,
|
|
47
|
+
category: decision.category,
|
|
48
|
+
timestamp: decision.timestamp,
|
|
49
|
+
reason: decision.reason,
|
|
50
|
+
context: decision.context || null,
|
|
51
|
+
chosen_value: decision.chosen_value,
|
|
52
|
+
inputs: decision.inputs || {}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Extract timing adjustments (timeout decisions)
|
|
57
|
+
const timeouts = decisionRecorder.getByCategory('TIMEOUT');
|
|
58
|
+
for (const decision of timeouts) {
|
|
59
|
+
timingAdjustments.push({
|
|
60
|
+
decision_id: decision.decision_id,
|
|
61
|
+
category: decision.category,
|
|
62
|
+
timestamp: decision.timestamp,
|
|
63
|
+
reason: decision.reason,
|
|
64
|
+
context: decision.context || null,
|
|
65
|
+
chosen_value: decision.chosen_value,
|
|
66
|
+
inputs: decision.inputs || {}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const contract = {
|
|
72
|
+
version: 1,
|
|
73
|
+
generatedAt: new Date().toISOString(),
|
|
74
|
+
adaptiveEvents,
|
|
75
|
+
retryEvents,
|
|
76
|
+
timingAdjustments,
|
|
77
|
+
summary: {
|
|
78
|
+
adaptiveEventsCount: adaptiveEvents.length,
|
|
79
|
+
retryEventsCount: retryEvents.length,
|
|
80
|
+
timingAdjustmentsCount: timingAdjustments.length,
|
|
81
|
+
isDeterministic: adaptiveEvents.length === 0 && retryEvents.length === 0
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
writeFileSync(contractPath, JSON.stringify(contract, null, 2), 'utf8');
|
|
86
|
+
|
|
87
|
+
return contractPath;
|
|
88
|
+
}
|
|
89
|
+
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 21.2 — Determinism Truth Lock: Strict Contract
|
|
3
|
+
*
|
|
4
|
+
* DETERMINISM CONTRACT:
|
|
5
|
+
*
|
|
6
|
+
* DETERMINISTIC means:
|
|
7
|
+
* - Same inputs (source code, URL, config)
|
|
8
|
+
* - Same environment (browser, OS, Node version)
|
|
9
|
+
* - Same config (budget, timeouts, flags)
|
|
10
|
+
* → identical normalized artifacts (findings, traces, evidence)
|
|
11
|
+
*
|
|
12
|
+
* NON_DETERMINISTIC means:
|
|
13
|
+
* - Any adaptive behavior occurred (adaptive stabilization, retries, dynamic timeouts)
|
|
14
|
+
* - Any timing variance that affects results
|
|
15
|
+
* - Any environment-dependent behavior
|
|
16
|
+
* - Tracking adaptive decisions is NOT determinism
|
|
17
|
+
*
|
|
18
|
+
* HARD RULE: If adaptiveEvents.length > 0 → verdict MUST be NON_DETERMINISTIC
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* PHASE 21.2: Determinism verdict (binary)
|
|
23
|
+
* PHASE 25: Extended with expected/unexpected distinction
|
|
24
|
+
*/
|
|
25
|
+
export const DETERMINISM_VERDICT = {
|
|
26
|
+
DETERMINISTIC: 'DETERMINISTIC',
|
|
27
|
+
NON_DETERMINISTIC_EXPECTED: 'NON_DETERMINISTIC_EXPECTED',
|
|
28
|
+
NON_DETERMINISTIC_UNEXPECTED: 'NON_DETERMINISTIC_UNEXPECTED',
|
|
29
|
+
NON_DETERMINISTIC: 'NON_DETERMINISTIC' // Backward compatibility
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* PHASE 21.2: Determinism reason codes (stable)
|
|
34
|
+
* PHASE 25: Extended with new reason codes
|
|
35
|
+
*/
|
|
36
|
+
export const DETERMINISM_REASON = {
|
|
37
|
+
ADAPTIVE_STABILIZATION_USED: 'ADAPTIVE_STABILIZATION_USED',
|
|
38
|
+
RETRY_TRIGGERED: 'RETRY_TRIGGERED',
|
|
39
|
+
TIMING_VARIANCE: 'TIMING_VARIANCE',
|
|
40
|
+
TRUNCATION_OCCURRED: 'TRUNCATION_OCCURRED',
|
|
41
|
+
ENVIRONMENT_VARIANCE: 'ENVIRONMENT_VARIANCE',
|
|
42
|
+
NO_ADAPTIVE_EVENTS: 'NO_ADAPTIVE_EVENTS',
|
|
43
|
+
RUN_FINGERPRINT_MISMATCH: 'RUN_FINGERPRINT_MISMATCH',
|
|
44
|
+
ARTIFACT_DIFF_DETECTED: 'ARTIFACT_DIFF_DETECTED',
|
|
45
|
+
VERIFIER_ERRORS_DETECTED: 'VERIFIER_ERRORS_DETECTED',
|
|
46
|
+
EXPECTED_ADAPTIVE_BEHAVIOR: 'EXPECTED_ADAPTIVE_BEHAVIOR',
|
|
47
|
+
UNEXPECTED_DIFF_WITHOUT_ADAPTIVE: 'UNEXPECTED_DIFF_WITHOUT_ADAPTIVE'
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* PHASE 21.2: Adaptive event categories that break determinism
|
|
52
|
+
*/
|
|
53
|
+
export const ADAPTIVE_EVENT_CATEGORIES = [
|
|
54
|
+
'ADAPTIVE_STABILIZATION',
|
|
55
|
+
'RETRY',
|
|
56
|
+
'TRUNCATION'
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* PHASE 21.2: Check if a decision category breaks determinism
|
|
61
|
+
*
|
|
62
|
+
* @param {string} category - Decision category
|
|
63
|
+
* @returns {boolean} True if this category breaks determinism
|
|
64
|
+
*/
|
|
65
|
+
export function isAdaptiveEventCategory(category) {
|
|
66
|
+
return ADAPTIVE_EVENT_CATEGORIES.includes(category);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* PHASE 21.2: Compute determinism verdict from DecisionRecorder
|
|
71
|
+
*
|
|
72
|
+
* HARD RULE: If any adaptive event occurred → NON_DETERMINISTIC
|
|
73
|
+
*
|
|
74
|
+
* @param {DecisionRecorder} decisionRecorder - Decision recorder instance
|
|
75
|
+
* @returns {Object} { verdict, reasons, adaptiveEvents }
|
|
76
|
+
*/
|
|
77
|
+
export function computeDeterminismVerdict(decisionRecorder) {
|
|
78
|
+
if (!decisionRecorder) {
|
|
79
|
+
return {
|
|
80
|
+
verdict: DETERMINISM_VERDICT.NON_DETERMINISTIC,
|
|
81
|
+
reasons: [DETERMINISM_REASON.ENVIRONMENT_VARIANCE],
|
|
82
|
+
adaptiveEvents: [],
|
|
83
|
+
message: 'DecisionRecorder not available - cannot verify determinism'
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const adaptiveEvents = [];
|
|
88
|
+
const reasons = [];
|
|
89
|
+
|
|
90
|
+
// Check for adaptive stabilization
|
|
91
|
+
const adaptiveStabilization = decisionRecorder.getByCategory('ADAPTIVE_STABILIZATION');
|
|
92
|
+
const adaptiveExtensions = adaptiveStabilization.filter(d =>
|
|
93
|
+
d.decision_id === 'ADAPTIVE_STABILIZATION_extended'
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
if (adaptiveExtensions.length > 0) {
|
|
97
|
+
adaptiveEvents.push(...adaptiveExtensions);
|
|
98
|
+
reasons.push(DETERMINISM_REASON.ADAPTIVE_STABILIZATION_USED);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Check for retries
|
|
102
|
+
const retries = decisionRecorder.getByCategory('RETRY');
|
|
103
|
+
if (retries.length > 0) {
|
|
104
|
+
adaptiveEvents.push(...retries);
|
|
105
|
+
reasons.push(DETERMINISM_REASON.RETRY_TRIGGERED);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Check for truncations
|
|
109
|
+
const truncations = decisionRecorder.getByCategory('TRUNCATION');
|
|
110
|
+
if (truncations.length > 0) {
|
|
111
|
+
adaptiveEvents.push(...truncations);
|
|
112
|
+
reasons.push(DETERMINISM_REASON.TRUNCATION_OCCURRED);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// HARD RULE: If any adaptive events → NON_DETERMINISTIC
|
|
116
|
+
if (adaptiveEvents.length > 0) {
|
|
117
|
+
return {
|
|
118
|
+
verdict: DETERMINISM_VERDICT.NON_DETERMINISTIC,
|
|
119
|
+
reasons,
|
|
120
|
+
adaptiveEvents: adaptiveEvents.map(e => ({
|
|
121
|
+
decision_id: e.decision_id,
|
|
122
|
+
category: e.category,
|
|
123
|
+
timestamp: e.timestamp,
|
|
124
|
+
reason: e.reason,
|
|
125
|
+
context: e.context || null
|
|
126
|
+
})),
|
|
127
|
+
message: `Non-deterministic: ${adaptiveEvents.length} adaptive event(s) detected`
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// No adaptive events → DETERMINISTIC
|
|
132
|
+
return {
|
|
133
|
+
verdict: DETERMINISM_VERDICT.DETERMINISTIC,
|
|
134
|
+
reasons: [DETERMINISM_REASON.NO_ADAPTIVE_EVENTS],
|
|
135
|
+
adaptiveEvents: [],
|
|
136
|
+
message: 'Deterministic: No adaptive events detected'
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 18 — Determinism Diff Builder
|
|
3
|
+
*
|
|
4
|
+
* Generates structured diffs between normalized artifacts from different runs.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { computeFindingIdentity } from './finding-identity.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* PHASE 18: Diff reason codes
|
|
11
|
+
* PHASE 25: Extended with new reason codes
|
|
12
|
+
*/
|
|
13
|
+
export const DIFF_REASON = {
|
|
14
|
+
MISSING_ARTIFACT: 'DET_DIFF_MISSING_ARTIFACT',
|
|
15
|
+
SCHEMA_MISMATCH: 'DET_DIFF_SCHEMA_MISMATCH',
|
|
16
|
+
FINDING_ADDED: 'DET_DIFF_FINDING_ADDED',
|
|
17
|
+
FINDING_REMOVED: 'DET_DIFF_FINDING_REMOVED',
|
|
18
|
+
FINDING_STATUS_CHANGED: 'DET_DIFF_FINDING_STATUS_CHANGED',
|
|
19
|
+
FINDING_SEVERITY_CHANGED: 'DET_DIFF_FINDING_SEVERITY_CHANGED',
|
|
20
|
+
CONFIDENCE_CHANGED: 'DET_DIFF_CONFIDENCE_CHANGED',
|
|
21
|
+
CONFIDENCE_REASONS_CHANGED: 'DET_DIFF_CONFIDENCE_REASONS_CHANGED',
|
|
22
|
+
GUARDRAILS_CHANGED: 'DET_DIFF_GUARDRAILS_CHANGED',
|
|
23
|
+
EVIDENCE_COMPLETENESS_CHANGED: 'DET_DIFF_EVIDENCE_COMPLETENESS_CHANGED',
|
|
24
|
+
EVIDENCE_MISSING: 'DET_DIFF_EVIDENCE_MISSING',
|
|
25
|
+
OBSERVATION_COUNT_CHANGED: 'DET_DIFF_OBSERVATION_COUNT_CHANGED',
|
|
26
|
+
FIELD_VALUE_CHANGED: 'DET_DIFF_FIELD_VALUE_CHANGED',
|
|
27
|
+
RUN_FINGERPRINT_MISMATCH: 'DET_DIFF_RUN_FINGERPRINT_MISMATCH',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* PHASE 18: Diff categories
|
|
32
|
+
*/
|
|
33
|
+
export const DIFF_CATEGORY = {
|
|
34
|
+
FINDINGS: 'FINDINGS',
|
|
35
|
+
EXPECTATIONS: 'EXPECTATIONS',
|
|
36
|
+
OBSERVATIONS: 'OBSERVATIONS',
|
|
37
|
+
EVIDENCE: 'EVIDENCE',
|
|
38
|
+
STATUS: 'STATUS',
|
|
39
|
+
ARTIFACTS: 'ARTIFACTS',
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* PHASE 18: Diff severity
|
|
44
|
+
*/
|
|
45
|
+
export const DIFF_SEVERITY = {
|
|
46
|
+
BLOCKER: 'BLOCKER',
|
|
47
|
+
WARN: 'WARN',
|
|
48
|
+
INFO: 'INFO',
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* PHASE 18: Diff artifacts
|
|
53
|
+
*
|
|
54
|
+
* @param {Object} artifactA - First artifact (normalized)
|
|
55
|
+
* @param {Object} artifactB - Second artifact (normalized)
|
|
56
|
+
* @param {string} artifactName - Name of artifact
|
|
57
|
+
* @param {Map} findingIdentityMap - Map of finding identity to finding (for matching)
|
|
58
|
+
* @returns {Array} Array of diff objects
|
|
59
|
+
*/
|
|
60
|
+
export function diffArtifacts(artifactA, artifactB, artifactName, findingIdentityMap = null) {
|
|
61
|
+
const diffs = [];
|
|
62
|
+
|
|
63
|
+
// Check if artifacts exist
|
|
64
|
+
if (!artifactA && artifactB) {
|
|
65
|
+
diffs.push({
|
|
66
|
+
category: DIFF_CATEGORY.ARTIFACTS,
|
|
67
|
+
severity: DIFF_SEVERITY.BLOCKER,
|
|
68
|
+
reasonCode: DIFF_REASON.MISSING_ARTIFACT,
|
|
69
|
+
message: `Artifact ${artifactName} missing in first run`,
|
|
70
|
+
artifact: artifactName,
|
|
71
|
+
});
|
|
72
|
+
return diffs;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (artifactA && !artifactB) {
|
|
76
|
+
diffs.push({
|
|
77
|
+
category: DIFF_CATEGORY.ARTIFACTS,
|
|
78
|
+
severity: DIFF_SEVERITY.BLOCKER,
|
|
79
|
+
reasonCode: DIFF_REASON.MISSING_ARTIFACT,
|
|
80
|
+
message: `Artifact ${artifactName} missing in second run`,
|
|
81
|
+
artifact: artifactName,
|
|
82
|
+
});
|
|
83
|
+
return diffs;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!artifactA && !artifactB) {
|
|
87
|
+
return diffs; // Both missing, no diff
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Artifact-specific diffing
|
|
91
|
+
if (artifactName === 'findings') {
|
|
92
|
+
diffs.push(...diffFindings(artifactA, artifactB, findingIdentityMap));
|
|
93
|
+
} else if (artifactName === 'runMeta') {
|
|
94
|
+
diffs.push(...diffRunMeta(artifactA, artifactB));
|
|
95
|
+
} else if (artifactName === 'determinismContract') {
|
|
96
|
+
diffs.push(...diffDeterminismContract(artifactA, artifactB));
|
|
97
|
+
} else if (artifactName === 'confidenceReport' || artifactName === 'guardrailsReport' || artifactName === 'evidenceIntent') {
|
|
98
|
+
diffs.push(...diffReportArtifact(artifactA, artifactB, artifactName));
|
|
99
|
+
} else {
|
|
100
|
+
// Generic diff for other artifacts
|
|
101
|
+
diffs.push(...diffGeneric(artifactA, artifactB, artifactName));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return diffs;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Diff findings artifacts
|
|
109
|
+
*/
|
|
110
|
+
function diffFindings(artifactA, artifactB, findingIdentityMap) {
|
|
111
|
+
const diffs = [];
|
|
112
|
+
|
|
113
|
+
const findingsA = artifactA.findings || [];
|
|
114
|
+
const findingsB = artifactB.findings || [];
|
|
115
|
+
|
|
116
|
+
// Check counts
|
|
117
|
+
if (findingsA.length !== findingsB.length) {
|
|
118
|
+
diffs.push({
|
|
119
|
+
category: DIFF_CATEGORY.FINDINGS,
|
|
120
|
+
severity: DIFF_SEVERITY.BLOCKER,
|
|
121
|
+
reasonCode: DIFF_REASON.OBSERVATION_COUNT_CHANGED,
|
|
122
|
+
message: `Finding count changed: ${findingsA.length} → ${findingsB.length}`,
|
|
123
|
+
artifact: 'findings',
|
|
124
|
+
field: 'findings.length',
|
|
125
|
+
oldValue: findingsA.length,
|
|
126
|
+
newValue: findingsB.length,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Build identity maps if not provided
|
|
131
|
+
const mapA = new Map();
|
|
132
|
+
const mapB = new Map();
|
|
133
|
+
|
|
134
|
+
for (const finding of findingsA) {
|
|
135
|
+
const identity = findingIdentityMap ? findingIdentityMap.get(finding) : computeFindingIdentitySimple(finding);
|
|
136
|
+
mapA.set(identity, finding);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
for (const finding of findingsB) {
|
|
140
|
+
const identity = findingIdentityMap ? findingIdentityMap.get(finding) : computeFindingIdentitySimple(finding);
|
|
141
|
+
mapB.set(identity, finding);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Find added findings
|
|
145
|
+
for (const [identity, finding] of mapB) {
|
|
146
|
+
if (!mapA.has(identity)) {
|
|
147
|
+
diffs.push({
|
|
148
|
+
category: DIFF_CATEGORY.FINDINGS,
|
|
149
|
+
severity: DIFF_SEVERITY.BLOCKER,
|
|
150
|
+
reasonCode: DIFF_REASON.FINDING_ADDED,
|
|
151
|
+
message: `Finding added: ${finding.type || 'unknown'}`,
|
|
152
|
+
artifact: 'findings',
|
|
153
|
+
findingIdentity: identity,
|
|
154
|
+
finding: finding,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Find removed findings
|
|
160
|
+
for (const [identity, finding] of mapA) {
|
|
161
|
+
if (!mapB.has(identity)) {
|
|
162
|
+
diffs.push({
|
|
163
|
+
category: DIFF_CATEGORY.FINDINGS,
|
|
164
|
+
severity: DIFF_SEVERITY.BLOCKER,
|
|
165
|
+
reasonCode: DIFF_REASON.FINDING_REMOVED,
|
|
166
|
+
message: `Finding removed: ${finding.type || 'unknown'}`,
|
|
167
|
+
artifact: 'findings',
|
|
168
|
+
findingIdentity: identity,
|
|
169
|
+
finding: finding,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Find changed findings
|
|
175
|
+
for (const [identity, findingA] of mapA) {
|
|
176
|
+
const findingB = mapB.get(identity);
|
|
177
|
+
if (findingB) {
|
|
178
|
+
diffs.push(...diffFinding(findingA, findingB, identity));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return diffs;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Diff individual finding
|
|
187
|
+
*/
|
|
188
|
+
function diffFinding(findingA, findingB, identity) {
|
|
189
|
+
const diffs = [];
|
|
190
|
+
|
|
191
|
+
// Check status/severity
|
|
192
|
+
const statusA = findingA.severity || findingA.status;
|
|
193
|
+
const statusB = findingB.severity || findingB.status;
|
|
194
|
+
if (statusA !== statusB) {
|
|
195
|
+
diffs.push({
|
|
196
|
+
category: DIFF_CATEGORY.FINDINGS,
|
|
197
|
+
severity: DIFF_SEVERITY.BLOCKER,
|
|
198
|
+
reasonCode: DIFF_REASON.FINDING_SEVERITY_CHANGED,
|
|
199
|
+
message: `Finding severity changed: ${statusA} → ${statusB}`,
|
|
200
|
+
artifact: 'findings',
|
|
201
|
+
findingIdentity: identity,
|
|
202
|
+
field: 'severity',
|
|
203
|
+
oldValue: statusA,
|
|
204
|
+
newValue: statusB,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Check confidence
|
|
209
|
+
const confA = findingA.confidence || 0;
|
|
210
|
+
const confB = findingB.confidence || 0;
|
|
211
|
+
if (Math.abs(confA - confB) > 0.001) {
|
|
212
|
+
diffs.push({
|
|
213
|
+
category: DIFF_CATEGORY.FINDINGS,
|
|
214
|
+
severity: DIFF_SEVERITY.WARN,
|
|
215
|
+
reasonCode: DIFF_REASON.CONFIDENCE_CHANGED,
|
|
216
|
+
message: `Finding confidence changed: ${confA.toFixed(3)} → ${confB.toFixed(3)}`,
|
|
217
|
+
artifact: 'findings',
|
|
218
|
+
findingIdentity: identity,
|
|
219
|
+
field: 'confidence',
|
|
220
|
+
oldValue: confA,
|
|
221
|
+
newValue: confB,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Check confidence reasons
|
|
226
|
+
const reasonsA = findingA.confidenceReasons || [];
|
|
227
|
+
const reasonsB = findingB.confidenceReasons || [];
|
|
228
|
+
if (JSON.stringify(reasonsA.sort()) !== JSON.stringify(reasonsB.sort())) {
|
|
229
|
+
diffs.push({
|
|
230
|
+
category: DIFF_CATEGORY.FINDINGS,
|
|
231
|
+
severity: DIFF_SEVERITY.WARN,
|
|
232
|
+
reasonCode: DIFF_REASON.CONFIDENCE_REASONS_CHANGED,
|
|
233
|
+
message: `Finding confidence reasons changed`,
|
|
234
|
+
artifact: 'findings',
|
|
235
|
+
findingIdentity: identity,
|
|
236
|
+
field: 'confidenceReasons',
|
|
237
|
+
oldValue: reasonsA,
|
|
238
|
+
newValue: reasonsB,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Check guardrails
|
|
243
|
+
const guardrailsA = findingA.guardrails;
|
|
244
|
+
const guardrailsB = findingB.guardrails;
|
|
245
|
+
if (guardrailsA || guardrailsB) {
|
|
246
|
+
if (!guardrailsA || !guardrailsB) {
|
|
247
|
+
diffs.push({
|
|
248
|
+
category: DIFF_CATEGORY.FINDINGS,
|
|
249
|
+
severity: DIFF_SEVERITY.WARN,
|
|
250
|
+
reasonCode: DIFF_REASON.GUARDRAILS_CHANGED,
|
|
251
|
+
message: `Finding guardrails presence changed`,
|
|
252
|
+
artifact: 'findings',
|
|
253
|
+
findingIdentity: identity,
|
|
254
|
+
field: 'guardrails',
|
|
255
|
+
});
|
|
256
|
+
} else if (guardrailsA.finalDecision !== guardrailsB.finalDecision) {
|
|
257
|
+
diffs.push({
|
|
258
|
+
category: DIFF_CATEGORY.FINDINGS,
|
|
259
|
+
severity: DIFF_SEVERITY.WARN,
|
|
260
|
+
reasonCode: DIFF_REASON.GUARDRAILS_CHANGED,
|
|
261
|
+
message: `Finding guardrails decision changed: ${guardrailsA.finalDecision} → ${guardrailsB.finalDecision}`,
|
|
262
|
+
artifact: 'findings',
|
|
263
|
+
findingIdentity: identity,
|
|
264
|
+
field: 'guardrails.finalDecision',
|
|
265
|
+
oldValue: guardrailsA.finalDecision,
|
|
266
|
+
newValue: guardrailsB.finalDecision,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Check evidence completeness
|
|
272
|
+
const evidenceA = findingA.evidenceCompleteness;
|
|
273
|
+
const evidenceB = findingB.evidenceCompleteness;
|
|
274
|
+
if (evidenceA || evidenceB) {
|
|
275
|
+
if (!evidenceA || !evidenceB) {
|
|
276
|
+
diffs.push({
|
|
277
|
+
category: DIFF_CATEGORY.EVIDENCE,
|
|
278
|
+
severity: DIFF_SEVERITY.BLOCKER,
|
|
279
|
+
reasonCode: DIFF_REASON.EVIDENCE_COMPLETENESS_CHANGED,
|
|
280
|
+
message: `Finding evidence completeness presence changed`,
|
|
281
|
+
artifact: 'findings',
|
|
282
|
+
findingIdentity: identity,
|
|
283
|
+
field: 'evidenceCompleteness',
|
|
284
|
+
});
|
|
285
|
+
} else if (evidenceA.isComplete !== evidenceB.isComplete) {
|
|
286
|
+
diffs.push({
|
|
287
|
+
category: DIFF_CATEGORY.EVIDENCE,
|
|
288
|
+
severity: DIFF_SEVERITY.BLOCKER,
|
|
289
|
+
reasonCode: DIFF_REASON.EVIDENCE_COMPLETENESS_CHANGED,
|
|
290
|
+
message: `Finding evidence completeness changed: ${evidenceA.isComplete} → ${evidenceB.isComplete}`,
|
|
291
|
+
artifact: 'findings',
|
|
292
|
+
findingIdentity: identity,
|
|
293
|
+
field: 'evidenceCompleteness.isComplete',
|
|
294
|
+
oldValue: evidenceA.isComplete,
|
|
295
|
+
newValue: evidenceB.isComplete,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Check evidencePackage presence
|
|
301
|
+
const evidencePackageA = findingA.evidencePackage;
|
|
302
|
+
const evidencePackageB = findingB.evidencePackage;
|
|
303
|
+
if (!evidencePackageA && evidencePackageB) {
|
|
304
|
+
diffs.push({
|
|
305
|
+
category: DIFF_CATEGORY.EVIDENCE,
|
|
306
|
+
severity: DIFF_SEVERITY.BLOCKER,
|
|
307
|
+
reasonCode: DIFF_REASON.EVIDENCE_MISSING,
|
|
308
|
+
message: `Finding evidence package missing in first run`,
|
|
309
|
+
artifact: 'findings',
|
|
310
|
+
findingIdentity: identity,
|
|
311
|
+
field: 'evidencePackage',
|
|
312
|
+
});
|
|
313
|
+
} else if (evidencePackageA && !evidencePackageB) {
|
|
314
|
+
diffs.push({
|
|
315
|
+
category: DIFF_CATEGORY.EVIDENCE,
|
|
316
|
+
severity: DIFF_SEVERITY.BLOCKER,
|
|
317
|
+
reasonCode: DIFF_REASON.EVIDENCE_MISSING,
|
|
318
|
+
message: `Finding evidence package missing in second run`,
|
|
319
|
+
artifact: 'findings',
|
|
320
|
+
findingIdentity: identity,
|
|
321
|
+
field: 'evidencePackage',
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return diffs;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Diff generic artifacts
|
|
330
|
+
*/
|
|
331
|
+
function diffGeneric(artifactA, artifactB, artifactName) {
|
|
332
|
+
const diffs = [];
|
|
333
|
+
|
|
334
|
+
// Simple deep comparison
|
|
335
|
+
const jsonA = JSON.stringify(artifactA, null, 2);
|
|
336
|
+
const jsonB = JSON.stringify(artifactB, null, 2);
|
|
337
|
+
|
|
338
|
+
if (jsonA !== jsonB) {
|
|
339
|
+
diffs.push({
|
|
340
|
+
category: DIFF_CATEGORY.ARTIFACTS,
|
|
341
|
+
severity: DIFF_SEVERITY.WARN,
|
|
342
|
+
reasonCode: DIFF_REASON.FIELD_VALUE_CHANGED,
|
|
343
|
+
message: `Artifact ${artifactName} content changed`,
|
|
344
|
+
artifact: artifactName,
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return diffs;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Simple finding identity computation (fallback)
|
|
353
|
+
*/
|
|
354
|
+
function computeFindingIdentitySimple(finding) {
|
|
355
|
+
const parts = [
|
|
356
|
+
finding.type || 'unknown',
|
|
357
|
+
finding.interaction?.type || '',
|
|
358
|
+
finding.interaction?.selector || '',
|
|
359
|
+
finding.expectation?.targetPath || '',
|
|
360
|
+
finding.expectation?.urlPath || '',
|
|
361
|
+
];
|
|
362
|
+
return parts.join('|');
|
|
363
|
+
}
|
|
364
|
+
|