@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,88 @@
1
+ import { decisionCapabilities, evaluateRequestPath, listPatterns, livePlan, materializeSyntheticClassificationSetup, runOfflinePattern, showPattern, syntheticClassificationSetup, validateDecisionInput, } from '../../decision/driver.js';
2
+ function jsonResult(value, exitCode = 0) {
3
+ return { exitCode, message: JSON.stringify(value, null, 2), rawOutput: true };
4
+ }
5
+ function usage() {
6
+ return [
7
+ 'Usage: aiwg decision <capabilities|status|patterns|validate|evaluate|setup> [options]',
8
+ '',
9
+ 'Commands:',
10
+ ' capabilities|status Show offline readiness, primitives, config and feature availability',
11
+ ' patterns list List governed decision pattern packs',
12
+ ' patterns show <id> Show one pattern pack and schemas',
13
+ ' patterns offline-run <id> [fixture] Run a recorded offline fixture through the decision runtime',
14
+ ' patterns live-plan <id> [--opt-in] [--credential-resolved] [--egress-approved]',
15
+ ' validate <request|definition|ruleset|binding> <path>',
16
+ ' evaluate --request <path> [--host-policy-module <path>]',
17
+ ' setup synthetic-classification [--output-dir <dir>]',
18
+ ].join('\n');
19
+ }
20
+ function takeOption(args, name) {
21
+ const index = args.indexOf(name);
22
+ if (index < 0)
23
+ return undefined;
24
+ return args[index + 1];
25
+ }
26
+ function patternId(value) {
27
+ if (!value)
28
+ throw new Error('Pattern id is required');
29
+ return value;
30
+ }
31
+ async function executeDecision(ctx) {
32
+ const [command, subcommand, ...rest] = ctx.args;
33
+ const options = { cwd: ctx.cwd, frameworkRoot: ctx.frameworkRoot, env: process.env };
34
+ try {
35
+ if (!command || command === '--help' || command === '-h')
36
+ return { exitCode: 0, message: usage(), rawOutput: true };
37
+ if (command === 'capabilities' || command === 'status')
38
+ return jsonResult(decisionCapabilities(options));
39
+ if (command === 'patterns') {
40
+ if (subcommand === 'list')
41
+ return jsonResult(listPatterns());
42
+ if (subcommand === 'show')
43
+ return jsonResult(showPattern(patternId(rest[0])));
44
+ if (subcommand === 'offline-run')
45
+ return jsonResult(await runOfflinePattern(patternId(rest[0]), rest[1]));
46
+ if (subcommand === 'live-plan')
47
+ return jsonResult(livePlan(patternId(rest[0]), {
48
+ explicitOptIn: rest.includes('--opt-in'),
49
+ credentialResolved: rest.includes('--credential-resolved'),
50
+ egressApproved: rest.includes('--egress-approved'),
51
+ }));
52
+ }
53
+ if (command === 'validate') {
54
+ const target = subcommand;
55
+ const result = await validateDecisionInput(target, rest[0] ?? '', options);
56
+ return jsonResult(result, result.valid ? 0 : 2);
57
+ }
58
+ if (command === 'evaluate') {
59
+ const request = takeOption(ctx.args, '--request');
60
+ if (!request)
61
+ return { exitCode: 2, message: 'decision evaluate requires --request <path>' };
62
+ const result = await evaluateRequestPath(request, options, {
63
+ hostPolicyModulePath: takeOption(ctx.args, '--host-policy-module'),
64
+ });
65
+ return jsonResult(result, result.exitCode);
66
+ }
67
+ if (command === 'setup' && subcommand === 'synthetic-classification') {
68
+ const outputDir = takeOption(ctx.args, '--output-dir');
69
+ if (outputDir)
70
+ return jsonResult(await materializeSyntheticClassificationSetup(outputDir, {}, options));
71
+ return jsonResult(syntheticClassificationSetup({}, options));
72
+ }
73
+ return { exitCode: 2, message: usage(), rawOutput: true };
74
+ }
75
+ catch (error) {
76
+ return { exitCode: 1, message: error instanceof Error ? error.message : String(error) };
77
+ }
78
+ }
79
+ export const decisionHandler = {
80
+ id: 'decision',
81
+ name: 'Decision Driver',
82
+ description: 'Inspect, validate and run governed decision classification workflows',
83
+ category: 'utility',
84
+ aliases: [],
85
+ help: async () => ({ exitCode: 0, message: usage(), rawOutput: true }),
86
+ execute: executeDecision,
87
+ };
88
+ //# sourceMappingURL=decision.js.map
@@ -0,0 +1,154 @@
1
+ function jsonResult(value, exitCode = 0) {
2
+ return { exitCode, message: JSON.stringify(value, null, 2), rawOutput: true };
3
+ }
4
+ function usage() {
5
+ return [
6
+ 'Usage: aiwg gates <validate|evaluate|show|list> [options]',
7
+ '',
8
+ 'Commands:',
9
+ ' validate <pack|binding|report> <path> [--pack-dir <dir>] [--aiwg-root <dir>]',
10
+ ' evaluate --binding <file> --metrics <file> --holdout <file> [--upstream <file>] --now <iso> --trusted-binding-digest <sha256> [--pack-dir <dir>] [--aiwg-root <dir>]',
11
+ ' show <id> [--pack-dir <dir>] [--aiwg-root <dir>]',
12
+ ' list [--namespace <ns>] [--rule [<rule-id>]] [--pack-dir <dir>] [--aiwg-root <dir>]',
13
+ '',
14
+ 'Packs live in <bundle>/gate-packs/*.gatepack.yaml|json and register as',
15
+ 'aiwg:<bundle>/<name> (shipped).',
16
+ 'Single files and bundle directories passed via --pack-dir are always',
17
+ 'project: packs; aiwg: is reserved for the installed tree, and a --pack-dir',
18
+ 'manifest claiming a shipped or installed bundle id refuses. An --aiwg-root',
19
+ 'override is untrusted:',
20
+ 'list/show/validate still inspect it, but evaluate refuses it.',
21
+ 'Evaluation is pure and offline: pass --now <iso> for the fake clock.',
22
+ 'The holdout and upstream files must already carry their sealed digests,',
23
+ 'and --upstream is required whenever the binding observes an',
24
+ 'upstream-ceiling gate. Project floors load from .aiwg/aiwg.config in the',
25
+ 'cwd (unreadable or invalid config refuses evaluation). Reports printed',
26
+ "here carry attestation: 'offline-cli'.",
27
+ ].join('\n');
28
+ }
29
+ function takeOption(args, name) {
30
+ const index = args.indexOf(name);
31
+ if (index < 0)
32
+ return undefined;
33
+ return args[index + 1];
34
+ }
35
+ function takeAllOptions(args, name) {
36
+ const values = [];
37
+ for (let index = 0; index < args.length; index += 1) {
38
+ if (args[index] === name && index + 1 < args.length)
39
+ values.push(args[index + 1]);
40
+ }
41
+ return values;
42
+ }
43
+ function driverOptions(ctx) {
44
+ // The shipped root resolves from the installed framework location, never
45
+ // from the cwd: any checkout could otherwise impersonate the aiwg:
46
+ // namespace. An explicit --aiwg-root is kept for inspection only; packs
47
+ // loaded from it are marked untrusted and refused by evaluate.
48
+ const aiwgRoot = takeOption(ctx.args, '--aiwg-root') ?? ctx.frameworkRoot;
49
+ const packDirs = takeAllOptions(ctx.args, '--pack-dir');
50
+ return { cwd: ctx.cwd, aiwgRoot, ...(packDirs.length ? { packDirs } : {}) };
51
+ }
52
+ /** Positional args with each known value-flag and its value skipped in order. */
53
+ function positionals(args, skipCommand) {
54
+ const valueFlags = new Set(['--pack-dir', '--aiwg-root', '--binding', '--metrics', '--holdout', '--upstream', '--now', '--namespace', '--rule', '--trusted-binding-digest']);
55
+ const found = [];
56
+ let skipNext = false;
57
+ for (let index = 0; index < args.length; index += 1) {
58
+ const arg = args[index];
59
+ if (skipNext) {
60
+ skipNext = false;
61
+ continue;
62
+ }
63
+ if (valueFlags.has(arg)) {
64
+ skipNext = true;
65
+ continue;
66
+ }
67
+ if (arg.startsWith('-'))
68
+ continue;
69
+ if (arg === skipCommand && index === 0)
70
+ continue;
71
+ found.push(arg);
72
+ }
73
+ return found;
74
+ }
75
+ async function executeGates(ctx) {
76
+ const [command] = ctx.args;
77
+ try {
78
+ if (!command || command === '--help' || command === '-h')
79
+ return { exitCode: 0, message: usage(), rawOutput: true };
80
+ const { validateGateFile, evaluateGatesFromFiles, showGatePack, listGatePacks, packsReferencedByRule, } = await import('../../gates/driver.js');
81
+ const options = driverOptions(ctx);
82
+ if (command === 'validate') {
83
+ const args = positionals(ctx.args, 'validate');
84
+ const target = (args[0] ?? '');
85
+ const targetPath = args[1] ?? '';
86
+ if (!['pack', 'binding', 'report', 'auto'].includes(target) || !targetPath) {
87
+ return { exitCode: 2, message: `gates validate requires <pack|binding|report|auto> <path>\n\n${usage()}`, rawOutput: true };
88
+ }
89
+ const result = await validateGateFile(target === 'auto' ? 'auto' : target, targetPath, options);
90
+ return jsonResult(result, result.valid ? 0 : 2);
91
+ }
92
+ if (command === 'evaluate') {
93
+ const binding = takeOption(ctx.args, '--binding');
94
+ const metrics = takeOption(ctx.args, '--metrics');
95
+ const holdout = takeOption(ctx.args, '--holdout');
96
+ const upstream = takeOption(ctx.args, '--upstream');
97
+ const now = takeOption(ctx.args, '--now');
98
+ const trustedBindingDigest = takeOption(ctx.args, '--trusted-binding-digest');
99
+ if (!binding || !metrics || !holdout || !now || !trustedBindingDigest) {
100
+ return { exitCode: 2, message: `gates evaluate requires --binding <file> --metrics <file> --holdout <file> --now <iso> --trusted-binding-digest <sha256:..>\n\n${usage()}`, rawOutput: true };
101
+ }
102
+ if (!/^sha256:[0-9a-f]{64}$/.test(trustedBindingDigest)) {
103
+ return { exitCode: 2, message: `gates evaluate requires --trusted-binding-digest <sha256:..> (64 hex chars)\n\n${usage()}`, rawOutput: true };
104
+ }
105
+ const report = await evaluateGatesFromFiles({
106
+ bindingPath: binding, metricsPath: metrics, holdoutPath: holdout,
107
+ ...(upstream ? { upstreamPath: upstream } : {}), now,
108
+ trustedBindingDigest: trustedBindingDigest,
109
+ }, options);
110
+ return jsonResult(report, 0);
111
+ }
112
+ if (command === 'show') {
113
+ const id = positionals(ctx.args, 'show')[0];
114
+ if (!id)
115
+ return { exitCode: 2, message: `gates show requires <id>\n\n${usage()}`, rawOutput: true };
116
+ return jsonResult(showGatePack(id, options), 0);
117
+ }
118
+ if (command === 'list') {
119
+ const namespace = takeOption(ctx.args, '--namespace');
120
+ const ruleIndex = ctx.args.indexOf('--rule');
121
+ const ruleValue = ruleIndex >= 0 ? ctx.args[ruleIndex + 1] : undefined;
122
+ const rule = ruleIndex >= 0 ? (ruleValue && !ruleValue.startsWith('-') ? ruleValue : null) : undefined;
123
+ const packs = listGatePacks({ ...options, ...(namespace ? { namespace } : {}) });
124
+ if (rule === undefined)
125
+ return jsonResult({ schema: 'aiwg-gates-list/v1', packs }, 0);
126
+ if (rule === null) {
127
+ return jsonResult({
128
+ schema: 'aiwg-gates-list/v1', packs,
129
+ enforcedBy: { status: 'stub', note: 'Rule enforcedBy coverage is tracked in #2839; rules without enforcedBy remain guidance.' },
130
+ }, 0);
131
+ }
132
+ const referenced = new Set(await packsReferencedByRule(rule, options));
133
+ return jsonResult({
134
+ schema: 'aiwg-gates-list/v1', rule,
135
+ packs: packs.filter(pack => referenced.has(pack.id)),
136
+ enforcedBy: { status: 'stub', note: 'Rule enforcedBy coverage is tracked in #2839.' },
137
+ }, 0);
138
+ }
139
+ return { exitCode: 2, message: usage(), rawOutput: true };
140
+ }
141
+ catch (error) {
142
+ return { exitCode: 1, message: error instanceof Error ? error.message : String(error) };
143
+ }
144
+ }
145
+ export const gatesHandler = {
146
+ id: 'gates',
147
+ name: 'Gates',
148
+ description: 'Validate, evaluate and inspect declarative gate packs offline',
149
+ category: 'utility',
150
+ aliases: [],
151
+ help: async () => ({ exitCode: 0, message: usage(), rawOutput: true }),
152
+ execute: executeGates,
153
+ };
154
+ //# sourceMappingURL=gates.js.map
@@ -73,12 +73,14 @@ import { installationHandler } from './installation.js';
73
73
  import { uhpHandler } from './uhp.js';
