@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,430 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { mkdir, readFile, rm } from 'node:fs/promises';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
|
+
import { pathToFileURL } from 'node:url';
|
|
5
|
+
import { evaluateDecisionRuleset } from '../evaluate.js';
|
|
6
|
+
import { JevDecisionAdapter } from '../adapters/jev.js';
|
|
7
|
+
import { assertContextArtifactRoot, assertContextLiveSource } from '../context-live-qualification.js';
|
|
8
|
+
import { captureQualificationLifetime } from '../qualification/capture.js';
|
|
9
|
+
import { QUALIFICATION_PRIVACY_SURFACES, scanQualificationPrivacy } from '../qualification/privacy.js';
|
|
10
|
+
import { executeQualificationPlan, verifyQualificationArtifacts, writeQualificationEvidenceManifest } from '../qualification/runner.js';
|
|
11
|
+
import { qualificationIntegrityAllowlistProblems } from '../qualification/release.js';
|
|
12
|
+
import { redactText } from '../../governance/redaction.js';
|
|
13
|
+
import { heldoutDigest, heldoutRequest, heldoutReservationMicros, heldoutReservationTokens, HeldoutError, HELDOUT_ENV_GATE, heldoutRowsInScope, planHeldoutCollection, validateHeldoutBundle } from './contract.js';
|
|
14
|
+
import { appendHeldoutSpend, appendHeldoutEvent, heldoutDirectory, heldoutEvidenceDigest, heldoutRunsRoot, readHeldoutFile, readHeldoutJournal, reconcileHeldoutBaseline, scanHeldoutSpend, writeHeldoutFile, validateHeldoutJournal, heldoutCollectionAllowance } from './journal.js';
|
|
15
|
+
import { readHeldoutFrozen, sealHeldoutCalibrationPhase, validateHeldoutPhaseAccess } from './calibration.js';
|
|
16
|
+
import { D17_MULTIFACT_CALIBRATION_NOT_AFTER, usesD17MultifactGenerator } from './study-generators.js';
|
|
17
|
+
const byteDigest = (bytes) => `sha256:${createHash('sha256').update(bytes).digest('hex')}`;
|
|
18
|
+
const delay = (ms) => new Promise(done => setTimeout(done, ms));
|
|
19
|
+
const key = (a) => `${a.rowId}/${a.requestId}`;
|
|
20
|
+
/** Only the one repository resolver can be loaded; neither corpus nor provider output chooses executable code. */
|
|
21
|
+
async function liveHost(options) {
|
|
22
|
+
const path = resolve(options.sourceRoot, 'tools/decision/jev-credential-resolver.mjs');
|
|
23
|
+
if (byteDigest(await readFile(path)) !== options.bundle.approval.credentialResolverDigest)
|
|
24
|
+
throw new HeldoutError('resolver-pin');
|
|
25
|
+
const module = await import(pathToFileURL(path).href);
|
|
26
|
+
module.assertTlsVerification();
|
|
27
|
+
return module.createJevCredentialResolver();
|
|
28
|
+
}
|
|
29
|
+
/** Default-off, sequential collection. No runtime alias, calibration record, gate or workflow is changed. */
|
|
30
|
+
export async function collectHeldoutStudy(options) {
|
|
31
|
+
if (options.enabled !== true)
|
|
32
|
+
return { status: 'disabled' };
|
|
33
|
+
const offline = options.offline;
|
|
34
|
+
if (offline !== undefined && (!offline || typeof offline.transport !== 'function'
|
|
35
|
+
|| typeof offline.host?.resolveCredential !== 'function' || typeof offline.host?.dispose !== 'function')) {
|
|
36
|
+
throw new HeldoutError('offline-options');
|
|
37
|
+
}
|
|
38
|
+
// Capture the validated seam so caller mutation cannot re-enable the adapter's fetch fallback.
|
|
39
|
+
const transport = offline?.transport;
|
|
40
|
+
const offlineHost = offline?.host;
|
|
41
|
+
if (!offline && process.env[HELDOUT_ENV_GATE] !== '1')
|
|
42
|
+
throw new HeldoutError('live-gate');
|
|
43
|
+
if (!offline && process.env.NODE_TLS_REJECT_UNAUTHORIZED !== undefined && process.env.NODE_TLS_REJECT_UNAUTHORIZED !== '1') {
|
|
44
|
+
throw new HeldoutError('tls');
|
|
45
|
+
}
|
|
46
|
+
validateHeldoutBundle(options.bundle, options.trustedApprovalDigest);
|
|
47
|
+
// Clone after admission: caller mutation cannot change a dispatched or recorded pin.
|
|
48
|
+
const bundle = structuredClone(options.bundle), { corpus, preregistration: plan, approval: a } = bundle;
|
|
49
|
+
if (a.calibration.mode !== 'staged' || a.calibration.phase !== 'test')
|
|
50
|
+
await planHeldoutCollection(bundle, options.trustedApprovalDigest);
|
|
51
|
+
await assertContextLiveSource(options.sourceRoot, a.sourceCommit);
|
|
52
|
+
await assertContextArtifactRoot(options.sourceRoot, options.artifactRoot);
|
|
53
|
+
await heldoutDirectory(heldoutRunsRoot(options.artifactRoot));
|
|
54
|
+
const lock = join(options.artifactRoot, 'research', 'qualification', 'heldout', 'dispatch.lock');
|
|
55
|
+
try {
|
|
56
|
+
await mkdir(lock, { mode: 0o700 });
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
throw new HeldoutError('dispatch-locked');
|
|
60
|
+
}
|
|
61
|
+
let host;
|
|
62
|
+
let uncertain = false;
|
|
63
|
+
try {
|
|
64
|
+
// Missing/corrupt baselines in an existing ledger require explicit reconciliation, never a reset.
|
|
65
|
+
await scanHeldoutSpend(options.artifactRoot, a.study);
|
|
66
|
+
const baselineDigests = await reconcileHeldoutBaseline(options.artifactRoot, a);
|
|
67
|
+
const prior = await scanHeldoutSpend(options.artifactRoot, a.study);
|
|
68
|
+
// An unacknowledged execution is never silently retried, even in a different study.
|
|
69
|
+
if (prior.counterBlocked || prior.attempts.some(attempt => !attempt.result || attempt.result.disposition === 'stop'))
|
|
70
|
+
throw new HeldoutError('prior-stop');
|
|
71
|
+
if (!offline && prior.runs.some(run => run.corpusDigest === a.corpusDigest && run.source === 'injected-transport'))
|
|
72
|
+
throw new HeldoutError('injected-prior');
|
|
73
|
+
const old = prior.attempts.filter(attempt => attempt.study === a.study && attempt.corpusDigest === a.corpusDigest);
|
|
74
|
+
if (old.some(attempt => attempt.preregistrationDigest !== a.preregistrationDigest))
|
|
75
|
+
throw new HeldoutError('changed-preregistration');
|
|
76
|
+
const latest = new Map();
|
|
77
|
+
for (const attempt of old)
|
|
78
|
+
if (attempt.ordinal >= (latest.get(key(attempt))?.ordinal ?? 0))
|
|
79
|
+
latest.set(key(attempt), attempt);
|
|
80
|
+
const now = options.now ?? Date.now, sleep = options.sleep ?? delay, started = now();
|
|
81
|
+
// D17-MF (#2850) is scored only through the registered D17 member calibrator: a session whose whole dispatch window
|
|
82
|
+
// (start plus the preregistered session limit) does not end before the calibrator expires never starts. A seed
|
|
83
|
+
// already frozen in the ledger under a different corpus (for example a revised generator) is never collected again;
|
|
84
|
+
// the same corpus under another preregistration is refused above (changed-preregistration).
|
|
85
|
+
const notAfter = usesD17MultifactGenerator(corpus.rows) ? Date.parse(D17_MULTIFACT_CALIBRATION_NOT_AFTER) : null;
|
|
86
|
+
if (notAfter !== null) {
|
|
87
|
+
if (!Number.isSafeInteger(started) || started + plan.sessionLimitMs > notAfter)
|
|
88
|
+
throw new HeldoutError('calibration-expired');
|
|
89
|
+
for (const other of prior.runs) {
|
|
90
|
+
const recorded = await readHeldoutFile(join(heldoutRunsRoot(options.artifactRoot), other.runId, 'frozen.json'));
|
|
91
|
+
if (recorded.bundle?.corpus?.provenance?.seed === corpus.provenance.seed && other.corpusDigest !== a.corpusDigest)
|
|
92
|
+
throw new HeldoutError('seed-reused');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const testPhaseAccessAt = a.calibration.mode === 'staged' && a.calibration.phase === 'test' ? new Date(started).toISOString() : null;
|
|
96
|
+
await validateHeldoutPhaseAccess(bundle, heldoutRunsRoot(options.artifactRoot), prior.runs, testPhaseAccessAt);
|
|
97
|
+
if (testPhaseAccessAt !== null)
|
|
98
|
+
await planHeldoutCollection(bundle, options.trustedApprovalDigest);
|
|
99
|
+
const rows = heldoutRowsInScope(bundle);
|
|
100
|
+
const run = join(heldoutRunsRoot(options.artifactRoot), a.runId);
|
|
101
|
+
await mkdir(run, { mode: 0o700 });
|
|
102
|
+
await writeHeldoutFile(join(run, 'frozen.json'), { schemaVersion: 'decision-heldout-frozen/v1', source: offline ? 'injected-transport' : 'provider', approval: { study: a.study, runId: a.runId }, bundle, baselineDigests, testPhaseAccessAt, collectionStartedAt: new Date(started).toISOString(), digest: heldoutDigest(bundle), priorRuns: prior.runs.filter(r => r.study === a.study && r.corpusDigest === a.corpusDigest) });
|
|
103
|
+
const events = [];
|
|
104
|
+
const allowance = heldoutCollectionAllowance(a, prior);
|
|
105
|
+
let calls = 0, reserved = 0, accounted = 0, tokens = 0, lastDispatch = started - plan.minDispatchIntervalMs;
|
|
106
|
+
let reason = null, checkpoint = false;
|
|
107
|
+
const canaries = new Set(['heldout-synthetic-privacy-canary']);
|
|
108
|
+
const failed = new Set(old.filter(attempt => attempt.result?.disposition === 'measurement-failure').map(attempt => attempt.rowId));
|
|
109
|
+
const overSlice = (slice, split) => {
|
|
110
|
+
const rows = corpus.rows.filter(row => row.slice === slice && row.split === split);
|
|
111
|
+
return rows.filter(row => failed.has(row.id)).length * 10000 > rows.length * plan.providerFailurePolicy.maximumSliceFailureBps;
|
|
112
|
+
};
|
|
113
|
+
const attemptSource = offline ? 'injected-transport' : 'provider';
|
|
114
|
+
const safe = (value) => {
|
|
115
|
+
const text = JSON.stringify(value);
|
|
116
|
+
return typeof text === 'string' && ![...canaries].some(secret => text.includes(secret) || text.includes(JSON.stringify(secret).slice(1, -1)))
|
|
117
|
+
&& redactText(text).sensitivity === 'none';
|
|
118
|
+
};
|
|
119
|
+
// The scoped resolver starts only after every input and durable accounting check.
|
|
120
|
+
const obtainHost = async () => host ??= offlineHost ?? await liveHost({ ...options, bundle });
|
|
121
|
+
for (const row of rows) {
|
|
122
|
+
if (reason || checkpoint)
|
|
123
|
+
break;
|
|
124
|
+
if (options.signal?.aborted) {
|
|
125
|
+
reason = 'cancelled';
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
if (failed.has(row.id) || overSlice(row.slice, row.split))
|
|
129
|
+
continue;
|
|
130
|
+
const remaining = row.requests.filter(request => latest.get(`${row.id}/${request.id}`)?.result?.disposition !== 'success');
|
|
131
|
+
if (!remaining.length)
|
|
132
|
+
continue;
|
|
133
|
+
// Checkpoint before a subject/paired arms: enough time for every bounded attempt and interval.
|
|
134
|
+
const pairBound = remaining.length * (1 + plan.providerFailurePolicy.maxRetries) * (plan.requestTimeoutMs + plan.minDispatchIntervalMs);
|
|
135
|
+
if (now() - started + pairBound > plan.sessionLimitMs * 0.8) {
|
|
136
|
+
checkpoint = true;
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
for (const request of remaining) {
|
|
140
|
+
if (reason || failed.has(row.id))
|
|
141
|
+
break;
|
|
142
|
+
const previous = latest.get(`${row.id}/${request.id}`);
|
|
143
|
+
const first = previous ? previous.ordinal + 1 : 1;
|
|
144
|
+
for (let ordinal = first; ordinal <= 1 + plan.providerFailurePolicy.maxRetries; ordinal++) {
|
|
145
|
+
if (options.signal?.aborted) {
|
|
146
|
+
reason = 'cancelled';
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
await assertContextLiveSource(options.sourceRoot, a.sourceCommit);
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
reason = 'source-drift';
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
if (notAfter !== null && now() + plan.requestTimeoutMs > notAfter) {
|
|
157
|
+
reason = 'calibration-expired';
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
const { execution, requestDigest, requestBytes, estimatedTokens: inputTokenBound } = await heldoutRequest(corpus, plan, a, row, request);
|
|
161
|
+
const reserveMicros = heldoutReservationMicros(a, inputTokenBound);
|
|
162
|
+
const reserveTokens = heldoutReservationTokens(plan, inputTokenBound);
|
|
163
|
+
if (calls + 1 > allowance.calls || tokens + reserveTokens > allowance.tokens
|
|
164
|
+
|| accounted + reserveMicros > allowance.usdMicros) {
|
|
165
|
+
reason = 'budget-exhausted';
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
await sleep(Math.max(0, plan.minDispatchIntervalMs - (now() - lastDispatch)));
|
|
169
|
+
if (options.signal?.aborted) {
|
|
170
|
+
reason = 'cancelled';
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
if (now() - started + plan.requestTimeoutMs > plan.sessionLimitMs * 0.8) {
|
|
174
|
+
checkpoint = true;
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
const attempt = { schemaVersion: 'decision-heldout-attempt/v1', study: a.study, runId: a.runId,
|
|
178
|
+
corpusDigest: a.corpusDigest, preregistrationDigest: a.preregistrationDigest, approvalDigest: options.trustedApprovalDigest,
|
|
179
|
+
rowId: row.id, requestId: request.id, ordinal, reservedUsdMicros: reserveMicros, reservedTokens: reserveTokens,
|
|
180
|
+
requestDigest, result: null };
|
|
181
|
+
// fsync completes before credential lookup or any transport call. A crash retains this charge.
|
|
182
|
+
uncertain = true;
|
|
183
|
+
await appendHeldoutSpend(options.artifactRoot, attempt);
|
|
184
|
+
await appendHeldoutEvent(run, events, attempt);
|
|
185
|
+
tokens += reserveTokens;
|
|
186
|
+
calls++;
|
|
187
|
+
reserved += reserveMicros;
|
|
188
|
+
accounted += reserveMicros;
|
|
189
|
+
const callStarted = now();
|
|
190
|
+
lastDispatch = callStarted;
|
|
191
|
+
let observation = null, receipt = null;
|
|
192
|
+
let wireProblem = null, responseDigest = null;
|
|
193
|
+
let wireMetadata = null;
|
|
194
|
+
const traces = [];
|
|
195
|
+
const controller = new AbortController();
|
|
196
|
+
const caller = () => controller.abort();
|
|
197
|
+
options.signal?.addEventListener('abort', caller, { once: true });
|
|
198
|
+
const timer = setTimeout(() => controller.abort(), plan.requestTimeoutMs);
|
|
199
|
+
const captured = await captureQualificationLifetime(async () => {
|
|
200
|
+
const resolver = await obtainHost();
|
|
201
|
+
const inner = new JevDecisionAdapter({ region: a.region, now, fetch: (url, init) => {
|
|
202
|
+
if (typeof init?.body !== 'string' || Buffer.byteLength(init.body, 'utf8') !== requestBytes
|
|
203
|
+
|| heldoutDigest(JSON.parse(init.body)) !== requestDigest)
|
|
204
|
+
throw new HeldoutError('request-bound');
|
|
205
|
+
if (transport)
|
|
206
|
+
return transport(url, init);
|
|
207
|
+
if (process.env[HELDOUT_ENV_GATE] !== '1')
|
|
208
|
+
throw new HeldoutError('live-gate');
|
|
209
|
+
if (process.env.NODE_TLS_REJECT_UNAUTHORIZED !== undefined && process.env.NODE_TLS_REJECT_UNAUTHORIZED !== '1')
|
|
210
|
+
throw new HeldoutError('tls');
|
|
211
|
+
return fetch(url, init);
|
|
212
|
+
},
|
|
213
|
+
observeResponseBody: (body, metadata) => {
|
|
214
|
+
responseDigest = byteDigest(new TextEncoder().encode(body));
|
|
215
|
+
wireMetadata = metadata;
|
|
216
|
+
if (!safe(body))
|
|
217
|
+
wireProblem = 'credential-anomaly';
|
|
218
|
+
else if (metadata.actualModel !== null && metadata.actualModel !== a.servedModel)
|
|
219
|
+
wireProblem = 'served-model';
|
|
220
|
+
else if (a.priceBound.outputTokenBound !== undefined && (metadata.usage.outputTokens ?? 0) > a.priceBound.outputTokenBound)
|
|
221
|
+
wireProblem = 'output-bound';
|
|
222
|
+
else if ((metadata.usage.inputTokens ?? 0) > inputTokenBound
|
|
223
|
+
|| (metadata.usage.inputTokens ?? 0) + (metadata.usage.outputTokens ?? 0) > reserveTokens)
|
|
224
|
+
wireProblem = 'usage-bound';
|
|
225
|
+
else if (metadata.usage.costUsd !== null && metadata.usage.costUsd * 1_000_000 > reserveMicros)
|
|
226
|
+
wireProblem = 'price-bound';
|
|
227
|
+
} });
|
|
228
|
+
const adapter = { id: inner.id, version: inner.version, capabilities: () => inner.capabilities(),
|
|
229
|
+
evaluate: async (req) => { observation = await inner.evaluate(req); return observation; } };
|
|
230
|
+
receipt = await evaluateDecisionRuleset({ ...execution, definitions: { [execution.definition.metadata.id]: execution.definition }, input: row.input,
|
|
231
|
+
runId: a.runId, invocationId: `${row.id}-${request.id}-${ordinal}`, adapters: { jev: adapter }, signal: controller.signal, now,
|
|
232
|
+
resolveCredential: async (reference) => {
|
|
233
|
+
if (reference !== a.credentialRef)
|
|
234
|
+
throw new HeldoutError('credential-scope');
|
|
235
|
+
const bytes = await resolver.resolveCredential(reference);
|
|
236
|
+
try {
|
|
237
|
+
const text = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
|
|
238
|
+
if (!text || /[\s\x00-\x1f\x7f]/.test(text))
|
|
239
|
+
throw new HeldoutError('credential-shape');
|
|
240
|
+
canaries.add(text);
|
|
241
|
+
return bytes.slice();
|
|
242
|
+
}
|
|
243
|
+
finally {
|
|
244
|
+
bytes.fill(0);
|
|
245
|
+
}
|
|
246
|
+
}, projection: { resolve: () => execution.projection }, telemetry: { hook: { emit: span => { traces.push(span); } } } });
|
|
247
|
+
}, { suppressOutput: true });
|
|
248
|
+
clearTimeout(timer);
|
|
249
|
+
options.signal?.removeEventListener('abort', caller);
|
|
250
|
+
// No raw provider exceptions, model IDs or request IDs cross this boundary when a secret matches.
|
|
251
|
+
const raw = observation;
|
|
252
|
+
const metadata = wireMetadata;
|
|
253
|
+
const observed = raw && metadata ? { ...raw, actualModel: metadata.actualModel, usage: metadata.usage } : raw;
|
|
254
|
+
const completed = receipt;
|
|
255
|
+
const captures = QUALIFICATION_PRIVACY_SURFACES.map(surface => ({ surface,
|
|
256
|
+
content: captured.captures.find(c => c.surface === surface)?.content ?? (surface === 'receipt' ? JSON.stringify(completed)
|
|
257
|
+
: surface === 'trace' ? JSON.stringify(traces) : surface === 'activity-record' ? JSON.stringify(observed) : '') }));
|
|
258
|
+
const privacy = scanQualificationPrivacy(captures, [...canaries]);
|
|
259
|
+
const clean = privacy.clean && safe(observed) && safe(completed) && wireProblem !== 'credential-anomaly';
|
|
260
|
+
let disposition = 'stop';
|
|
261
|
+
let outcome = 'execution-uncertain';
|
|
262
|
+
if (!clean)
|
|
263
|
+
outcome = 'credential-anomaly';
|
|
264
|
+
else if (wireProblem)
|
|
265
|
+
outcome = wireProblem;
|
|
266
|
+
else if (options.signal?.aborted)
|
|
267
|
+
outcome = 'cancelled';
|
|
268
|
+
else if (captured.threw)
|
|
269
|
+
outcome = 'dispatch-rejected';
|
|
270
|
+
else if (observed?.actualModel && observed.actualModel !== a.servedModel)
|
|
271
|
+
outcome = 'served-model';
|
|
272
|
+
else if (observed && ((observed.usage.inputTokens ?? 0) > inputTokenBound
|
|
273
|
+
|| (observed.usage.inputTokens ?? 0) + (observed.usage.outputTokens ?? 0) > reserveTokens))
|
|
274
|
+
outcome = 'usage-bound';
|
|
275
|
+
else if (observed?.usage.costUsd !== null && observed?.usage.costUsd !== undefined && observed.usage.costUsd * 1_000_000 > reserveMicros)
|
|
276
|
+
outcome = 'price-bound';
|
|
277
|
+
else if (observed?.status === 'success') {
|
|
278
|
+
if (observed.actualModel !== a.servedModel)
|
|
279
|
+
outcome = 'served-model';
|
|
280
|
+
else if (observed.usage.inputTokens === null || observed.usage.outputTokens === null)
|
|
281
|
+
outcome = 'usage-unknown';
|
|
282
|
+
else {
|
|
283
|
+
disposition = 'success';
|
|
284
|
+
outcome = 'ok';
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
else if (observed?.dispatchCertainty === 'terminal-response' && observed.remoteExecution !== 'unknown'
|
|
288
|
+
&& (observed.reason === 'invalid-output' || observed.reason === 'overloaded'
|
|
289
|
+
|| observed.reason === 'service-error' && (observed.httpStatus ?? 0) >= 500)) {
|
|
290
|
+
disposition = ordinal <= plan.providerFailurePolicy.maxRetries ? 'retryable' : 'measurement-failure';
|
|
291
|
+
outcome = observed.reason;
|
|
292
|
+
}
|
|
293
|
+
else
|
|
294
|
+
outcome = observed?.reason ?? 'execution-uncertain';
|
|
295
|
+
// Unknown identity or usage on terminal errors is retained as unknown; only safe terminal failures may retry.
|
|
296
|
+
const numeric = (value) => typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : null;
|
|
297
|
+
attempt.result = { disposition, reason: outcome, servedModel: clean ? observed?.actualModel ?? null : null,
|
|
298
|
+
inputTokens: numeric(observed?.usage.inputTokens), outputTokens: numeric(observed?.usage.outputTokens),
|
|
299
|
+
providerCostUsd: numeric(observed?.usage.costUsd), responseDigest: clean ? responseDigest : null,
|
|
300
|
+
receipt: clean ? completed : null, receiptDigest: clean && completed ? heldoutDigest(completed) : null,
|
|
301
|
+
traceDigest: heldoutDigest(clean ? traces : []), latencyMs: Math.max(0, now() - callStarted),
|
|
302
|
+
accountedUsdMicros: Math.max(reserveMicros, Math.ceil((numeric(observed?.usage.costUsd) ?? 0) * 1_000_000), Math.ceil((numeric(observed?.usage.inputTokens) ?? 0) * Math.max(0.1, a.priceBound.inputUsdPerMTok)
|
|
303
|
+
+ (numeric(observed?.usage.outputTokens) ?? 0) * a.priceBound.outputUsdPerMTok) + Math.ceil(a.priceBound.perRequestUsd * 1_000_000)) };
|
|
304
|
+
accounted += attempt.result.accountedUsdMicros - reserveMicros;
|
|
305
|
+
await writeHeldoutFile(join(run, `trace-${events.length}.json`), clean ? traces : []);
|
|
306
|
+
await appendHeldoutSpend(options.artifactRoot, attempt);
|
|
307
|
+
await appendHeldoutEvent(run, events, attempt);
|
|
308
|
+
latest.set(key(attempt), attempt);
|
|
309
|
+
uncertain = false;
|
|
310
|
+
if (disposition === 'stop') {
|
|
311
|
+
reason = outcome;
|
|
312
|
+
uncertain = !observed || observed.remoteExecution === 'unknown';
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
if (disposition === 'measurement-failure') {
|
|
316
|
+
failed.add(row.id);
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
if (disposition === 'success')
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
try {
|
|
325
|
+
await assertContextLiveSource(options.sourceRoot, a.sourceCommit);
|
|
326
|
+
}
|
|
327
|
+
catch {
|
|
328
|
+
reason = 'source-drift';
|
|
329
|
+
}
|
|
330
|
+
const all = [...latest.values()];
|
|
331
|
+
const completedRows = rows.filter(row => row.requests.every(request => latest.get(`${row.id}/${request.id}`)?.result?.disposition === 'success')).length;
|
|
332
|
+
const missingRows = rows.filter(row => row.requests.some(request => latest.get(`${row.id}/${request.id}`)?.result?.disposition !== 'success')).map(row => row.id);
|
|
333
|
+
const evidenceDigest = await heldoutEvidenceDigest(run, events);
|
|
334
|
+
const calibrationPhaseRecordDigest = !reason && !checkpoint && a.calibration.mode === 'staged' && a.calibration.phase === 'calibration'
|
|
335
|
+
? await sealHeldoutCalibrationPhase(run, new Date(now()).toISOString()) : null;
|
|
336
|
+
const summary = { schemaVersion: 'decision-heldout-summary/v1', status: reason ? 'stopped' : checkpoint ? 'checkpoint' : 'complete', reason,
|
|
337
|
+
source: attemptSource, study: a.study, runId: a.runId, reservedUsdMicros: reserved, completedRows,
|
|
338
|
+
measurementFailures: [...failed].filter(id => rows.some(row => row.id === id)), missingRows, evidenceDigest, calibrationPhaseRecordDigest, decision: 'HOLD' };
|
|
339
|
+
await writeHeldoutFile(join(run, 'summary.json'), summary);
|
|
340
|
+
// Recorded mode re-reads the durable ledger, rather than trusting a live-mode callback.
|
|
341
|
+
const manifest = await executeQualificationPlan({ artifactRoot: run, manifest: { schemaVersion: 'decision-qualification-run/v1',
|
|
342
|
+
mode: 'recorded', runId: 'replay', generatedAt: new Date(now()).toISOString(), sourceCommit: a.sourceCommit, dirty: false,
|
|
343
|
+
cases: [{ id: 'HELDOUT-LEDGER', kind: 'baseline', mandatory: true, candidateTests: [], evidenceIds: ['REC-HELDOUT-01'] }] },
|
|
344
|
+
executors: { 'HELDOUT-LEDGER': async () => {
|
|
345
|
+
const reread = await readHeldoutJournal(run);
|
|
346
|
+
return { outcome: heldoutDigest(reread) === heldoutDigest(events) ? 'pass' : 'fail',
|
|
347
|
+
details: { source: attemptSource, evidenceDigest, complete: !missingRows.length, observations: all.length } };
|
|
348
|
+
} }, sanitizeDetails: details => details });
|
|
349
|
+
await writeHeldoutFile(join(run, 'qualification.json'), manifest);
|
|
350
|
+
await writeQualificationEvidenceManifest(manifest, run, options.sourceRoot, {
|
|
351
|
+
'HELDOUT-LEDGER': ['src/decision/heldout/collector.ts', 'src/decision/projection.ts', 'src/decision/adapters/jev.ts'],
|
|
352
|
+
});
|
|
353
|
+
return summary;
|
|
354
|
+
}
|
|
355
|
+
finally {
|
|
356
|
+
try {
|
|
357
|
+
host?.dispose();
|
|
358
|
+
}
|
|
359
|
+
catch { /* resolver disposal never exposes private error text */ }
|
|
360
|
+
finally {
|
|
361
|
+
// A hung/uncertain remote operation keeps the global dispatch lock until operator reconciliation.
|
|
362
|
+
if (!uncertain)
|
|
363
|
+
await rm(lock, { recursive: true });
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
/** Scoring is local and advisory. Digest-bound upstream integrity can only retain HOLD or tighten to ROLLBACK. */
|
|
368
|
+
export async function scoreHeldoutStudy(input) {
|
|
369
|
+
const frozen = await readHeldoutFrozen(input.run, input.trustedApprovalDigest);
|
|
370
|
+
const { corpus, preregistration, approval } = frozen.bundle;
|
|
371
|
+
await validateHeldoutPhaseAccess(frozen.bundle, resolve(input.run, '..'), frozen.priorRuns, frozen.testPhaseAccessAt);
|
|
372
|
+
if (heldoutDigest(input.gold) !== corpus.provenance.goldDigest || input.moduleDigest !== preregistration.scorerDigest
|
|
373
|
+
|| heldoutDigest(input.integrity) !== input.trustedIntegrityDigest)
|
|
374
|
+
throw new HeldoutError('scoring-pins');
|
|
375
|
+
const problems = qualificationIntegrityAllowlistProblems(input.integrity);
|
|
376
|
+
if (problems.includes('integrity-invalid'))
|
|
377
|
+
throw new HeldoutError('integrity-invalid');
|
|
378
|
+
const events = await readHeldoutJournal(input.run);
|
|
379
|
+
await validateHeldoutJournal(frozen.bundle, events);
|
|
380
|
+
if (await heldoutEvidenceDigest(input.run, events) !== input.trustedEvidenceDigest)
|
|
381
|
+
throw new HeldoutError('evidence-pin');
|
|
382
|
+
const manifest = await readHeldoutFile(join(input.run, 'qualification.json'));
|
|
383
|
+
if (manifest.mode !== 'recorded' || !(await verifyQualificationArtifacts(manifest, input.run)).every(v => v.verified)
|
|
384
|
+
|| !manifest.evidence.length || manifest.evidence.some(e => e.outcome !== 'pass'))
|
|
385
|
+
throw new HeldoutError('recorded-evidence');
|
|
386
|
+
for (const prior of frozen.priorRuns) {
|
|
387
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/.test(prior.runId) || prior.corpusDigest !== preregistration.corpusDigest || prior.study !== corpus.study)
|
|
388
|
+
throw new HeldoutError('prior-evidence');
|
|
389
|
+
const path = resolve(input.run, '..', prior.runId), journal = await readHeldoutJournal(path);
|
|
390
|
+
const previous = await readHeldoutFrozen(path);
|
|
391
|
+
if (previous.bundle.approval.preregistrationDigest !== approval.preregistrationDigest)
|
|
392
|
+
throw new HeldoutError('prior-evidence');
|
|
393
|
+
await validateHeldoutJournal(previous.bundle, journal);
|
|
394
|
+
await validateHeldoutPhaseAccess(previous.bundle, resolve(input.run, '..'), previous.priorRuns, previous.testPhaseAccessAt);
|
|
395
|
+
if (await heldoutEvidenceDigest(path, journal) !== prior.evidenceDigest)
|
|
396
|
+
throw new HeldoutError('prior-evidence');
|
|
397
|
+
events.unshift(...journal);
|
|
398
|
+
}
|
|
399
|
+
const latest = new Map();
|
|
400
|
+
for (const { attempt } of events)
|
|
401
|
+
latest.set(`${key(attempt)}/${attempt.ordinal}`, attempt);
|
|
402
|
+
const rows = heldoutRowsInScope(frozen.bundle);
|
|
403
|
+
const attempts = [...latest.values()].filter(attempt => rows.some(row => row.id === attempt.rowId));
|
|
404
|
+
const complete = rows.every(row => row.requests.every(request => attempts.some(a => a.rowId === row.id
|
|
405
|
+
&& a.requestId === request.id && a.result?.disposition === 'success')));
|
|
406
|
+
const approvedCalibration = structuredClone(approval.calibration);
|
|
407
|
+
const calibrated = approvedCalibration.mode === 'uncalibrated-diagnostic'
|
|
408
|
+
|| approvedCalibration.mode === 'staged' && approvedCalibration.phase === 'calibration' ? false : null;
|
|
409
|
+
const diagnostics = await input.module.score({ corpus: { ...corpus, rows }, preregistration, attempts, gold: input.gold,
|
|
410
|
+
integrity: structuredClone(input.integrity), approvedCalibration: structuredClone(approvedCalibration), calibrated });
|
|
411
|
+
if (calibrated === false && (!diagnostics || typeof diagnostics !== 'object'
|
|
412
|
+
|| diagnostics.calibrated !== false
|
|
413
|
+
|| 'decision' in diagnostics && diagnostics.decision === 'PROMOTE'
|
|
414
|
+
|| 'd09Qualified' in diagnostics && diagnostics.d09Qualified !== false
|
|
415
|
+
|| 'calibratedGate' in diagnostics && diagnostics.calibratedGate !== false))
|
|
416
|
+
throw new HeldoutError('uncalibrated-report');
|
|
417
|
+
// The collector validates no artifact itself. When a staged test-phase scorer reports that it validated the exact
|
|
418
|
+
// approved calibration binding (its `calibration.calibrationSetDigest`), the top level reflects that study claim;
|
|
419
|
+
// every other scorer (including D29, which reports no such binding) keeps the collector's not-performed default.
|
|
420
|
+
const study = diagnostics;
|
|
421
|
+
const studyValidated = approvedCalibration.mode === 'staged' && approvedCalibration.phase === 'test' && calibrated === null
|
|
422
|
+
&& study?.calibrated === true && study.d09Qualified === true && study.calibration?.mode === 'staged-test'
|
|
423
|
+
&& study.calibration.calibrationSetDigest === approvedCalibration.calibrationArtifactDigest;
|
|
424
|
+
return { schemaVersion: 'decision-heldout-score/v1', source: frozen.priorRuns.some(run => run.source === 'injected-transport') ? 'injected-transport' : frozen.source, evidenceDigest: input.trustedEvidenceDigest, integrityDigest: input.trustedIntegrityDigest,
|
|
425
|
+
complete, diagnostics, approvedCalibration, calibrated: studyValidated ? true : calibrated, d09Qualified: studyValidated,
|
|
426
|
+
calibratedGate: studyValidated && study?.calibratedGate === true,
|
|
427
|
+
calibrationArtifactValidation: studyValidated ? 'performed-by-study-scorer' : 'not-performed', integrityProblems: problems, decision: input.integrity.release_gate.decision === 'ROLLBACK'
|
|
428
|
+
|| input.integrity.compromise_labels.length > 0 ? 'ROLLBACK' : 'HOLD' };
|
|
429
|
+
}
|
|
430
|
+
//# sourceMappingURL=collector.js.map
|