@aiwg/cli 2026.9.19 → 2026.9.21

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 (272) hide show
  1. package/agentic/code/providers/capability-matrix.yaml +58 -6
  2. package/agentic/code/providers/model-capabilities.v1.json +34 -0
  3. package/agentic/code/providers/model-catalog.v1.json +55 -13
  4. package/bin/aiwg.mjs +2 -0
  5. package/dist/src/api/index.d.ts +1 -0
  6. package/dist/src/api/index.js +1 -0
  7. package/dist/src/artifacts/enrichment/cli.js +1 -1
  8. package/dist/src/artifacts/views/cli.js +1 -1
  9. package/dist/src/audit/operator-decision.js +10 -0
  10. package/dist/src/auth/credential-store.js +127 -27
  11. package/dist/src/catalog/builtin-models.json +272 -40
  12. package/dist/src/catalog/cli.js +1 -1
  13. package/dist/src/catalog/cli.mjs +1 -1
  14. package/dist/src/cli/handlers/effect.js +872 -0
  15. package/dist/src/cli/handlers/help.js +2 -0
  16. package/dist/src/cli/handlers/index.js +4 -2
  17. package/dist/src/cli/handlers/models.js +41 -10
  18. package/dist/src/cli/handlers/ralph.js +1 -1
  19. package/dist/src/cli/handlers/sessions.js +113 -37
  20. package/dist/src/cli/handlers/subcommands.js +31 -0
  21. package/dist/src/cli/handlers/use.js +46 -9
  22. package/dist/src/cli/handlers/utilities.js +10 -0
  23. package/dist/src/cli/handlers/workspace.js +3 -1
  24. package/dist/src/cli/scope-resolver.js +112 -9
  25. package/dist/src/cli/services/deployment-verification.js +5 -0
  26. package/dist/src/config/aiwg-config.js +3 -0
  27. package/dist/src/decision/acceptance-rollout.js +93 -0
  28. package/dist/src/decision/acceptance.js +175 -0
  29. package/dist/src/decision/adapters/jev.js +234 -13
  30. package/dist/src/decision/adapters/llm-subagent.js +60 -12
  31. package/dist/src/decision/admission.js +550 -0
  32. package/dist/src/decision/batch-benchmark.js +47 -0
  33. package/dist/src/decision/batch-receipts/accounting.js +75 -0
  34. package/dist/src/decision/batch-receipts/export.js +40 -0
  35. package/dist/src/decision/batch-receipts/index.js +9 -0
  36. package/dist/src/decision/batch-receipts/protection.js +40 -0
  37. package/dist/src/decision/batch-receipts/receipt.js +44 -0
  38. package/dist/src/decision/batch-receipts/result-store.js +341 -0
  39. package/dist/src/decision/batch-receipts/store.js +362 -0
  40. package/dist/src/decision/batch-receipts/types.js +2 -0
  41. package/dist/src/decision/batch-receipts/validate.js +127 -0
  42. package/dist/src/decision/batch.js +68 -0
  43. package/dist/src/decision/calibration/index.js +4 -0
  44. package/dist/src/decision/calibration/receipts.js +130 -0
  45. package/dist/src/decision/calibration/registry.js +258 -0
  46. package/dist/src/decision/calibration/types.js +2 -0
  47. package/dist/src/decision/compile-cache/benchmark.js +62 -0
  48. package/dist/src/decision/compile-cache/compatibility.js +65 -0
  49. package/dist/src/decision/compile-cache/file-store.js +280 -0
  50. package/dist/src/decision/compile-cache/identity.js +26 -0
  51. package/dist/src/decision/compile-cache/index.js +9 -0
  52. package/dist/src/decision/compile-cache/prefix.js +21 -0
  53. package/dist/src/decision/compile-cache/records.js +86 -0
  54. package/dist/src/decision/compile-cache/runtime.js +68 -0
  55. package/dist/src/decision/compile-cache/store.js +108 -0
  56. package/dist/src/decision/compile-cache/types.js +2 -0
  57. package/dist/src/decision/context-plan.js +234 -0
  58. package/dist/src/decision/context-qualification.js +60 -0
  59. package/dist/src/decision/ensemble/aggregate.js +215 -0
  60. package/dist/src/decision/ensemble/contract.js +316 -0
  61. package/dist/src/decision/ensemble/drift.js +51 -0
  62. package/dist/src/decision/ensemble/index.js +6 -0
  63. package/dist/src/decision/ensemble/report.js +71 -0
  64. package/dist/src/decision/ensemble/types.js +2 -0
  65. package/dist/src/decision/evaluate.js +1024 -78
  66. package/dist/src/decision/file-lifecycle-store.js +168 -0
  67. package/dist/src/decision/graph-beam.js +40 -0
  68. package/dist/src/decision/graph-budget.js +110 -0
  69. package/dist/src/decision/graph-decision-bridge.js +93 -0
  70. package/dist/src/decision/graph-evidence.js +123 -0
  71. package/dist/src/decision/graph-flow-adapter.js +56 -0
  72. package/dist/src/decision/graph-flow.js +105 -0
  73. package/dist/src/decision/graph-parallel.js +79 -0
  74. package/dist/src/decision/graph-patterns.js +20 -0
  75. package/dist/src/decision/graph-run-store.js +66 -0
  76. package/dist/src/decision/graph-run.js +166 -0
  77. package/dist/src/decision/graph-skill-bridge.js +91 -0
  78. package/dist/src/decision/graph-templates.js +56 -0
  79. package/dist/src/decision/graph.js +115 -0
  80. package/dist/src/decision/index.js +35 -0
  81. package/dist/src/decision/job-accounting.js +93 -0
  82. package/dist/src/decision/job-contract.js +186 -0
  83. package/dist/src/decision/job-effects.js +95 -0
  84. package/dist/src/decision/job-evaluate.js +80 -0
  85. package/dist/src/decision/job-gateway.js +121 -0
  86. package/dist/src/decision/job-http.js +194 -0
  87. package/dist/src/decision/job-payload-store.js +258 -0
  88. package/dist/src/decision/job-poll-limiter.js +92 -0
  89. package/dist/src/decision/job-quota.js +158 -0
  90. package/dist/src/decision/job-runtime.js +240 -0
  91. package/dist/src/decision/job-scheduler.js +26 -0
  92. package/dist/src/decision/job-service.js +117 -0
  93. package/dist/src/decision/job-store.js +264 -0
  94. package/dist/src/decision/job-worker.js +141 -0
  95. package/dist/src/decision/lifecycle.js +148 -0
  96. package/dist/src/decision/patterns/artifacts.js +173 -0
  97. package/dist/src/decision/patterns/catalog.js +108 -0
  98. package/dist/src/decision/patterns/durable-review.js +237 -0
  99. package/dist/src/decision/patterns/index.js +8 -0
  100. package/dist/src/decision/patterns/playground.js +401 -0
  101. package/dist/src/decision/patterns/recorded.js +43 -0
  102. package/dist/src/decision/patterns/specs.js +174 -0
  103. package/dist/src/decision/patterns/types.js +2 -0
  104. package/dist/src/decision/portable-secrets.js +53 -0
  105. package/dist/src/decision/projection.js +210 -0
  106. package/dist/src/decision/qualification/capture.js +67 -0
  107. package/dist/src/decision/qualification/drift.js +68 -0
  108. package/dist/src/decision/qualification/gate-evidence.js +172 -0
  109. package/dist/src/decision/qualification/gates.js +83 -0
  110. package/dist/src/decision/qualification/index.js +12 -0
  111. package/dist/src/decision/qualification/load.js +275 -0
  112. package/dist/src/decision/qualification/manifest.js +37 -0
  113. package/dist/src/decision/qualification/privacy.js +41 -0
  114. package/dist/src/decision/qualification/quality.js +213 -0
  115. package/dist/src/decision/qualification/release.js +116 -0
  116. package/dist/src/decision/qualification/runner.js +408 -0
  117. package/dist/src/decision/qualification/types.js +2 -0
  118. package/dist/src/decision/receipts.js +30 -7
  119. package/dist/src/decision/result-cache/index.js +6 -0
  120. package/dist/src/decision/result-cache/integrity.js +28 -0
  121. package/dist/src/decision/result-cache/key.js +28 -0
  122. package/dist/src/decision/result-cache/service.js +92 -0
  123. package/dist/src/decision/result-cache/store.js +327 -0
  124. package/dist/src/decision/result-cache/types.js +3 -0
  125. package/dist/src/decision/review/authorization.js +110 -0
  126. package/dist/src/decision/review/effect-ledger-adapter.js +321 -0
  127. package/dist/src/decision/review/index.js +12 -0
  128. package/dist/src/decision/review/migrate.js +34 -0
  129. package/dist/src/decision/review/operator-audit.js +59 -0
  130. package/dist/src/decision/review/recovery-adapters.js +252 -0
  131. package/dist/src/decision/review/recovery.js +33 -0
  132. package/dist/src/decision/review/service.js +498 -0
  133. package/dist/src/decision/review/store.js +219 -0
  134. package/dist/src/decision/review/types.js +2 -0
  135. package/dist/src/decision/review/validate.js +205 -0
  136. package/dist/src/decision/scheduler.js +115 -0
  137. package/dist/src/decision/telemetry/context.js +42 -0
  138. package/dist/src/decision/telemetry/debug-sidecar.js +152 -0
  139. package/dist/src/decision/telemetry/exporter.js +77 -0
  140. package/dist/src/decision/telemetry/file-debug-backend.js +112 -0
  141. package/dist/src/decision/telemetry/index.js +13 -0
  142. package/dist/src/decision/telemetry/mapping.js +146 -0
  143. package/dist/src/decision/telemetry/metrics.js +164 -0
  144. package/dist/src/decision/telemetry/otlp-http.js +183 -0
  145. package/dist/src/decision/telemetry/redaction.js +93 -0
  146. package/dist/src/decision/telemetry/retention.js +109 -0
  147. package/dist/src/decision/telemetry/runtime.js +310 -0
  148. package/dist/src/decision/telemetry/trace.js +158 -0
  149. package/dist/src/decision/telemetry/types.js +3 -0
  150. package/dist/src/decision/validate.js +135 -5
  151. package/dist/src/effects/checkpoint-sinks.js +94 -0
  152. package/dist/src/effects/checkpoint.js +76 -0
  153. package/dist/src/effects/decision-links.js +54 -0
  154. package/dist/src/effects/errors.js +48 -0
  155. package/dist/src/effects/identity.js +178 -0
  156. package/dist/src/effects/index.js +25 -0
  157. package/dist/src/effects/keyring.js +160 -0
  158. package/dist/src/effects/keys.js +130 -0
  159. package/dist/src/effects/ledger.js +435 -0
  160. package/dist/src/effects/lifecycle.js +110 -0
  161. package/dist/src/effects/lock-recovery.js +269 -0
  162. package/dist/src/effects/reader.js +103 -0
  163. package/dist/src/effects/records.js +114 -0
  164. package/dist/src/effects/redaction.js +51 -0
  165. package/dist/src/effects/schema.js +73 -0
  166. package/dist/src/effects/store.js +239 -0
  167. package/dist/src/effects/types.js +49 -0
  168. package/dist/src/effects/verifiers/decision.js +168 -0
  169. package/dist/src/effects/verifiers/file.js +106 -0
  170. package/dist/src/effects/verifiers/git.js +206 -0
  171. package/dist/src/effects/verifiers/index.js +90 -0
  172. package/dist/src/effects/verifiers/registry.js +148 -0
  173. package/dist/src/effects/verifiers/review.js +123 -0
  174. package/dist/src/effects/verifiers/tracker.js +299 -0
  175. package/dist/src/effects/verifiers/types.js +21 -0
  176. package/dist/src/effects/verify.js +295 -0
  177. package/dist/src/evidence/bundle.js +2 -0
  178. package/dist/src/extensions/commands/definitions.js +28 -0
  179. package/dist/src/extensions/manifest.js +1 -0
  180. package/dist/src/marketplace/provenance.js +25 -27
  181. package/dist/src/mcp/cli.mjs +11 -1
  182. package/dist/src/mcp/grok-build-config.mjs +189 -0
  183. package/dist/src/mcp/registry.js +21 -1
  184. package/dist/src/mcp/registry.mjs +20 -1
  185. package/dist/src/models/grok-build-models.js +356 -0
  186. package/dist/src/models/index.js +1 -0
  187. package/dist/src/models/model-capabilities.v1.json +34 -0
  188. package/dist/src/models/model-catalog.v1.json +55 -13
  189. package/dist/src/models/model-discovery.js +41 -5
  190. package/dist/src/models/provider-policy.js +3 -2
  191. package/dist/src/plugin/skill-command-translator.js +1 -0
  192. package/dist/src/providers/capability-matrix.yaml +58 -6
  193. package/dist/src/providers/grok-build-acp.js +192 -0
  194. package/dist/src/providers/grok-build-dispatch.js +251 -0
  195. package/dist/src/providers/grok-build-headless.js +170 -0
  196. package/dist/src/providers/muse-paths.js +105 -0
  197. package/dist/src/providers/provider-definitions.js +93 -7
  198. package/dist/src/providers/provider-definitions.mjs +21 -1
  199. package/dist/src/providers/provider-inventory.js +3 -0
  200. package/dist/src/security/artifact-attestation.js +4 -8
  201. package/dist/src/security/signing.js +119 -0
  202. package/dist/src/sessions/adapters/grok-build.js +187 -0
  203. package/dist/src/sessions/adapters/muse.js +412 -0
  204. package/dist/src/sessions/contracts.js +1 -1
  205. package/dist/src/sessions/grok-build-cli.js +196 -0
  206. package/dist/src/sessions/index.js +3 -0
  207. package/dist/src/sessions/workspace-discovery.js +9 -5
  208. package/dist/src/skills/deployer.js +33 -5
  209. package/dist/src/smiths/context-pipeline/claude-hook.js +2 -0
  210. package/dist/src/smiths/context-pipeline/context-file-safety.js +18 -0
  211. package/dist/src/smiths/context-pipeline/finalization.js +1 -32
  212. package/dist/src/smiths/context-pipeline/generator.js +12 -0
  213. package/dist/src/smiths/context-pipeline/index.js +1 -1
  214. package/dist/src/smiths/context-pipeline/managed-hook.js +2 -0
  215. package/dist/src/smiths/context-pipeline/muse-bridge.js +50 -0
  216. package/dist/src/smiths/context-pipeline/provider-policy.js +5 -0
  217. package/dist/src/smiths/context-pipeline/workspace-context.js +22 -16
  218. package/dist/src/smiths/hook-bridge/grok-build-translator.js +64 -0
  219. package/dist/src/smiths/hook-bridge/index.js +3 -0
  220. package/dist/src/smiths/hook-bridge/shim.js +2 -0
  221. package/dist/src/smiths/hook-bridge/types.js +5 -0
  222. package/dist/src/smiths/skillsmith/collision-detector.js +1 -0
  223. package/dist/src/storage/protected-files.js +102 -0
  224. package/dist/src/tracker/forge-http.js +371 -0
  225. package/dist/src/tracker/project-inputs.js +41 -0
  226. package/package.json +2 -1
  227. package/schemas/decision/BatchReceipt.v1.schema.json +50 -0
  228. package/schemas/decision/CalibrationArtifact.v1.schema.json +76 -0
  229. package/schemas/decision/CalibrationCompatibility.v1.schema.json +18 -0
  230. package/schemas/decision/CalibrationGovernanceReceipt.v1.schema.json +44 -0
  231. package/schemas/decision/DecisionAdmissionEvidence.v1.schema.json +43 -0
  232. package/schemas/decision/DecisionBinding.schema.json +309 -0
  233. package/schemas/decision/DecisionBinding.v1alpha2.schema.json +369 -0
  234. package/schemas/decision/DecisionCacheTelemetry.v1.schema.json +20 -0
  235. package/schemas/decision/DecisionChampionChallenger.v1.schema.json +100 -0
  236. package/schemas/decision/DecisionCompileCacheEntry.v1.schema.json +35 -0
  237. package/schemas/decision/DecisionCompileCacheIdentity.v1.schema.json +26 -0
  238. package/schemas/decision/DecisionCompileCacheTombstone.v1.schema.json +31 -0
  239. package/schemas/decision/DecisionContextEvidence.v1.schema.json +136 -0
  240. package/schemas/decision/DecisionDefinition.schema.json +159 -0
  241. package/schemas/decision/DecisionDefinition.v1alpha2.schema.json +203 -0
  242. package/schemas/decision/DecisionDriftResponse.v1.schema.json +55 -0
  243. package/schemas/decision/DecisionEnsembleAggregate.v1.schema.json +86 -0
  244. package/schemas/decision/DecisionEnsembleIntegrityReport.v1.schema.json +64 -0
  245. package/schemas/decision/DecisionEnsemblePolicy.v1.schema.json +130 -0
  246. package/schemas/decision/DecisionGraph.v1.schema.json +77 -0
  247. package/schemas/decision/DecisionJob.v1.schema.json +71 -0
  248. package/schemas/decision/DecisionLifecyclePolicy.v1.schema.json +52 -0
  249. package/schemas/decision/DecisionProjectionPolicy.v1.schema.json +65 -0
  250. package/schemas/decision/DecisionProviderPrefixCompatibility.v1.schema.json +45 -0
  251. package/schemas/decision/DecisionProviderPrefixEvidence.v1.schema.json +17 -0
  252. package/schemas/decision/DecisionProviderPrefixIdentity.v1.schema.json +36 -0
  253. package/schemas/decision/DecisionResult.schema.json +524 -0
  254. package/schemas/decision/DecisionResult.v1alpha2.schema.json +619 -0
  255. package/schemas/decision/DecisionResultCacheEntry.v1.schema.json +53 -0
  256. package/schemas/decision/DecisionReview.schema.json +29 -0
  257. package/schemas/decision/DecisionRuleset.schema.json +313 -0
  258. package/schemas/decision/DecisionRuleset.v1alpha2.schema.json +313 -0
  259. package/schemas/decision/DecisionTelemetryTrace.v1.schema.json +95 -0
  260. package/schemas/decision/RulesetResult.schema.json +721 -0
  261. package/schemas/decision/RulesetResult.v1alpha2.schema.json +891 -0
  262. package/schemas/security/pr-evidence-receipt.v1.schema.json +77 -0
  263. package/schemas/security/threat-assessment-report.v1.schema.json +71 -1
  264. package/tools/agents/deploy-agents.mjs +21 -6
  265. package/tools/agents/providers/base.mjs +7 -4
  266. package/tools/agents/providers/codex.mjs +1 -1
  267. package/tools/agents/providers/grok-build.d.mts +7 -0
  268. package/tools/agents/providers/grok-build.mjs +249 -9
  269. package/tools/agents/providers/model-role.mjs +6 -3
  270. package/tools/agents/providers/muse-hooks.mjs +785 -0
  271. package/tools/agents/providers/muse-paths.mjs +112 -0
  272. package/tools/agents/providers/muse.mjs +336 -0