74
74
  import { schemaHandler } from './schema.js';
75
75
  import { datasetHandler } from './dataset.js';
76
+ import { decisionHandler } from './decision.js';
77
+ import { gatesHandler } from './gates.js';
76
78
  // Re-export individual handlers
77
79
  export {
78
80
  // Maintenance
79
81
  helpHandler, versionHandler, authHandler, doctorHandler, contextFirewallHandler, updateHandler, refreshHandler, regenerateHandler, workspaceContextHandler,
80
82
  // Framework management
81
- useHandler, listHandler, removeHandler, promoteHandler, installHandler, packagesHandler, marketplaceHandler, initHandler, setupHandler, setupGenerateHandler, setupRunHandler, setupValidateHandler, issueHandler, issueAuditHandler, runHandler, jobHandler, costReportHandler, evidenceHandler, artifactVerifyHandler, outputModeHandler, writerProfileHandler, botHandoffHandler, buildVerifyHandler, writingHandler, schemaHandler, datasetHandler, installationHandler,
83
+ useHandler, listHandler, removeHandler, promoteHandler, installHandler, packagesHandler, marketplaceHandler, initHandler, setupHandler, setupGenerateHandler, setupRunHandler, setupValidateHandler, issueHandler, issueAuditHandler, runHandler, jobHandler, costReportHandler, evidenceHandler, artifactVerifyHandler, outputModeHandler, writerProfileHandler, botHandoffHandler, buildVerifyHandler, writingHandler, schemaHandler, datasetHandler, decisionHandler, gatesHandler, installationHandler,
82
84
  // Project
83
85
  newBundleHandler, quickrefHandler, newProjectHandler, sessionHandler, sessionsHandler,
84
86
  // Workspace
@@ -169,6 +171,8 @@ export const allHandlers = [
169
171
  writingHandler,
170
172
  schemaHandler,
171
173
  datasetHandler,
174
+ decisionHandler,
175
+ gatesHandler,
172
176
  // Workspace management
173
177
  ...workspaceHandlers,
174
178
  // Subcommand handlers (MCP, catalog, index, skills)
@@ -64,6 +64,7 @@ Options:
64
64
  --provider-home <path> Override the provider home root (testing/portable homes)
65
65
  --codex-root <path> Explicitly authorize a shared Codex sessions/export root
66
66
  --omp-root <path> Explicitly authorize an OMP profile sessions root
67
+ --muse-root <path> Explicitly authorize a Muse Code sessions root
67
68
  --dsh-root <path> Explicitly authorize a DeepSeek Harness sessions root
68
69
  --confirm, --yes Confirm a persistent discovered batch import
69
70
  --lock-wait-ms <n> Maximum import-lease wait (default 5000)
@@ -1172,6 +1173,8 @@ async function discoverWorkspace(ctx, args) {
1172
1173
  : undefined,
1173
1174
  ompRoot: args.values.has('--omp-root')
1174
1175
  ? resolve(ctx.cwd, args.values.get('--omp-root')) : undefined,
1176
+ museRoot: args.values.has('--muse-root')
1177
+ ? resolve(ctx.cwd, args.values.get('--muse-root')) : undefined,
1175
1178
  dshRoot: args.values.has('--dsh-root')
1176
1179
  ? resolve(ctx.cwd, args.values.get('--dsh-root')) : undefined,
1177
1180
  codexRoot: args.values.has('--codex-root')
@@ -1439,14 +1442,14 @@ function providerDisposition(provider) {
1439
1442
  }
1440
1443
  if (provider === 'muse') {
1441
1444
  return {
1442
- provider, disposition: 'manual-only', operationalState: 'available',
1443
- supportedOperations: ['inspect', 'stream'],
1444
- acquisitionModes: ['manual-export'],
1445
- reasonCode: 'MANUAL_SOURCE_SELECTION_REQUIRED',
1446
- remediation: 'Run `muse export --session <id-or-session.jsonl>` and explicitly select the trajectory JSON; sessions live under $XDG_DATA_HOME/muse/sessions, but that native log format is internal, so auto-discover is unsupported.',
1445
+ provider, disposition: 'implemented', operationalState: 'available',
1446
+ supportedOperations: ['discover', 'inspect', 'stream'],
1447
+ acquisitionModes: ['manual-export', 'jsonl'],
1448
+ reasonCode: null,
1449
+ remediation: 'Import an explicit `muse export` trajectory JSON, or pass `--muse-root` with an explicitly authorized $XDG_DATA_HOME/muse/sessions root for evidence-gated native discovery.',
1447
1450
  evidence: {
1448
1451
  adapterVersion: MUSE_ADAPTER_VERSION,
1449
- verifiedAt: '2026-09-24',
1452
+ verifiedAt: '2026-10-02',
1450
1453
  documentation: 'docs/providers/muse-sessions.md',
1451
1454
  },
1452
1455
  };
@@ -1559,7 +1562,7 @@ function parseArgs(argv) {
1559
1562
  '--entity', '--sensitivity', '--extraction-state', '--page-size', '--max-documents',
1560
1563
  '--state', '--reviewer', '--reason', '--policy-version', '--min-confidence',
1561
1564
  '--consumer', '--actor-class', '--reason-code', '--dependent-action', '--basis',
1562
- '--manifest', '--provider-home', '--codex-root', '--omp-root', '--dsh-root', '--lock-wait-ms', '--min-coverage', '--gap',
1565
+ '--manifest', '--provider-home', '--codex-root', '--omp-root', '--muse-root', '--dsh-root', '--lock-wait-ms', '--min-coverage', '--gap',
1563
1566
  '--inactivity-threshold',
1564
1567
  '--control-events',
1565
1568
  '--session', '--status', '--actor', '--group-by',
@@ -29,6 +29,13 @@ const REGISTRY_ID_TO_USE_NAME = {
29
29
  'research-complete': 'research',
30
30
  'forensics-complete': 'forensics',
31
31
  };
32
+ function flagValue(args, flag) {
33
+ const index = args.indexOf(flag);
34
+ if (index >= 0)
35
+ return args[index + 1];
36
+ const prefix = `${flag}=`;
37
+ return args.find((arg) => arg.startsWith(prefix))?.slice(prefix.length);
38
+ }
32
39
  /**
33
40
  * Read the installed frameworks from the on-disk registry.
34
41
  */
@@ -519,6 +526,19 @@ export const doctorHandler = {
519
526
  catch (error) {
520
527
  console.log(`\n── Grok Build native extensions ──\n ⚠ unable to audit: ${error instanceof Error ? error.message : String(error)}`);
521
528
  }
529
+ try {
530
+ const projectDir = ctx.cwd || process.cwd();
531
+ const provider = flagValue(ctx.args, '--provider');
532
+ const requested = provider === 'muse' || ctx.args.includes('--all-providers');
533
+ const { inspectMuseNative, isMuseDeployedProject } = await import('../../mcp/muse-native-config.mjs');
534
+ if (requested || await isMuseDeployedProject(projectDir)) {
535
+ const native = await inspectMuseNative({ projectDir });
536
+ console.log(`\n── Muse Code native extensions ──\n CLI: ${native.cli}\n Hooks: ${native.hooks}\n MCP: ${native.mcp}`);
537
+ }
538
+ }
539
+ catch (error) {
540
+ console.log(`\n── Muse Code native extensions ──\n ⚠ unable to audit: ${error instanceof Error ? error.message : String(error)}`);
541
+ }
522
542
  try {
523
543
  const { buildAgentSkillsDoctorSection } = await import('../../skills/doctor.js');
524
544
  const section = buildAgentSkillsDoctorSection(ctx.cwd || process.cwd());
@@ -17,6 +17,7 @@ import { validateAuthorization, } from '../policy/authorization.js';
17
17
  import { projectAiwgPath, projectControlPath, resolveProjectAiwgDir, } from './project-artifacts.js';
18
18
  import { defaultThreatAssessmentConfig, validateThreatAssessmentConfig, } from '../security/threat-assessment-config.js';
19
19
  import { defaultArtifactOutputs, validateArtifactOutputs } from '../artifacts/output-policy.js';
20
+ import { validateGatesConfig } from '../gates/floors.js';
20
21
  import { validateUhpConfig } from '../uhp/config.js';
21
22
  const CONFIG_FILENAME = 'aiwg.config';
22
23
  /**
@@ -174,7 +175,7 @@ export function resolveDelivery(delivery) {
174
175
  * should tune. Conservative 10 default.
175
176
  * - grokbot: desktop multi-agent; conservative 4 until native evidence.
176
177
  * - grok-build: experimental; conservative 4 until Wave 2/3 evidence.
177
- * - muse: experimental; conservative 4 until Wave 2/3 evidence.
178
+ * - muse: stable; conservative 4 until native concurrency evidence.
178
179
  * - unknown: conservative 4 default.
179
180
  */
180
181
  export const PROVIDER_PARALLELISM_DEFAULTS = {
@@ -192,7 +193,7 @@ export const PROVIDER_PARALLELISM_DEFAULTS = {
192
193
  // Desktop multi-agent; conservative until native concurrency evidence exists.
193
194
  grokbot: { max_parallel_subagents: 4, max_parallel_ralph_loops: 2, max_parallel_mc_missions: 4 },
194
195
  'grok-build': { max_parallel_subagents: 4, max_parallel_ralph_loops: 2, max_parallel_mc_missions: 4 },
195
- // Muse Code is experimental; conservative until Wave 2/3 native concurrency evidence (#235).
196
+ // Muse Code is stable; keep conservative defaults until native concurrency evidence (#235).
196
197
  muse: { max_parallel_subagents: 4, max_parallel_ralph_loops: 2, max_parallel_mc_missions: 4 },
197
198
  // Conservative defaults for remaining stable/experimental harnesses (#249).
198
199
  openhuman: { max_parallel_subagents: 4, max_parallel_ralph_loops: 2, max_parallel_mc_missions: 4 },
@@ -882,6 +883,15 @@ export async function readAiwgConfig(projectDir) {
882
883
  const uhpErrors = validateUhpConfig(parsed.uhp);
883
884
  if (uhpErrors.length > 0)
884
885
  throw new Error(`Invalid .aiwg/aiwg.config:\n${uhpErrors.join('\n')}`);
886
+ // Gates errors warn (non-fatal): an invalid `gates` section must not brick
887
+ // every command that reads the config. Gates entry points (`aiwg gates
888
+ // evaluate`, binding resolution with floors) re-validate strictly via
889
+ // `validateGatesConfig` and fail closed there; `writeAiwgConfig` below
890
+ // stays strict so bad policy is never persisted.
891
+ const gatesErrors = validateGatesConfig(parsed.gates);
892
+ if (gatesErrors.length > 0) {
893
+ console.warn(`Invalid gates section in .aiwg/aiwg.config (ignored):\n${gatesErrors.join('\n')}`);
894
+ }
885
895
  return parsed;
886
896
  }
887
897
  /**
@@ -903,6 +913,9 @@ export async function writeAiwgConfig(projectDir, config) {
903
913
  const uhpErrors = validateUhpConfig(config.uhp);
904
914
  if (uhpErrors.length > 0)
905
915
  throw new Error(`Invalid .aiwg/aiwg.config:\n${uhpErrors.join('\n')}`);
916
+ const gatesErrors = validateGatesConfig(config.gates);
917
+ if (gatesErrors.length > 0)
918
+ throw new Error(`Invalid .aiwg/aiwg.config:\n${gatesErrors.join('\n')}`);
906
919
  const localPath = getConfigPath(projectDir);
907
920
  const artifactDir = resolveProjectAiwgDir(projectDir);
908
921
  const artifactPath = join(artifactDir, CONFIG_FILENAME);
@@ -76,6 +76,31 @@ export function applyPrimitiveAcceptance(definition, policy, observation) {
76
76
  }
77
77
  return routed(observation, policy, policy.defaultRoute, 'default', evidence);
78
78
  }
79
+ export function applyTargetAcceptance(definition, target, observation) {
80
+ if (observation.status !== 'success')
81
+ return observation;
82
+ if (target.acceptance.mode === 'typed-value')
83
+ return observation;
84
+ if (target.acceptance.mode === 'primitive-policy')
85
+ return applyPrimitiveAcceptance(definition, target.acceptance, observation);
86
+ if (!observation.uncertainty || observation.uncertainty.confidence === null)
87
+ return abstained('missing-confidence', observation);
88
+ if (observation.uncertainty.profile !== target.acceptance.profile)
89
+ return abstained('confidence-profile-mismatch', observation);
90
+ // Compare on the probability scale: minimumBps / 10_000 is the exact double for the decimal threshold,
91
+ // whereas confidence * 10_000 can round below it (0.7 * 10_000 === 6999.999999999999).
92
+ return observation.uncertainty.confidence < target.acceptance.minimumBps / 10_000
93
+ ? abstained('low-confidence', observation) : observation;
94
+ }
95
+ // Mirrors the evaluator's failure observation: an abstention keeps provenance but drops the value.
96
+ function abstained(reason, observation) {
97
+ return {
98
+ status: 'abstained', reason, uncertainty: observation.uncertainty ?? null,
99
+ actualModel: observation.actualModel ?? null,
100
+ usage: observation.usage ?? { inputTokens: null, outputTokens: null, costUsd: null },
101
+ requestId: observation.requestId ?? null,
102
+ };
103
+ }
79
104
  function deriveEvidence(definition, observation) {
80
105
  const values = {};
81
106
  const add = (metric, value, normalized, provenance, calibrationRef = null) => {
@@ -34,6 +34,7 @@ export class JevDecisionAdapter {
34
34
  pinnedFetch;
35
35
  now;
36
36
  region;
37
+ observeResponseBody;
37
38
  constructor(options = {}) {
38
39
  this.endpoint = options.endpoint ?? JEV_ENDPOINT;
39
40
  this.fetchImpl = options.fetch ?? fetch;
@@ -42,6 +43,23 @@ export class JevDecisionAdapter {
42
43
  this.pinnedFetch = options.pinnedFetch ?? pinnedHttpsFetch;
43
44
  this.now = options.now ?? Date.now;
44
45
  this.region = options.region?.trim() ? options.region : null;
46
+ this.observeResponseBody = options.observeResponseBody;
47
+ }
48
+ observeResponse(body) {
49
+ if (!this.observeResponseBody)
50
+ return;
51
+ let parsed = {};
52
+ try {
53
+ parsed = asRecord(parseBoundedJson(body));
54
+ }
55
+ catch { /* Invalid answers still have bounded body evidence. */ }
56
+ let usage = emptyUsage();
57
+ try {
58
+ usage = normalizeUsage(parsed.usage);
59
+ }
60
+ catch { /* Missing usage is unknown, including on auth errors. */ }
61
+ this.observeResponseBody(body, { actualModel: typeof parsed.model === 'string' && parsed.model ? parsed.model : null,
62
+ usage });
45
63
  }
46
64
  async capabilities() {
47
65
  return {
@@ -167,6 +185,7 @@ export class JevDecisionAdapter {
167
185
  if (!response.ok) {
168
186
  try {
169
187
  const errorBody = await readBoundedBody(response, signal);
188
+ this.observeResponse(errorBody);
170
189
  if (!metadata.requestId && response.headers.get('content-type')?.includes('json')) {
171
190
  let parsedError;
172
191
  try {
@@ -198,6 +217,7 @@ export class JevDecisionAdapter {
198
217
  let parsed;
199
218
  try {
200
219
  const encoded = await readBoundedBody(response, signal);
220
+ this.observeResponse(encoded);
201
221
  parsed = parseBoundedJson(encoded);
202
222
  if (request.signal.aborted)
203
223
  return externalInterruption(request, 'terminal-response', metadata);
@@ -304,7 +324,7 @@ export class JevDecisionAdapter {
304
324
  }
305
325
  if (!response.ok) {
306
326
  try {
307
- await readBoundedBody(response, signal);
327
+ this.observeResponse(await readBoundedBody(response, signal));
308
328
  }
309
329
  catch {
310
330
  return batchFailure(requests, failure('invalid-output', metadata));
@@ -313,7 +333,9 @@ export class JevDecisionAdapter {
313
333
  return batchFailure(requests, failure(mapStatus(response.status), { ...metadata, ...(retryAfterMs === null ? {} : { retryAfterMs }) }));
314
334
  }
315
335
  try {
316
- const parsed = parseBoundedJson(await readBoundedBody(response, signal));
336
+ const encoded = await readBoundedBody(response, signal);
337
+ this.observeResponse(encoded);
338
+ const parsed = parseBoundedJson(encoded);
317
339
  const normalized = normalizeBatchResponse(requests, parsed).map(answer => ({
318
340
  questionId: answer.questionId,
319
341
  observation: { ...answer.observation, ...metadata },
@@ -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);