@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,84 @@
|
|
|
1
|
+
import { pairedBinaryDifferenceInterval, pairedMeanDifferenceBootstrap, pairedNonInferiority, wilsonScoreInterval } from '../qualification/quality.js';
|
|
2
|
+
import { pairedDeltaFinding } from '../ensemble/contract.js';
|
|
3
|
+
import { D17_ANALYSIS, validateD17Analysis } from './protocol.js';
|
|
4
|
+
const mean = (values) => values.reduce((sum, n) => sum + n, 0) / values.length;
|
|
5
|
+
const p95 = (values) => [...values].sort((a, b) => a - b)[Math.ceil(values.length * 0.95) - 1];
|
|
6
|
+
/** Abstentions are false for correctness, but true events in their own paired endpoint. */
|
|
7
|
+
function interval(pairs, endpoint, levelBps) {
|
|
8
|
+
const counts = { both: 0, candidateOnly: 0, baselineOnly: 0, neither: 0 };
|
|
9
|
+
for (const pair of pairs) {
|
|
10
|
+
const c = endpoint(pair.challenger), b = endpoint(pair.champion);
|
|
11
|
+
counts[c ? b ? 'both' : 'candidateOnly' : b ? 'baselineOnly' : 'neither']++;
|
|
12
|
+
}
|
|
13
|
+
return pairedBinaryDifferenceInterval({ counts, levelBps, method: 'newcombe-10' });
|
|
14
|
+
}
|
|
15
|
+
/** Local descriptive statistics, never a release authorization. Missing endpoints withhold native reporting. */
|
|
16
|
+
export function d17Statistics(pairs, expectedIds, protocol = D17_ANALYSIS) {
|
|
17
|
+
validateD17Analysis(protocol);
|
|
18
|
+
const p = protocol;
|
|
19
|
+
if (new Set(expectedIds).size !== expectedIds.length || new Set(pairs.map(row => row.id)).size !== pairs.length
|
|
20
|
+
|| pairs.some(row => !expectedIds.includes(row.id) || !p.slices.some(slice => slice === row.slice)))
|
|
21
|
+
throw new Error('D17 pair membership');
|
|
22
|
+
for (const row of pairs)
|
|
23
|
+
for (const arm of [row.champion, row.challenger]) {
|
|
24
|
+
if (typeof arm.accepted !== 'boolean' || typeof arm.correct !== 'boolean' || !arm.accepted && arm.correct
|
|
25
|
+
|| [arm.probability, arm.brier].some(n => typeof n !== 'number' || !Number.isFinite(n) || n < 0 || n > 1)
|
|
26
|
+
|| [arm.latencyMs, arm.reservedCostMicros].some(n => typeof n !== 'number' || !Number.isFinite(n) || n < 0)
|
|
27
|
+
|| arm.tokens !== null && (!Number.isSafeInteger(arm.tokens) || arm.tokens < 0))
|
|
28
|
+
throw new Error('D17 unknown or invalid measurement');
|
|
29
|
+
}
|
|
30
|
+
const findings = [];
|
|
31
|
+
if (pairs.length !== expectedIds.length || pairs.length < p.minimumPairs)
|
|
32
|
+
findings.push('incomplete-pairs');
|
|
33
|
+
const slices = p.slices.map(slice => {
|
|
34
|
+
const rows = pairs.filter(row => row.slice === slice);
|
|
35
|
+
const delta = rows.length ? mean(rows.map(row => Number(row.challenger.correct) - Number(row.champion.correct))) : null;
|
|
36
|
+
if (rows.length < p.minimumPerSlice)
|
|
37
|
+
findings.push(`slice-insufficient:${slice}`);
|
|
38
|
+
return { slice, n: rows.length, qualityDelta: delta, harm: delta !== null && delta < 0, descriptiveOnly: true };
|
|
39
|
+
});
|
|
40
|
+
const metrics = (arm) => ({ quality: Number(arm.correct), calibration: arm.brier,
|
|
41
|
+
'risk-coverage': Number(arm.accepted && !arm.correct), abstention: Number(!arm.accepted), latency: arm.latencyMs,
|
|
42
|
+
tokens: arm.tokens, cost: arm.reservedCostMicros, slice: Number(arm.correct) });
|
|
43
|
+
const pairedDeltas = p.native.map(threshold => {
|
|
44
|
+
const complete = pairs.filter(row => metrics(row.champion)[threshold.metric] !== null && metrics(row.challenger)[threshold.metric] !== null);
|
|
45
|
+
const item = { metric: threshold.metric, pairs: complete.length, delta: complete.length ? mean(complete.map(row => metrics(row.challenger)[threshold.metric] - metrics(row.champion)[threshold.metric])) : null };
|
|
46
|
+
const finding = pairedDeltaFinding(threshold, item);
|
|
47
|
+
if (finding)
|
|
48
|
+
findings.push(finding);
|
|
49
|
+
return item;
|
|
50
|
+
});
|
|
51
|
+
const quality = pairs.length ? interval(pairs, arm => arm.correct, p.interval.levelBps) : null;
|
|
52
|
+
const abstention = pairs.length ? interval(pairs, arm => !arm.accepted, p.abstention.levelBps) : null;
|
|
53
|
+
const brier = pairs.length >= 2 ? pairedMeanDifferenceBootstrap({ differences: pairs.map(row => row.challenger.brier - row.champion.brier),
|
|
54
|
+
levelBps: p.brier.levelBps, seed: p.brier.seed, resamples: p.brier.resamples, bounds: [-1, 1] }) : null;
|
|
55
|
+
const accepted = pairs.filter(row => row.challenger.accepted), errors = accepted.filter(row => !row.challenger.correct).length;
|
|
56
|
+
const risk = accepted.length ? wilsonScoreInterval({ events: errors, n: accepted.length, levelBps: p.risk.levelBps }) : null;
|
|
57
|
+
const coverage = pairs.length ? wilsonScoreInterval({ events: accepted.length, n: pairs.length, levelBps: p.risk.levelBps }) : null;
|
|
58
|
+
const acceptedErrorUpperBps = risk ? Math.ceil(risk[1] * 10000) : null;
|
|
59
|
+
const coverageLowerBps = coverage ? Math.floor(coverage[0] * 10000) : null;
|
|
60
|
+
const p95LatencyIncreaseMs = pairs.length ? p95(pairs.map(row => row.challenger.latencyMs)) - p95(pairs.map(row => row.champion.latencyMs)) : null;
|
|
61
|
+
if (!quality || pairedNonInferiority({ interval: quality, marginBps: p.interval.nonInferiorityMarginBps }).decision !== 'non-inferior')
|
|
62
|
+
findings.push('quality-ni');
|
|
63
|
+
if (!brier || brier.upperBps > p.brier.maximumUpperBps)
|
|
64
|
+
findings.push('brier-upper');
|
|
65
|
+
if (!abstention || abstention.upperBps > p.abstention.maximumUpperBps)
|
|
66
|
+
findings.push('abstention-upper');
|
|
67
|
+
if (acceptedErrorUpperBps === null || acceptedErrorUpperBps > p.risk.maximumAcceptedErrorUpperBps)
|
|
68
|
+
findings.push('accepted-risk-upper');
|
|
69
|
+
if (coverageLowerBps === null || coverageLowerBps < p.risk.minimumCoverageLowerBps)
|
|
70
|
+
findings.push('coverage-lower');
|
|
71
|
+
if (p95LatencyIncreaseMs === null || p95LatencyIncreaseMs > p.maximumP95LatencyIncreaseMs)
|
|
72
|
+
findings.push('latency-p95');
|
|
73
|
+
const observed = new Set(pairs.map(row => row.id));
|
|
74
|
+
return { sampleN: pairs.length, missingIds: expectedIds.filter(id => !observed.has(id)), slices, pairedDeltas,
|
|
75
|
+
quality, brier, abstention, acceptedN: accepted.length, acceptedErrors: errors, acceptedErrorUpperBps, coverageLowerBps,
|
|
76
|
+
p95LatencyIncreaseMs, latencyDifferencesMs: pairs.map(row => row.challenger.latencyMs - row.champion.latencyMs),
|
|
77
|
+
netSavingsMicros: pairs.reduce((sum, row) => sum + row.champion.reservedCostMicros - row.challenger.reservedCostMicros, 0),
|
|
78
|
+
costBasis: 'nonrefundable-reservations-including-failed-attempts',
|
|
79
|
+
benefitSupported: quality !== null && quality.lowerBps > p.interval.benefitLowerExclusiveBps,
|
|
80
|
+
findings: findings.sort(), statisticalGate: findings.length ? 'HOLD' : 'pass',
|
|
81
|
+
worstCaseFailureAsError: { n: expectedIds.length, championCorrect: pairs.filter(row => row.champion.correct).length,
|
|
82
|
+
challengerCorrect: pairs.filter(row => row.challenger.correct).length, missingPairsCountedAsErrors: expectedIds.length - pairs.length } };
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=statistics.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'node:crypto';
|
|
2
2
|
import { canonicalJson } from '../security/artifact-trust.js';
|
|
3
3
|
import { composeRuleset } from './compose.js';
|
|
4
|
-
import {
|
|
4
|
+
import { applyTargetAcceptance, validatePrimitiveAcceptancePolicy } from './acceptance.js';
|
|
5
5
|
import { DecisionPreDispatchError, decisionInvocationFingerprint, nextReceipt } from './receipts.js';
|
|
6
6
|
import { admitEntry, EntryAdmissionError } from './entry.js';
|
|
7
7
|
import { correlateAtomicBatch, decisionBatchQuestionId, planNativeDecisionBatches } from './batch.js';
|
|
@@ -16,6 +16,7 @@ import { providerPrefixEvidence } from './compile-cache/prefix.js';
|
|
|
16
16
|
import { digestCachedResult, RESULT_CACHE_KEY_VERSION } from './result-cache/index.js';
|
|
17
17
|
import { DecisionProjectionError, normalizeProjectionOrigin, projectDecisionState, } from './projection.js';
|
|
18
18
|
import { DecisionRuntimeTrace, runtimeTraceOf } from './telemetry/runtime.js';
|
|
19
|
+
import { gatePreprocessedEvidenceDispatch, isEmptyPreprocessingLineage, isWellFormedPreprocessingLineage, PREPROCESSING_LOCAL_ORIGIN, } from './preprocessed-evidence.js';
|
|
19
20
|
import { assertContextQualified } from './context-qualification.js';
|
|
20
21
|
import { assertContextPlanCurrent, ContextPlanError, planDecisionContext, recordContextActualUsage, } from './context-plan.js';
|
|
21
22
|
import { DECISION_API_VERSION, DECISION_API_VERSION_STRUCTURED } from './types.js';
|
|
@@ -34,6 +35,10 @@ export async function evaluateDecisionRuleset(request) {
|
|
|
34
35
|
/** A cache hit is a historical result accompanied by a NEW caller receipt, not a fresh adapter attempt. */
|
|
35
36
|
async function evaluateWithResultCache(request) {
|
|
36
37
|
const config = request.resultCache;
|
|
38
|
+
// A cache hit would bypass the D24 pre-dispatch lineage gate, so lineage is never cached.
|
|
39
|
+
if (preprocessingGateApplies(request)) {
|
|
40
|
+
throw new Error('Result cache does not support preprocessing lineage');
|
|
41
|
+
}
|
|
37
42
|
if (!config.policy.sideEffectFree || !request.receiptStore || !request.calibrationPin || !request.policyPin
|
|
38
43
|
|| !config.recordCallerReceipt) {
|
|
39
44
|
throw new Error('Result cache requires side-effect-free policy, durable receipts, policy and calibration pins');
|
|
@@ -163,10 +168,15 @@ async function evaluateDecisionRulesetUngated(request) {
|
|
|
163
168
|
rulesetPin = artifactPin(request.ruleset);
|
|
164
169
|
bindingPin = artifactPin(request.binding);
|
|
165
170
|
// Pin and execute immutable snapshots; adapters receive separate per-attempt copies.
|
|
171
|
+
// D24 lineage and verification are host-supplied too: the gate and the recorded
|
|
172
|
+
// result share one snapshot, so caller-owned mutation during evaluation cannot
|
|
173
|
+
// change the verdict or the recorded lineage.
|
|
166
174
|
request = {
|
|
167
175
|
...request,
|
|
168
176
|
ruleset: structuredClone(request.ruleset), binding: structuredClone(request.binding),
|
|
169
177
|
definitions: structuredClone(request.definitions), input: structuredClone(request.input),
|
|
178
|
+
preprocessingLineage: snapshotPreprocessingLineage(request.preprocessingLineage),
|
|
179
|
+
preprocessingVerification: snapshotPreprocessingVerification(request.preprocessingVerification),
|
|
170
180
|
};
|
|
171
181
|
base = resultBase(request, rulesetPin, bindingPin);
|
|
172
182
|
validateBinding(request.binding, request.ruleset);
|
|
@@ -195,6 +205,19 @@ async function evaluateDecisionRulesetUngated(request) {
|
|
|
195
205
|
if (contextPlan)
|
|
196
206
|
base = withRulesetContext(base, contextPlan, []);
|
|
197
207
|
runtimeTraceOf(request)?.validated(false);
|
|
208
|
+
// D24: stored preprocessing lineage is gated before receipts, credentials and transport.
|
|
209
|
+
if (preprocessingGateApplies(request)) {
|
|
210
|
+
const gate = gatePreprocessedEvidenceDispatch(request.preprocessingLineage, request.preprocessingVerification, resolved.flatMap(item => request.binding.spec.evaluations[item.alias].targets
|
|
211
|
+
.map(target => preprocessingDestination(request, item.alias, target))), request.input, request.ruleset.spec.evaluations.map(evaluation => evaluation.inputPointer));
|
|
212
|
+
base = { ...base, spec: { ...base.spec, preprocessingLineage: base.spec.preprocessingLineage
|
|
213
|
+
? { ...base.spec.preprocessingLineage, dispatchGate: gate }
|
|
214
|
+
: { schemaVersion: 'decision-preprocessing-lineage/v1', status: 'review', references: [], traces: [], dispatchGate: gate } } };
|
|
215
|
+
if (gate.outcome === 'refused')
|
|
216
|
+
return failureResult(base, 'data-boundary-denied');
|
|
217
|
+
if (gate.outcome === 'review') {
|
|
218
|
+
return { ...base, spec: { ...base.spec, status: 'review', reason: 'insufficient-information', matchedRules: [], evaluations: {} } };
|
|
219
|
+
}
|
|
220
|
+
}
|
|
198
221
|
const batchPolicy = request.batchReceipts;
|
|
199
222
|
if (batchPolicy?.unknownCostBound && (!Number.isSafeInteger(batchPolicy.unknownCostBound.upperBoundMicros)
|
|
200
223
|
|| batchPolicy.unknownCostBound.upperBoundMicros < 0 || !batchPolicy.unknownCostBound.policyId
|
|
@@ -205,6 +228,14 @@ async function evaluateDecisionRulesetUngated(request) {
|
|
|
205
228
|
|| batchPolicy.unknownCostBound.upperBoundMicros > batchPolicy.maxCostMicros)) {
|
|
206
229
|
return failureResult(base, 'budget-exhausted');
|
|
207
230
|
}
|
|
231
|
+
// #2797: a context-planned invocation clones the full plan into every evaluation, so the
|
|
232
|
+
// completed result can exceed entry admission after provider work. Preflight the worst-case
|
|
233
|
+
// completion envelope here — before receipts, credentials, admission control and transport —
|
|
234
|
+
// and reject without dispatch when no completion could be admissible.
|
|
235
|
+
if (contextPlan && !contextResultEnvelopeFits(contextPlan, resolved, request.binding)) {
|
|
236
|
+
const rejected = failureResult(base, 'invalid-input');
|
|
237
|
+
return { ...rejected, spec: { ...rejected.spec, contextFailure: { schemaVersion: 'decision-context-failure/v1', reason: 'invalid-input' } } };
|
|
238
|
+
}
|
|
208
239
|
const fingerprint = decisionInvocationFingerprint({
|
|
209
240
|
invocationId: request.invocationId,
|
|
210
241
|
value: request.input,
|
|
@@ -437,10 +468,24 @@ async function evaluateDecisionRulesetUngated(request) {
|
|
|
437
468
|
const batchSpan = runtimeTraceOf(request)?.startBatch(plan.candidates.map(candidate => candidate.alias), plan.groupId, durableReceipt ? durableReceipt.attempts.length + 1 : dispatchCount);
|
|
438
469
|
if (batchSpan)
|
|
439
470
|
batchSpans.push(batchSpan);
|
|
471
|
+
// A request owns its usage even when it fails before an answer is available.
|
|
472
|
+
// Durable mode already has that owner in its batch receipt.
|
|
473
|
+
const usageOwner = !durableReceipt ? {
|
|
474
|
+
groupId: plan.groupId, ordinal: dispatchCount, questionIds: [...questionIds],
|
|
475
|
+
usage: { inputTokens: null, outputTokens: null, costUsd: null },
|
|
476
|
+
requestId: null,
|
|
477
|
+
} : undefined;
|
|
478
|
+
if (usageOwner)
|
|
479
|
+
(base.spec.batchRequests ??= []).push(usageOwner);
|
|
440
480
|
const response = await adapter.evaluateMany({ decisionSubject: plan.decisionSubject,
|
|
441
481
|
requests: preparedRequests, ...(batchSpan ? { traceContext: batchSpan.traceContext } : {}) });
|
|
442
482
|
if (batchSpan)
|
|
443
483
|
batchSpan.response = response;
|
|
484
|
+
if (usageOwner) {
|
|
485
|
+
usageOwner.usage = structuredClone(response.sharedUsage);
|
|
486
|
+
const ids = uniqueNonNull(response.answers.map(answer => answer.observation.requestId));
|
|
487
|
+
usageOwner.requestId = ids.length === 1 && validOpaqueRequestId(ids[0]) ? ids[0] : null;
|
|
488
|
+
}
|
|
444
489
|
if (contextPlan && response.sharedUsage.inputTokens !== null) {
|
|
445
490
|
recordRuntimeContextUsage(contextPlan, questionIds, response.sharedUsage.inputTokens, contextUsage);
|
|
446
491
|
}
|
|
@@ -581,6 +626,8 @@ async function evaluateDecisionRulesetUngated(request) {
|
|
|
581
626
|
}
|
|
582
627
|
}
|
|
583
628
|
runtimeTraceOf(request)?.finishBatch(batchSpans, durableReceipt);
|
|
629
|
+
// Both durable and result-owned native requests keep accounting off answers.
|
|
630
|
+
observations = resultOnlyBatchObservations(observations);
|
|
584
631
|
plan.candidates.forEach((candidate, index) => {
|
|
585
632
|
const item = resolved.find(value => value.alias === candidate.alias);
|
|
586
633
|
const evidence = { mode: 'native', groupId: plan.groupId, questionId: questionIds[index] };
|
|
@@ -920,6 +967,65 @@ async function invokeWithDeadline(context, adapter, target, deadlineEpochMs, ord
|
|
|
920
967
|
}
|
|
921
968
|
/** Requests whose projection evidence prohibited automatic action (allowed incomplete context). */
|
|
922
969
|
const projectionBlockedAutomaticAction = new WeakSet();
|
|
970
|
+
/**
|
|
971
|
+
* D24 snapshot of host-supplied lineage. Uncloneable content cannot be well-formed
|
|
972
|
+
* lineage, so it is passed through for the gate to refuse exactly as before.
|
|
973
|
+
*/
|
|
974
|
+
function snapshotPreprocessingLineage(lineage) {
|
|
975
|
+
if (lineage === undefined)
|
|
976
|
+
return undefined;
|
|
977
|
+
try {
|
|
978
|
+
return structuredClone(lineage);
|
|
979
|
+
}
|
|
980
|
+
catch {
|
|
981
|
+
return lineage;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
/**
|
|
985
|
+
* D24 snapshot of host-supplied verification. Manifests, lifecycle and bindings are
|
|
986
|
+
* data and snapshotted with the lineage; `now` is host code and carried by reference
|
|
987
|
+
* like the other request callbacks. Uncloneable data is passed through for the gate
|
|
988
|
+
* to judge.
|
|
989
|
+
*/
|
|
990
|
+
function snapshotPreprocessingVerification(verification) {
|
|
991
|
+
if (verification === undefined)
|
|
992
|
+
return undefined;
|
|
993
|
+
const { now, ...rest } = verification;
|
|
994
|
+
try {
|
|
995
|
+
const snapshot = structuredClone(rest);
|
|
996
|
+
return now === undefined ? snapshot : { ...snapshot, now };
|
|
997
|
+
}
|
|
998
|
+
catch {
|
|
999
|
+
return verification;
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
/**
|
|
1003
|
+
* D24 applies when lineage is supplied or the host supplied verification expecting lineage.
|
|
1004
|
+
* Requests with neither keep the text-native path unchanged.
|
|
1005
|
+
*/
|
|
1006
|
+
function preprocessingGateApplies(request) {
|
|
1007
|
+
return !isEmptyPreprocessingLineage(request.preprocessingLineage) || request.preprocessingVerification !== undefined;
|
|
1008
|
+
}
|
|
1009
|
+
/**
|
|
1010
|
+
* The D10 destination a target would send derived text to: the projection policy's provider and
|
|
1011
|
+
* origin, or the local no-egress destination when no projection is configured (projection then
|
|
1012
|
+
* denies any network adapter before credentials). `null` means the destination cannot be bound.
|
|
1013
|
+
*/
|
|
1014
|
+
function preprocessingDestination(request, alias, target) {
|
|
1015
|
+
const projection = request.projection;
|
|
1016
|
+
if (!projection)
|
|
1017
|
+
return { provider: target.adapter, origin: PREPROCESSING_LOCAL_ORIGIN };
|
|
1018
|
+
if (isUnprojectedLocalOptOut(projection))
|
|
1019
|
+
return null;
|
|
1020
|
+
try {
|
|
1021
|
+
const policy = projection.resolve({ alias, target: structuredClone(target) });
|
|
1022
|
+
return typeof policy?.provider === 'string' && typeof policy.origin === 'string'
|
|
1023
|
+
? { provider: policy.provider, origin: policy.origin } : null;
|
|
1024
|
+
}
|
|
1025
|
+
catch {
|
|
1026
|
+
return null;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
923
1029
|
function isUnprojectedLocalOptOut(projection) {
|
|
924
1030
|
return !!projection && projection.mode === 'unprojected-local';
|
|
925
1031
|
}
|
|
@@ -1062,17 +1168,7 @@ function normalizeObservation(definition, target, observation) {
|
|
|
1062
1168
|
validateDecisionValue(definition, observation.value);
|
|
1063
1169
|
if (observation.uncertainty?.distribution)
|
|
1064
1170
|
validateDistribution(definition, observation.uncertainty.distribution);
|
|
1065
|
-
|
|
1066
|
-
return observation;
|
|
1067
|
-
if (target.acceptance.mode === 'primitive-policy')
|
|
1068
|
-
return applyPrimitiveAcceptance(definition, target.acceptance, observation);
|
|
1069
|
-
if (!observation.uncertainty || observation.uncertainty.confidence === null)
|
|
1070
|
-
return observationFailure('missing-confidence', 'abstained', observation);
|
|
1071
|
-
if (observation.uncertainty.profile !== target.acceptance.profile)
|
|
1072
|
-
return observationFailure('confidence-profile-mismatch', 'abstained', observation);
|
|
1073
|
-
if (observation.uncertainty.confidence * 10_000 < target.acceptance.minimumBps)
|
|
1074
|
-
return observationFailure('low-confidence', 'abstained', observation);
|
|
1075
|
-
return observation;
|
|
1171
|
+
return applyTargetAcceptance(definition, target, observation);
|
|
1076
1172
|
}
|
|
1077
1173
|
function normalizeObservationForRuntime(input) {
|
|
1078
1174
|
let observation = input.observation;
|
|
@@ -1143,7 +1239,10 @@ function resultBase(request, ruleset, binding) {
|
|
|
1143
1239
|
apiVersion: resultVersion(request), kind: 'RulesetResult',
|
|
1144
1240
|
metadata: { id: request.invocationId, version: '1.0.0', description: `Ruleset result for ${request.ruleset.metadata.id}` },
|
|
1145
1241
|
spec: { ruleset, binding, runId: request.runId, invocationId: request.invocationId, status: 'error', reason: 'evaluation-failed', matchedRules: [], evaluations: {},
|
|
1146
|
-
...(isUnprojectedLocalOptOut(request.projection) ? { projection: { mode: 'unprojected-local', authority: 'host' } } : {})
|
|
1242
|
+
...(isUnprojectedLocalOptOut(request.projection) ? { projection: { mode: 'unprojected-local', authority: 'host' } } : {}),
|
|
1243
|
+
// Only a lineage the D24 gate can read is copied; a malformed one is recorded by the gate as refused.
|
|
1244
|
+
...(!isEmptyPreprocessingLineage(request.preprocessingLineage) && isWellFormedPreprocessingLineage(request.preprocessingLineage)
|
|
1245
|
+
? { preprocessingLineage: structuredClone(request.preprocessingLineage) } : {}) },
|
|
1147
1246
|
};
|
|
1148
1247
|
}
|
|
1149
1248
|
/**
|
|
@@ -1158,7 +1257,7 @@ function resultVersion(request) {
|
|
|
1158
1257
|
|| request.ruleset.apiVersion === DECISION_API_VERSION_STRUCTURED || request.binding.apiVersion === DECISION_API_VERSION_STRUCTURED
|
|
1159
1258
|
|| Object.values(request.definitions).some(definition => definition.apiVersion === DECISION_API_VERSION_STRUCTURED)
|
|
1160
1259
|
|| request.batching || request.batchReceipts || request.scheduler?.enabled || request.providerPrefix || request.context
|
|
1161
|
-
|| request.resultCache?.policy.enabled) {
|
|
1260
|
+
|| request.resultCache?.policy.enabled || preprocessingGateApplies(request)) {
|
|
1162
1261
|
return DECISION_API_VERSION_STRUCTURED;
|
|
1163
1262
|
}
|
|
1164
1263
|
return DECISION_API_VERSION;
|
|
@@ -1259,6 +1358,120 @@ function contextPartitionedBatchPlans(plans, contextPlan) {
|
|
|
1259
1358
|
}
|
|
1260
1359
|
return split;
|
|
1261
1360
|
}
|
|
1361
|
+
/**
|
|
1362
|
+
* Pre-dispatch worst-case bound (#2797) on the completed result envelope of a context-planned
|
|
1363
|
+
* invocation.
|
|
1364
|
+
*
|
|
1365
|
+
* Every evaluation carries one full clone of the context plan alongside the ruleset-level plan,
|
|
1366
|
+
* so the envelope grows with the alias count even when the provider request fits. Usage is
|
|
1367
|
+
* recorded cumulatively as evaluations complete — evaluation i carries i+1 usage entries — and
|
|
1368
|
+
* entries are upserted by key, so retries add none; every evaluation carrying all n entries is
|
|
1369
|
+
* therefore the true upper bound. Attempts are bounded per alias by the binding: no alias can
|
|
1370
|
+
* record more than its targets' retries plus one, nor more than the invocation-wide maxAttempts.
|
|
1371
|
+
* Each probe attempt carries the same evidence a real single dispatch records (requestIdSource
|
|
1372
|
+
* and the single-dispatch batch object), and each probe distribution spans the definition's full
|
|
1373
|
+
* answer domain. Entry admission is monotonic in every counted dimension, so a probe that cannot
|
|
1374
|
+
* satisfy admission proves no completion of the invocation could either; the caller must then
|
|
1375
|
+
* reject before receipts, credentials, admission control or transport instead of discarding
|
|
1376
|
+
* completed provider work after dispatch. A fitting envelope still passes through writer-version
|
|
1377
|
+
* admission when the completed result is written. Null, unknown or non-safe-integer counts fail
|
|
1378
|
+
* closed.
|
|
1379
|
+
*/
|
|
1380
|
+
export function contextResultEnvelopeFits(plan, items, binding) {
|
|
1381
|
+
if (!binding || !Number.isSafeInteger(binding.spec?.maxAttempts) || binding.spec.maxAttempts < 1)
|
|
1382
|
+
return false;
|
|
1383
|
+
for (const item of items) {
|
|
1384
|
+
const configured = binding.spec.evaluations?.[item.alias];
|
|
1385
|
+
if (!configured || !Array.isArray(configured.targets) || !configured.targets.length)
|
|
1386
|
+
return false;
|
|
1387
|
+
let perAlias = 0;
|
|
1388
|
+
for (const target of configured.targets) {
|
|
1389
|
+
const maxRetries = target?.retry?.maxRetries;
|
|
1390
|
+
if (!Number.isSafeInteger(maxRetries) || maxRetries < 0)
|
|
1391
|
+
return false;
|
|
1392
|
+
perAlias += maxRetries + 1;
|
|
1393
|
+
}
|
|
1394
|
+
if (!Number.isSafeInteger(perAlias) || Math.min(binding.spec.maxAttempts, perAlias) < 1)
|
|
1395
|
+
return false;
|
|
1396
|
+
if (contextResultEnvelopeDistributionKeys(item.definition).length === 0)
|
|
1397
|
+
return false;
|
|
1398
|
+
}
|
|
1399
|
+
try {
|
|
1400
|
+
admitEntry(contextResultEnvelopeProbe(plan, items, binding));
|
|
1401
|
+
return true;
|
|
1402
|
+
}
|
|
1403
|
+
catch (error) {
|
|
1404
|
+
if (error instanceof EntryAdmissionError)
|
|
1405
|
+
return false;
|
|
1406
|
+
throw error;
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
/** Answer-domain keys of a definition, matching distribution validation support. */
|
|
1410
|
+
function contextResultEnvelopeDistributionKeys(definition) {
|
|
1411
|
+
const answer = definition?.spec?.answer;
|
|
1412
|
+
if (!answer)
|
|
1413
|
+
return [];
|
|
1414
|
+
if (answer.kind === 'choice')
|
|
1415
|
+
return answer.options.map(option => option.id);
|
|
1416
|
+
if (answer.kind === 'ordinal-score')
|
|
1417
|
+
return answer.levels.map((_, index) => String(index));
|
|
1418
|
+
return ['false', 'true'];
|
|
1419
|
+
}
|
|
1420
|
+
function contextResultEnvelopeProbe(plan, items, binding) {
|
|
1421
|
+
const pin = (id) => ({ id, version: '1.0.0', digest: `sha256:${'0'.repeat(64)}` });
|
|
1422
|
+
const firstPartition = plan.partitions[0];
|
|
1423
|
+
const usageFor = (alias) => firstPartition && firstPartition.questionIds.length
|
|
1424
|
+
? recordContextActualUsage(plan, firstPartition.id, 0, firstPartition.questionIds.includes(alias) ? alias : firstPartition.questionIds[0])
|
|
1425
|
+
: { schemaVersion: 'decision-context-usage/v1', planDigest: plan.planDigest, partitionId: 'probe', questionIds: [alias],
|
|
1426
|
+
estimator: { ...plan.estimator }, providerProfile: { id: plan.providerProfile.id, version: plan.providerProfile.version },
|
|
1427
|
+
estimatedInputTokens: 0, actualInputTokens: 0, estimationErrorTokens: 0, estimationErrorBps: null };
|
|
1428
|
+
// Worst case: every evaluation carries every usage entry (cumulative recording, upserted by key).
|
|
1429
|
+
const usages = items.map(item => usageFor(item.alias));
|
|
1430
|
+
const evaluations = Object.fromEntries(items.map(item => {
|
|
1431
|
+
const configured = binding.spec.evaluations[item.alias];
|
|
1432
|
+
const target = configured.targets[0];
|
|
1433
|
+
const questionId = decisionBatchQuestionId(item.alias);
|
|
1434
|
+
let perAlias = 0;
|
|
1435
|
+
for (const candidate of configured.targets)
|
|
1436
|
+
perAlias += candidate.retry.maxRetries + 1;
|
|
1437
|
+
const attemptCount = Math.min(binding.spec.maxAttempts, perAlias);
|
|
1438
|
+
const keys = contextResultEnvelopeDistributionKeys(item.definition);
|
|
1439
|
+
const peak = keys.reduce((longest, key) => key.length >= longest.length ? key : longest, keys[0]);
|
|
1440
|
+
const value = item.definition.spec.answer.kind === 'ordinal-score' ? Number(peak)
|
|
1441
|
+
: item.definition.spec.answer.kind === 'choice' ? peak : peak === 'true';
|
|
1442
|
+
const distribution = Object.fromEntries(keys.map(key => [key, key === peak ? 1 : 0]));
|
|
1443
|
+
const attempts = Array.from({ length: attemptCount }, (_, index) => ({
|
|
1444
|
+
ordinal: index + 1, adapter: target.adapter, adapterVersion: target.adapterVersion,
|
|
1445
|
+
requestedModel: target.model, actualModel: target.model,
|
|
1446
|
+
subagent: target.subagent ?? null, status: 'success', reason: 'none', durationMs: 0,
|
|
1447
|
+
usage: { inputTokens: 0, outputTokens: 0, costUsd: null },
|
|
1448
|
+
requestId: 'probe', requestIdSource: 'typesafe', httpStatus: 200,
|
|
1449
|
+
// A retried attempt records its backoff; only the final attempt has none.
|
|
1450
|
+
...(index < attemptCount - 1 ? { retryDelayMs: 0 } : {}),
|
|
1451
|
+
// Single-dispatch evidence, as recorded when native batching does not apply. The native
|
|
1452
|
+
// batch object carries a subset of these fields, so the single shape bounds both phases.
|
|
1453
|
+
batch: { mode: 'single', groupId: `single_${questionId.slice(2)}`, questionId, degradationReason: 'unsupported' },
|
|
1454
|
+
}));
|
|
1455
|
+
return [item.alias, {
|
|
1456
|
+
apiVersion: DECISION_API_VERSION_STRUCTURED, kind: 'DecisionResult',
|
|
1457
|
+
metadata: { id: `context-result-envelope-probe-${item.alias}`, version: '1.0.0', description: `Decision result for ${item.alias}` },
|
|
1458
|
+
spec: { decision: pin(`probe-${item.alias}`), ruleset: pin('probe-ruleset'), binding: pin('probe-binding'),
|
|
1459
|
+
alias: item.alias, runId: 'context-result-envelope-probe', invocationId: 'context-result-envelope-probe',
|
|
1460
|
+
status: 'success', value, reason: 'none',
|
|
1461
|
+
uncertainty: { source: 'provider', profile: 'typesafe-distribution-v1', calibration: 'vendor-claimed', confidence: 1,
|
|
1462
|
+
distribution, calibrationRef: null },
|
|
1463
|
+
attempts, context: { plan: structuredClone(plan), actualUsage: usages.map(usage => structuredClone(usage)) } },
|
|
1464
|
+
}];
|
|
1465
|
+
}));
|
|
1466
|
+
return { apiVersion: DECISION_API_VERSION_STRUCTURED, kind: 'RulesetResult',
|
|
1467
|
+
metadata: { id: 'context-result-envelope-probe', version: '1.0.0', description: 'Context result envelope probe' },
|
|
1468
|
+
// A matched completion carries its outcome and matched rule ids; ruleset-declared outcomes are
|
|
1469
|
+
// bounded by artifact admission of the ruleset itself, so the probe carries representative ones.
|
|
1470
|
+
spec: { ruleset: pin('probe-ruleset'), binding: pin('probe-binding'), runId: 'context-result-envelope-probe',
|
|
1471
|
+
invocationId: 'context-result-envelope-probe', status: 'completed', reason: 'none', matchedRules: ['review'],
|
|
1472
|
+
outcome: 'review',
|
|
1473
|
+
evaluations, context: { plan: structuredClone(plan), actualUsage: usages } } };
|
|
1474
|
+
}
|
|
1262
1475
|
function recordRuntimeContextUsage(plan, questionIds, actualInputTokens, evidence) {
|
|
1263
1476
|
const ids = typeof questionIds === 'string' ? [questionIds] : questionIds;
|
|
1264
1477
|
const partition = plan.partitions.find(candidate => ids.every(id => candidate.questionIds.includes(id)));
|
|
@@ -3,6 +3,10 @@ export function decisionResultForExport(result, policy = {}) {
|
|
|
3
3
|
const copy = structuredClone(result);
|
|
4
4
|
if (policy.includeProviderRequestIds)
|
|
5
5
|
return copy;
|
|
6
|
+
if (copy.kind === 'RulesetResult') {
|
|
7
|
+
for (const request of copy.spec.batchRequests ?? [])
|
|
8
|
+
request.requestId = null;
|
|
9
|
+
}
|
|
6
10
|
const decisions = copy.kind === 'DecisionResult'
|
|
7
11
|
? [copy] : Object.values(copy.spec.evaluations);
|
|
8
12
|
for (const decision of decisions) {
|