@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.
Files changed (209) 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 +177 -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 +29 -3
  15. package/dist/src/decision/ask.js +352 -0
  16. package/dist/src/decision/calibration/registry.js +9 -0
  17. package/dist/src/decision/compose.js +74 -15
  18. package/dist/src/decision/context-live-promotion.js +344 -0
  19. package/dist/src/decision/context-live-qualification.js +591 -0
  20. package/dist/src/decision/context-pruning.js +847 -0
  21. package/dist/src/decision/driver.js +432 -0
  22. package/dist/src/decision/egress-live-qualification.js +874 -0
  23. package/dist/src/decision/ensemble/contract.js +64 -1
  24. package/dist/src/decision/ensemble/index.js +1 -0
  25. package/dist/src/decision/ensemble/report.js +8 -42
  26. package/dist/src/decision/ensemble/runtime.js +571 -0
  27. package/dist/src/decision/ensemble-study/artifacts.js +85 -0
  28. package/dist/src/decision/ensemble-study/calibration.js +443 -0
  29. package/dist/src/decision/ensemble-study/corpus.js +229 -0
  30. package/dist/src/decision/ensemble-study/multifact-artifacts.js +23 -0
  31. package/dist/src/decision/ensemble-study/multifact-study.js +596 -0
  32. package/dist/src/decision/ensemble-study/multifact.js +241 -0
  33. package/dist/src/decision/ensemble-study/protocol.js +77 -0
  34. package/dist/src/decision/ensemble-study/score.js +462 -0
  35. package/dist/src/decision/ensemble-study/staged.js +87 -0
  36. package/dist/src/decision/ensemble-study/statistics.js +84 -0
  37. package/dist/src/decision/evaluate.js +227 -14
  38. package/dist/src/decision/export.js +4 -0
  39. package/dist/src/decision/feature-export.js +1074 -0
  40. package/dist/src/decision/graph-index.js +16 -0
  41. package/dist/src/decision/graph-live-qualification.js +1041 -0
  42. package/dist/src/decision/graph-live-workload.js +245 -0
  43. package/dist/src/decision/graph-skill-bridge.js +21 -3
  44. package/dist/src/decision/heldout/calibration.js +140 -0
  45. package/dist/src/decision/heldout/collector.js +430 -0
  46. package/dist/src/decision/heldout/contract.js +309 -0
  47. package/dist/src/decision/heldout/d29-generator-ids.js +12 -0
  48. package/dist/src/decision/heldout/d29-passage-baseline-v2.js +93 -0
  49. package/dist/src/decision/heldout/d29-passage-baseline-v3.js +100 -0
  50. package/dist/src/decision/heldout/d29-passage-baseline.js +66 -0
  51. package/dist/src/decision/heldout/d29-pools-v8.js +575 -0
  52. package/dist/src/decision/heldout/d29-pools.js +288 -0
  53. package/dist/src/decision/heldout/d29-v6.js +169 -0
  54. package/dist/src/decision/heldout/d29-v7.js +202 -0
  55. package/dist/src/decision/heldout/d29-v8.js +586 -0
  56. package/dist/src/decision/heldout/generator-registry.js +67 -0
  57. package/dist/src/decision/heldout/generators.js +696 -0
  58. package/dist/src/decision/heldout/index.js +8 -0
  59. package/dist/src/decision/heldout/journal.js +346 -0
  60. package/dist/src/decision/heldout/study-generators.js +64 -0
  61. package/dist/src/decision/heldout/types.js +2 -0
  62. package/dist/src/decision/host-policy.js +61 -0
  63. package/dist/src/decision/index.js +13 -0
  64. package/dist/src/decision/interop-profile.v1.json +112 -0
  65. package/dist/src/decision/interop.js +653 -0
  66. package/dist/src/decision/issue-triage/index.js +3 -0
  67. package/dist/src/decision/issue-triage/runtime.js +1069 -0
  68. package/dist/src/decision/issue-triage/types.js +2 -0
  69. package/dist/src/decision/jev-credentials.js +121 -0
  70. package/dist/src/decision/patterns/catalog.js +28 -8
  71. package/dist/src/decision/patterns/playground.js +28 -11
  72. package/dist/src/decision/patterns/specs.js +19 -10
  73. package/dist/src/decision/preprocessed-evidence.js +724 -0
  74. package/dist/src/decision/qualification/capture.js +8 -2
  75. package/dist/src/decision/qualification/gate-evidence.js +19 -4
  76. package/dist/src/decision/qualification/quality.js +57 -49
  77. package/dist/src/decision/qualification/release.js +76 -3
  78. package/dist/src/decision/qualification/runner.js +5 -2
  79. package/dist/src/decision/routing/contract.js +175 -0
  80. package/dist/src/decision/routing/index.js +5 -0
  81. package/dist/src/decision/routing/report.js +216 -0
  82. package/dist/src/decision/routing/runtime.js +685 -0
  83. package/dist/src/decision/routing/types.js +2 -0
  84. package/dist/src/decision/sdlc-screening.js +1157 -0
  85. package/dist/src/decision/sensitivity/comparative.js +154 -0
  86. package/dist/src/decision/sensitivity/contract.js +218 -0
  87. package/dist/src/decision/sensitivity/index.js +5 -0
  88. package/dist/src/decision/sensitivity/runtime.js +912 -0
  89. package/dist/src/decision/sensitivity/types.js +2 -0
  90. package/dist/src/decision/validate.js +26 -0
  91. package/dist/src/extensions/commands/definitions.js +38 -0
  92. package/dist/src/extensions/manifest.js +44 -0
  93. package/dist/src/gates/discovery.js +315 -0
  94. package/dist/src/gates/driver.js +388 -0
  95. package/dist/src/gates/evaluate.js +719 -0
  96. package/dist/src/gates/floors.js +255 -0
  97. package/dist/src/gates/index.js +17 -0
  98. package/dist/src/gates/providers/evidence.js +38 -0
  99. package/dist/src/gates/providers/index.js +21 -0
  100. package/dist/src/gates/providers/isolated-child-source.js +446 -0
  101. package/dist/src/gates/providers/loader.js +1069 -0
  102. package/dist/src/gates/providers/paired.js +52 -0
  103. package/dist/src/gates/providers/proportion.js +60 -0
  104. package/dist/src/gates/providers/registry.js +40 -0
  105. package/dist/src/gates/providers/scalar.js +41 -0
  106. package/dist/src/gates/providers/screening.js +53 -0
  107. package/dist/src/gates/providers/types.js +25 -0
  108. package/dist/src/gates/registry.js +1003 -0
  109. package/dist/src/gates/report.js +102 -0
  110. package/dist/src/gates/schema.js +56 -0
  111. package/dist/src/gates/stats/binomial.js +26 -0
  112. package/dist/src/gates/stats/bootstrap.js +62 -0
  113. package/dist/src/gates/stats/clopper-pearson.js +95 -0
  114. package/dist/src/gates/stats/digest.js +26 -0
  115. package/dist/src/gates/stats/error.js +5 -0
  116. package/dist/src/gates/stats/index.js +9 -0
  117. package/dist/src/gates/stats/normal.js +35 -0
  118. package/dist/src/gates/stats/paired.js +120 -0
  119. package/dist/src/gates/stats/splits.js +30 -0
  120. package/dist/src/gates/types.js +8 -0
  121. package/dist/src/governance/redaction.js +14 -4
  122. package/dist/src/mcp/muse-native-config.mjs +123 -0
  123. package/dist/src/mcp/tools/decision.mjs +228 -0
  124. package/dist/src/mcp/tools/subsystems.mjs +2 -0
  125. package/dist/src/providers/capability-matrix.yaml +2 -1
  126. package/dist/src/providers/provider-definitions.js +2 -1
  127. package/dist/src/sessions/adapters/muse.js +336 -39
  128. package/dist/src/sessions/batch-import.js +7 -0
  129. package/dist/src/sessions/workspace-discovery.js +13 -2
  130. package/package.json +1 -1
  131. package/schemas/decision/ConformalFrozenOpenData.v2.schema.json +371 -0
  132. package/schemas/decision/ConformalOpenDataReport.v2.schema.json +421 -0
  133. package/schemas/decision/ConformalPreregistration.v2.schema.json +529 -0
  134. package/schemas/decision/ContextPruningCandidate.v1.schema.json +47 -0
  135. package/schemas/decision/ContextPruningEvaluationReport.v1.schema.json +758 -0
  136. package/schemas/decision/ContextPruningPreregistration.v1.schema.json +175 -0
  137. package/schemas/decision/ContextPruningReceipt.v1.schema.json +72 -0
  138. package/schemas/decision/D17Multifact.v1.schema.json +1851 -0
  139. package/schemas/decision/D17StudyAnalysis.v1.schema.json +42 -0
  140. package/schemas/decision/D17StudyAnalysis.v2.schema.json +51 -0
  141. package/schemas/decision/D17StudyCalibration.v1.schema.json +819 -0
  142. package/schemas/decision/D17StudyDryRun.v1.schema.json +169 -0
  143. package/schemas/decision/D17StudyDryRun.v2.schema.json +239 -0
  144. package/schemas/decision/D17StudyGold.v1.schema.json +108 -0
  145. package/schemas/decision/D17StudyGuide.v1.schema.json +63 -0
  146. package/schemas/decision/D17StudyNativeTemplates.v1.schema.json +619 -0
  147. package/schemas/decision/D17StudyProtocol.v1.schema.json +241 -0
  148. package/schemas/decision/D17StudyReport.v1.schema.json +902 -0
  149. package/schemas/decision/D17StudyReport.v2.schema.json +1319 -0
  150. package/schemas/decision/D17StudyReview.v1.schema.json +175 -0
  151. package/schemas/decision/D17StudySplits.v1.schema.json +217 -0
  152. package/schemas/decision/D29Study.v1.schema.json +1903 -0
  153. package/schemas/decision/D29Study.v2.schema.json +1344 -0
  154. package/schemas/decision/D29Study.v3.schema.json +609 -0
  155. package/schemas/decision/D29Study.v4.schema.json +1133 -0
  156. package/schemas/decision/D29Study.v6.schema.json +1205 -0
  157. package/schemas/decision/D29Study.v7.schema.json +1471 -0
  158. package/schemas/decision/D29Study.v8.schema.json +671 -0
  159. package/schemas/decision/D29Study.v9.schema.json +778 -0
  160. package/schemas/decision/DecisionComparativeReplayAuditSample.v1.schema.json +137 -0
  161. package/schemas/decision/DecisionComparativeReplayCorpus.v1.schema.json +258 -0
  162. package/schemas/decision/DecisionComparativeReplayFreeze.v1.schema.json +74 -0
  163. package/schemas/decision/DecisionComparativeReplayGold.v1.schema.json +50 -0
  164. package/schemas/decision/DecisionComparativeReplayGoldAsset.v1.schema.json +37 -0
  165. package/schemas/decision/DecisionComparativeReplayIntegrity.v1.schema.json +81 -0
  166. package/schemas/decision/DecisionComparativeReplayMembers.v1.schema.json +52 -0
  167. package/schemas/decision/DecisionComparativeReplayPreregistration.v1.schema.json +113 -0
  168. package/schemas/decision/DecisionComparativeReplayReport.v1.schema.json +407 -0
  169. package/schemas/decision/DecisionComparativeReplayReviewPackets.v1.schema.json +260 -0
  170. package/schemas/decision/DecisionComparativeReplaySplit.v1.schema.json +84 -0
  171. package/schemas/decision/DecisionFeatureObservation.v1.schema.json +564 -0
  172. package/schemas/decision/DecisionFeatureSet.v1.schema.json +155 -0
  173. package/schemas/decision/DecisionInteropMapping.v1.schema.json +85 -0
  174. package/schemas/decision/DecisionIssueTriageEvaluationManifest.v1.schema.json +174 -0
  175. package/schemas/decision/DecisionIssueTriageEvaluationReport.v1.schema.json +280 -0
  176. package/schemas/decision/DecisionIssueTriagePilotPack.v1.schema.json +114 -0
  177. package/schemas/decision/DecisionRouteReceipt.v1.schema.json +155 -0
  178. package/schemas/decision/DecisionRoutingPolicy.v1.schema.json +132 -0
  179. package/schemas/decision/DecisionRoutingShadowReport.v1.schema.json +170 -0
  180. package/schemas/decision/DecisionRoutingTask.v1.schema.json +49 -0
  181. package/schemas/decision/DecisionRuleset.schema.json +8 -2
  182. package/schemas/decision/DecisionRuleset.v1alpha2.schema.json +8 -2
  183. package/schemas/decision/DecisionSensitivityPlan.v1.schema.json +191 -0
  184. package/schemas/decision/DecisionSensitivityReport.v1.schema.json +163 -0
  185. package/schemas/decision/HeldoutApproval.v1.schema.json +312 -0
  186. package/schemas/decision/HeldoutAttempt.v1.schema.json +211 -0
  187. package/schemas/decision/HeldoutBaseline.v1.schema.json +90 -0
  188. package/schemas/decision/HeldoutCalibrationPhase.v1.schema.json +87 -0
  189. package/schemas/decision/HeldoutCorpus.v1.schema.json +167 -0
  190. package/schemas/decision/HeldoutEvent.v1.schema.json +41 -0
  191. package/schemas/decision/HeldoutFrozen.v1.schema.json +142 -0
  192. package/schemas/decision/HeldoutPreregistration.v1.schema.json +222 -0
  193. package/schemas/decision/HeldoutSpendEvent.v1.schema.json +122 -0
  194. package/schemas/decision/HeldoutSpendHead.v1.schema.json +25 -0
  195. package/schemas/decision/HeldoutSpendHead.v2.schema.json +46 -0
  196. package/schemas/decision/HeldoutSummary.v1.schema.json +101 -0
  197. package/schemas/decision/OpaDecisionLogExport.v1.schema.json +65 -0
  198. package/schemas/decision/PreprocessedEvidence.v1.schema.json +306 -0
  199. package/schemas/decision/RulesetResult.v1alpha2.schema.json +175 -0
  200. package/schemas/decision/SdlcEvidenceScreening.v1.schema.json +499 -0
  201. package/schemas/decision/SdlcScreeningPreregistration.v1.schema.json +132 -0
  202. package/schemas/decision/SdlcScreeningPreregistration.v2.schema.json +138 -0
  203. package/schemas/decision/SdlcScreeningRelease.v1.schema.json +654 -0
  204. package/schemas/decision/SdlcScreeningRelease.v2.schema.json +654 -0
  205. package/tools/agents/deploy-agents.mjs +1 -1
  206. package/tools/agents/providers/base.mjs +40 -18
  207. package/tools/agents/providers/factory.mjs +10 -2
  208. package/tools/agents/providers/muse-hooks.mjs +1 -29
  209. package/tools/agents/providers/muse.mjs +6 -3
