@aiwg/cli 2026.9.24 → 2026.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/agentic/code/providers/capability-matrix.yaml +2 -1
  2. package/dist/src/artifacts/discovery-shadow-evaluator.js +177 -0
  3. package/dist/src/artifacts/discovery-shadow.js +195 -0
  4. package/dist/src/artifacts/index-builder.js +47 -9
  5. package/dist/src/artifacts/query-engine.js +94 -1
  6. package/dist/src/artifacts/types.js +1 -0
  7. package/dist/src/cli/handlers/decision.js +88 -0
  8. package/dist/src/cli/handlers/gates.js +154 -0
  9. package/dist/src/cli/handlers/index.js +5 -1
  10. package/dist/src/cli/handlers/sessions.js +10 -7
  11. package/dist/src/cli/handlers/utilities.js +20 -0
  12. package/dist/src/config/aiwg-config.js +15 -2
  13. package/dist/src/decision/acceptance.js +25 -0
  14. package/dist/src/decision/adapters/jev.js +24 -2
  15. package/dist/src/decision/calibration/registry.js +9 -0
  16. package/dist/src/decision/compose.js +74 -15
  17. package/dist/src/decision/context-live-promotion.js +344 -0
  18. package/dist/src/decision/context-live-qualification.js +591 -0
  19. package/dist/src/decision/context-pruning.js +847 -0
  20. package/dist/src/decision/driver.js +430 -0
  21. package/dist/src/decision/egress-live-qualification.js +874 -0
  22. package/dist/src/decision/ensemble/contract.js +64 -1
  23. package/dist/src/decision/ensemble/index.js +1 -0
  24. package/dist/src/decision/ensemble/report.js +8 -42
  25. package/dist/src/decision/ensemble/runtime.js +571 -0
  26. package/dist/src/decision/ensemble-study/artifacts.js +85 -0
  27. package/dist/src/decision/ensemble-study/calibration.js +443 -0
  28. package/dist/src/decision/ensemble-study/corpus.js +229 -0
  29. package/dist/src/decision/ensemble-study/multifact-artifacts.js +23 -0
  30. package/dist/src/decision/ensemble-study/multifact-study.js +596 -0
  31. package/dist/src/decision/ensemble-study/multifact.js +241 -0
  32. package/dist/src/decision/ensemble-study/protocol.js +77 -0
  33. package/dist/src/decision/ensemble-study/score.js +462 -0
  34. package/dist/src/decision/ensemble-study/staged.js +87 -0
  35. package/dist/src/decision/ensemble-study/statistics.js +84 -0
  36. package/dist/src/decision/evaluate.js +227 -14
  37. package/dist/src/decision/export.js +4 -0
  38. package/dist/src/decision/feature-export.js +1074 -0
  39. package/dist/src/decision/graph-index.js +16 -0
  40. package/dist/src/decision/graph-live-qualification.js +1041 -0
  41. package/dist/src/decision/graph-live-workload.js +245 -0
  42. package/dist/src/decision/graph-skill-bridge.js +21 -3
  43. package/dist/src/decision/heldout/calibration.js +140 -0
  44. package/dist/src/decision/heldout/collector.js +430 -0
  45. package/dist/src/decision/heldout/contract.js +309 -0
  46. package/dist/src/decision/heldout/d29-generator-ids.js +12 -0
  47. package/dist/src/decision/heldout/d29-passage-baseline-v2.js +93 -0
  48. package/dist/src/decision/heldout/d29-passage-baseline-v3.js +100 -0
  49. package/dist/src/decision/heldout/d29-passage-baseline.js +66 -0
  50. package/dist/src/decision/heldout/d29-pools-v8.js +575 -0
  51. package/dist/src/decision/heldout/d29-pools.js +288 -0
  52. package/dist/src/decision/heldout/d29-v6.js +169 -0
  53. package/dist/src/decision/heldout/d29-v7.js +202 -0
  54. package/dist/src/decision/heldout/d29-v8.js +586 -0
  55. package/dist/src/decision/heldout/generator-registry.js +67 -0
  56. package/dist/src/decision/heldout/generators.js +696 -0
  57. package/dist/src/decision/heldout/index.js +8 -0
  58. package/dist/src/decision/heldout/journal.js +346 -0
  59. package/dist/src/decision/heldout/study-generators.js +64 -0
  60. package/dist/src/decision/heldout/types.js +2 -0
  61. package/dist/src/decision/host-policy.js +61 -0
  62. package/dist/src/decision/index.js +11 -0
  63. package/dist/src/decision/interop-profile.v1.json +112 -0
  64. package/dist/src/decision/interop.js +653 -0
  65. package/dist/src/decision/issue-triage/index.js +3 -0
  66. package/dist/src/decision/issue-triage/runtime.js +1069 -0
  67. package/dist/src/decision/issue-triage/types.js +2 -0
  68. package/dist/src/decision/patterns/catalog.js +28 -8
  69. package/dist/src/decision/patterns/playground.js +28 -11
  70. package/dist/src/decision/patterns/specs.js +19 -10
  71. package/dist/src/decision/preprocessed-evidence.js +724 -0
  72. package/dist/src/decision/qualification/capture.js +8 -2
  73. package/dist/src/decision/qualification/gate-evidence.js +19 -4
  74. package/dist/src/decision/qualification/quality.js +57 -49
  75. package/dist/src/decision/qualification/release.js +76 -3
  76. package/dist/src/decision/qualification/runner.js +5 -2
  77. package/dist/src/decision/routing/contract.js +175 -0
  78. package/dist/src/decision/routing/index.js +5 -0
  79. package/dist/src/decision/routing/report.js +216 -0
  80. package/dist/src/decision/routing/runtime.js +685 -0
  81. package/dist/src/decision/routing/types.js +2 -0
  82. package/dist/src/decision/sdlc-screening.js +1157 -0
  83. package/dist/src/decision/sensitivity/comparative.js +154 -0
  84. package/dist/src/decision/sensitivity/contract.js +218 -0
  85. package/dist/src/decision/sensitivity/index.js +5 -0
  86. package/dist/src/decision/sensitivity/runtime.js +912 -0
  87. package/dist/src/decision/sensitivity/types.js +2 -0
  88. package/dist/src/decision/validate.js +26 -0
  89. package/dist/src/extensions/commands/definitions.js +38 -0
  90. package/dist/src/extensions/manifest.js +44 -0
  91. package/dist/src/gates/discovery.js +315 -0
  92. package/dist/src/gates/driver.js +388 -0
  93. package/dist/src/gates/evaluate.js +719 -0
  94. package/dist/src/gates/floors.js +255 -0
  95. package/dist/src/gates/index.js +17 -0
  96. package/dist/src/gates/providers/evidence.js +38 -0
  97. package/dist/src/gates/providers/index.js +21 -0
  98. package/dist/src/gates/providers/isolated-child-source.js +446 -0
  99. package/dist/src/gates/providers/loader.js +1069 -0
  100. package/dist/src/gates/providers/paired.js +52 -0
  101. package/dist/src/gates/providers/proportion.js +60 -0
  102. package/dist/src/gates/providers/registry.js +40 -0
  103. package/dist/src/gates/providers/scalar.js +41 -0
  104. package/dist/src/gates/providers/screening.js +53 -0
  105. package/dist/src/gates/providers/types.js +25 -0
  106. package/dist/src/gates/registry.js +1003 -0
  107. package/dist/src/gates/report.js +102 -0
  108. package/dist/src/gates/schema.js +56 -0
  109. package/dist/src/gates/stats/binomial.js +26 -0
  110. package/dist/src/gates/stats/bootstrap.js +62 -0
  111. package/dist/src/gates/stats/clopper-pearson.js +95 -0
  112. package/dist/src/gates/stats/digest.js +26 -0
  113. package/dist/src/gates/stats/error.js +5 -0
  114. package/dist/src/gates/stats/index.js +9 -0
  115. package/dist/src/gates/stats/normal.js +35 -0
  116. package/dist/src/gates/stats/paired.js +120 -0
  117. package/dist/src/gates/stats/splits.js +30 -0
  118. package/dist/src/gates/types.js +8 -0
  119. package/dist/src/governance/redaction.js +14 -4
  120. package/dist/src/mcp/muse-native-config.mjs +123 -0
  121. package/dist/src/mcp/tools/decision.mjs +188 -0
  122. package/dist/src/mcp/tools/subsystems.mjs +2 -0
  123. package/dist/src/providers/capability-matrix.yaml +2 -1
  124. package/dist/src/providers/provider-definitions.js +2 -1
  125. package/dist/src/sessions/adapters/muse.js +336 -39
  126. package/dist/src/sessions/batch-import.js +7 -0
  127. package/dist/src/sessions/workspace-discovery.js +13 -2
  128. package/package.json +1 -1
  129. package/schemas/decision/ConformalFrozenOpenData.v2.schema.json +371 -0
  130. package/schemas/decision/ConformalOpenDataReport.v2.schema.json +421 -0
  131. package/schemas/decision/ConformalPreregistration.v2.schema.json +529 -0
  132. package/schemas/decision/ContextPruningCandidate.v1.schema.json +47 -0
  133. package/schemas/decision/ContextPruningEvaluationReport.v1.schema.json +758 -0
  134. package/schemas/decision/ContextPruningPreregistration.v1.schema.json +175 -0
  135. package/schemas/decision/ContextPruningReceipt.v1.schema.json +72 -0
  136. package/schemas/decision/D17Multifact.v1.schema.json +1851 -0
  137. package/schemas/decision/D17StudyAnalysis.v1.schema.json +42 -0
  138. package/schemas/decision/D17StudyAnalysis.v2.schema.json +51 -0
  139. package/schemas/decision/D17StudyCalibration.v1.schema.json +819 -0
  140. package/schemas/decision/D17StudyDryRun.v1.schema.json +169 -0
  141. package/schemas/decision/D17StudyDryRun.v2.schema.json +239 -0
  142. package/schemas/decision/D17StudyGold.v1.schema.json +108 -0
  143. package/schemas/decision/D17StudyGuide.v1.schema.json +63 -0
  144. package/schemas/decision/D17StudyNativeTemplates.v1.schema.json +619 -0
  145. package/schemas/decision/D17StudyProtocol.v1.schema.json +241 -0
  146. package/schemas/decision/D17StudyReport.v1.schema.json +902 -0
  147. package/schemas/decision/D17StudyReport.v2.schema.json +1319 -0
  148. package/schemas/decision/D17StudyReview.v1.schema.json +175 -0
  149. package/schemas/decision/D17StudySplits.v1.schema.json +217 -0
  150. package/schemas/decision/D29Study.v1.schema.json +1903 -0
  151. package/schemas/decision/D29Study.v2.schema.json +1344 -0
  152. package/schemas/decision/D29Study.v3.schema.json +609 -0
  153. package/schemas/decision/D29Study.v4.schema.json +1133 -0
  154. package/schemas/decision/D29Study.v6.schema.json +1205 -0
  155. package/schemas/decision/D29Study.v7.schema.json +1471 -0
  156. package/schemas/decision/D29Study.v8.schema.json +671 -0
  157. package/schemas/decision/D29Study.v9.schema.json +778 -0
  158. package/schemas/decision/DecisionComparativeReplayAuditSample.v1.schema.json +137 -0
  159. package/schemas/decision/DecisionComparativeReplayCorpus.v1.schema.json +258 -0
  160. package/schemas/decision/DecisionComparativeReplayFreeze.v1.schema.json +74 -0
  161. package/schemas/decision/DecisionComparativeReplayGold.v1.schema.json +50 -0
  162. package/schemas/decision/DecisionComparativeReplayGoldAsset.v1.schema.json +37 -0
  163. package/schemas/decision/DecisionComparativeReplayIntegrity.v1.schema.json +81 -0
  164. package/schemas/decision/DecisionComparativeReplayMembers.v1.schema.json +52 -0
  165. package/schemas/decision/DecisionComparativeReplayPreregistration.v1.schema.json +113 -0
  166. package/schemas/decision/DecisionComparativeReplayReport.v1.schema.json +407 -0
  167. package/schemas/decision/DecisionComparativeReplayReviewPackets.v1.schema.json +260 -0
  168. package/schemas/decision/DecisionComparativeReplaySplit.v1.schema.json +84 -0
  169. package/schemas/decision/DecisionFeatureObservation.v1.schema.json +564 -0
  170. package/schemas/decision/DecisionFeatureSet.v1.schema.json +155 -0
  171. package/schemas/decision/DecisionInteropMapping.v1.schema.json +85 -0
  172. package/schemas/decision/DecisionIssueTriageEvaluationManifest.v1.schema.json +174 -0
  173. package/schemas/decision/DecisionIssueTriageEvaluationReport.v1.schema.json +280 -0
  174. package/schemas/decision/DecisionIssueTriagePilotPack.v1.schema.json +114 -0
  175. package/schemas/decision/DecisionRouteReceipt.v1.schema.json +155 -0
  176. package/schemas/decision/DecisionRoutingPolicy.v1.schema.json +132 -0
  177. package/schemas/decision/DecisionRoutingShadowReport.v1.schema.json +170 -0
  178. package/schemas/decision/DecisionRoutingTask.v1.schema.json +49 -0
  179. package/schemas/decision/DecisionRuleset.schema.json +8 -2
  180. package/schemas/decision/DecisionRuleset.v1alpha2.schema.json +8 -2
  181. package/schemas/decision/DecisionSensitivityPlan.v1.schema.json +191 -0
  182. package/schemas/decision/DecisionSensitivityReport.v1.schema.json +163 -0
  183. package/schemas/decision/HeldoutApproval.v1.schema.json +312 -0
  184. package/schemas/decision/HeldoutAttempt.v1.schema.json +211 -0
  185. package/schemas/decision/HeldoutBaseline.v1.schema.json +90 -0
  186. package/schemas/decision/HeldoutCalibrationPhase.v1.schema.json +87 -0
  187. package/schemas/decision/HeldoutCorpus.v1.schema.json +167 -0
  188. package/schemas/decision/HeldoutEvent.v1.schema.json +41 -0
  189. package/schemas/decision/HeldoutFrozen.v1.schema.json +142 -0
  190. package/schemas/decision/HeldoutPreregistration.v1.schema.json +222 -0
  191. package/schemas/decision/HeldoutSpendEvent.v1.schema.json +122 -0
  192. package/schemas/decision/HeldoutSpendHead.v1.schema.json +25 -0
  193. package/schemas/decision/HeldoutSpendHead.v2.schema.json +46 -0
  194. package/schemas/decision/HeldoutSummary.v1.schema.json +101 -0
  195. package/schemas/decision/OpaDecisionLogExport.v1.schema.json +65 -0
  196. package/schemas/decision/PreprocessedEvidence.v1.schema.json +306 -0
  197. package/schemas/decision/RulesetResult.v1alpha2.schema.json +175 -0
  198. package/schemas/decision/SdlcEvidenceScreening.v1.schema.json +499 -0
  199. package/schemas/decision/SdlcScreeningPreregistration.v1.schema.json +132 -0
  200. package/schemas/decision/SdlcScreeningPreregistration.v2.schema.json +138 -0
  201. package/schemas/decision/SdlcScreeningRelease.v1.schema.json +654 -0
  202. package/schemas/decision/SdlcScreeningRelease.v2.schema.json +654 -0
  203. package/tools/agents/deploy-agents.mjs +1 -1
  204. package/tools/agents/providers/base.mjs +26 -7
  205. package/tools/agents/providers/factory.mjs +10 -2
  206. package/tools/agents/providers/muse-hooks.mjs +1 -29
  207. package/tools/agents/providers/muse.mjs +6 -3
