@aiwg/cli 2026.9.24 → 2026.10.1
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/agentic/code/providers/capability-matrix.yaml +2 -1
- package/dist/src/artifacts/discovery-shadow-evaluator.js +177 -0
- package/dist/src/artifacts/discovery-shadow.js +195 -0
- package/dist/src/artifacts/index-builder.js +47 -9
- package/dist/src/artifacts/query-engine.js +94 -1
- package/dist/src/artifacts/types.js +1 -0
- package/dist/src/cli/handlers/decision.js +177 -0
- package/dist/src/cli/handlers/gates.js +154 -0
- package/dist/src/cli/handlers/index.js +5 -1
- package/dist/src/cli/handlers/sessions.js +10 -7
- package/dist/src/cli/handlers/utilities.js +20 -0
- package/dist/src/config/aiwg-config.js +15 -2
- package/dist/src/decision/acceptance.js +25 -0
- package/dist/src/decision/adapters/jev.js +29 -3
- package/dist/src/decision/ask.js +352 -0
- package/dist/src/decision/calibration/registry.js +9 -0
- package/dist/src/decision/compose.js +74 -15
- package/dist/src/decision/context-live-promotion.js +344 -0
- package/dist/src/decision/context-live-qualification.js +591 -0
- package/dist/src/decision/context-pruning.js +847 -0
- package/dist/src/decision/driver.js +432 -0
- package/dist/src/decision/egress-live-qualification.js +874 -0
- package/dist/src/decision/ensemble/contract.js +64 -1
- package/dist/src/decision/ensemble/index.js +1 -0
- package/dist/src/decision/ensemble/report.js +8 -42
- package/dist/src/decision/ensemble/runtime.js +571 -0
- package/dist/src/decision/ensemble-study/artifacts.js +85 -0
- package/dist/src/decision/ensemble-study/calibration.js +443 -0
- package/dist/src/decision/ensemble-study/corpus.js +229 -0
- package/dist/src/decision/ensemble-study/multifact-artifacts.js +23 -0
- package/dist/src/decision/ensemble-study/multifact-study.js +596 -0
- package/dist/src/decision/ensemble-study/multifact.js +241 -0
- package/dist/src/decision/ensemble-study/protocol.js +77 -0
- package/dist/src/decision/ensemble-study/score.js +462 -0
- package/dist/src/decision/ensemble-study/staged.js +87 -0
- package/dist/src/decision/ensemble-study/statistics.js +84 -0
- package/dist/src/decision/evaluate.js +227 -14
- package/dist/src/decision/export.js +4 -0
- package/dist/src/decision/feature-export.js +1074 -0
- package/dist/src/decision/graph-index.js +16 -0
- package/dist/src/decision/graph-live-qualification.js +1041 -0
- package/dist/src/decision/graph-live-workload.js +245 -0
- package/dist/src/decision/graph-skill-bridge.js +21 -3
- package/dist/src/decision/heldout/calibration.js +140 -0
- package/dist/src/decision/heldout/collector.js +430 -0
- package/dist/src/decision/heldout/contract.js +309 -0
- package/dist/src/decision/heldout/d29-generator-ids.js +12 -0
- package/dist/src/decision/heldout/d29-passage-baseline-v2.js +93 -0
- package/dist/src/decision/heldout/d29-passage-baseline-v3.js +100 -0
- package/dist/src/decision/heldout/d29-passage-baseline.js +66 -0
- package/dist/src/decision/heldout/d29-pools-v8.js +575 -0
- package/dist/src/decision/heldout/d29-pools.js +288 -0
- package/dist/src/decision/heldout/d29-v6.js +169 -0
- package/dist/src/decision/heldout/d29-v7.js +202 -0
- package/dist/src/decision/heldout/d29-v8.js +586 -0
- package/dist/src/decision/heldout/generator-registry.js +67 -0
- package/dist/src/decision/heldout/generators.js +696 -0
- package/dist/src/decision/heldout/index.js +8 -0
- package/dist/src/decision/heldout/journal.js +346 -0
- package/dist/src/decision/heldout/study-generators.js +64 -0
- package/dist/src/decision/heldout/types.js +2 -0
- package/dist/src/decision/host-policy.js +61 -0
- package/dist/src/decision/index.js +13 -0
- package/dist/src/decision/interop-profile.v1.json +112 -0
- package/dist/src/decision/interop.js +653 -0
- package/dist/src/decision/issue-triage/index.js +3 -0
- package/dist/src/decision/issue-triage/runtime.js +1069 -0
- package/dist/src/decision/issue-triage/types.js +2 -0
- package/dist/src/decision/jev-credentials.js +121 -0
- package/dist/src/decision/patterns/catalog.js +28 -8
- package/dist/src/decision/patterns/playground.js +28 -11
- package/dist/src/decision/patterns/specs.js +19 -10
- package/dist/src/decision/preprocessed-evidence.js +724 -0
- package/dist/src/decision/qualification/capture.js +8 -2
- package/dist/src/decision/qualification/gate-evidence.js +19 -4
- package/dist/src/decision/qualification/quality.js +57 -49
- package/dist/src/decision/qualification/release.js +76 -3
- package/dist/src/decision/qualification/runner.js +5 -2
- package/dist/src/decision/routing/contract.js +175 -0
- package/dist/src/decision/routing/index.js +5 -0
- package/dist/src/decision/routing/report.js +216 -0
- package/dist/src/decision/routing/runtime.js +685 -0
- package/dist/src/decision/routing/types.js +2 -0
- package/dist/src/decision/sdlc-screening.js +1157 -0
- package/dist/src/decision/sensitivity/comparative.js +154 -0
- package/dist/src/decision/sensitivity/contract.js +218 -0
- package/dist/src/decision/sensitivity/index.js +5 -0
- package/dist/src/decision/sensitivity/runtime.js +912 -0
- package/dist/src/decision/sensitivity/types.js +2 -0
- package/dist/src/decision/validate.js +26 -0
- package/dist/src/extensions/commands/definitions.js +38 -0
- package/dist/src/extensions/manifest.js +44 -0
- package/dist/src/gates/discovery.js +315 -0
- package/dist/src/gates/driver.js +388 -0
- package/dist/src/gates/evaluate.js +719 -0
- package/dist/src/gates/floors.js +255 -0
- package/dist/src/gates/index.js +17 -0
- package/dist/src/gates/providers/evidence.js +38 -0
- package/dist/src/gates/providers/index.js +21 -0
- package/dist/src/gates/providers/isolated-child-source.js +446 -0
- package/dist/src/gates/providers/loader.js +1069 -0
- package/dist/src/gates/providers/paired.js +52 -0
- package/dist/src/gates/providers/proportion.js +60 -0
- package/dist/src/gates/providers/registry.js +40 -0
- package/dist/src/gates/providers/scalar.js +41 -0
- package/dist/src/gates/providers/screening.js +53 -0
- package/dist/src/gates/providers/types.js +25 -0
- package/dist/src/gates/registry.js +1003 -0
- package/dist/src/gates/report.js +102 -0
- package/dist/src/gates/schema.js +56 -0
- package/dist/src/gates/stats/binomial.js +26 -0
- package/dist/src/gates/stats/bootstrap.js +62 -0
- package/dist/src/gates/stats/clopper-pearson.js +95 -0
- package/dist/src/gates/stats/digest.js +26 -0
- package/dist/src/gates/stats/error.js +5 -0
- package/dist/src/gates/stats/index.js +9 -0
- package/dist/src/gates/stats/normal.js +35 -0
- package/dist/src/gates/stats/paired.js +120 -0
- package/dist/src/gates/stats/splits.js +30 -0
- package/dist/src/gates/types.js +8 -0
- package/dist/src/governance/redaction.js +14 -4
- package/dist/src/mcp/muse-native-config.mjs +123 -0
- package/dist/src/mcp/tools/decision.mjs +228 -0
- package/dist/src/mcp/tools/subsystems.mjs +2 -0
- package/dist/src/providers/capability-matrix.yaml +2 -1
- package/dist/src/providers/provider-definitions.js +2 -1
- package/dist/src/sessions/adapters/muse.js +336 -39
- package/dist/src/sessions/batch-import.js +7 -0
- package/dist/src/sessions/workspace-discovery.js +13 -2
- package/package.json +1 -1
- package/schemas/decision/ConformalFrozenOpenData.v2.schema.json +371 -0
- package/schemas/decision/ConformalOpenDataReport.v2.schema.json +421 -0
- package/schemas/decision/ConformalPreregistration.v2.schema.json +529 -0
- package/schemas/decision/ContextPruningCandidate.v1.schema.json +47 -0
- package/schemas/decision/ContextPruningEvaluationReport.v1.schema.json +758 -0
- package/schemas/decision/ContextPruningPreregistration.v1.schema.json +175 -0
- package/schemas/decision/ContextPruningReceipt.v1.schema.json +72 -0
- package/schemas/decision/D17Multifact.v1.schema.json +1851 -0
- package/schemas/decision/D17StudyAnalysis.v1.schema.json +42 -0
- package/schemas/decision/D17StudyAnalysis.v2.schema.json +51 -0
- package/schemas/decision/D17StudyCalibration.v1.schema.json +819 -0
- package/schemas/decision/D17StudyDryRun.v1.schema.json +169 -0
- package/schemas/decision/D17StudyDryRun.v2.schema.json +239 -0
- package/schemas/decision/D17StudyGold.v1.schema.json +108 -0
- package/schemas/decision/D17StudyGuide.v1.schema.json +63 -0
- package/schemas/decision/D17StudyNativeTemplates.v1.schema.json +619 -0
- package/schemas/decision/D17StudyProtocol.v1.schema.json +241 -0
- package/schemas/decision/D17StudyReport.v1.schema.json +902 -0
- package/schemas/decision/D17StudyReport.v2.schema.json +1319 -0
- package/schemas/decision/D17StudyReview.v1.schema.json +175 -0
- package/schemas/decision/D17StudySplits.v1.schema.json +217 -0
- package/schemas/decision/D29Study.v1.schema.json +1903 -0
- package/schemas/decision/D29Study.v2.schema.json +1344 -0
- package/schemas/decision/D29Study.v3.schema.json +609 -0
- package/schemas/decision/D29Study.v4.schema.json +1133 -0
- package/schemas/decision/D29Study.v6.schema.json +1205 -0
- package/schemas/decision/D29Study.v7.schema.json +1471 -0
- package/schemas/decision/D29Study.v8.schema.json +671 -0
- package/schemas/decision/D29Study.v9.schema.json +778 -0
- package/schemas/decision/DecisionComparativeReplayAuditSample.v1.schema.json +137 -0
- package/schemas/decision/DecisionComparativeReplayCorpus.v1.schema.json +258 -0
- package/schemas/decision/DecisionComparativeReplayFreeze.v1.schema.json +74 -0
- package/schemas/decision/DecisionComparativeReplayGold.v1.schema.json +50 -0
- package/schemas/decision/DecisionComparativeReplayGoldAsset.v1.schema.json +37 -0
- package/schemas/decision/DecisionComparativeReplayIntegrity.v1.schema.json +81 -0
- package/schemas/decision/DecisionComparativeReplayMembers.v1.schema.json +52 -0
- package/schemas/decision/DecisionComparativeReplayPreregistration.v1.schema.json +113 -0
- package/schemas/decision/DecisionComparativeReplayReport.v1.schema.json +407 -0
- package/schemas/decision/DecisionComparativeReplayReviewPackets.v1.schema.json +260 -0
- package/schemas/decision/DecisionComparativeReplaySplit.v1.schema.json +84 -0
- package/schemas/decision/DecisionFeatureObservation.v1.schema.json +564 -0
- package/schemas/decision/DecisionFeatureSet.v1.schema.json +155 -0
- package/schemas/decision/DecisionInteropMapping.v1.schema.json +85 -0
- package/schemas/decision/DecisionIssueTriageEvaluationManifest.v1.schema.json +174 -0
- package/schemas/decision/DecisionIssueTriageEvaluationReport.v1.schema.json +280 -0
- package/schemas/decision/DecisionIssueTriagePilotPack.v1.schema.json +114 -0
- package/schemas/decision/DecisionRouteReceipt.v1.schema.json +155 -0
- package/schemas/decision/DecisionRoutingPolicy.v1.schema.json +132 -0
- package/schemas/decision/DecisionRoutingShadowReport.v1.schema.json +170 -0
- package/schemas/decision/DecisionRoutingTask.v1.schema.json +49 -0
- package/schemas/decision/DecisionRuleset.schema.json +8 -2
- package/schemas/decision/DecisionRuleset.v1alpha2.schema.json +8 -2
- package/schemas/decision/DecisionSensitivityPlan.v1.schema.json +191 -0
- package/schemas/decision/DecisionSensitivityReport.v1.schema.json +163 -0
- package/schemas/decision/HeldoutApproval.v1.schema.json +312 -0
- package/schemas/decision/HeldoutAttempt.v1.schema.json +211 -0
- package/schemas/decision/HeldoutBaseline.v1.schema.json +90 -0
- package/schemas/decision/HeldoutCalibrationPhase.v1.schema.json +87 -0
- package/schemas/decision/HeldoutCorpus.v1.schema.json +167 -0
- package/schemas/decision/HeldoutEvent.v1.schema.json +41 -0
- package/schemas/decision/HeldoutFrozen.v1.schema.json +142 -0
- package/schemas/decision/HeldoutPreregistration.v1.schema.json +222 -0
- package/schemas/decision/HeldoutSpendEvent.v1.schema.json +122 -0
- package/schemas/decision/HeldoutSpendHead.v1.schema.json +25 -0
- package/schemas/decision/HeldoutSpendHead.v2.schema.json +46 -0
- package/schemas/decision/HeldoutSummary.v1.schema.json +101 -0
- package/schemas/decision/OpaDecisionLogExport.v1.schema.json +65 -0
- package/schemas/decision/PreprocessedEvidence.v1.schema.json +306 -0
- package/schemas/decision/RulesetResult.v1alpha2.schema.json +175 -0
- package/schemas/decision/SdlcEvidenceScreening.v1.schema.json +499 -0
- package/schemas/decision/SdlcScreeningPreregistration.v1.schema.json +132 -0
- package/schemas/decision/SdlcScreeningPreregistration.v2.schema.json +138 -0
- package/schemas/decision/SdlcScreeningRelease.v1.schema.json +654 -0
- package/schemas/decision/SdlcScreeningRelease.v2.schema.json +654 -0
- package/tools/agents/deploy-agents.mjs +1 -1
- package/tools/agents/providers/base.mjs +40 -18
- package/tools/agents/providers/factory.mjs +10 -2
- package/tools/agents/providers/muse-hooks.mjs +1 -29
- package/tools/agents/providers/muse.mjs +6 -3
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { pairedBinaryDifferenceInterval, wilsonScoreInterval } from '../qualification/quality.js';
|
|
2
|
+
import { qualificationIntegrityAllowlistProblems } from '../qualification/release.js';
|
|
3
|
+
import { checkSensitivitySchema, sensitivityDigest, validateSensitivityReport } from './contract.js';
|
|
4
|
+
export const REPLAY_SLICES = ['threshold-boundary', 'priority-loss', 'unchanged-control', 'unreplayable'];
|
|
5
|
+
const DIGEST = /^sha256:[0-9a-f]{64}$/;
|
|
6
|
+
const bpsUpper = (events, n, levelBps) => n ? Math.ceil(wilsonScoreInterval({ events, n, levelBps })[1] * 10_000) : null;
|
|
7
|
+
export function validateComparativeReplayPreregistration(value) {
|
|
8
|
+
checkSensitivitySchema('comparativePlan', value);
|
|
9
|
+
if (value.minimumTestRoots < value.minimumSliceRoots * REPLAY_SLICES.length) {
|
|
10
|
+
throw new Error('comparative replay slice support exceeds total minimum');
|
|
11
|
+
}
|
|
12
|
+
const { digest, ...payload } = value;
|
|
13
|
+
if (sensitivityDigest(payload) !== digest)
|
|
14
|
+
throw new Error('comparative replay preregistration digest mismatch');
|
|
15
|
+
}
|
|
16
|
+
/** Rebuild from trusted preregistration, stored reports and independently authored gold. */
|
|
17
|
+
export function buildComparativeReplayReport(input) {
|
|
18
|
+
const plan = input.preregistration;
|
|
19
|
+
validateComparativeReplayPreregistration(plan);
|
|
20
|
+
if (plan.digest !== input.trustedPreregistrationDigest)
|
|
21
|
+
throw new Error('untrusted comparative replay preregistration');
|
|
22
|
+
checkSensitivitySchema('comparativeIntegrity', input.integrity);
|
|
23
|
+
if (!DIGEST.test(input.trustedIntegrityDigest) || sensitivityDigest(input.integrity) !== input.trustedIntegrityDigest) {
|
|
24
|
+
throw new Error('untrusted eval-integrity metadata');
|
|
25
|
+
}
|
|
26
|
+
if (!Number.isFinite(Date.parse(input.accessedAt)) || Date.parse(input.accessedAt) <= Date.parse(plan.frozenAt)) {
|
|
27
|
+
throw new Error('test access must follow preregistration freeze');
|
|
28
|
+
}
|
|
29
|
+
checkSensitivitySchema('comparativeMembers', input.members);
|
|
30
|
+
if (sensitivityDigest(input.members) !== plan.splitDigest
|
|
31
|
+
|| sensitivityDigest(input.members.map(({ id, payloadDigest }) => ({ id, payloadDigest }))) !== plan.corpusDigest) {
|
|
32
|
+
throw new Error('frozen comparative replay membership mismatch');
|
|
33
|
+
}
|
|
34
|
+
const members = new Map(input.members.map(member => [member.id, member]));
|
|
35
|
+
if (members.size !== input.members.length || input.roots.length !== input.members.length) {
|
|
36
|
+
throw new Error('incomplete or duplicate comparative replay membership');
|
|
37
|
+
}
|
|
38
|
+
const gold = input.roots.map(({ id, gold }) => ({ id, gold })).sort((a, b) => a.id.localeCompare(b.id));
|
|
39
|
+
checkSensitivitySchema('comparativeGold', gold);
|
|
40
|
+
if (sensitivityDigest(gold) !== plan.goldDigest)
|
|
41
|
+
throw new Error('frozen comparative replay gold mismatch');
|
|
42
|
+
const findings = new Set();
|
|
43
|
+
const ids = new Set();
|
|
44
|
+
const sources = new Set();
|
|
45
|
+
const rows = [];
|
|
46
|
+
let backendCalls = 0;
|
|
47
|
+
let tokens = 0;
|
|
48
|
+
let costMicros = 0;
|
|
49
|
+
let exactReproduction = true;
|
|
50
|
+
for (const root of input.roots) {
|
|
51
|
+
if (!root.id || ids.has(root.id) || !REPLAY_SLICES.includes(root.slice) || !DIGEST.test(root.sourceResultDigest)) {
|
|
52
|
+
throw new Error('invalid or duplicate comparative replay root');
|
|
53
|
+
}
|
|
54
|
+
ids.add(root.id);
|
|
55
|
+
if (sources.has(root.sourceResultDigest))
|
|
56
|
+
throw new Error('duplicate source scenario');
|
|
57
|
+
sources.add(root.sourceResultDigest);
|
|
58
|
+
const member = members.get(root.id);
|
|
59
|
+
if (!member || member.slice !== root.slice || member.split !== root.split
|
|
60
|
+
|| member.sourceResultDigest !== root.sourceResultDigest)
|
|
61
|
+
throw new Error('comparative replay root membership mismatch');
|
|
62
|
+
const report = validateSensitivityReport(root.report);
|
|
63
|
+
if (report.digest !== root.trustedReportDigest || report.analysisKind !== 'policy-replay'
|
|
64
|
+
|| report.plan.digest !== member.planDigest || report.status !== 'completed' || report.rows.length !== 1 || report.baseline.resultRef.digest !== root.sourceResultDigest) {
|
|
65
|
+
findings.add(`report-invalid:${root.id}`);
|
|
66
|
+
}
|
|
67
|
+
if (report.privacy.redaction !== 'hash-values' || report.privacy.summaryPrecisionBps < 100
|
|
68
|
+
|| report.rows.some(row => !['reused-stored-evidence', 'deduplicated-control', 'unreplayable'].includes(row.inference)
|
|
69
|
+
|| row.freshInvocationId !== null || row.counterfactualReceipt !== null
|
|
70
|
+
|| row.sourceReceipt.digest !== root.sourceResultDigest
|
|
71
|
+
|| row.resourceUse.backendCalls !== 0 || row.resourceUse.tokens !== 0 || row.resourceUse.costMicros !== 0)) {
|
|
72
|
+
findings.add(`not-zero-call-policy-replay:${root.id}`);
|
|
73
|
+
}
|
|
74
|
+
if (root.reproductionDigest !== report.digest)
|
|
75
|
+
exactReproduction = false;
|
|
76
|
+
backendCalls += report.budget.backendCalls;
|
|
77
|
+
tokens += report.budget.tokens;
|
|
78
|
+
costMicros += report.budget.costMicros;
|
|
79
|
+
const row = report.rows[0];
|
|
80
|
+
const predictedUnreplayable = row?.inference === 'unreplayable';
|
|
81
|
+
const falseNoChange = root.gold.unreplayable && !predictedUnreplayable;
|
|
82
|
+
const correct = !!row && report.status === 'completed'
|
|
83
|
+
&& predictedUnreplayable === root.gold.unreplayable
|
|
84
|
+
&& row.deltas.status === root.gold.status
|
|
85
|
+
&& row.deltas.outcomeChanged === root.gold.outcomeChanged
|
|
86
|
+
&& row.deltas.acceptanceChanged === root.gold.acceptanceChanged
|
|
87
|
+
&& row.deltas.ruleChanged === root.gold.ruleChanged;
|
|
88
|
+
rows.push({ id: root.id, slice: root.slice, split: root.split, reportDigest: report.digest, correct, falseNoChange });
|
|
89
|
+
}
|
|
90
|
+
rows.sort((a, b) => a.id.localeCompare(b.id));
|
|
91
|
+
const testRows = rows.filter(row => row.split === 'test');
|
|
92
|
+
const errors = testRows.filter(row => !row.correct).length;
|
|
93
|
+
const falseNoChange = testRows.filter(row => row.falseNoChange).length;
|
|
94
|
+
const pairedLowerBps = testRows.length ? pairedBinaryDifferenceInterval({ counts: { both: testRows.length - errors,
|
|
95
|
+
candidateOnly: 0, baselineOnly: errors, neither: 0 }, levelBps: plan.levelBps, method: plan.pairedMethod }).lowerBps : null;
|
|
96
|
+
const errorUpperBps = bpsUpper(errors, testRows.length, plan.levelBps);
|
|
97
|
+
const slices = Object.fromEntries(REPLAY_SLICES.map(slice => {
|
|
98
|
+
const items = testRows.filter(row => row.slice === slice);
|
|
99
|
+
const sliceErrors = items.filter(row => !row.correct).length;
|
|
100
|
+
return [slice, { roots: items.length, errors: sliceErrors, errorUpperBps: bpsUpper(sliceErrors, items.length, plan.levelBps) }];
|
|
101
|
+
}));
|
|
102
|
+
if (testRows.length < plan.minimumTestRoots)
|
|
103
|
+
findings.add('test-support-insufficient');
|
|
104
|
+
if (errors > 0)
|
|
105
|
+
findings.add('diagnostic-errors');
|
|
106
|
+
if (falseNoChange > 0)
|
|
107
|
+
findings.add('false-no-change');
|
|
108
|
+
if (pairedLowerBps === null || pairedLowerBps < plan.nonInferiorityMarginBps)
|
|
109
|
+
findings.add('non-inferiority-failed');
|
|
110
|
+
if (errorUpperBps === null || errorUpperBps > plan.maxErrorUpperBps)
|
|
111
|
+
findings.add('error-bound-failed');
|
|
112
|
+
for (const slice of REPLAY_SLICES) {
|
|
113
|
+
const metric = slices[slice];
|
|
114
|
+
if (metric.roots < plan.minimumSliceRoots || metric.errors > 0
|
|
115
|
+
|| metric.errorUpperBps === null || metric.errorUpperBps > plan.maxSliceErrorUpperBps)
|
|
116
|
+
findings.add(`slice-failed:${slice}`);
|
|
117
|
+
}
|
|
118
|
+
if (backendCalls > plan.maxBackendCalls || tokens > plan.maxTokens || costMicros > plan.maxCostMicros)
|
|
119
|
+
findings.add('zero-call-budget-violated');
|
|
120
|
+
if (!exactReproduction)
|
|
121
|
+
findings.add('reproduction-mismatch');
|
|
122
|
+
const diagnosticDecision = findings.size === 0 ? 'pass' : 'fail';
|
|
123
|
+
for (const problem of qualificationIntegrityAllowlistProblems(input.integrity))
|
|
124
|
+
findings.add(problem);
|
|
125
|
+
if (input.integrity.sample_n !== testRows.length)
|
|
126
|
+
findings.add('integrity-sample-mismatch');
|
|
127
|
+
if (input.reviewerAuditDigest !== null)
|
|
128
|
+
throw new Error('operator review ingestion is not implemented');
|
|
129
|
+
findings.add('reviewer-audit-pending');
|
|
130
|
+
const upstreamDecision = input.integrity.release_gate.decision;
|
|
131
|
+
const decision = upstreamDecision === 'ROLLBACK' || input.integrity.integrity_state === 'compromised'
|
|
132
|
+
|| input.integrity.compromise_labels.length > 0 ? 'ROLLBACK'
|
|
133
|
+
: upstreamDecision === 'HOLD' || findings.size > 0 ? 'HOLD' : 'PROMOTE';
|
|
134
|
+
const payload = {
|
|
135
|
+
schemaVersion: 'decision-comparative-replay-report/v1', mode: 'shadow',
|
|
136
|
+
semantics: 'associative-sensitivity-not-causal', actionAuthorization: 'not-authorized',
|
|
137
|
+
preregistrationDigest: plan.digest, corpusDigest: plan.corpusDigest, goldDigest: plan.goldDigest,
|
|
138
|
+
splitDigest: plan.splitDigest, accessedAt: input.accessedAt, integrity: input.integrity,
|
|
139
|
+
integrityDigest: input.trustedIntegrityDigest, roots: rows,
|
|
140
|
+
metrics: { testRoots: testRows.length, errors, falseNoChange, pairedLowerBps, errorUpperBps,
|
|
141
|
+
slices, backendCalls, tokens, costMicros, exactReproduction },
|
|
142
|
+
diagnosticDecision, reviewStatus: 'pending', findings: [...findings].sort(), upstreamDecision, decision,
|
|
143
|
+
};
|
|
144
|
+
const report = { ...payload, digest: sensitivityDigest(payload) };
|
|
145
|
+
checkSensitivitySchema('comparativeReport', report);
|
|
146
|
+
return report;
|
|
147
|
+
}
|
|
148
|
+
export function validateComparativeReplayReport(report, input) {
|
|
149
|
+
checkSensitivitySchema('comparativeReport', report);
|
|
150
|
+
const expected = buildComparativeReplayReport(input);
|
|
151
|
+
if (sensitivityDigest(report) !== sensitivityDigest(expected))
|
|
152
|
+
throw new Error('comparative replay report differs from bound inputs');
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=comparative.js.map
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, resolve } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import Ajv2020 from 'ajv/dist/2020.js';
|
|
6
|
+
import addFormats from 'ajv-formats';
|
|
7
|
+
import { canonicalJson } from '../../security/artifact-trust.js';
|
|
8
|
+
import { admitEntry, DEFAULT_ENTRY_LIMITS } from '../entry.js';
|
|
9
|
+
export class SensitivityContractError extends Error {
|
|
10
|
+
layer;
|
|
11
|
+
details;
|
|
12
|
+
constructor(message, layer = 'semantic', details = []) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.layer = layer;
|
|
15
|
+
this.details = details;
|
|
16
|
+
this.name = 'SensitivityContractError';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export const SENSITIVITY_SCHEMA_FILES = {
|
|
20
|
+
plan: 'DecisionSensitivityPlan.v1.schema.json',
|
|
21
|
+
report: 'DecisionSensitivityReport.v1.schema.json',
|
|
22
|
+
comparativePlan: 'DecisionComparativeReplayPreregistration.v1.schema.json',
|
|
23
|
+
comparativeReport: 'DecisionComparativeReplayReport.v1.schema.json',
|
|
24
|
+
comparativeMembers: 'DecisionComparativeReplayMembers.v1.schema.json',
|
|
25
|
+
comparativeGold: 'DecisionComparativeReplayGold.v1.schema.json',
|
|
26
|
+
comparativeIntegrity: 'DecisionComparativeReplayIntegrity.v1.schema.json',
|
|
27
|
+
};
|
|
28
|
+
let validators = null;
|
|
29
|
+
function schemaValidator(kind) {
|
|
30
|
+
if (!validators) {
|
|
31
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
32
|
+
const dir = [resolve(here, '../../../schemas/decision'), resolve(here, '../../../../schemas/decision')]
|
|
33
|
+
.find(path => existsSync(resolve(path, SENSITIVITY_SCHEMA_FILES.plan)));
|
|
34
|
+
if (!dir)
|
|
35
|
+
throw new Error('Decision sensitivity schema directory is unavailable');
|
|
36
|
+
const ajv = new Ajv2020({ strict: true, allErrors: true });
|
|
37
|
+
addFormats(ajv);
|
|
38
|
+
validators = new Map(Object.keys(SENSITIVITY_SCHEMA_FILES).map(name => [
|
|
39
|
+
name,
|
|
40
|
+
ajv.compile(JSON.parse(readFileSync(resolve(dir, SENSITIVITY_SCHEMA_FILES[name]), 'utf8'))),
|
|
41
|
+
]));
|
|
42
|
+
}
|
|
43
|
+
return validators.get(kind);
|
|
44
|
+
}
|
|
45
|
+
export function checkSensitivitySchema(kind, value) {
|
|
46
|
+
try {
|
|
47
|
+
admitEntry(value, kind.startsWith('comparative')
|
|
48
|
+
? { ...DEFAULT_ENTRY_LIMITS, serializedBytes: 2_097_152, properties: 32_768, entries: 65_536, memoryBytes: 16_777_216 }
|
|
49
|
+
: DEFAULT_ENTRY_LIMITS);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
throw new SensitivityContractError(`${kind} admission denied`, 'admission');
|
|
53
|
+
}
|
|
54
|
+
const check = schemaValidator(kind);
|
|
55
|
+
if (!check(value)) {
|
|
56
|
+
throw new SensitivityContractError(`${kind} does not match its v1 schema`, 'schema', (check.errors ?? []).map(error => `${error.instancePath || '/'} ${error.message ?? 'invalid'}`));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export function sensitivityDigest(value) {
|
|
60
|
+
return `sha256:${createHash('sha256').update(canonicalJson(value)).digest('hex')}`;
|
|
61
|
+
}
|
|
62
|
+
export function validateSensitivityPlan(value) {
|
|
63
|
+
checkSensitivitySchema('plan', value);
|
|
64
|
+
const plan = value;
|
|
65
|
+
const problems = [];
|
|
66
|
+
if (plan.mode !== 'disabled' && plan.mode !== 'shadow')
|
|
67
|
+
problems.push('sensitivity mode must be disabled or shadow');
|
|
68
|
+
if (plan.sourceSubject.tenantId !== plan.tenantId || plan.sourceSubject.workspaceId !== plan.workspaceId
|
|
69
|
+
|| plan.sourceSubject.projectId !== plan.projectId)
|
|
70
|
+
problems.push('source subject must be in the plan tenant/workspace/project');
|
|
71
|
+
if (Date.parse(plan.authorization.expiresAt) <= Date.parse(plan.authorization.approvedAt))
|
|
72
|
+
problems.push('authorization expiry must follow approval');
|
|
73
|
+
if (plan.variants.length > plan.budgets.maxVariants)
|
|
74
|
+
problems.push('variant count exceeds maxVariants');
|
|
75
|
+
if (plan.baselineStability.enabled && plan.baselineStability.repeats > plan.budgets.maxBackendCalls) {
|
|
76
|
+
problems.push('baseline stability repeats exceed backend-call budget');
|
|
77
|
+
}
|
|
78
|
+
if (plan.analysisKind === 'policy-replay' && plan.baselineStability.enabled && plan.baselineStability.repeats > 0) {
|
|
79
|
+
problems.push('baseline stability repeats require input reevaluation');
|
|
80
|
+
}
|
|
81
|
+
if (plan.analysisKind === 'policy-replay' && plan.budgets.maxBackendCalls !== 0) {
|
|
82
|
+
problems.push('policy replay must reserve zero backend calls');
|
|
83
|
+
}
|
|
84
|
+
if (plan.analysisKind === 'input-reevaluation' && plan.privacy.egress !== 'no-external-egress') {
|
|
85
|
+
problems.push('input reevaluation must use no-external-egress in the offline analyzer');
|
|
86
|
+
}
|
|
87
|
+
const allowed = new Set(plan.allowedPaths);
|
|
88
|
+
const domains = new Map(plan.pathDomains.map(domain => [domain.path, domain]));
|
|
89
|
+
for (const path of plan.allowedPaths) {
|
|
90
|
+
if (!domains.has(path))
|
|
91
|
+
problems.push(`allowed path ${path} has no D10-approved value domain`);
|
|
92
|
+
if (hasForbiddenPointerSegment(path))
|
|
93
|
+
problems.push(`allowed path ${path} contains a prohibited pointer segment`);
|
|
94
|
+
if (forbiddenAuthorityPath(path))
|
|
95
|
+
problems.push(`path ${path} can alter model, credential, executor, permission or legal authority`);
|
|
96
|
+
if (plan.analysisKind === 'policy-replay' && !policyReplayPath(path))
|
|
97
|
+
problems.push(`policy replay path ${path} is not deterministic policy`);
|
|
98
|
+
if (plan.analysisKind === 'input-reevaluation' && !path.startsWith('/input/'))
|
|
99
|
+
problems.push(`input reevaluation path ${path} must be under /input`);
|
|
100
|
+
}
|
|
101
|
+
const ids = new Set();
|
|
102
|
+
const precisionPaths = new Map();
|
|
103
|
+
for (const variant of plan.variants) {
|
|
104
|
+
if (ids.has(variant.id))
|
|
105
|
+
problems.push(`variant ${variant.id} is duplicated`);
|
|
106
|
+
if (/^baseline-/i.test(variant.id))
|
|
107
|
+
problems.push(`variant ${variant.id} uses a reserved baseline identifier`);
|
|
108
|
+
ids.add(variant.id);
|
|
109
|
+
for (const change of variant.changes) {
|
|
110
|
+
if (!allowed.has(change.path))
|
|
111
|
+
problems.push(`variant ${variant.id} changes undeclared path ${change.path}`);
|
|
112
|
+
if (hasForbiddenPointerSegment(change.path))
|
|
113
|
+
problems.push(`variant ${variant.id} uses prohibited pointer segment at ${change.path}`);
|
|
114
|
+
if (looksExecutable(change.value))
|
|
115
|
+
problems.push(`variant ${variant.id} contains executable or locator-like value at ${change.path}`);
|
|
116
|
+
if (plan.analysisKind === 'policy-replay' && changesTargetIdentity(change.value)) {
|
|
117
|
+
problems.push(`variant ${variant.id} would change target identity at ${change.path}`);
|
|
118
|
+
}
|
|
119
|
+
const domain = domains.get(change.path);
|
|
120
|
+
if (domain && !domain.values.some(value => canonicalJson(value) === canonicalJson(change.value))) {
|
|
121
|
+
problems.push(`variant ${variant.id} value is outside the approved domain for ${change.path}`);
|
|
122
|
+
}
|
|
123
|
+
const action = actionValue(change.value);
|
|
124
|
+
if (action && !plan.authorization.authorizedActions.includes(action))
|
|
125
|
+
problems.push(`variant ${variant.id} names unauthorized action ${action}`);
|
|
126
|
+
const label = labelValue(change.value);
|
|
127
|
+
if (label && !plan.authorization.authorizedLabels.includes(label))
|
|
128
|
+
problems.push(`variant ${variant.id} names unauthorized label ${label}`);
|
|
129
|
+
if (typeof change.value === 'number') {
|
|
130
|
+
const list = precisionPaths.get(change.path) ?? [];
|
|
131
|
+
list.push(change.value);
|
|
132
|
+
precisionPaths.set(change.path, list);
|
|
133
|
+
const bps = Math.round(change.value);
|
|
134
|
+
if (Math.abs(change.value - bps) > 0 || bps % plan.privacy.summaryPrecisionBps !== 0) {
|
|
135
|
+
problems.push(`variant ${variant.id} uses precision finer than the report policy at ${change.path}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (plan.probeControl.prohibitMembershipQueries) {
|
|
141
|
+
for (const path of plan.allowedPaths) {
|
|
142
|
+
if (/member|membership|exists|subject|user|account/i.test(path))
|
|
143
|
+
problems.push(`membership-style path ${path} is prohibited`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (plan.privacy.differencing === 'deny') {
|
|
147
|
+
for (const [path, values] of precisionPaths) {
|
|
148
|
+
const unique = [...new Set(values)].sort((a, b) => a - b);
|
|
149
|
+
if (unique.length > 2)
|
|
150
|
+
problems.push(`path ${path} looks like repeated threshold probing`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (problems.length)
|
|
154
|
+
throw new SensitivityContractError(`sensitivity plan rejected: ${problems[0]}`, 'semantic', problems);
|
|
155
|
+
return plan;
|
|
156
|
+
}
|
|
157
|
+
export function validateSensitivityReport(value) {
|
|
158
|
+
checkSensitivitySchema('report', value);
|
|
159
|
+
const report = value;
|
|
160
|
+
const { digest, ...payload } = report;
|
|
161
|
+
const problems = [];
|
|
162
|
+
if (sensitivityDigest(payload) !== digest)
|
|
163
|
+
problems.push('report digest does not match its content');
|
|
164
|
+
if (report.actionAuthorization !== 'not-authorized')
|
|
165
|
+
problems.push('sensitivity report cannot authorize actions');
|
|
166
|
+
if (report.semantics !== 'associative-sensitivity-not-causal')
|
|
167
|
+
problems.push('sensitivity report cannot claim causal explanation');
|
|
168
|
+
if (report.rows.some(row => row.inference === 'new-invocation' && !row.freshInvocationId))
|
|
169
|
+
problems.push('new invocations must name a fresh invocation ID');
|
|
170
|
+
if (report.rows.some(row => row.changes.some(change => rawValueLeak(change)))) {
|
|
171
|
+
problems.push('report changes must not contain raw values');
|
|
172
|
+
}
|
|
173
|
+
if (problems.length)
|
|
174
|
+
throw new SensitivityContractError(`sensitivity report rejected: ${problems[0]}`, 'semantic', problems);
|
|
175
|
+
return report;
|
|
176
|
+
}
|
|
177
|
+
// Binding changes are limited to acceptance policy and fallback routing below a single target;
|
|
178
|
+
// replacing a target, the target list or a whole evaluation could swap the executor.
|
|
179
|
+
function policyReplayPath(path) {
|
|
180
|
+
return path.startsWith('/ruleset/spec/rules/') || path === '/ruleset/spec/defaultOutcome'
|
|
181
|
+
|| path === '/ruleset/spec/failureOutcome' || path === '/ruleset/spec/conflict'
|
|
182
|
+
|| /^\/binding\/spec\/evaluations\/[^/]+\/(?:targets\/\d+\/acceptance|fallbackOn)(?:\/.*)?$/.test(path);
|
|
183
|
+
}
|
|
184
|
+
function forbiddenAuthorityPath(path) {
|
|
185
|
+
return /(?:^|\/)(model|requestedModel|credentialRef|credential|executor|permission|permissions|legalCandidates|subagent|adapter|adapterVersion)(?:$|\/)/i.test(path);
|
|
186
|
+
}
|
|
187
|
+
const TARGET_IDENTITY_KEYS = new Set(['adapter', 'adapterVersion', 'model', 'requestedModel', 'subagent', 'credentialRef']);
|
|
188
|
+
function changesTargetIdentity(value) {
|
|
189
|
+
if (Array.isArray(value))
|
|
190
|
+
return value.some(changesTargetIdentity);
|
|
191
|
+
if (value && typeof value === 'object') {
|
|
192
|
+
return Object.entries(value).some(([key, item]) => TARGET_IDENTITY_KEYS.has(key) || changesTargetIdentity(item));
|
|
193
|
+
}
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
function hasForbiddenPointerSegment(path) {
|
|
197
|
+
return path.split('/').slice(1).map(part => part.replace(/~1/g, '/').replace(/~0/g, '~'))
|
|
198
|
+
.some(part => part === '__proto__' || part === 'constructor' || part === 'prototype');
|
|
199
|
+
}
|
|
200
|
+
function looksExecutable(value) {
|
|
201
|
+
if (typeof value === 'string')
|
|
202
|
+
return /(?:\$\{|=>|function\s*\(|require\s*\(|process\.env|https?:\/\/|bearer\s+|-----BEGIN|vault:|secret:)/i.test(value);
|
|
203
|
+
if (Array.isArray(value))
|
|
204
|
+
return value.some(looksExecutable);
|
|
205
|
+
if (value && typeof value === 'object')
|
|
206
|
+
return Object.entries(value).some(([key, item]) => /credential|secret|token|password|privateKey/i.test(key) || looksExecutable(item));
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
function actionValue(value) {
|
|
210
|
+
return value && typeof value === 'object' && !Array.isArray(value) && typeof value.action === 'string' ? value.action : null;
|
|
211
|
+
}
|
|
212
|
+
function labelValue(value) {
|
|
213
|
+
return value && typeof value === 'object' && !Array.isArray(value) && typeof value.label === 'string' ? value.label : null;
|
|
214
|
+
}
|
|
215
|
+
function rawValueLeak(value) {
|
|
216
|
+
return Object.hasOwn(value, 'value') || Object.hasOwn(value, 'raw') || Object.hasOwn(value, 'state') || Object.hasOwn(value, 'prompt');
|
|
217
|
+
}
|
|
218
|
+
//# sourceMappingURL=contract.js.map
|