@@ -0,0 +1,352 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { JEV_ENDPOINT, JevDecisionAdapter } from './adapters/jev.js';
3
+ import { evaluateDecisionRuleset } from './evaluate.js';
4
+ import { artifactPin } from './validate.js';
5
+ import { JEV_CREDENTIAL_REF, JEV_DEFAULT_REGION, lookupJevCredential, removeJevCredentialFile, resolveJevCredentialBytes, writeJevCredentialFile, } from './jev-credentials.js';
6
+ const CONTEXT_LIMIT_BYTES = 32 * 1024;
7
+ const MODEL = 'jev-latest';
8
+ const PURPOSE = 'agent-bounded-decision';
9
+ const INPUT_SCHEMA = {
10
+ type: 'object',
11
+ properties: { message: { type: 'string', minLength: 1 } },
12
+ required: ['message'],
13
+ additionalProperties: false,
14
+ };
15
+ export class DecisionAskUsageError extends Error {
16
+ reason;
17
+ constructor(reason, message) {
18
+ super(message);
19
+ this.reason = reason;
20
+ this.name = 'DecisionAskUsageError';
21
+ }
22
+ }
23
+ function usageError(reason, message) {
24
+ throw new DecisionAskUsageError(reason, message);
25
+ }
26
+ function byteLength(value) {
27
+ return Buffer.byteLength(value, 'utf8');
28
+ }
29
+ function validateQuestion(question) {
30
+ if (!question.trim())
31
+ usageError('question-required', 'decision ask requires --question <q>');
32
+ }
33
+ export function validateDecisionAskInput(input) {
34
+ validateQuestion(input.question);
35
+ if (input.context !== undefined && byteLength(input.context) > CONTEXT_LIMIT_BYTES) {
36
+ usageError('context-too-large', 'decision ask context exceeds 32 KiB');
37
+ }
38
+ const threshold = input.threshold ?? 0.8;
39
+ if (!Number.isFinite(threshold) || threshold < 0 || threshold > 1) {
40
+ usageError('invalid-threshold', '--threshold must be a probability from 0 to 1');
41
+ }
42
+ const timeoutMs = input.timeoutMs ?? 15_000;
43
+ if (!Number.isInteger(timeoutMs) || timeoutMs <= 0)
44
+ usageError('invalid-timeout', '--timeout-ms must be a positive integer');
45
+ if (input.mode.kind === 'choices') {
46
+ if (input.mode.choices.length < 2 || input.mode.choices.length > 255)
47
+ usageError('invalid-choices', '--choices requires 2..255 ids');
48
+ const seen = new Set();
49
+ for (const { id, description } of input.mode.choices.map(parseChoice)) {
50
+ if (!/^[A-Za-z0-9_.-]{1,64}$/.test(id))
51
+ usageError('invalid-choices', 'choice ids must match [A-Za-z0-9_.-]{1,64}');
52
+ if (description !== undefined && (!description || description.length > 200)) {
53
+ usageError('invalid-choices', 'choice descriptions after = must be 1..200 characters');
54
+ }
55
+ if (seen.has(id))
56
+ usageError('invalid-choices', 'choice ids must not contain duplicates');
57
+ seen.add(id);
58
+ }
59
+ }
60
+ if (input.mode.kind === 'scale') {
61
+ const count = input.mode.high - input.mode.low + 1;
62
+ if (!Number.isInteger(input.mode.low) || !Number.isInteger(input.mode.high) || input.mode.low > input.mode.high || count < 2 || count > 10) {
63
+ usageError('invalid-scale', '--scale must be an integer range like 1-5 with at most 10 levels');
64
+ }
65
+ }
66
+ }
67
+ function usage(usageValue) {
68
+ if (!usageValue)
69
+ return null;
70
+ return { inputTokens: usageValue.inputTokens, outputTokens: usageValue.outputTokens };
71
+ }
72
+ function fallback(reason, latencyMs, attempt) {
73
+ return {
74
+ schema: 'aiwg-decision-ask/v1',
75
+ status: 'fallback',
76
+ answer: null,
77
+ confidence: null,
78
+ fallback: 'llm',
79
+ reason,
80
+ model: attempt?.actualModel ?? null,
81
+ usage: usage(attempt?.usage),
82
+ latencyMs,
83
+ };
84
+ }
85
+ /** A choice is `id` or `id=description`; the description tells Jev what the option means. */
86
+ function parseChoice(entry) {
87
+ const separator = entry.indexOf('=');
88
+ if (separator < 0)
89
+ return { id: entry.trim() };
90
+ return { id: entry.slice(0, separator).trim(), description: entry.slice(separator + 1).trim() };
91
+ }
92
+ function scaleLevel(value, low, high) {
93
+ const edge = value === low ? ' (lowest)' : value === high ? ' (highest)' : '';
94
+ return `Level ${value} on a ${low} to ${high} scale${edge}.`;
95
+ }
96
+ function answerKind(mode) {
97
+ if (mode.kind === 'yes-no')
98
+ return {
99
+ kind: 'truth-probability',
100
+ trueDescription: 'Yes.',
101
+ falseDescription: 'No.',
102
+ };
103
+ if (mode.kind === 'choices')
104
+ return {
105
+ kind: 'choice',
106
+ options: mode.choices.map(parseChoice).map(({ id, description }) => ({ id, description: description ?? id })),
107
+ };
108
+ return {
109
+ kind: 'ordinal-score',
110
+ levels: Array.from({ length: mode.high - mode.low + 1 }, (_, index) => scaleLevel(mode.low + index, mode.low, mode.high)),
111
+ };
112
+ }
113
+ function definition(input) {
114
+ return {
115
+ apiVersion: 'decision.aiwg.io/v1alpha2',
116
+ kind: 'DecisionDefinition',
117
+ metadata: { id: 'aiwg.ask.answer', version: '1.0.0', description: 'Bounded advisory ask decision' },
118
+ spec: {
119
+ purpose: PURPOSE,
120
+ inputSchema: INPUT_SCHEMA,
121
+ question: input.question,
122
+ answer: answerKind(input.mode),
123
+ requiredCapabilities: [input.mode.kind === 'yes-no' ? 'truth-probability' : input.mode.kind === 'choices' ? 'choice' : 'ordinal-score'],
124
+ },
125
+ };
126
+ }
127
+ function ruleset(definitionValue) {
128
+ return {
129
+ apiVersion: 'decision.aiwg.io/v1alpha2',
130
+ kind: 'DecisionRuleset',
131
+ metadata: { id: 'aiwg.ask.ruleset', version: '1.0.0', description: 'Bounded advisory ask ruleset' },
132
+ spec: {
133
+ purpose: PURPOSE,
134
+ inputSchema: INPUT_SCHEMA,
135
+ evaluations: [{ alias: 'answer', decision: artifactPin(definitionValue), inputPointer: '' }],
136
+ rules: [{
137
+ id: 'answered',
138
+ priority: 1,
139
+ when: { op: 'exists', left: { source: 'decision', alias: 'answer', pointer: '/value' } },
140
+ outcome: 'answered',
141
+ }],
142
+ composition: 'first-match',
143
+ conflict: 'review',
144
+ defaultOutcome: 'fallback',
145
+ failureOutcome: 'fallback',
146
+ outputSchema: { enum: ['answered', 'fallback'] },
147
+ },
148
+ };
149
+ }
150
+ function binding(rulesetValue, primitive, timeoutMs) {
151
+ const target = {
152
+ adapter: 'jev',
153
+ adapterVersion: '1.0.0',
154
+ model: MODEL,
155
+ requiredCapabilities: [primitive],
156
+ acceptance: { mode: 'typed-value' },
157
+ timeoutMs,
158
+ retry: { maxRetries: 0, initialDelayMs: 250, maxDelayMs: 2_000 },
159
+ credentialRef: JEV_CREDENTIAL_REF,
160
+ };
161
+ return {
162
+ apiVersion: 'decision.aiwg.io/v1alpha2',
163
+ kind: 'DecisionBinding',
164
+ metadata: { id: 'aiwg.ask.binding', version: '1.0.0', description: 'Bounded advisory ask Jev binding' },
165
+ spec: {
166
+ ruleset: artifactPin(rulesetValue),
167
+ totalTimeoutMs: timeoutMs,
168
+ maxAttempts: 1,
169
+ concurrency: 1,
170
+ evaluations: { answer: { targets: [target], fallbackOn: [] } },
171
+ },
172
+ };
173
+ }
174
+ function projectionPolicy(region, endpoint) {
175
+ const origin = new URL(endpoint ?? JEV_ENDPOINT).origin;
176
+ return {
177
+ version: '1.0.0',
178
+ provider: 'jev',
179
+ model: MODEL,
180
+ origin,
181
+ region,
182
+ purpose: PURPOSE,
183
+ allowIncompleteContext: false,
184
+ fields: [{
185
+ pointer: '/message',
186
+ output: 'message',
187
+ source: 'operator',
188
+ subject: 'aiwg-decision-ask',
189
+ trust: 'untrusted',
190
+ sensitivity: 'internal',
191
+ purpose: PURPOSE,
192
+ retentionClass: 'ephemeral',
193
+ accessScopes: ['decision-runtime'],
194
+ exportPolicy: 'sanitized',
195
+ deletionPolicy: 'erase',
196
+ backupPolicy: 'not-persisted',
197
+ allowedProviders: ['jev'],
198
+ allowedModels: [MODEL],
199
+ allowedOrigins: [origin],
200
+ allowedRegions: [region],
201
+ }],
202
+ };
203
+ }
204
+ function contextMessage(input) {
205
+ return input.context === undefined || input.context === ''
206
+ ? input.question
207
+ : `${input.question}\n\nContext:\n${input.context}`;
208
+ }
209
+ function confidenceFor(mode, evaluation) {
210
+ const uncertainty = evaluation.spec.uncertainty;
211
+ const value = evaluation.spec.value;
212
+ if (mode.kind === 'yes-no' && typeof value === 'number')
213
+ return Math.max(value, 1 - value);
214
+ if (uncertainty?.distribution && mode.kind === 'choices' && typeof value === 'string')
215
+ return uncertainty.distribution[value] ?? uncertainty.confidence;
216
+ if (uncertainty?.distribution && mode.kind === 'scale')
217
+ return Math.max(...Object.values(uncertainty.distribution));
218
+ return uncertainty?.confidence ?? null;
219
+ }
220
+ function mostLikelyIndex(distribution) {
221
+ if (!distribution)
222
+ return null;
223
+ let best = null;
224
+ for (const [index, probability] of Object.entries(distribution)) {
225
+ if (best === null || probability > (distribution[String(best)] ?? -1))
226
+ best = Number(index);
227
+ }
228
+ return best;
229
+ }
230
+ function publicAnswer(mode, value, distribution) {
231
+ if (mode.kind === 'yes-no') {
232
+ const probability = typeof value === 'number' ? value : null;
233
+ return probability === null ? { answer: null } : { answer: probability >= 0.5, probability };
234
+ }
235
+ if (mode.kind === 'scale') {
236
+ // Jev returns the distribution-weighted mean index; report the most likely level instead.
237
+ const index = mostLikelyIndex(distribution) ?? (typeof value === 'number' ? Math.round(value) : null);
238
+ return index === null ? { answer: null } : { answer: mode.low + index };
239
+ }
240
+ return typeof value === 'string' ? { answer: value } : { answer: null };
241
+ }
242
+ export async function askDecision(input, options = {}) {
243
+ validateDecisionAskInput(input);
244
+ const started = options.now?.() ?? Date.now();
245
+ let credential;
246
+ try {
247
+ credential = await lookupJevCredential(options);
248
+ }
249
+ catch {
250
+ return fallback('not-configured', 0);
251
+ }
252
+ if (!credential?.enabled || !credential.token)
253
+ return fallback('not-configured', 0);
254
+ const timeoutMs = input.timeoutMs ?? 15_000;
255
+ const def = definition(input);
256
+ const rules = ruleset(def);
257
+ const bind = binding(rules, def.spec.answer.kind, timeoutMs);
258
+ const adapter = options.adapter ?? options.createAdapter?.({ region: credential.region, endpoint: credential.endpoint })
259
+ ?? new JevDecisionAdapter({ region: credential.region, ...(credential.endpoint ? {
260
+ endpoint: credential.endpoint,
261
+ allowedOrigins: [new URL(credential.endpoint).origin],
262
+ } : {}) });
263
+ let result;
264
+ try {
265
+ result = await (options.evaluate ?? evaluateDecisionRuleset)({
266
+ ruleset: rules,
267
+ binding: bind,
268
+ definitions: { [def.metadata.id]: def },
269
+ input: { message: contextMessage(input) },
270
+ runId: options.runId ?? 'aiwg-decision-ask',
271
+ invocationId: options.invocationId ?? `ask-${Date.now()}`,
272
+ adapters: { jev: adapter },
273
+ resolveCredential: options.resolveCredential ?? (logicalRef => resolveJevCredentialBytes(logicalRef, options)),
274
+ projection: { resolve: () => projectionPolicy(credential.region, credential.endpoint) },
275
+ });
276
+ }
277
+ catch {
278
+ return fallback('jev-error', Math.max(0, (options.now?.() ?? Date.now()) - started));
279
+ }
280
+ const latencyMs = Math.max(0, (options.now?.() ?? Date.now()) - started);
281
+ const evaluation = result.spec.evaluations.answer;
282
+ const attempt = evaluation?.spec.attempts.at(-1);
283
+ if (!evaluation || evaluation.spec.status !== 'success')
284
+ return fallback(evaluation?.spec.reason ?? result.spec.reason, latencyMs, attempt);
285
+ const confidence = confidenceFor(input.mode, evaluation);
286
+ const threshold = input.threshold ?? 0.8;
287
+ if (confidence === null || confidence < threshold)
288
+ return fallback('low-confidence', latencyMs, attempt);
289
+ const answer = publicAnswer(input.mode, evaluation.spec.value, evaluation.spec.uncertainty?.distribution);
290
+ return {
291
+ schema: 'aiwg-decision-ask/v1',
292
+ status: 'answered',
293
+ answer: answer.answer,
294
+ confidence,
295
+ ...(answer.probability === undefined ? {} : { probability: answer.probability }),
296
+ fallback: null,
297
+ reason: 'none',
298
+ model: attempt?.actualModel ?? null,
299
+ usage: usage(attempt?.usage),
300
+ latencyMs,
301
+ };
302
+ }
303
+ export async function readDecisionContextFile(filePath) {
304
+ const content = await readFile(filePath, 'utf8');
305
+ if (byteLength(content) > CONTEXT_LIMIT_BYTES)
306
+ usageError('context-too-large', 'decision ask context exceeds 32 KiB');
307
+ return content;
308
+ }
309
+ export async function setupJev(input = {}, options = {}) {
310
+ const region = input.region?.trim() || JEV_DEFAULT_REGION;
311
+ if (input.remove) {
312
+ await removeJevCredentialFile(options);
313
+ return {
314
+ schema: 'aiwg-decision-jev-setup/v1',
315
+ configured: false,
316
+ credentialSource: 'file',
317
+ region,
318
+ next: ['Run aiwg decision setup jev --token-stdin to configure Jev again.'],
319
+ };
320
+ }
321
+ const environment = options.env ?? process.env;
322
+ const envToken = environment.JEV_API_KEY?.trim() || environment.AIWG_DECISION_JEV_API_KEY?.trim();
323
+ const token = input.token?.trim() || envToken;
324
+ if (!token)
325
+ usageError('token-required', 'Provide --token-stdin or set JEV_API_KEY or AIWG_DECISION_JEV_API_KEY.');
326
+ await writeJevCredentialFile({
327
+ token,
328
+ region,
329
+ ...(input.endpoint?.trim() ? { endpoint: input.endpoint.trim() } : {}),
330
+ enabled: true,
331
+ }, options);
332
+ const result = {
333
+ schema: 'aiwg-decision-jev-setup/v1',
334
+ configured: true,
335
+ credentialSource: input.token?.trim() ? 'file' : 'env',
336
+ region,
337
+ next: ['Run aiwg decision ask --question "<q>" --yes-no --json.'],
338
+ };
339
+ if (input.verify) {
340
+ const verified = await askDecision({
341
+ question: 'Is this Jev decision setup verification request well formed?',
342
+ mode: { kind: 'yes-no' },
343
+ threshold: 0,
344
+ timeoutMs: 15_000,
345
+ }, options);
346
+ result.verified = verified.status === 'answered';
347
+ result.model = verified.model;
348
+ result.usage = verified.usage;
349
+ }
350
+ return result;
351
+ }
352
+ //# sourceMappingURL=ask.js.map
@@ -88,6 +88,11 @@ export class CalibrationRegistry {
88
88
  const rollback = history.find(event => event.revision === eligibility.rollbackTarget.aliasRevision);
89
89
  if (!rollback || rollback.actualIdentityDigest !== eligibility.rollbackTarget.identityDigest)
90
90
  throw new CalibrationRegistryError('rollback target no longer matches immutable alias history');
91
+ const current = history.at(-1);
92
+ if (current !== rollback || current.kind === 'retired')
93
+ throw new CalibrationRegistryError('alias no longer holds the champion this promotion was approved to replace');
94
+ if (history.some(event => event.promotionEligibilityId === eligibility.id))
95
+ throw new CalibrationRegistryError(`promotion eligibility '${eligibility.id}' was already used`);
91
96
  const event = immutable({ revision: history.length + 1, alias: eligibility.alias,
92
97
  actualIdentityDigest: eligibility.candidateIdentityDigest, actualModel: eligibility.candidateActualModel, recordedAt: new Date(validDate(at)).toISOString(),
93
98
  kind: 'promoted', promotionEligibilityId: eligibility.id });
@@ -172,6 +177,7 @@ export class CalibrationRegistry {
172
177
  aliasHistory(alias) { return structuredClone(this.aliases.get(alias) ?? []); }
173
178
  driftEvents(alias) { return structuredClone(alias ? this.drift.filter(event => event.alias === alias) : this.drift); }
174
179
  promotionHistory() { return structuredClone([...this.eligibility.values()]); }
180
+ promotionEligibility(id) { const record = this.eligibility.get(id); return record ? structuredClone(record) : null; }
175
181
  artifactHistory() { return structuredClone([...this.artifacts.values()]); }
176
182
  compatibilityHistory() { return structuredClone(this.relations); }
177
183
  }
@@ -190,6 +196,9 @@ function calibrationUsability(artifact, at) {
190
196
  reasons.push('calibration-bound-exceeded');
191
197
  if (artifact.metrics.selectiveRisk > artifact.profile.maximumSelectiveRisk)
192
198
  reasons.push('selective-risk-bound-exceeded');
199
+ // An artifact is unusable before it takes effect: a back-dated request cannot borrow a later calibration.
200
+ if (validDate(at) < validDate(artifact.effectiveAt))
201
+ reasons.push('calibration-not-yet-effective');
193
202
  if (validDate(at) >= validDate(artifact.effectiveAt) + artifact.profile.expiresAfterDays * 86_400_000)
194
203
  reasons.push('calibration-expired');
195
204
  return reasons;
@@ -1,7 +1,16 @@
1
1
  import { evaluatePredicate } from './predicates.js';
2
2
  import { validateAgainstSchema } from './validate.js';
3
3
  export function composeRuleset(ruleset, input, evaluations) {
4
- const failed = Object.values(evaluations).some(result => result.spec.status !== 'success');
4
+ const referencedAliases = referencedDecisionAliases(ruleset);
5
+ const requiredEvaluations = new Map(ruleset.spec.evaluations
6
+ .filter(evaluation => referencedAliases.includes(evaluation.alias))
7
+ .map(evaluation => [evaluation.alias, evaluation]));
8
+ const suppliedFailed = Object.values(evaluations).some(result => !acceptedEvaluation(result));
9
+ const requiredFailed = [...requiredEvaluations].some(([alias, evaluation]) => {
10
+ const result = evaluations[alias];
11
+ return !result || result.spec.alias !== alias || !acceptedEvaluation(result) || !samePin(result.spec.decision, evaluation.decision);
12
+ });
13
+ const failed = suppliedFailed || requiredFailed;
5
14
  if (failed) {
6
15
  validateAgainstSchema(ruleset.spec.outputSchema, ruleset.spec.failureOutcome, 'failureOutcome');
7
16
  return {
@@ -18,31 +27,81 @@ export function composeRuleset(ruleset, input, evaluations) {
18
27
  validateAgainstSchema(ruleset.spec.outputSchema, ruleset.spec.defaultOutcome, 'defaultOutcome');
19
28
  return { status: 'defaulted', reason: 'no-match', outcome: structuredClone(ruleset.spec.defaultOutcome), matchedRules: [] };
20
29
  }
21
- if (ruleset.spec.composition === 'collect') {
22
- const outcome = matches.map(rule => structuredClone(rule.outcome));
30
+ if (ruleset.spec.composition === 'collect' || ruleset.spec.composition.startsWith('collect-')) {
31
+ const collected = matches.map(rule => structuredClone(rule.outcome));
32
+ const outcome = ruleset.spec.composition === 'collect' ? collected : aggregateCollect(collected, ruleset.spec.composition);
23
33
  validateAgainstSchema(ruleset.spec.outputSchema, outcome, 'collected outcome');
24
34
  return { status: 'completed', reason: 'none', outcome, matchedRules: matches.map(rule => rule.id) };
25
35
  }
36
+ if (ruleset.spec.composition === 'unique' && matches.length > 1)
37
+ return conflictResult(ruleset, matches);
38
+ if (ruleset.spec.composition === 'any') {
39
+ const first = sortJson(matches[0].outcome);
40
+ if (matches.some(rule => JSON.stringify(sortJson(rule.outcome)) !== JSON.stringify(first)))
41
+ return conflictResult(ruleset, matches);
42
+ const outcome = structuredClone(matches[0].outcome);
43
+ validateAgainstSchema(ruleset.spec.outputSchema, outcome, 'matched outcome');
44
+ return { status: 'completed', reason: 'none', outcome, matchedRules: matches.map(rule => rule.id) };
45
+ }
26
46
  const top = matches.filter(rule => rule.priority === matches[0].priority);
27
47
  const unique = new Map();
28
48
  for (const rule of top)
29
49
  unique.set(JSON.stringify(sortJson(rule.outcome)), rule.outcome);
30
- if (unique.size > 1) {
31
- if (ruleset.spec.conflict === 'review') {
32
- validateAgainstSchema(ruleset.spec.outputSchema, ruleset.spec.failureOutcome, 'failureOutcome');
33
- return {
34
- status: 'review',
35
- reason: 'conflicting-outcomes',
36
- outcome: structuredClone(ruleset.spec.failureOutcome),
37
- matchedRules: top.map(rule => rule.id),
38
- };
39
- }
40
- return { status: 'error', reason: 'conflicting-outcomes', matchedRules: top.map(rule => rule.id) };
41
- }
50
+ if (unique.size > 1)
51
+ return conflictResult(ruleset, top);
42
52
  const outcome = structuredClone(top[0].outcome);
43
53
  validateAgainstSchema(ruleset.spec.outputSchema, outcome, 'matched outcome');
44
54
  return { status: 'completed', reason: 'none', outcome, matchedRules: top.map(rule => rule.id) };
45
55
  }
56
+ function acceptedEvaluation(result) {
57
+ return result.spec.status === 'success'
58
+ && (result.spec.acceptance === undefined || result.spec.acceptance.disposition === 'act');
59
+ }
60
+ function samePin(left, right) {
61
+ return left.id === right.id && left.version === right.version && left.digest === right.digest;
62
+ }
63
+ function referencedDecisionAliases(ruleset) {
64
+ const aliases = new Set();
65
+ const visit = (predicate) => {
66
+ if ('all' in predicate)
67
+ predicate.all.forEach(visit);
68
+ else if ('any' in predicate)
69
+ predicate.any.forEach(visit);
70
+ else if ('not' in predicate)
71
+ visit(predicate.not);
72
+ else if (predicate.left.source === 'decision' && predicate.left.alias)
73
+ aliases.add(predicate.left.alias);
74
+ };
75
+ ruleset.spec.rules.forEach(rule => visit(rule.when));
76
+ return [...aliases].sort();
77
+ }
78
+ function conflictResult(ruleset, matches) {
79
+ if (ruleset.spec.conflict === 'review') {
80
+ validateAgainstSchema(ruleset.spec.outputSchema, ruleset.spec.failureOutcome, 'failureOutcome');
81
+ return {
82
+ status: 'review',
83
+ reason: 'conflicting-outcomes',
84
+ outcome: structuredClone(ruleset.spec.failureOutcome),
85
+ matchedRules: matches.map(rule => rule.id),
86
+ };
87
+ }
88
+ return { status: 'error', reason: 'conflicting-outcomes', matchedRules: matches.map(rule => rule.id) };
89
+ }
90
+ function aggregateCollect(values, composition) {
91
+ if (composition === 'collect-count')
92
+ return values.length;
93
+ if (!values.every(value => typeof value === 'number' && Number.isFinite(value))) {
94
+ throw new Error(`${composition} requires finite numeric outcomes`);
95
+ }
96
+ const numbers = values;
97
+ if (composition === 'collect-sum')
98
+ return numbers.reduce((sum, value) => sum + value, 0);
99
+ if (composition === 'collect-min')
100
+ return Math.min(...numbers);
101
+ if (composition === 'collect-max')
102
+ return Math.max(...numbers);
103
+ return values;
104
+ }
46
105
  function sortJson(value) {
47
106
  if (Array.isArray(value))
48
107
  return value.map(sortJson);