@@ -0,0 +1,234 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { canonicalJson } from '../security/artifact-trust.js';
3
+ export class ContextPlanError extends Error {
4
+ reason;
5
+ details;
6
+ constructor(reason, message, details = {}) {
7
+ super(message);
8
+ this.reason = reason;
9
+ this.details = details;
10
+ this.name = 'ContextPlanError';
11
+ }
12
+ }
13
+ /** Conservative, deterministic fallback; it does not claim vendor-tokenizer equivalence. */
14
+ export class CanonicalJsonByteEstimator {
15
+ version;
16
+ bytesPerToken;
17
+ id;
18
+ constructor(version = '1.0.0', bytesPerToken = 3) {
19
+ this.version = version;
20
+ this.bytesPerToken = bytesPerToken;
21
+ if (!Number.isSafeInteger(bytesPerToken) || bytesPerToken < 1) {
22
+ throw new ContextPlanError('invalid-profile', 'bytesPerToken must be a positive integer');
23
+ }
24
+ // A different byte/token ratio is a different estimator, even if its caller reuses a version.
25
+ this.id = bytesPerToken === 3 ? 'canonical-json-utf8-ceil' : `canonical-json-utf8-ceil-bpt${bytesPerToken}`;
26
+ }
27
+ estimate(value) {
28
+ const serializedBytes = Buffer.byteLength(canonicalJson(value), 'utf8');
29
+ return { serializedBytes, tokens: Math.ceil(serializedBytes / this.bytesPerToken) };
30
+ }
31
+ }
32
+ export function planDecisionContext(input, profile, estimator) {
33
+ validate(input, profile, estimator);
34
+ const effectiveAggregateTokens = effectiveLimit(profile.limits.aggregateTokens, profile.safetyMarginBps);
35
+ const effectiveLongestTokens = effectiveLimit(profile.limits.stateAndLongestQuestionTokens, profile.safetyMarginBps);
36
+ const state = checkedEstimate(estimator, input.authorizedState);
37
+ if (state.tokens + profile.requestEnvelopeTokens > effectiveAggregateTokens
38
+ || state.tokens + profile.requestEnvelopeTokens > effectiveLongestTokens) {
39
+ throw new ContextPlanError('oversized-state', 'authorized state cannot fit the effective provider limits', {
40
+ stateTokens: state.tokens, requestEnvelopeTokens: profile.requestEnvelopeTokens,
41
+ effectiveAggregateTokens, effectiveStateAndLongestQuestionTokens: effectiveLongestTokens,
42
+ });
43
+ }
44
+ const sorted = [...input.questions].sort((left, right) => left.id < right.id ? -1 : left.id > right.id ? 1 : 0);
45
+ const estimates = new Map(sorted.map(question => [question.id, checkedEstimate(estimator, question.entry)]));
46
+ for (const question of sorted) {
47
+ const tokens = estimates.get(question.id).tokens;
48
+ if (state.tokens + tokens + profile.requestEnvelopeTokens > effectiveLongestTokens
49
+ || state.tokens + tokens + profile.requestEnvelopeTokens > effectiveAggregateTokens) {
50
+ throw new ContextPlanError('oversized-question', `question '${question.id}' cannot fit with the authorized state`, {
51
+ questionId: question.id, questionTokens: tokens, stateTokens: state.tokens,
52
+ effectiveAggregateTokens, effectiveStateAndLongestQuestionTokens: effectiveLongestTokens,
53
+ });
54
+ }
55
+ }
56
+ const waves = dependencyWaves(sorted);
57
+ const allQuestionTokens = sorted.reduce((sum, question) => sum + estimates.get(question.id).tokens, 0);
58
+ const longestQuestionTokens = sorted.reduce((max, question) => Math.max(max, estimates.get(question.id).tokens), 0);
59
+ const rawAggregate = state.tokens + allQuestionTokens + profile.requestEnvelopeTokens;
60
+ const rawLongest = state.tokens + longestQuestionTokens + profile.requestEnvelopeTokens;
61
+ const stateDigest = digest(input.authorizedState);
62
+ const partitions = [];
63
+ for (const [wave, waveQuestions] of waves.entries()) {
64
+ const groups = new Map();
65
+ for (const question of waveQuestions) {
66
+ const key = question.compatibilityKey ?? 'default';
67
+ const group = groups.get(key) ?? [];
68
+ group.push(question);
69
+ groups.set(key, group);
70
+ }
71
+ for (const key of [...groups.keys()].sort()) {
72
+ const pending = groups.get(key);
73
+ let current = [];
74
+ let currentTokens = state.tokens + profile.requestEnvelopeTokens;
75
+ const flush = () => {
76
+ if (!current.length)
77
+ return;
78
+ const questionTokens = Object.fromEntries(current.map(question => [question.id, estimates.get(question.id).tokens]));
79
+ const aggregateTokens = state.tokens + profile.requestEnvelopeTokens
80
+ + Object.values(questionTokens).reduce((sum, tokens) => sum + tokens, 0);
81
+ const partitionLongest = Math.max(...Object.values(questionTokens));
82
+ const split = pending.length !== current.length;
83
+ const reason = wave > 0 ? 'dependency-wave' : split ? 'aggregate-limit-partition' : 'fits-original-group';
84
+ const questionIds = current.map(question => question.id);
85
+ partitions.push({
86
+ id: `ctx-w${wave}-${key.replace(/[^A-Za-z0-9_.-]/g, '_')}-${String(partitions.length + 1).padStart(4, '0')}`,
87
+ wave, compatibilityKey: key, subject: input.subject, stateDigest, questionIds,
88
+ questionDigests: Object.fromEntries(current.map(question => [question.id, digest(question.entry)])),
89
+ questionDependencies: Object.fromEntries(current.map(question => [
90
+ question.id, [...new Set(question.dependsOn ?? [])].sort(),
91
+ ])),
92
+ estimate: { stateTokens: state.tokens, questionTokens, longestQuestionTokens: partitionLongest,
93
+ aggregateTokens, stateAndLongestQuestionTokens: state.tokens + profile.requestEnvelopeTokens + partitionLongest },
94
+ reason,
95
+ });
96
+ current = [];
97
+ currentTokens = state.tokens + profile.requestEnvelopeTokens;
98
+ };
99
+ for (const question of pending) {
100
+ const tokens = estimates.get(question.id).tokens;
101
+ if (current.length && currentTokens + tokens > effectiveAggregateTokens)
102
+ flush();
103
+ current.push(question);
104
+ currentTokens += tokens;
105
+ }
106
+ flush();
107
+ }
108
+ }
109
+ const profileDigest = digest(profile);
110
+ const inputDigest = digest({ ...input, questions: sorted.map(question => question.dependsOn
111
+ ? { ...question, dependsOn: [...new Set(question.dependsOn)].sort() }
112
+ : { ...question }) });
113
+ const assumptionsDigest = digest({ profileDigest, estimator: { id: estimator.id, version: estimator.version } });
114
+ const withoutDigest = {
115
+ schemaVersion: 'decision-context-plan/v1', assumptionsDigest, inputDigest,
116
+ subject: input.subject, authorizationDigest: input.authorizationDigest,
117
+ incompleteContext: input.incompleteContext, automaticActionAllowed: !input.incompleteContext,
118
+ estimator: { id: estimator.id, version: estimator.version },
119
+ providerProfile: { id: profile.id, version: profile.version, digest: profileDigest },
120
+ limits: { documentedAggregateTokens: profile.limits.aggregateTokens,
121
+ documentedStateAndLongestQuestionTokens: profile.limits.stateAndLongestQuestionTokens,
122
+ safetyMarginBps: profile.safetyMarginBps, effectiveAggregateTokens,
123
+ effectiveStateAndLongestQuestionTokens: effectiveLongestTokens },
124
+ rawEstimate: { stateTokens: state.tokens, allQuestionTokens, longestQuestionTokens,
125
+ aggregateTokens: rawAggregate, stateAndLongestQuestionTokens: rawLongest },
126
+ partitions,
127
+ };
128
+ return { ...withoutDigest, planDigest: digest(withoutDigest) };
129
+ }
130
+ /** Replanning is intentional: a changed profile, estimator or authorized input invalidates the old plan. */
131
+ export function assertContextPlanCurrent(plan, input, profile, estimator) {
132
+ const current = planDecisionContext(input, profile, estimator);
133
+ if (current.planDigest !== plan.planDigest) {
134
+ throw new ContextPlanError('stale-plan', 'context plan assumptions or authorized input changed', {
135
+ plannedDigest: plan.planDigest, currentDigest: current.planDigest,
136
+ });
137
+ }
138
+ }
139
+ /** Provider usage augments evidence and never mutates or recomputes the original plan. */
140
+ export function recordContextActualUsage(plan, partitionId, actualInputTokens, questionId) {
141
+ const partition = plan.partitions.find(candidate => candidate.id === partitionId);
142
+ if (!partition)
143
+ throw new ContextPlanError('invalid-input', `unknown context partition '${partitionId}'`);
144
+ if (!Number.isSafeInteger(actualInputTokens) || actualInputTokens < 0) {
145
+ throw new ContextPlanError('invalid-input', 'actualInputTokens must be a non-negative safe integer');
146
+ }
147
+ const questionIds = questionId === undefined ? [...partition.questionIds]
148
+ : typeof questionId === 'string' ? [questionId] : [...questionId];
149
+ if (!questionIds.length || new Set(questionIds).size !== questionIds.length
150
+ || questionIds.some(id => !partition.questionIds.includes(id))) {
151
+ throw new ContextPlanError('invalid-input', 'usage questions must be unique members of the context partition');
152
+ }
153
+ questionIds.sort();
154
+ const selected = new Set(questionIds);
155
+ const estimatedInputTokens = partition.estimate.aggregateTokens
156
+ - Object.entries(partition.estimate.questionTokens)
157
+ .filter(([id]) => !selected.has(id))
158
+ .reduce((sum, [, tokens]) => sum + tokens, 0);
159
+ const estimationErrorTokens = actualInputTokens - estimatedInputTokens;
160
+ return {
161
+ schemaVersion: 'decision-context-usage/v1', planDigest: plan.planDigest, partitionId, questionIds,
162
+ estimator: { ...plan.estimator }, providerProfile: { id: plan.providerProfile.id, version: plan.providerProfile.version },
163
+ estimatedInputTokens, actualInputTokens, estimationErrorTokens,
164
+ estimationErrorBps: actualInputTokens === 0 ? null : Math.round(estimationErrorTokens * 10_000 / actualInputTokens),
165
+ };
166
+ }
167
+ function validate(input, profile, estimator) {
168
+ if (!input.subject || !input.questions.length || !/^sha256:[0-9a-f]{64}$/.test(input.authorizationDigest)) {
169
+ throw new ContextPlanError('invalid-input', 'subject, questions, and a SHA-256 authorization digest are required');
170
+ }
171
+ if (!profile.id || !profile.version || !profile.estimator.id || !profile.estimator.version
172
+ || !Number.isSafeInteger(profile.limits.aggregateTokens) || profile.limits.aggregateTokens <= 0
173
+ || !Number.isSafeInteger(profile.limits.stateAndLongestQuestionTokens) || profile.limits.stateAndLongestQuestionTokens <= 0
174
+ || !Number.isSafeInteger(profile.requestEnvelopeTokens) || profile.requestEnvelopeTokens < 0
175
+ || !Number.isInteger(profile.safetyMarginBps) || profile.safetyMarginBps < 0 || profile.safetyMarginBps >= 10_000) {
176
+ throw new ContextPlanError('invalid-profile', 'provider context profile is incomplete or has invalid limits');
177
+ }
178
+ if (profile.estimator.id !== estimator.id || profile.estimator.version !== estimator.version) {
179
+ throw new ContextPlanError('estimator-profile-mismatch', 'provider profile was qualified with another estimator version');
180
+ }
181
+ const ids = new Set();
182
+ for (const question of input.questions) {
183
+ if (!question.id || question.subject !== input.subject || ids.has(question.id)) {
184
+ throw new ContextPlanError('invalid-input', 'question IDs must be unique and every question must preserve the plan subject');
185
+ }
186
+ ids.add(question.id);
187
+ }
188
+ }
189
+ function dependencyWaves(questions) {
190
+ const byId = new Map(questions.map(question => [question.id, question]));
191
+ const depths = new Map();
192
+ const visiting = new Set();
193
+ const depth = (id) => {
194
+ const known = depths.get(id);
195
+ if (known !== undefined)
196
+ return known;
197
+ if (visiting.has(id))
198
+ throw new ContextPlanError('dependency-error', `question dependency cycle includes '${id}'`);
199
+ visiting.add(id);
200
+ const question = byId.get(id);
201
+ let value = 0;
202
+ for (const dependency of [...new Set(question.dependsOn ?? [])].sort()) {
203
+ if (!byId.has(dependency))
204
+ throw new ContextPlanError('dependency-error', `question '${id}' has unknown dependency '${dependency}'`);
205
+ if (dependency === id)
206
+ throw new ContextPlanError('dependency-error', `question '${id}' depends on itself`);
207
+ value = Math.max(value, depth(dependency) + 1);
208
+ }
209
+ visiting.delete(id);
210
+ depths.set(id, value);
211
+ return value;
212
+ };
213
+ for (const question of questions)
214
+ depth(question.id);
215
+ const waves = [];
216
+ for (const question of questions)
217
+ (waves[depth(question.id)] ??= []).push(question);
218
+ return waves;
219
+ }
220
+ function checkedEstimate(estimator, value) {
221
+ const result = estimator.estimate(value);
222
+ if (!result || !Number.isSafeInteger(result.tokens) || result.tokens < 0
223
+ || !Number.isSafeInteger(result.serializedBytes) || result.serializedBytes < 0) {
224
+ throw new ContextPlanError('invalid-profile', 'estimator returned invalid token or byte counts');
225
+ }
226
+ return result;
227
+ }
228
+ function effectiveLimit(limit, marginBps) {
229
+ return Math.floor(limit * (10_000 - marginBps) / 10_000);
230
+ }
231
+ function digest(value) {
232
+ return `sha256:${createHash('sha256').update(canonicalJson(value)).digest('hex')}`;
233
+ }
234
+ //# sourceMappingURL=context-plan.js.map
@@ -0,0 +1,60 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { canonicalJson } from '../security/artifact-trust.js';
3
+ import { ContextPlanError, planDecisionContext, } from './context-plan.js';
4
+ /** Retains a body-free, immutable comparison. Reject split samples: usage is per request, not per group. */
5
+ export function assertContextQualified(qualification, profile, estimator) {
6
+ const digest = `sha256:${createHash('sha256').update(canonicalJson(profile)).digest('hex')}`;
7
+ const cases = qualification?.cases;
8
+ if (qualification?.schemaVersion !== 'decision-context-qualification/v1'
9
+ || qualification.profile.digest !== digest || qualification.profile.id !== profile.id
10
+ || qualification.profile.version !== profile.version || qualification.estimator.id !== estimator.id
11
+ || qualification.estimator.version !== estimator.version || !qualification.qualifiedForEnforcement
12
+ || qualification.reason !== 'provider-comparisons-within-margin' || !Array.isArray(cases) || !cases.length
13
+ || new Set(cases.map(c => c.caseId)).size !== cases.length
14
+ || cases.some(c => c.source !== 'provider' || !Number.isSafeInteger(c.actualInputTokens)
15
+ || !Number.isSafeInteger(c.estimatedInputTokens) || c.actualInputTokens < 0 || c.estimatedInputTokens < 0
16
+ || c.errorTokens !== c.actualInputTokens - c.estimatedInputTokens
17
+ || c.undercountBps !== (c.actualInputTokens === 0 ? 0
18
+ : Math.max(0, Math.ceil(c.errorTokens * 10_000 / c.actualInputTokens))))
19
+ || qualification.worstUndercountBps !== Math.max(...cases.map(c => c.undercountBps))
20
+ || qualification.worstUndercountBps > profile.safetyMarginBps) {
21
+ throw new ContextPlanError('rollout-unqualified', 'context enforcement requires matching provider-backed qualification');
22
+ }
23
+ }
24
+ export function compareContextUsage(comparisons, profile, estimator) {
25
+ if (!comparisons.length || new Set(comparisons.map(c => c.caseId)).size !== comparisons.length) {
26
+ throw new ContextPlanError('invalid-input', 'qualification requires unique nonempty comparison cases');
27
+ }
28
+ const cases = comparisons.map(sample => {
29
+ if (!sample.caseId || !sample.usageRef || !['provider', 'synthetic'].includes(sample.source)
30
+ || !Number.isSafeInteger(sample.actualInputTokens) || sample.actualInputTokens < 0) {
31
+ throw new ContextPlanError('invalid-input', 'comparison requires an ID, usage reference and nonnegative provider usage');
32
+ }
33
+ const plan = planDecisionContext(sample.input, profile, estimator);
34
+ if (plan.partitions.length !== 1 || plan.partitions[0].questionIds.length !== sample.input.questions.length) {
35
+ throw new ContextPlanError('invalid-input', 'comparison must correspond to exactly one complete provider request');
36
+ }
37
+ const estimatedInputTokens = plan.partitions[0].estimate.aggregateTokens;
38
+ const errorTokens = sample.actualInputTokens - estimatedInputTokens;
39
+ return {
40
+ caseId: sample.caseId, source: sample.source, usageRef: sample.usageRef,
41
+ planDigest: plan.planDigest, estimatedInputTokens, actualInputTokens: sample.actualInputTokens,
42
+ errorTokens, undercountBps: sample.actualInputTokens === 0 ? 0
43
+ : Math.max(0, Math.ceil(errorTokens * 10_000 / sample.actualInputTokens)),
44
+ };
45
+ }).sort((a, b) => a.caseId.localeCompare(b.caseId, 'en'));
46
+ const worstUndercountBps = Math.max(...cases.map(c => c.undercountBps));
47
+ const providerOnly = cases.every(c => c.source === 'provider');
48
+ // If estimate is lower than actual by more than reserved margin, the effective limit may overflow.
49
+ const qualifiedForEnforcement = providerOnly && worstUndercountBps <= profile.safetyMarginBps;
50
+ return {
51
+ schemaVersion: 'decision-context-qualification/v1',
52
+ profile: { id: profile.id, version: profile.version,
53
+ digest: `sha256:${createHash('sha256').update(canonicalJson(profile)).digest('hex')}` },
54
+ estimator: { id: estimator.id, version: estimator.version }, cases, worstUndercountBps,
55
+ qualifiedForEnforcement,
56
+ reason: !providerOnly ? 'synthetic-only' : qualifiedForEnforcement
57
+ ? 'provider-comparisons-within-margin' : 'undercount-exceeds-margin',
58
+ };
59
+ }
60
+ //# sourceMappingURL=context-qualification.js.map
@@ -0,0 +1,215 @@
1
+ import { admitEntry } from '../entry.js';
2
+ import { compareEnsembleKeys, ensembleContractDigest, EnsembleContractError, validateEnsemblePolicy, } from './contract.js';
3
+ /** Deterministic reference aggregation over already-recorded member results. It never dispatches.
4
+ * Member order does not matter: results are put in canonical (memberId, sampleIndex) order first,
5
+ * so every sum and tie-break runs in the same sequence for any permutation of the input. */
6
+ export function aggregateEnsembleResults(policyInput, resultsInput, options = {}) {
7
+ const { policy, digest: policyDigest } = validateEnsemblePolicy(policyInput, options);
8
+ try {
9
+ admitEntry(resultsInput);
10
+ }
11
+ catch {
12
+ throw new EnsembleContractError('member results admission denied', 'admission');
13
+ }
14
+ const members = new Map(policy.members.map(member => [member.id, member]));
15
+ const results = [...resultsInput].sort((a, b) => compareEnsembleKeys(a.memberId, b.memberId) || a.sampleIndex - b.sampleIndex);
16
+ validateResults(policy, members, results);
17
+ const succeeded = results.filter(result => result.status === 'succeeded');
18
+ const n = succeeded.length;
19
+ const labels = labelSpace(policy);
20
+ const statistics = {
21
+ votes: null, meanProbability: null, meanDistribution: null, meanScore: null, medianScore: null, dispersion: null,
22
+ };
23
+ const { algorithm, tieRule } = policy.aggregation;
24
+ const { metric } = policy.disagreement;
25
+ let candidate = null;
26
+ let tie = false;
27
+ const votes = () => {
28
+ const counts = Object.fromEntries(labels.map(label => [label, 0]));
29
+ for (const result of succeeded)
30
+ counts[vote(policy, result)] += 1;
31
+ return counts;
32
+ };
33
+ const distributions = () => succeeded.map(result => distributionOf(policy, labels, result));
34
+ const meanOf = (items) => Object.fromEntries(labels.map(label => [label, n ? round(items.reduce((sum, item) => sum + item[label], 0) / n) : 0]));
35
+ const scores = () => succeeded.map(result => result.value);
36
+ const pick = (weights) => {
37
+ const top = Math.max(...labels.map(label => weights[label]));
38
+ const winners = labels.filter(label => weights[label] === top);
39
+ tie = winners.length > 1;
40
+ return n ? winners[0] : null;
41
+ };
42
+ if (algorithm === 'majority-v1' || metric === 'vote-share-v1' || metric === 'normalized-entropy-v1')
43
+ statistics.votes = votes();
44
+ if (policy.primitive === 'truth-probability' && (algorithm === 'mean-probability-v1' || metric === 'jensen-shannon-v1')) {
45
+ statistics.meanProbability = n ? round(succeeded.reduce((sum, result) => sum + result.value, 0) / n) : null;
46
+ }
47
+ if (policy.primitive === 'choice' && (algorithm === 'mean-probability-v1' || metric === 'jensen-shannon-v1'))
48
+ statistics.meanDistribution = meanOf(distributions());
49
+ if (policy.primitive === 'ordinal-score' && n) {
50
+ const values = scores();
51
+ const mean = values.reduce((sum, value) => sum + value, 0) / n;
52
+ statistics.meanScore = round(mean);
53
+ statistics.dispersion = round(Math.sqrt(values.reduce((sum, value) => sum + (value - mean) ** 2, 0) / n));
54
+ const sorted = [...values].sort((a, b) => a - b);
55
+ const middle = Math.floor(n / 2);
56
+ statistics.medianScore = round(n % 2 ? sorted[middle] : (sorted[middle - 1] + sorted[middle]) / 2);
57
+ }
58
+ if (algorithm === 'majority-v1')
59
+ candidate = pick(statistics.votes);
60
+ else if (algorithm === 'mean-probability-v1')
61
+ candidate = policy.primitive === 'choice' ? pick(statistics.meanDistribution) : statistics.meanProbability;
62
+ else if (algorithm === 'score-distribution-mean-v1')
63
+ candidate = statistics.meanScore;
64
+ else if (n) {
65
+ // score-median-v1: an even count with distinct middle values is a tie; the stable rule takes the lower value.
66
+ const sorted = scores().sort((a, b) => a - b);
67
+ const middle = Math.floor(n / 2);
68
+ tie = n % 2 === 0 && sorted[middle - 1] !== sorted[middle];
69
+ candidate = tie ? sorted[middle - 1] : statistics.medianScore;
70
+ }
71
+ const disagreementValue = n === 0 ? 1 : disagreement(policy, statistics, distributions, n);
72
+ const valueBps = Math.round(Math.min(1, Math.max(0, disagreementValue)) * 10_000);
73
+ const exceeded = valueBps > policy.disagreement.thresholdBps;
74
+ let disposition = 'accept';
75
+ let reason = 'aggregated';
76
+ if (n < policy.acceptance.minimumSuccessfulMembers) {
77
+ disposition = policy.acceptance.onInsufficientMembers;
78
+ reason = 'insufficient-members';
79
+ }
80
+ else if (tie && tieRule !== 'lowest-canonical-value') {
81
+ disposition = tieRule;
82
+ reason = 'tie';
83
+ }
84
+ else if (exceeded) {
85
+ disposition = policy.disagreement.onExceeded;
86
+ reason = 'disagreement-exceeded';
87
+ }
88
+ const contributing = succeeded.map(result => members.get(result.memberId));
89
+ const warnings = new Set();
90
+ if (results.some(result => result.status !== 'succeeded'))
91
+ warnings.add('member-failures-present');
92
+ if (contributing.some(member => !member.calibration))
93
+ warnings.add('uncalibrated-members');
94
+ if (n >= 2 && valueBps <= policy.acceptance.highAgreementWarningBps) {
95
+ warnings.add('high-agreement-not-correctness');
96
+ if (limitedIndependence(contributing))
97
+ warnings.add('shared-systematic-error-risk');
98
+ }
99
+ const payload = {
100
+ schemaVersion: 'decision-ensemble-aggregate/v1',
101
+ policy: { id: policy.id, version: policy.version, digest: policyDigest },
102
+ primitive: policy.primitive,
103
+ algorithm: { id: algorithm, version: versionOf(algorithm) },
104
+ semantics: 'stability-signal-not-correctness',
105
+ provenance: 'derived',
106
+ members: results.map(result => ({ memberId: result.memberId, sampleIndex: result.sampleIndex, status: result.status,
107
+ resultDigest: result.resultDigest, value: result.value })),
108
+ counts: { declared: results.length, succeeded: n, failed: results.filter(result => result.status === 'failed').length,
109
+ abstained: results.filter(result => result.status === 'abstained').length },
110
+ outcome: { disposition, value: disposition === 'accept' ? candidate : null, reason, tie, tieRule: tie ? tieRule : null },
111
+ statistics,
112
+ disagreement: { metric: { id: metric, version: versionOf(metric) }, valueBps, thresholdBps: policy.disagreement.thresholdBps, exceeded },
113
+ warnings: [...warnings].sort(compareEnsembleKeys),
114
+ correctnessGate: { status: 'not-satisfied', reason: 'agreement-is-not-correctness-evidence' },
115
+ };
116
+ return { ...payload, digest: ensembleContractDigest(payload) };
117
+ }
118
+ function validateResults(policy, members, results) {
119
+ const problems = [];
120
+ const seen = new Set();
121
+ const needsDistribution = policy.primitive === 'choice'
122
+ && (policy.aggregation.algorithm === 'mean-probability-v1' || policy.disagreement.metric === 'jensen-shannon-v1');
123
+ for (const result of results) {
124
+ const at = `result ${result.memberId}#${result.sampleIndex}`;
125
+ const member = members.get(result.memberId);
126
+ if (!member) {
127
+ problems.push(`${at} names an unknown member`);
128
+ continue;
129
+ }
130
+ if (!Number.isSafeInteger(result.sampleIndex) || result.sampleIndex < 0 || result.sampleIndex >= member.samples)
131
+ problems.push(`${at} is outside the planned samples`);
132
+ const key = `${result.memberId}\u0000${result.sampleIndex}`;
133
+ if (seen.has(key))
134
+ problems.push(`${at} is duplicated`);
135
+ seen.add(key);
136
+ if (!/^sha256:[0-9a-f]{64}$/.test(result.resultDigest))
137
+ problems.push(`${at} must pin its retained result lineage`);
138
+ if (!['succeeded', 'failed', 'abstained'].includes(result.status))
139
+ problems.push(`${at} has an unknown status`);
140
+ if (result.status !== 'succeeded') {
141
+ if (result.value !== null || result.distribution !== null)
142
+ problems.push(`${at} is not successful and cannot carry a value`);
143
+ continue;
144
+ }
145
+ if (result.uncertaintyProfile !== member.uncertaintyProfile)
146
+ problems.push(`${at} uncertainty profile does not match its member`);
147
+ const value = result.value;
148
+ if (policy.primitive === 'choice' && (typeof value !== 'string' || !policy.options.includes(value)))
149
+ problems.push(`${at} is not a declared choice option`);
150
+ if (policy.primitive === 'truth-probability' && (typeof value !== 'number' || !(value >= 0 && value <= 1)))
151
+ problems.push(`${at} is not a probability`);
152
+ if (policy.primitive === 'ordinal-score' && (typeof value !== 'number' || !(value >= 0 && value <= policy.levels - 1)))
153
+ problems.push(`${at} is outside the score levels`);
154
+ if (result.distribution !== null) {
155
+ const entries = Object.entries(result.distribution);
156
+ const allowed = policy.primitive === 'choice' ? policy.options : ['false', 'true'];
157
+ if (policy.primitive === 'ordinal-score' || entries.some(([label, p]) => !allowed.includes(label) || !(p >= 0 && p <= 1))
158
+ || Math.abs(entries.reduce((sum, [, p]) => sum + p, 0) - 1) > 1e-6)
159
+ problems.push(`${at} has an invalid distribution`);
160
+ }
161
+ else if (needsDistribution)
162
+ problems.push(`${at} needs a provider distribution for ${policy.aggregation.algorithm}/${policy.disagreement.metric}`);
163
+ }
164
+ const planned = policy.members.reduce((sum, member) => sum + member.samples, 0);
165
+ if (results.length !== planned)
166
+ problems.push(`every planned sample must be recorded, including failures (${results.length}/${planned})`);
167
+ if (problems.length)
168
+ throw new EnsembleContractError(`member results rejected: ${problems[0]}`, 'semantic', problems);
169
+ }
170
+ function labelSpace(policy) {
171
+ return policy.primitive === 'choice' ? [...policy.options].sort(compareEnsembleKeys) : policy.primitive === 'truth-probability' ? ['false', 'true'] : [];
172
+ }
173
+ /** Noul votes true at p >= 0.5; the threshold is part of majority-v1 and vote-share-v1. */
174
+ function vote(policy, result) {
175
+ return policy.primitive === 'truth-probability' ? (result.value >= 0.5 ? 'true' : 'false') : result.value;
176
+ }
177
+ function distributionOf(policy, labels, result) {
178
+ if (policy.primitive === 'truth-probability') {
179
+ const p = result.value;
180
+ return { false: 1 - p, true: p };
181
+ }
182
+ return Object.fromEntries(labels.map(label => [label, result.distribution?.[label] ?? 0]));
183
+ }
184
+ function entropyBits(weights) {
185
+ return -weights.reduce((sum, p) => sum + (p > 0 ? p * Math.log2(p) : 0), 0);
186
+ }
187
+ function disagreement(policy, statistics, distributions, n) {
188
+ const metric = policy.disagreement.metric;
189
+ if (metric === 'vote-share-v1')
190
+ return 1 - Math.max(...Object.values(statistics.votes)) / n;
191
+ if (metric === 'normalized-entropy-v1') {
192
+ const counts = Object.values(statistics.votes);
193
+ return entropyBits(counts.map(count => count / n)) / Math.log2(counts.length);
194
+ }
195
+ if (metric === 'jensen-shannon-v1') {
196
+ const items = distributions();
197
+ const labels = Object.keys(items[0] ?? {});
198
+ const scale = Math.log2(Math.min(n, labels.length));
199
+ if (!(scale > 0))
200
+ return 0;
201
+ const mean = labels.map(label => items.reduce((sum, item) => sum + item[label], 0) / n);
202
+ const within = items.reduce((sum, item) => sum + entropyBits(labels.map(label => item[label])), 0) / n;
203
+ return (entropyBits(mean) - within) / scale;
204
+ }
205
+ // score-dispersion-v1: population standard deviation over its maximum, half the level span.
206
+ return (statistics.dispersion ?? 0) / ((policy.levels - 1) / 2);
207
+ }
208
+ function limitedIndependence(members) {
209
+ const identities = new Set(members.map(member => `${member.model.provider}\u0000${member.model.backend}\u0000${member.model.pinnedVersion}`));
210
+ return identities.size < 2 || members.every(member => member.memberType === 'repeated-sample' || member.memberType === 'prompt-adapter');
211
+ }
212
+ function versionOf(id) { return id.slice(id.lastIndexOf('-') + 1); }
213
+ /** Removes binary floating-point noise from derived statistics; raw member values are retained unchanged. */
214
+ function round(value) { return Math.round(value * 1e12) / 1e12; }
215
+ //# sourceMappingURL=aggregate.js.map