@@ -0,0 +1,591 @@
1
+ /** TV-12 collection-only composition. Never changes production rollout or promotes qualification. */
2
+ import { createHash } from 'node:crypto';
3
+ import { execFileSync } from 'node:child_process';
4
+ import { mkdir, writeFile, realpath, readdir, readFile, rm } from 'node:fs/promises';
5
+ import { resolve, join, relative, isAbsolute } from 'node:path';
6
+ import { canonicalJson } from '../security/artifact-trust.js';
7
+ import { admitEntry, DEFAULT_ENTRY_LIMITS } from './entry.js';
8
+ import { CanonicalJsonByteEstimator, ContextPlanError, planDecisionContext } from './context-plan.js';
9
+ import { compareContextUsage } from './context-qualification.js';
10
+ import { compileJevQuestion, JevDecisionAdapter, JEV_ENDPOINT } from './adapters/jev.js';
11
+ import { projectDecisionState, partitionProjectedState } from './projection.js';
12
+ import { DecisionAdmissionRegistry } from './admission.js';
13
+ import { artifactPin, assertArtifactPin, validateDefinition, validateRuleset, validateBinding } from './validate.js';
14
+ import { executeQualificationPlan, writeQualificationEvidenceManifest } from './qualification/runner.js';
15
+ export const contextLiveDigest = (value) => `sha256:${createHash('sha256').update(canonicalJson(value)).digest('hex')}`;
16
+ const estimator = new CanonicalJsonByteEstimator();
17
+ const subject = 'synthetic-tv12';
18
+ const liveHosts = new WeakSet();
19
+ /** Mirrors the DecisionBinding `credentialRef` pattern, so a reference that binding validation refuses is rejected at approval. */
20
+ export const CREDENTIAL_REF = /^[a-z][a-z0-9.-]+$/;
21
+ /** #2681 hard cap on all TV-12 provider spend: every collection and canary run, reserved amounts. */
22
+ export const TV12_ISSUE_USD_CAP = 2;
23
+ /** Fixed ledger location beneath the canonical artifact root; every run directory lives here. */
24
+ export const contextLiveRunsRoot = (artifactRoot) => join(artifactRoot, 'research', 'qualification', '2681', 'runs');
25
+ const SPEND_LOCK = '.tv12-spend.lock';
26
+ const micros = (usd) => Math.ceil(usd * 1_000_000);
27
+ /**
28
+ * Sums earlier #2681 spend in the ledger: a run's charged reservation when its summary exists, otherwise
29
+ * (a crash or an unfinished run) its full approved budget. Unrecognized or unreadable entries fail closed.
30
+ */
31
+ export async function contextLivePriorSpendUsd(runsRoot) {
32
+ let entries;
33
+ try {
34
+ entries = await readdir(runsRoot, { withFileTypes: true });
35
+ }
36
+ catch (error) {
37
+ if (error.code === 'ENOENT')
38
+ return 0;
39
+ throw error;
40
+ }
41
+ let total = 0;
42
+ const json = async (path) => JSON.parse(await readFile(path, 'utf8'));
43
+ for (const entry of entries) {
44
+ if (entry.name === SPEND_LOCK)
45
+ continue;
46
+ if (!entry.isDirectory())
47
+ throw new Error('TV-12 ledger contains an unrecognized entry');
48
+ const dir = join(runsRoot, entry.name);
49
+ const names = await readdir(dir);
50
+ let spend;
51
+ if (names.includes('summary.json'))
52
+ spend = (await json(join(dir, 'summary.json')))?.reserved?.usd;
53
+ else if (names.includes('approval.json'))
54
+ spend = (await json(join(dir, 'approval.json')))?.budget?.usd;
55
+ else if (names.includes('canary-approval.json'))
56
+ spend = (await json(join(dir, 'canary-approval.json')))?.plan?.budget?.usd;
57
+ if (!names.includes('approval.json') && !names.includes('canary-approval.json'))
58
+ throw new Error('TV-12 ledger contains an unrecognized run');
59
+ if (typeof spend !== 'number' || !Number.isFinite(spend) || spend < 0)
60
+ throw new Error('TV-12 ledger spend is unreadable');
61
+ total += micros(spend);
62
+ }
63
+ return total / 1_000_000;
64
+ }
65
+ /**
66
+ * Holds the ledger lock while `run` executes. The run's USD ceiling is the smaller of its approval and
67
+ * the issue cap minus all prior spend; no remaining budget refuses before any write or credential use.
68
+ */
69
+ export async function withContextLiveSpendLedger(artifactRoot, approvedUsd, run) {
70
+ const runsRoot = contextLiveRunsRoot(artifactRoot);
71
+ await mkdir(runsRoot, { recursive: true, mode: 0o700 });
72
+ const lock = join(runsRoot, SPEND_LOCK);
73
+ try {
74
+ await writeFile(lock, `${process.pid}\n`, { flag: 'wx', mode: 0o600 });
75
+ }
76
+ catch {
77
+ throw new Error('TV-12 spend ledger lock is held; inspect and remove a stale lock manually');
78
+ }
79
+ try {
80
+ const priorUsd = await contextLivePriorSpendUsd(runsRoot);
81
+ const remaining = micros(TV12_ISSUE_USD_CAP) - micros(priorUsd);
82
+ if (remaining <= 0)
83
+ throw new Error('TV-12 issue spend cap exhausted');
84
+ return await run(runsRoot, { capUsd: TV12_ISSUE_USD_CAP, priorUsd, runUsdCeiling: Math.min(micros(approvedUsd), remaining) / 1_000_000 });
85
+ }
86
+ finally {
87
+ await rm(lock, { force: true });
88
+ }
89
+ }
90
+ const schema = { type: 'object', properties: { payload: {} }, required: ['payload'], additionalProperties: false };
91
+ function definition(id, kind = 'choice', count = 2) {
92
+ return { apiVersion: 'decision.aiwg.io/v1alpha1', kind: 'DecisionDefinition', metadata: { id, version: '1.0.0', description: 'TV-12 synthetic collection' },
93
+ spec: { purpose: 'Synthetic TV-12 collection only.', inputSchema: schema, question: 'Assess this synthetic entry.',
94
+ answer: kind === 'choice' ? { kind, options: Array.from({ length: count }, (_, i) => ({ id: `option_${i}`, description: `Synthetic category ${i}.` })) }
95
+ : { kind, levels: Array.from({ length: count }, (_, i) => `Synthetic level ${i}.`) }, requiredCapabilities: [kind] } };
96
+ }
97
+ function projection(model, region) {
98
+ return { version: 'tv12-v1', provider: 'jev', model, region, origin: new URL(JEV_ENDPOINT).origin, purpose: 'tv12', allowIncompleteContext: false,
99
+ fields: [{ pointer: '/payload', output: 'payload', source: 'synthetic-corpus', subject, trust: 'untrusted', sensitivity: 'public', purpose: 'tv12',
100
+ retentionClass: 'qualification', accessScopes: ['tv12-reviewer'], exportPolicy: 'denied', deletionPolicy: 'erase', backupPolicy: 'not-persisted',
101
+ allowedProviders: ['jev'], allowedModels: [model], allowedOrigins: [new URL(JEV_ENDPOINT).origin], allowedRegions: [region] }] };
102
+ }
103
+ async function compiled(item, model, region) {
104
+ item.definitions.forEach(validateDefinition);
105
+ const policy = projection(model, region);
106
+ const projected = await projectDecisionState(item.input, policy);
107
+ const input = { subject, authorizedState: partitionProjectedState(projected.state, projected.evidence),
108
+ authorizationDigest: contextLiveDigest(policy), incompleteContext: false,
109
+ questions: item.definitions.map((d, i) => ({ id: `q${i}`, subject, entry: JSON.parse(compileJevQuestion(d).question) })) };
110
+ return { input, projected, policy };
111
+ }
112
+ /** Collection-only ruleset: one alias per compiled question, review outcome, no action execution. */
113
+ export function contextLiveRuleset(itemId, aliases, definitions) {
114
+ return { apiVersion: 'decision.aiwg.io/v1alpha1', kind: 'DecisionRuleset', metadata: { id: `tv12-${itemId}`, version: '1.0.0', description: 'TV-12 synthetic collection' },
115
+ spec: { purpose: 'TV-12 collection without action execution.', inputSchema: schema,
116
+ evaluations: aliases.map((alias, i) => ({ alias, decision: artifactPin(definitions[i]), inputPointer: '' })),
117
+ rules: [{ id: 'review', priority: 1, when: { op: 'eq', left: { source: 'decision', alias: aliases[0], pointer: '/status' }, right: 'success' }, outcome: 'review' }],
118
+ composition: 'first-match', conflict: 'review', defaultOutcome: 'review', failureOutcome: 'review', outputSchema: { enum: ['review'] } } };
119
+ }
120
+ /** Pinned Jev target: approved model and logical credential reference, no retries or fallbacks. */
121
+ export function contextLiveTarget(approval, adapterVersion, timeoutMs) {
122
+ return { adapter: 'jev', adapterVersion, model: approval.model, credentialRef: approval.secretServiceReference,
123
+ requiredCapabilities: [], acceptance: { mode: 'typed-value' }, timeoutMs,
124
+ retry: { maxRetries: 0, initialDelayMs: 0, maxDelayMs: 0 } };
125
+ }
126
+ export function contextLiveBinding(ruleset, aliases, target, timeoutMs) {
127
+ return { apiVersion: 'decision.aiwg.io/v1alpha1', kind: 'DecisionBinding', metadata: { id: 'tv12-binding', version: '1.0.0', description: 'TV-12 synthetic collection' },
128
+ spec: { ruleset: artifactPin(ruleset), totalTimeoutMs: timeoutMs, maxAttempts: aliases.length, concurrency: 1,
129
+ evaluations: Object.fromEntries(aliases.map(alias => [alias, { targets: [target], fallbackOn: [] }])) } };
130
+ }
131
+ /** Exposes the exact D10-projected planning input used by collection, for record binding and the canary. */
132
+ export async function compileContextLiveCase(item, model, region) {
133
+ return compiled(item, model, region);
134
+ }
135
+ /** Builds targets from actual compiled question and trust-partitioned provider state shapes. */
136
+ export async function generateContextLiveCorpus(profile) {
137
+ admitEntry(profile);
138
+ if (profile.limits.aggregateTokens !== 64_000 || profile.limits.stateAndLongestQuestionTokens !== 32_000)
139
+ throw new Error('TV-12 requires documented limits');
140
+ // Validate profile semantics before generating or writing any preparatory output.
141
+ const probe = await compiled({ id: 'profile-probe', dimension: 'probe', input: { payload: '' }, definitions: [definition('probe')] }, 'preparation-model', 'preparation-region');
142
+ planDecisionContext(probe.input, profile, estimator);
143
+ const cases = [];
144
+ for (const dimension of ['longest', 'aggregate'])
145
+ for (const boundary of ['raw', 'effective'])
146
+ for (const offset of [-1, 0, 1]) {
147
+ const limit = dimension === 'longest' ? profile.limits.stateAndLongestQuestionTokens : profile.limits.aggregateTokens;
148
+ const target = Math.floor(limit * (boundary === 'raw' ? 1 : 1 - profile.safetyMarginBps / 10_000)) + offset;
149
+ const item = { id: `${dimension}-${boundary}-${offset + 1}`, dimension: `${dimension}-${boundary}`, input: { payload: '' },
150
+ definitions: Array.from({ length: dimension === 'aggregate' ? 4 : 1 }, (_, i) => definition(`${dimension}-${boundary}-${offset + 1}-d${i}`)) };
151
+ // Aggregate targets pad four independent question instructions; longest targets pad state.
152
+ let low = 0, high = target * 3;
153
+ const score = async (n) => {
154
+ if (dimension === 'longest')
155
+ item.input.payload = Array.from({ length: 4 }, (_, i) => 'x'.repeat(Math.floor(n / 4) + (i < n % 4 ? 1 : 0)));
156
+ else
157
+ item.definitions.forEach((d, i) => { d.spec.question = 'Assess. ' + 'x'.repeat(Math.floor(n / 4) + (i < n % 4 ? 1 : 0)); });
158
+ const { input } = await compiled(item, 'preparation-model', 'preparation-region');
159
+ return estimator.estimate(input.authorizedState).tokens + input.questions.reduce((sum, q) => sum + estimator.estimate(q.entry).tokens, 0) + profile.requestEnvelopeTokens;
160
+ };
161
+ while (low < high) {
162
+ const mid = Math.floor((low + high) / 2);
163
+ if (await score(mid) < target)
164
+ low = mid + 1;
165
+ else
166
+ high = mid;
167
+ }
168
+ if (await score(low) !== target)
169
+ throw new Error('Unreachable estimator target');
170
+ cases.push(item);
171
+ }
172
+ cases.push({ id: 'unicode-nested', dimension: 'unicode-nested', input: { payload: { nested: [{ text: '合成 🙂 é'.repeat(100) }] } }, definitions: [definition('unicode')] }, { id: 'choice-255', dimension: 'choice-255', input: { payload: 'synthetic' }, definitions: [definition('choice255', 'choice', 255)] }, { id: 'score-10', dimension: 'score-10', input: { payload: 'synthetic' }, definitions: [definition('score10', 'ordinal-score', 10)] }, { id: 'many-short', dimension: 'many-short', input: { payload: 'synthetic' }, definitions: Array.from({ length: 24 }, (_, i) => definition(`short${i}`)) });
173
+ const dominant = definition('dominant');
174
+ dominant.spec.question += 'x'.repeat(30_000);
175
+ cases.push({ id: 'dominant', dimension: 'dominant', input: { payload: 'synthetic' }, definitions: [dominant, definition('short')] });
176
+ return { schemaVersion: 'context-live-corpus/v1', syntheticOnly: true, profile: structuredClone(profile), cases };
177
+ }
178
+ export const TV12_MAX_RETRIES = 1;
179
+ export const TV12_MIN_MEASURED_CASES = 12;
180
+ const retryableReasons = ['invalid-output', 'service-error', 'overloaded'];
181
+ export function contextLivePreregistration(corpus, marginRule) {
182
+ return { schemaVersion: 'context-live-preregistration/v2', corpusDigest: contextLiveDigest(corpus), estimator: { id: estimator.id, version: estimator.version },
183
+ metrics: ['absolute-error-tokens', 'undercount-bps'], marginRule, stopFraction: 0.8, automaticPromotion: false,
184
+ providerFailurePolicy: { maxRetriesPerRequest: TV12_MAX_RETRIES, retryableReasons: [...retryableReasons], terminalResponsesOnly: true,
185
+ accounting: 'reserve every attempt before dispatch; never refund', exhausted: 'measurement-failure-and-continue',
186
+ marginPopulation: 'fully measured cases only; exclude every partition of a failed case', minimumMeasuredCases: TV12_MIN_MEASURED_CASES,
187
+ rationale: '12 of 13 admissible cases; at most one missing case, no coverage claim for its input class',
188
+ stillStopImmediately: ['served-model-change', 'usage-bound', 'budget', 'credential-anomaly', 'source-drift',
189
+ 'missing-success-request-id', 'unknown-success-model', 'missing-success-usage', 'deadline', 'uncertain-execution', 'other-provider-outcome'] } };
190
+ }
191
+ export function validateContextLiveApproval(approval, corpus) {
192
+ admitEntry(approval);
193
+ admitEntry(corpus, { ...DEFAULT_ENTRY_LIMITS, serializedBytes: 8_388_608, properties: 100_000, entries: 200_000, memoryBytes: 67_108_864 });
194
+ const keys = ['schemaVersion', 'approved', 'reviewer', 'stagingWorkspace', 'runId', 'sourceCommit', 'exactHeadCi', 'model', 'apiRevision', 'region',
195
+ 'secretServiceReference', 'credentialResolverDigest', 'corpusDigest', 'preregistrationDigest', 'budget', 'perRequestBound', 'marginRule'];
196
+ if (Object.keys(approval).some(key => !keys.includes(key))
197
+ || Object.keys(approval.perRequestBound ?? {}).sort().join(',') !== 'approvalReference,totalTokens,usd'
198
+ || Object.keys(approval.marginRule ?? {}).sort().join(',') !== 'extraReserveBps,maximumMarginBps')
199
+ throw new Error('Unknown TV-12 approval field');
200
+ if (approval.schemaVersion !== 'context-live-approval/v1' || approval.approved !== true || corpus.schemaVersion !== 'context-live-corpus/v1' || corpus.syntheticOnly !== true
201
+ || ![approval.reviewer, approval.stagingWorkspace, approval.runId, approval.exactHeadCi, approval.model, approval.region, approval.secretServiceReference,
202
+ approval.perRequestBound?.approvalReference].every(v => typeof v === 'string' && v.trim()) || approval.apiRevision !== 'v1'
203
+ || !/^sha256:[a-f0-9]{64}$/.test(approval.credentialResolverDigest) || !/^[a-f0-9]{40}$/.test(approval.sourceCommit) || !/^[a-zA-Z0-9_-]+$/.test(approval.runId)
204
+ || /latest|unknown/i.test(approval.model) || approval.region === 'unknown' || !CREDENTIAL_REF.test(approval.secretServiceReference)
205
+ || !approval.budget || Object.keys(approval.budget).sort().join(',') !== 'requests,tokens,usd,wallClockMs' || Object.values(approval.budget).some(n => !Number.isFinite(n) || n <= 0)
206
+ || approval.budget.usd > TV12_ISSUE_USD_CAP
207
+ || approval.budget.usd > Number.MAX_SAFE_INTEGER / 1_000_000 || approval.perRequestBound.usd > Number.MAX_SAFE_INTEGER / 1_000_000
208
+ || !Number.isSafeInteger(approval.budget.requests) || !Number.isSafeInteger(approval.budget.tokens) || !Number.isSafeInteger(approval.budget.wallClockMs)
209
+ || !Number.isSafeInteger(approval.perRequestBound.totalTokens) || approval.perRequestBound.totalTokens < 1
210
+ || !Number.isFinite(approval.perRequestBound.usd) || approval.perRequestBound.usd <= 0
211
+ || !Number.isSafeInteger(approval.marginRule?.extraReserveBps) || approval.marginRule.extraReserveBps < 0
212
+ || !Number.isSafeInteger(approval.marginRule?.maximumMarginBps) || approval.marginRule.maximumMarginBps < approval.marginRule.extraReserveBps
213
+ || approval.marginRule.maximumMarginBps >= 10_000
214
+ || approval.corpusDigest !== contextLiveDigest(corpus) || approval.preregistrationDigest !== contextLiveDigest(contextLivePreregistration(corpus, approval.marginRule))) {
215
+ throw new Error('Incomplete or mismatched TV-12 approval');
216
+ }
217
+ if (corpus.profile.limits.aggregateTokens !== 64_000 || corpus.profile.limits.stateAndLongestQuestionTokens !== 32_000
218
+ || corpus.profile.estimator.id !== estimator.id || corpus.profile.estimator.version !== estimator.version
219
+ || !corpus.cases.length || new Set(corpus.cases.map(c => c.id)).size !== corpus.cases.length)
220
+ throw new Error('Invalid TV-12 corpus');
221
+ // Checked before any spend: a selectable margin above the collection margin could split a collected request.
222
+ if (corpus.profile.safetyMarginBps < approval.marginRule.maximumMarginBps)
223
+ throw new Error('TV-12 collection margin is below the preregistered maximum');
224
+ }
225
+ export async function assertContextLiveSource(root, expected) {
226
+ const git = (...args) => execFileSync('git', args, { cwd: root, encoding: 'utf8', timeout: 10_000 }).trim();
227
+ if (git('rev-parse', 'HEAD') !== expected || git('status', '--porcelain', '--untracked-files=all'))
228
+ throw new Error('TV-12 requires clean exact source commit');
229
+ }
230
+ /** Pure accounting guard. Reservations are never refunded, including uncertain failed calls. */
231
+ export class ContextLiveBudget {
232
+ approval;
233
+ started;
234
+ now;
235
+ requests = 0;
236
+ tokens = 0;
237
+ usd = 0;
238
+ usdMicros = 0;
239
+ constructor(approval, started, now = Date.now) {
240
+ this.approval = approval;
241
+ this.started = started;
242
+ this.now = now;
243
+ }
244
+ reserve() {
245
+ const { budget, perRequestBound: bound } = this.approval;
246
+ if (this.requests + 1 > budget.requests * 0.8 || this.tokens + bound.totalTokens > budget.tokens * 0.8
247
+ || this.usdMicros + Math.ceil(bound.usd * 1_000_000) > Math.floor(budget.usd * 800_000) || this.now() - this.started >= budget.wallClockMs * 0.8)
248
+ throw new Error('TV-12 budget exhausted');
249
+ this.requests++;
250
+ this.tokens += bound.totalTokens;
251
+ this.usdMicros += Math.ceil(bound.usd * 1_000_000);
252
+ this.usd = this.usdMicros / 1_000_000;
253
+ }
254
+ }
255
+ /** Most requests the 80% stop admits, derived from the same arithmetic as `ContextLiveBudget.reserve`. */
256
+ export function contextLiveRequestCapacity(approval) {
257
+ const { budget, perRequestBound: bound } = approval;
258
+ return Math.min(Math.floor(budget.requests * 0.8), Math.floor(budget.tokens * 0.8 / bound.totalTokens), Math.floor(Math.floor(budget.usd * 800_000) / Math.ceil(bound.usd * 1_000_000)));
259
+ }
260
+ /** Dry run: the exact partitions collection would dispatch, without credentials, transport or writes. */
261
+ export async function estimateContextLiveCollection(approval, corpus) {
262
+ validateContextLiveApproval(approval, corpus);
263
+ const cases = [];
264
+ for (const item of corpus.cases) {
265
+ admitEntry(item);
266
+ admitEntry(item.input);
267
+ const { input, policy } = await compiled(item, approval.model, approval.region);
268
+ let plan;
269
+ try {
270
+ plan = planDecisionContext(input, corpus.profile, estimator);
271
+ }
272
+ catch (error) {
273
+ // Same rule as collection: a pre-dispatch rejection records the case and moves on.
274
+ if (error instanceof ContextPlanError && ['oversized-state', 'oversized-question'].includes(error.reason)) {
275
+ cases.push({ id: item.id, dimension: item.dimension, rejectedBeforeDispatch: true, partitions: [] });
276
+ continue;
277
+ }
278
+ throw error;
279
+ }
280
+ const { adapter, capabilities } = await contextLiveAdapter(approval, policy);
281
+ const timeoutMs = Math.max(1, Math.min(30_000, Math.floor(approval.budget.wallClockMs * 0.8)));
282
+ cases.push({ id: item.id, dimension: item.dimension, rejectedBeforeDispatch: false, partitions: plan.partitions.map(partition => {
283
+ const selected = input.questions.filter(q => partition.questionIds.includes(q.id));
284
+ const checked = planDecisionContext({ ...input, questions: selected }, corpus.profile, estimator);
285
+ if (checked.partitions.length !== 1 || checked.partitions[0].questionIds.length !== selected.length)
286
+ throw new Error('TV-12 partition is not one complete request');
287
+ contextLivePartitionRequest(item, selected.map(q => q.id), approval, adapter.version, capabilities, timeoutMs);
288
+ return { id: partition.id, questions: partition.questionIds.length, estimatedInputTokens: checked.partitions[0].estimate.aggregateTokens };
289
+ }) });
290
+ }
291
+ const partitions = cases.flatMap(c => c.partitions);
292
+ const requests = partitions.length;
293
+ const maximumRequestsWithRetries = requests * (1 + TV12_MAX_RETRIES);
294
+ const capacity = contextLiveRequestCapacity(approval);
295
+ const maxPartitionEstimate = Math.max(0, ...partitions.map(p => p.estimatedInputTokens));
296
+ return { schemaVersion: 'context-live-estimate/v2', providerCalls: 0, credentialResolved: false, cases, requests, maximumRequestsWithRetries,
297
+ estimatedInputTokens: partitions.reduce((sum, p) => sum + p.estimatedInputTokens, 0), maxPartitionEstimate,
298
+ reserved: { requests: maximumRequestsWithRetries, tokens: maximumRequestsWithRetries * approval.perRequestBound.totalTokens, usd: maximumRequestsWithRetries * Math.ceil(approval.perRequestBound.usd * 1_000_000) / 1_000_000 },
299
+ requestCapacityAtStop: capacity, fitsBeforeStop: maximumRequestsWithRetries <= capacity && maxPartitionEstimate <= approval.perRequestBound.totalTokens };
300
+ }
301
+ class CollectionStop extends Error {
302
+ evidence;
303
+ constructor(message, evidence) {
304
+ super(message);
305
+ this.evidence = evidence;
306
+ }
307
+ }
308
+ /** Built-in Jev adapter plus its destination checks; `capabilities()` makes no network call. */
309
+ async function contextLiveAdapter(approval, policy, offlineFetch) {
310
+ const adapter = new JevDecisionAdapter({ region: approval.region, ...(offlineFetch ? { fetch: offlineFetch } : {}) });
311
+ const capabilities = await adapter.capabilities();
312
+ if (!capabilities.batch?.native || capabilities.batch.atomic !== true || capabilities.egress?.mode !== 'network' || capabilities.egress.origin !== policy.origin
313
+ || capabilities.egress.region !== policy.region || adapter.version !== '1.0.0')
314
+ throw new Error('TV-12 adapter destination mismatch');
315
+ return { adapter, capabilities };
316
+ }
317
+ /**
318
+ * Every request-shape check that precedes admission, reservation and dispatch: answer shapes against
319
+ * adapter capabilities, ruleset and binding schemas (including the credential reference), and pins.
320
+ * Collection runs it per partition; the dry run runs the same function for the same partitions.
321
+ */
322
+ function contextLivePartitionRequest(item, aliases, approval, adapterVersion, capabilities, timeoutMs) {
323
+ const definitions = aliases.map(alias => item.definitions[Number(alias.slice(1))]);
324
+ const ruleset = contextLiveRuleset(item.id, aliases, definitions);
325
+ const target = contextLiveTarget(approval, adapterVersion, timeoutMs);
326
+ const binding = contextLiveBinding(ruleset, aliases, target, timeoutMs);
327
+ definitions.forEach(d => {
328
+ if (!capabilities.answerKinds.includes(d.spec.answer.kind) || d.spec.answer.kind === 'choice' && d.spec.answer.options.length > capabilities.maxOptions
329
+ || d.spec.answer.kind === 'ordinal-score' && d.spec.answer.levels.length > capabilities.maxLevels)
330
+ throw new Error('TV-12 unsupported answer shape');
331
+ });
332
+ validateRuleset(ruleset);
333
+ validateBinding(binding, ruleset);
334
+ assertArtifactPin(ruleset, binding.spec.ruleset, 'tv12 ruleset');
335
+ aliases.forEach((alias, i) => assertArtifactPin(definitions[i], ruleset.spec.evaluations[i].decision, alias));
336
+ return { definitions, ruleset, target, binding };
337
+ }
338
+ /** Collection-only native request. Exposed for offline guard tests; mocks remain synthetic. */
339
+ export async function collectContextLiveCase(item, corpus, approval, host, budget, signal, offlineFetch) {
340
+ validateContextLiveApproval(approval, corpus);
341
+ if (!offlineFetch && !liveHosts.has(host))
342
+ throw new Error('Live collection requires the source-verified runner');
343
+ admitEntry(item);
344
+ if (!corpus.cases.some(candidate => contextLiveDigest(candidate) === contextLiveDigest(item)))
345
+ throw new Error('Case is outside frozen corpus');
346
+ admitEntry(item.input);
347
+ const { input, projected, policy } = await compiled(item, approval.model, approval.region);
348
+ let plan;
349
+ try {
350
+ plan = planDecisionContext(input, corpus.profile, estimator);
351
+ }
352
+ catch (error) {
353
+ if (error instanceof ContextPlanError && ['oversized-state', 'oversized-question'].includes(error.reason))
354
+ return { rejected: true, records: [], failures: [], synthetic: !!offlineFetch };
355
+ throw error;
356
+ }
357
+ const { adapter, capabilities } = await contextLiveAdapter(approval, policy, offlineFetch);
358
+ const registry = new DecisionAdmissionRegistry();
359
+ const records = [];
360
+ const failures = [];
361
+ for (const partition of plan.partitions) {
362
+ for (let attempt = 1; attempt <= 1 + TV12_MAX_RETRIES; attempt++) {
363
+ if (signal.aborted)
364
+ throw new Error('TV-12 deadline reached');
365
+ const selected = input.questions.filter(q => partition.questionIds.includes(q.id));
366
+ const partitionInput = { ...input, questions: selected };
367
+ const checked = planDecisionContext(partitionInput, corpus.profile, estimator);
368
+ if (checked.partitions.length !== 1 || checked.partitions[0].questionIds.length !== selected.length)
369
+ throw new Error('TV-12 partition is not one complete request');
370
+ if (checked.partitions[0].estimate.aggregateTokens > approval.perRequestBound.totalTokens)
371
+ throw new Error('TV-12 approved token bound below estimate');
372
+ const timeoutMs = Math.max(1, Math.min(30_000, Math.floor(approval.budget.wallClockMs * 0.8 - (Date.now() - budget.started))));
373
+ const { definitions, target } = contextLivePartitionRequest(item, selected.map(q => q.id), approval, adapter.version, capabilities, timeoutMs);
374
+ const invocationId = `${approval.runId}:${item.id}:${partition.id}:${attempt}`;
375
+ const limits = { concurrency: 1, maxTokens: approval.perRequestBound.totalTokens, maxCostUsd: approval.perRequestBound.usd, allowUnknownCost: false };
376
+ const scheduler = { enabled: true, profileVersion: 'tv12-collection-v1', workspace: { id: approval.stagingWorkspace, limits },
377
+ principal: { id: approval.reviewer, limits }, providers: { jev: limits } };
378
+ const requestSignal = AbortSignal.any([signal, AbortSignal.timeout(timeoutMs)]);
379
+ const deadlineEpochMs = Date.now() + timeoutMs;
380
+ const lease = await registry.controllerFor(scheduler, Date.now).acquire({ budgetId: invocationId, workspaceId: approval.stagingWorkspace,
381
+ principalId: approval.reviewer, providerId: 'jev', estimate: { tokens: approval.perRequestBound.totalTokens, costUsd: approval.perRequestBound.usd,
382
+ attempts: 1, batchSize: selected.length, items: selected.length }, deadlineEpochMs, signal: requestSignal });
383
+ let success = false;
384
+ try {
385
+ // Reservation precedes adapter invocation, DNS, credential resolution and network.
386
+ budget.reserve();
387
+ const requests = selected.map((q, i) => ({ alias: q.id, questionId: q.id, definition: definitions[i],
388
+ input: projected.state, projectionEvidence: projected.evidence, target, invocationId, deadlineEpochMs, signal: requestSignal,
389
+ resolveCredential: async (reference) => {
390
+ if (requestSignal.aborted || reference !== approval.secretServiceReference)
391
+ throw new Error('TV-12 credential access denied');
392
+ return host.resolveCredential(reference);
393
+ }, compiledArtifact: compileJevQuestion(definitions[i]) }));
394
+ const wire = { state: input.authorizedState, model: approval.model, questions: Object.fromEntries(selected.map(q => [q.id, q.entry])) };
395
+ const response = requests.length > 1 ? await adapter.evaluateMany({ requests, decisionSubject: subject })
396
+ : await adapter.evaluate(requests[0]).then(observation => ({ answers: [{ questionId: selected[0].id, observation }], sharedUsage: observation.usage }));
397
+ const first = response.answers[0]?.observation;
398
+ const stopEvidence = { httpStatus: first?.httpStatus ?? null, requestId: first?.requestId ?? null,
399
+ servedModel: first?.actualModel === approval.model ? approval.model : null, ...response.sharedUsage };
400
+ const usage = response.sharedUsage;
401
+ // Safety stops dominate the measurement retry, even on a non-success response.
402
+ if (requestSignal.aborted || response.answers.some(a => a.observation.actualModel !== null && a.observation.actualModel !== approval.model)
403
+ || [usage.inputTokens, usage.outputTokens].some(n => n !== null && (!Number.isSafeInteger(n) || n < 0 || n > approval.perRequestBound.totalTokens))
404
+ || usage.inputTokens !== null && usage.outputTokens !== null && usage.inputTokens + usage.outputTokens > approval.perRequestBound.totalTokens
405
+ || usage.costUsd !== null && (!Number.isFinite(usage.costUsd) || usage.costUsd < 0 || usage.costUsd > approval.perRequestBound.usd)) {
406
+ throw new CollectionStop('TV-12 provider identity or usage bound failed', stopEvidence);
407
+ }
408
+ const failed = response.answers.filter(a => a.observation.status !== 'success');
409
+ if (response.answers.length === selected.length && failed.length > 0 && failed.every(a => a.observation.dispatchCertainty === 'terminal-response' && a.observation.remoteExecution !== 'unknown'
410
+ && retryableReasons.includes(a.observation.reason)
411
+ && (a.observation.httpStatus === 200 || a.observation.httpStatus >= 500 && a.observation.httpStatus <= 599))) {
412
+ const failure = { schemaVersion: 'context-live-provider-failure/v1', source: offlineFetch ? 'synthetic' : 'provider',
413
+ caseId: item.id, partitionId: partition.id, attempt, reason: failed[0].observation.reason,
414
+ measurementFailure: attempt === 1 + TV12_MAX_RETRIES, ...stopEvidence };
415
+ failures.push(failure);
416
+ await host.recordFailure?.(structuredClone(failure));
417
+ continue;
418
+ }
419
+ // Other outcomes stop collection, including first limit-related 4xx. No provider text escapes.
420
+ if (response.answers.length !== selected.length || response.answers.some(answer => answer.observation.status !== 'success'
421
+ || answer.observation.actualModel !== approval.model || !answer.observation.requestId))
422
+ throw new CollectionStop('TV-12 provider identity or outcome failed', stopEvidence);
423
+ const requestIds = new Set(response.answers.map(a => a.observation.requestId));
424
+ if (requestIds.size !== 1)
425
+ throw new Error('TV-12 inconsistent shared request identity');
426
+ if (!Number.isSafeInteger(usage.inputTokens) || !Number.isSafeInteger(usage.outputTokens) || usage.inputTokens < 0 || usage.outputTokens < 0
427
+ || usage.inputTokens + usage.outputTokens > approval.perRequestBound.totalTokens
428
+ || (usage.costUsd !== null && (!Number.isFinite(usage.costUsd) || usage.costUsd < 0 || usage.costUsd > approval.perRequestBound.usd))) {
429
+ throw new CollectionStop('TV-12 usage missing or approved bound exceeded', stopEvidence);
430
+ }
431
+ const usageArtifact = { source: offlineFetch ? 'synthetic' : 'provider', servedModel: approval.model, requestId: [...requestIds][0], ...usage };
432
+ const usageArtifactDigest = contextLiveDigest(usageArtifact);
433
+ const usageRef = `usage:${usageArtifactDigest}`;
434
+ const comparison = compareContextUsage([{ caseId: `${item.id}:${partition.id}`, input: partitionInput,
435
+ actualInputTokens: usage.inputTokens, source: offlineFetch ? 'synthetic' : 'provider', usageRef }], corpus.profile, estimator).cases[0];
436
+ const record = { source: offlineFetch ? 'synthetic' : 'provider', estimator: { id: estimator.id, version: estimator.version },
437
+ wireDigest: contextLiveDigest(wire), wireBytes: Buffer.byteLength(JSON.stringify(wire)), caseId: item.id, partitionId: partition.id, planDigest: checked.planDigest, profileDigest: checked.providerProfile.digest,
438
+ servedModel: approval.model, requestId: [...requestIds][0], estimatedInputTokens: comparison.estimatedInputTokens,
439
+ actualInputTokens: comparison.actualInputTokens, errorTokens: comparison.errorTokens, absoluteErrorTokens: Math.abs(comparison.errorTokens),
440
+ undercountBps: comparison.undercountBps, usageRef, usageArtifactDigest, ...usage };
441
+ records.push(record);
442
+ await host.record?.(structuredClone(record));
443
+ success = true;
444
+ break;
445
+ }
446
+ finally {
447
+ lease.release({ success });
448
+ }
449
+ }
450
+ }
451
+ return { rejected: false, records, failures, synthetic: !!offlineFetch };
452
+ }
453
+ /** `exact` (run evidence) requires the canonical root itself; `within` (prepared inputs) allows a descendant. */
454
+ export async function assertContextArtifactRoot(sourceRoot, artifactRoot, mode = 'exact') {
455
+ const route = JSON.parse(execFileSync('aiwg', ['artifacts', 'path', '--json', '--check-write'], { cwd: sourceRoot, encoding: 'utf8', timeout: 10_000 }));
456
+ if (typeof route.artifact_root !== 'string')
457
+ throw new Error('Canonical artifact root unavailable');
458
+ const delta = relative(await realpath(route.artifact_root), await realpath(artifactRoot));
459
+ if (mode === 'exact' ? delta !== '' : delta === '..' || delta.startsWith('../') || isAbsolute(delta))
460
+ throw new Error('TV-12 evidence must use the canonical artifact root');
461
+ }
462
+ /** Writes only sanitized evidence. The caller must resolve the canonical artifact root first. */
463
+ export async function runContextLiveCollection(options) {
464
+ const { sourceRoot, artifactRoot, host, offlineTransport } = options;
465
+ admitEntry(options.approval);
466
+ admitEntry(options.corpus, { ...DEFAULT_ENTRY_LIMITS, serializedBytes: 8_388_608, properties: 100_000, entries: 200_000, memoryBytes: 67_108_864 });
467
+ const approval = structuredClone(options.approval), corpus = structuredClone(options.corpus);
468
+ validateContextLiveApproval(approval, corpus);
469
+ await assertContextLiveSource(sourceRoot, approval.sourceCommit);
470
+ if (contextLiveDigest(await generateContextLiveCorpus(corpus.profile)) !== approval.corpusDigest)
471
+ throw new Error('TV-12 requires complete generated synthetic corpus');
472
+ await assertContextArtifactRoot(sourceRoot, artifactRoot);
473
+ try {
474
+ return await withContextLiveSpendLedger(artifactRoot, approval.budget.usd, (runsRoot, issueSpend) => collectWithinLedger(approval, corpus, sourceRoot, runsRoot, issueSpend, host, offlineTransport));
475
+ }
476
+ finally {
477
+ host.dispose?.();
478
+ }
479
+ }
480
+ async function collectWithinLedger(approval, corpus, sourceRoot, runsRoot, issueSpend, host, offlineTransport) {
481
+ // Immutable snapshots exist before any credential or provider operation; the approval is written first
482
+ // so an interrupted run is always charged its full approved budget by later ledger scans.
483
+ const directory = resolve(runsRoot, approval.runId);
484
+ await mkdir(directory, { recursive: false, mode: 0o700 });
485
+ await writeFile(join(directory, 'approval.json'), canonicalJson(approval), { flag: 'wx', mode: 0o600 });
486
+ await writeFile(join(directory, 'preregistration.json'), canonicalJson(contextLivePreregistration(corpus, approval.marginRule)), { flag: 'wx', mode: 0o600 });
487
+ await writeFile(join(directory, 'corpus.json'), canonicalJson(corpus), { flag: 'wx', mode: 0o600 });
488
+ const started = Date.now();
489
+ const controller = new AbortController();
490
+ const deadlineTimer = setTimeout(() => controller.abort(), Math.min(2_147_483_647, Math.floor(approval.budget.wallClockMs * 0.8)));
491
+ const signal = controller.signal;
492
+ try {
493
+ const budget = new ContextLiveBudget({ ...approval, budget: { ...approval.budget, usd: issueSpend.runUsdCeiling } }, started);
494
+ const expected = new Map();
495
+ for (const item of corpus.cases) {
496
+ try {
497
+ expected.set(item.id, planDecisionContext((await compiled(item, approval.model, approval.region)).input, corpus.profile, estimator).partitions.length);
498
+ }
499
+ catch (error) {
500
+ if (error instanceof ContextPlanError && ['oversized-state', 'oversized-question'].includes(error.reason))
501
+ expected.set(item.id, 0);
502
+ else
503
+ throw error;
504
+ }
505
+ }
506
+ const records = [];
507
+ const rejected = [];
508
+ const failed = [];
509
+ let stopped = false;
510
+ const frozenInputs = { corpus: { path: 'corpus.json', digest: approval.corpusDigest },
511
+ preregistration: { path: 'preregistration.json', digest: approval.preregistrationDigest },
512
+ approval: { path: 'approval.json', digest: contextLiveDigest(approval) } };
513
+ // Collection runs here, sequentially, before D11 recording. The generic D11 runner never invokes
514
+ // executors in `live` mode (a callback cannot authenticate a provider), so provider-backed outcomes
515
+ // are recorded afterwards in `recorded` mode; their provider proof is the retained request ID and
516
+ // usage digest in each comparison record.
517
+ const outcomes = new Map();
518
+ for (const item of corpus.cases) {
519
+ if (stopped) {
520
+ outcomes.set(item.id, { outcome: 'fail', details: { reason: 'prior-stop', frozenInputs } });
521
+ continue;
522
+ }
523
+ try {
524
+ await assertContextLiveSource(sourceRoot, approval.sourceCommit);
525
+ const collectionHost = { recordFailure: async (failure) => {
526
+ await writeFile(join(directory, `${failure.caseId}-${failure.partitionId}-attempt-${failure.attempt}-failure.json`), canonicalJson(failure), { flag: 'wx', mode: 0o600 });
527
+ await host.recordFailure?.(structuredClone(failure));
528
+ }, resolveCredential: async (reference) => {
529
+ await assertContextLiveSource(sourceRoot, approval.sourceCommit);
530
+ return host.resolveCredential(reference);
531
+ }, record: async (record) => {
532
+ const usage = { source: record.source, servedModel: record.servedModel, requestId: record.requestId, inputTokens: record.inputTokens, outputTokens: record.outputTokens, costUsd: record.costUsd };
533
+ if (contextLiveDigest(usage) !== record.usageArtifactDigest)
534
+ throw new Error('TV-12 usage digest mismatch');
535
+ await writeFile(join(directory, `usage-${record.usageArtifactDigest.slice(7)}.json`), canonicalJson(usage), { flag: 'wx', mode: 0o600 });
536
+ await writeFile(join(directory, `${record.caseId}-${record.partitionId}-comparison.json`), canonicalJson(record), { flag: 'wx', mode: 0o600 });
537
+ records.push(record);
538
+ await host.record?.(structuredClone(record));
539
+ } };
540
+ if (!offlineTransport)
541
+ liveHosts.add(collectionHost);
542
+ const collected = await collectContextLiveCase(item, corpus, approval, collectionHost, budget, signal, offlineTransport);
543
+ if (collected.rejected)
544
+ rejected.push(item.id);
545
+ const measurementFailure = collected.failures.some(f => f.measurementFailure);
546
+ if (measurementFailure)
547
+ failed.push(item.id);
548
+ outcomes.set(item.id, { outcome: measurementFailure ? 'fail' : 'pass', details: { frozenInputs, rejectedBeforeDispatch: collected.rejected,
549
+ ...(measurementFailure ? { reason: 'measurement-failure' } : {}), failures: collected.failures, records: collected.records } });
550
+ }
551
+ catch (error) {
552
+ stopped = true;
553
+ outcomes.set(item.id, { outcome: 'fail', details: { reason: 'collection-stopped', frozenInputs, ...(error instanceof CollectionStop ? { provider: error.evidence } : {}), records: records.filter(row => row.caseId === item.id), reserved: { requests: budget.requests, tokens: budget.tokens, usd: budget.usd } } });
554
+ }
555
+ }
556
+ const run = await executeQualificationPlan({ artifactRoot: runsRoot, timeoutMs: 60_000, concurrency: 1,
557
+ manifest: { schemaVersion: 'decision-qualification-run/v1', mode: offlineTransport ? 'offline' : 'recorded', runId: approval.runId, generatedAt: new Date().toISOString(),
558
+ sourceCommit: approval.sourceCommit, dirty: false, cases: corpus.cases.map(item => ({ id: item.id, kind: 'baseline', mandatory: true, candidateTests: [], evidenceIds: ['CTX-TV12'] })) },
559
+ executors: Object.fromEntries(corpus.cases.map(item => [item.id, async () => outcomes.get(item.id)])), sanitizeDetails: details => details });
560
+ let sourceDrift = false;
561
+ try {
562
+ await assertContextLiveSource(sourceRoot, approval.sourceCommit);
563
+ }
564
+ catch {
565
+ stopped = true;
566
+ sourceDrift = true;
567
+ }
568
+ const measured = corpus.cases.filter(item => !failed.includes(item.id) && outcomes.get(item.id)?.outcome === 'pass'
569
+ && expected.get(item.id) > 0 && expected.get(item.id) === records.filter(row => row.caseId === item.id).length).map(item => item.id);
570
+ const unmeasured = corpus.cases.map(item => item.id).filter(id => !measured.includes(id) && !failed.includes(id) && !rejected.includes(id));
571
+ const worst = Math.max(0, ...records.filter(row => measured.includes(row.caseId)).map(row => row.undercountBps));
572
+ const candidateMarginBps = !stopped && measured.length >= TV12_MIN_MEASURED_CASES ? worst + approval.marginRule.extraReserveBps : null;
573
+ const marginValid = candidateMarginBps !== null && candidateMarginBps <= approval.marginRule.maximumMarginBps;
574
+ const collectionSuccess = !stopped && records.length > 0 && run.evidence.length === corpus.cases.length && run.evidence.every(row => row.outcome === 'pass')
575
+ && corpus.cases.every(item => expected.get(item.id) === records.filter(row => row.caseId === item.id).length
576
+ && (expected.get(item.id) !== 0 || rejected.includes(item.id)));
577
+ const summary = { schemaVersion: 'context-live-collection/v2', sourceCommit: approval.sourceCommit, corpusDigest: approval.corpusDigest,
578
+ preregistrationDigest: approval.preregistrationDigest, frozenInputs, source: offlineTransport ? 'synthetic' : 'provider', collectionSuccess, collected: records.length, measured, failed, rejected, unmeasured, stopped, sourceDrift, marginValid, minimumMeasuredCases: TV12_MIN_MEASURED_CASES,
579
+ candidateMarginBps, withinApprovedMaximum: candidateMarginBps !== null && candidateMarginBps <= approval.marginRule.maximumMarginBps,
580
+ qualifiedForEnforcement: false, pending: ['reviewer-margin-approval', 'versioned-profile-qualification', 'enforce-canary', 'rollback-evidence'],
581
+ reserved: { requests: budget.requests, tokens: budget.tokens, usd: budget.usd }, issueSpend, elapsedMs: Date.now() - started };
582
+ await writeFile(join(directory, 'summary.json'), canonicalJson(summary), { flag: 'wx', mode: 0o600 });
583
+ await writeFile(join(directory, 'run-manifest.json'), canonicalJson(run), { flag: 'wx', mode: 0o600 });
584
+ const manifest = await writeQualificationEvidenceManifest(run, runsRoot, sourceRoot, Object.fromEntries(corpus.cases.map(item => [item.id, ['src/decision/context-live-qualification.ts', 'src/decision/context-plan.ts', 'src/decision/adapters/jev.ts']])));
585
+ return { ...summary, evidenceManifest: manifest.artifact, evidenceDigest: manifest.digest };
586
+ }
587
+ finally {
588
+ clearTimeout(deadlineTimer);
589
+ }
590
+ }
591
+ //# sourceMappingURL=context-live-qualification.js.map