@aiwg/cli 2026.10.1 → 2026.10.4

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 (361) hide show
  1. package/README.md +2 -2
  2. package/agentic/code/providers/capability-matrix.yaml +270 -0
  3. package/agentic/code/providers/model-capabilities.v1.json +3183 -24
  4. package/bin/aiwg.mjs +135 -47
  5. package/dist/src/a2a/codecs.js +9 -0
  6. package/dist/src/agents/grounding/knowledge-base.js +54 -6
  7. package/dist/src/artifacts/browser-export.js +6 -0
  8. package/dist/src/artifacts/checksum-manifest.js +1 -0
  9. package/dist/src/artifacts/citation-parser.js +173 -92
  10. package/dist/src/artifacts/citation-table-schema.js +59 -0
  11. package/dist/src/artifacts/cli.js +57 -18
  12. package/dist/src/artifacts/corpus-tools/snapshot.js +1 -1
  13. package/dist/src/artifacts/corpus-tools/vision-extract.js +107 -15
  14. package/dist/src/artifacts/corpus-views/ref-parser.js +3 -1
  15. package/dist/src/artifacts/corpus-views/renderers.js +3 -3
  16. package/dist/src/artifacts/discovery-eval.js +6 -4
  17. package/dist/src/artifacts/fortemi-core-query-adapter.js +36 -3
  18. package/dist/src/artifacts/fortemi-core-sync.js +166 -40
  19. package/dist/src/artifacts/framework-capabilities.js +171 -0
  20. package/dist/src/artifacts/framework-index-paths.js +73 -0
  21. package/dist/src/artifacts/framework-source-digest.js +75 -0
  22. package/dist/src/artifacts/index-builder.js +285 -131
  23. package/dist/src/artifacts/index-files.js +132 -18
  24. package/dist/src/artifacts/index-preparation.js +65 -0
  25. package/dist/src/artifacts/index-refresh.js +576 -0
  26. package/dist/src/artifacts/index-worker.mjs +9 -0
  27. package/dist/src/artifacts/prebuilt-build-lock.js +47 -4
  28. package/dist/src/artifacts/prebuilt-deployment.js +46 -0
  29. package/dist/src/artifacts/prebuilt-relations.js +80 -0
  30. package/dist/src/artifacts/query-engine.js +410 -88
  31. package/dist/src/artifacts/relations-builder.js +168 -0
  32. package/dist/src/artifacts/relations.js +148 -0
  33. package/dist/src/artifacts/repair.js +9 -4
  34. package/dist/src/artifacts/stats.js +5 -2
  35. package/dist/src/artifacts/types.js +30 -10
  36. package/dist/src/artifacts/watcher.js +1 -1
  37. package/dist/src/auth/credential-store.js +2 -1
  38. package/dist/src/catalog/cli.d.mts +5 -0
  39. package/dist/src/channel/manager.d.mts +20 -0
  40. package/dist/src/channel/manager.mjs +78 -12
  41. package/dist/src/cli/child-process-result.js +40 -0
  42. package/dist/src/cli/command-log.js +2 -3
  43. package/dist/src/cli/env.js +38 -16
  44. package/dist/src/cli/flag-parsing.js +236 -0
  45. package/dist/src/cli/handlers/artifacts.js +3 -0
  46. package/dist/src/cli/handlers/auth.js +3 -0
  47. package/dist/src/cli/handlers/decision.js +218 -5
  48. package/dist/src/cli/handlers/diagnose.js +13 -5
  49. package/dist/src/cli/handlers/effect.js +4 -2
  50. package/dist/src/cli/handlers/evidence.js +3 -0
  51. package/dist/src/cli/handlers/feedback.js +14 -5
  52. package/dist/src/cli/handlers/help.js +1 -0
  53. package/dist/src/cli/handlers/index.js +3 -1
  54. package/dist/src/cli/handlers/init.js +248 -30
  55. package/dist/src/cli/handlers/lint.js +5 -0
  56. package/dist/src/cli/handlers/mc.js +23 -0
  57. package/dist/src/cli/handlers/models.js +169 -10
  58. package/dist/src/cli/handlers/ralph-launcher.js +2 -0
  59. package/dist/src/cli/handlers/ralph.js +3 -0
  60. package/dist/src/cli/handlers/refresh.js +28 -0
  61. package/dist/src/cli/handlers/regenerate.js +14 -0
  62. package/dist/src/cli/handlers/resource-versions.js +3 -0
  63. package/dist/src/cli/handlers/rules.js +195 -0
  64. package/dist/src/cli/handlers/run.js +4 -0
  65. package/dist/src/cli/handlers/runtime-info.js +25 -11
  66. package/dist/src/cli/handlers/script-runner.js +19 -8
  67. package/dist/src/cli/handlers/session.js +406 -62
  68. package/dist/src/cli/handlers/sessions.js +243 -76
  69. package/dist/src/cli/handlers/setup-manifest.js +474 -79
  70. package/dist/src/cli/handlers/setup.js +180 -39
  71. package/dist/src/cli/handlers/steward.js +11 -0
  72. package/dist/src/cli/handlers/subcommands.js +60 -3
  73. package/dist/src/cli/handlers/team.js +18 -8
  74. package/dist/src/cli/handlers/uhp.js +3 -0
  75. package/dist/src/cli/handlers/use.js +307 -55
  76. package/dist/src/cli/handlers/utilities.js +125 -26
  77. package/dist/src/cli/handlers/version.js +8 -0
  78. package/dist/src/cli/prompt-utils.js +72 -0
  79. package/dist/src/cli/provider-resolution.js +11 -0
  80. package/dist/src/cli/router.js +15 -1
  81. package/dist/src/cli/services/deployment-verification.js +19 -9
  82. package/dist/src/cli/services/provider-prepare-coordinator.js +24 -0
  83. package/dist/src/cli/skill-usage.js +2 -3
  84. package/dist/src/config/aiwg-config.js +349 -35
  85. package/dist/src/config/cli.js +256 -23
  86. package/dist/src/config/decision-config.js +106 -0
  87. package/dist/src/config/dispatch-thresholds-runtime.d.mts +8 -0
  88. package/dist/src/config/dispatch-thresholds-runtime.mjs +38 -0
  89. package/dist/src/config/gitignore.js +50 -0
  90. package/dist/src/config/index-graph-overrides.js +36 -0
  91. package/dist/src/config/project-artifacts-health.d.mts +37 -0
  92. package/dist/src/config/project-artifacts-health.mjs +14 -3
  93. package/dist/src/config/project-artifacts-runtime.d.mts +16 -0
  94. package/dist/src/config/user-config-dir.d.mts +9 -0
  95. package/dist/src/config/user-config.js +38 -11
  96. package/dist/src/config/user-registry.js +23 -15
  97. package/dist/src/config/user-state.js +7 -0
  98. package/dist/src/decision/adapters/jev.js +75 -27
  99. package/dist/src/decision/ask.js +203 -43
  100. package/dist/src/decision/context-live-qualification.js +2 -2
  101. package/dist/src/decision/driver.js +14 -7
  102. package/dist/src/decision/ensemble/aggregate.js +23 -10
  103. package/dist/src/decision/ensemble-study/multifact-study.js +6 -2
  104. package/dist/src/decision/entry.js +25 -6
  105. package/dist/src/decision/graph-live-qualification.js +1 -1
  106. package/dist/src/decision/graph-run.js +9 -0
  107. package/dist/src/decision/heldout/collector.js +15 -14
  108. package/dist/src/decision/heldout/contract.js +30 -7
  109. package/dist/src/decision/index.js +1 -0
  110. package/dist/src/decision/interop.js +60 -8
  111. package/dist/src/decision/jev-credentials.js +15 -1
  112. package/dist/src/decision/ledger.js +942 -0
  113. package/dist/src/decision/offload-activation.js +137 -0
  114. package/dist/src/decision/offload.js +550 -0
  115. package/dist/src/decision/qualification/load.js +4 -6
  116. package/dist/src/decision/savings.js +28 -0
  117. package/dist/src/decision/validate.js +6 -3
  118. package/dist/src/effects/dispatch-verifier.js +252 -0
  119. package/dist/src/effects/index.js +1 -0
  120. package/dist/src/effects/types.js +5 -1
  121. package/dist/src/effects/verifiers/git.js +2 -1
  122. package/dist/src/effects/verifiers/tracker.js +26 -14
  123. package/dist/src/extensions/commands/definitions.js +19 -0
  124. package/dist/src/extensions/project-local-remove.js +36 -1
  125. package/dist/src/features/installer.js +2 -2
  126. package/dist/src/flow/graph-metadata.js +5 -0
  127. package/dist/src/gates/providers/loader.js +64 -18
  128. package/dist/src/installation/manager-command.d.mts +18 -0
  129. package/dist/src/installation/manager-command.mjs +15 -3
  130. package/dist/src/installation/manager.d.mts +49 -0
  131. package/dist/src/installation/manager.mjs +95 -5
  132. package/dist/src/issues/cli.js +4 -4
  133. package/dist/src/issues/local.js +24 -5
  134. package/dist/src/jobs/runner.js +1 -1
  135. package/dist/src/lint/cli.js +27 -1
  136. package/dist/src/lint/loader.js +2 -1
  137. package/dist/src/lint/reporters.js +25 -1
  138. package/dist/src/lint/rule-activation.js +43 -0
  139. package/dist/src/lint/runner.js +137 -62
  140. package/dist/src/mcp/adapters/codex-runtime.js +366 -56
  141. package/dist/src/mcp/claude-install.d.mts +4 -0
  142. package/dist/src/mcp/claude-install.mjs +41 -0
  143. package/dist/src/mcp/cli.d.mts +1 -0
  144. package/dist/src/mcp/cli.mjs +620 -85
  145. package/dist/src/mcp/config-file.d.mts +28 -0
  146. package/dist/src/mcp/config-file.mjs +279 -0
  147. package/dist/src/mcp/config-layers.d.mts +36 -0
  148. package/dist/src/mcp/config-layers.mjs +348 -0
  149. package/dist/src/mcp/credentials.d.mts +51 -0
  150. package/dist/src/mcp/credentials.mjs +484 -0
  151. package/dist/src/mcp/grok-build-config.d.mts +6 -0
  152. package/dist/src/mcp/grok-build-config.mjs +17 -27
  153. package/dist/src/mcp/helpers.mjs +109 -4
  154. package/dist/src/mcp/muse-native-config.d.mts +13 -0
  155. package/dist/src/mcp/muse-native-config.mjs +1 -0
  156. package/dist/src/mcp/omp-config.d.mts +4 -0
  157. package/dist/src/mcp/omp-config.mjs +50 -29
  158. package/dist/src/mcp/profiles.js +97 -23
  159. package/dist/src/mcp/profiles.mjs +99 -27
  160. package/dist/src/mcp/registry.js +248 -131
  161. package/dist/src/mcp/registry.mjs +169 -100
  162. package/dist/src/mcp/server-config.d.mts +3 -0
  163. package/dist/src/mcp/server-config.mjs +89 -0
  164. package/dist/src/mcp/server.mjs +127 -82
  165. package/dist/src/mcp/test-simple.mjs +2 -1
  166. package/dist/src/mcp/toml-editor.d.mts +4 -0
  167. package/dist/src/mcp/toml-string.d.mts +1 -0
  168. package/dist/src/mcp/toml-string.mjs +9 -0
  169. package/dist/src/mcp/toml-strip-mcp.d.mts +1 -0
  170. package/dist/src/mcp/toml-strip-mcp.mjs +135 -0
  171. package/dist/src/mcp/tool-filters.d.mts +46 -0
  172. package/dist/src/mcp/tool-filters.mjs +357 -0
  173. package/dist/src/mcp/tools/agentic-sandbox.mjs +3 -1
  174. package/dist/src/mcp/tools/decision.mjs +22 -3
  175. package/dist/src/mcp/tools/discovery.mjs +62 -55
  176. package/dist/src/mcp/tools/orchestration.mjs +50 -2
  177. package/dist/src/mcp/tools/subsystems.mjs +39 -8
  178. package/dist/src/mcp/tools/worker-invoke.mjs +2856 -0
  179. package/dist/src/mcp/tools/worker-shell-control.mjs +289 -0
  180. package/dist/src/mcp/tools/worker-stdin-runtime.mjs +79 -0
  181. package/dist/src/memory/project-registry.js +2 -2
  182. package/dist/src/mission-protocol/codecs.js +647 -1
  183. package/dist/src/mission-protocol/types.js +110 -0
  184. package/dist/src/mission-protocol/worker-status.js +2 -0
  185. package/dist/src/models/config-loader.js +76 -14
  186. package/dist/src/models/grok-build-models.js +93 -41
  187. package/dist/src/models/model-capabilities.v1.json +3183 -24
  188. package/dist/src/models/model-discovery.js +931 -67
  189. package/dist/src/models/provider-policy.js +719 -11
  190. package/dist/src/models/qualification-digest.d.mts +2 -0
  191. package/dist/src/models/qualification-digest.mjs +17 -0
  192. package/dist/src/models/resolver.js +261 -0
  193. package/dist/src/models/worker-direct-readiness.js +550 -0
  194. package/dist/src/models/worker-dispatch-offer.js +169 -0
  195. package/dist/src/models/worker-health-readiness.js +144 -0
  196. package/dist/src/models/worker-installation.d.mts +39 -0
  197. package/dist/src/models/worker-installation.mjs +986 -0
  198. package/dist/src/models/wrapper-route.js +18 -0
  199. package/dist/src/ops/registry.js +9 -14
  200. package/dist/src/plugins/standalone-packager.js +4 -3
  201. package/dist/src/process/bounded-child-process.d.mts +84 -0
  202. package/dist/src/process/bounded-child-process.mjs +430 -0
  203. package/dist/src/process/cli-command.d.mts +16 -0
  204. package/dist/src/process/cli-command.mjs +75 -0
  205. package/dist/src/process/signal-child.d.mts +6 -0
  206. package/dist/src/process/signal-child.mjs +7 -0
  207. package/dist/src/providers/capability-matrix.js +8 -0
  208. package/dist/src/providers/capability-matrix.yaml +270 -0
  209. package/dist/src/providers/deployed-skill-roots.js +20 -0
  210. package/dist/src/providers/grok-build-acp.js +3 -2
  211. package/dist/src/providers/grok-build-headless.js +53 -23
  212. package/dist/src/providers/grok-build-inspect.js +1 -0
  213. package/dist/src/providers/omp-agent.d.mts +8 -0
  214. package/dist/src/providers/omp-diagnostics.d.mts +1 -0
  215. package/dist/src/providers/omp-paths.d.mts +13 -0
  216. package/dist/src/providers/provider-definitions.js +9 -1
  217. package/dist/src/providers/provider-definitions.mjs +9 -2
  218. package/dist/src/providers/skill-ownership.d.mts +9 -0
  219. package/dist/src/providers/skill-ownership.mjs +88 -0
  220. package/dist/src/rules/activation.d.mts +21 -0
  221. package/dist/src/rules/activation.mjs +62 -0
  222. package/dist/src/rules/inline-budget.d.mts +26 -0
  223. package/dist/src/rules/inline-budget.mjs +66 -0
  224. package/dist/src/rules/inventory.js +129 -0
  225. package/dist/src/rules/pin-state.js +73 -0
  226. package/dist/src/rules/pins.d.mts +14 -0
  227. package/dist/src/rules/pins.mjs +112 -0
  228. package/dist/src/rules/rule-frontmatter.schema.json +197 -0
  229. package/dist/src/serve/executor-registry.js +16 -24
  230. package/dist/src/serve/fleet-mission-conductor.js +9 -0
  231. package/dist/src/serve/mission-conductor.js +47 -12
  232. package/dist/src/serve/shared-host-scheduler.js +2 -0
  233. package/dist/src/serve/worker-invocation.js +558 -0
  234. package/dist/src/serve/worker-stdin-frame.d.mts +10 -0
  235. package/dist/src/serve/worker-stdin-frame.mjs +107 -0
  236. package/dist/src/sessions/adapter-registry.js +49 -0
  237. package/dist/src/sessions/adapters/claude.js +39 -54
  238. package/dist/src/sessions/adapters/codex.js +82 -67
  239. package/dist/src/sessions/adapters/copilot.js +4 -4
  240. package/dist/src/sessions/adapters/cursor.js +40 -57
  241. package/dist/src/sessions/adapters/deepseek-harness.js +15 -12
  242. package/dist/src/sessions/adapters/factory.js +31 -27
  243. package/dist/src/sessions/adapters/generic.js +43 -9
  244. package/dist/src/sessions/adapters/grok-build.js +11 -11
  245. package/dist/src/sessions/adapters/grokbot-locator.js +111 -0
  246. package/dist/src/sessions/adapters/grokbot.js +434 -24
  247. package/dist/src/sessions/adapters/hermes.js +27 -29
  248. package/dist/src/sessions/adapters/muse-workspace-scan.js +128 -0
  249. package/dist/src/sessions/adapters/muse.js +200 -104
  250. package/dist/src/sessions/adapters/omp.js +28 -14
  251. package/dist/src/sessions/adapters/openclaw.js +89 -79
  252. package/dist/src/sessions/adapters/opencode.js +27 -24
  253. package/dist/src/sessions/adapters/openhuman.js +18 -60
  254. package/dist/src/sessions/adapters/pi.js +30 -14
  255. package/dist/src/sessions/adapters/warp.js +3 -3
  256. package/dist/src/sessions/adapters/windsurf.js +19 -48
  257. package/dist/src/sessions/batch-contracts.js +25 -5
  258. package/dist/src/sessions/batch-import.js +72 -51
  259. package/dist/src/sessions/candidates.js +47 -11
  260. package/dist/src/sessions/contracts.js +75 -2
  261. package/dist/src/sessions/coverage-preflight.js +134 -0
  262. package/dist/src/sessions/discovery.js +3 -0
  263. package/dist/src/sessions/export-plan.js +4 -3
  264. package/dist/src/sessions/grok-build-cli.js +67 -5
  265. package/dist/src/sessions/import-exclusions.js +72 -0
  266. package/dist/src/sessions/import-lease.js +34 -18
  267. package/dist/src/sessions/import-limits.js +37 -0
  268. package/dist/src/sessions/importer.js +84 -46
  269. package/dist/src/sessions/index.js +2 -0
  270. package/dist/src/sessions/lifecycle.js +45 -0
  271. package/dist/src/sessions/policy.js +133 -25
  272. package/dist/src/sessions/readers.js +106 -53
  273. package/dist/src/sessions/refresh.js +106 -0
  274. package/dist/src/sessions/repository.js +199 -63
  275. package/dist/src/sessions/workspace-discovery.js +315 -88
  276. package/dist/src/sessions/workspace-matching.js +107 -0
  277. package/dist/src/skills/adapters/clawhub.js +10 -8
  278. package/dist/src/skills/adapters/openclaw.js +10 -7
  279. package/dist/src/skills/importer.js +1 -0
  280. package/dist/src/smiths/context-pipeline/aiwg-md.js +11 -0
  281. package/dist/src/smiths/context-pipeline/claude-hook.js +3 -26
  282. package/dist/src/smiths/context-pipeline/finalization.js +6 -5
  283. package/dist/src/smiths/context-pipeline/generator.js +21 -4
  284. package/dist/src/smiths/context-pipeline/parallelism-section.js +195 -14
  285. package/dist/src/smiths/context-pipeline/workspace-context.js +116 -9
  286. package/dist/src/smiths/hook-bridge/codex-translator.js +6 -5
  287. package/dist/src/smiths/mcpsmith/example.js +1 -1
  288. package/dist/src/smiths/mcpsmith/generator.js +1 -1
  289. package/dist/src/smiths/toolsmith/runtime-discovery.d.mts +10 -0
  290. package/dist/src/smiths/toolsmith/runtime-discovery.mjs +85 -31
  291. package/dist/src/storage/backends/fs.js +18 -76
  292. package/dist/src/storage/backends/logseq.js +68 -7
  293. package/dist/src/storage/backends/obsidian.js +1 -0
  294. package/dist/src/storage/fortemi-qualification-receipt.js +2 -2
  295. package/dist/src/testing/generators/test-code-generator.js +57 -30
  296. package/dist/src/tracker/forge-http.js +2 -1
  297. package/dist/src/uhp/client.js +11 -1
  298. package/dist/src/uhp/mission.js +5 -0
  299. package/dist/src/update/checker.d.mts +2 -0
  300. package/dist/src/update/checker.mjs +5 -5
  301. package/dist/src/update/notifier.mjs +54 -2
  302. package/dist/src/update/service.d.mts +22 -0
  303. package/dist/src/utils/contained-filesystem.d.mts +37 -0
  304. package/dist/src/utils/contained-filesystem.mjs +683 -0
  305. package/dist/src/utils/temporary-directory.js +36 -0
  306. package/dist/src/writing/example-generator.js +1 -1
  307. package/dist/src/writing/fidelity.js +18 -1
  308. package/package.json +32 -2
  309. package/schemas/mission-v1.schema.json +2429 -0
  310. package/schemas/models/model-catalog.v1.schema.json +301 -0
  311. package/schemas/models/model-config-compatibility.v1.schema.json +281 -0
  312. package/schemas/models/model-policy.v1.schema.json +14 -0
  313. package/schemas/models/provider-model-capabilities.v1.schema.json +1072 -0
  314. package/tools/_resolve-impl.mjs +39 -2
  315. package/tools/agents/deploy-agents.mjs +64 -23
  316. package/tools/agents/providers/base.mjs +153 -166
  317. package/tools/agents/providers/claude.mjs +8 -24
  318. package/tools/agents/providers/codex.mjs +18 -11
  319. package/tools/agents/providers/cursor.mjs +19 -18
  320. package/tools/agents/providers/grok-build.mjs +3 -1
  321. package/tools/agents/providers/grokbot.mjs +11 -12
  322. package/tools/agents/providers/hermes.mjs +62 -49
  323. package/tools/agents/providers/omp.mjs +5 -2
  324. package/tools/agents/providers/openclaw-translator.mjs +19 -5
  325. package/tools/agents/providers/openclaw.mjs +9 -4
  326. package/tools/agents/providers/pi.mjs +5 -0
  327. package/tools/agents/providers/provider-prepare-channel.mjs +5 -0
  328. package/tools/agents/providers/provider-prepare-coordinator.mjs +247 -0
  329. package/tools/agents/providers/provider-prepare-worker.mjs +84 -0
  330. package/tools/agents/providers/rule-delivery.mjs +1270 -0
  331. package/tools/agents/providers/warp.mjs +20 -26
  332. package/tools/cli/decision-offload-report.mjs +40 -0
  333. package/tools/cli/workspace-status.mjs +862 -0
  334. package/tools/plugin/package-plugins.mjs +186 -8
  335. package/tools/plugin/plugin-links.mjs +114 -0
  336. package/tools/providers/antigravity-transport.mjs +3 -1
  337. package/tools/providers/deepseek-harness-transport.mjs +360 -0
  338. package/tools/providers/omp-transport.mjs +194 -0
  339. package/tools/ralph-external/lib/claude-adapter.mjs +389 -0
  340. package/tools/ralph-external/lib/codex-adapter.mjs +432 -0
  341. package/tools/ralph-external/lib/consecutive-failure-circuit-breaker.mjs +1748 -0
  342. package/tools/ralph-external/lib/deepseek-harness-adapter.mjs +397 -0
  343. package/tools/ralph-external/lib/factory-adapter.mjs +503 -0
  344. package/tools/ralph-external/lib/launcher-recovery-evidence.mjs +55 -0
  345. package/tools/ralph-external/lib/muse-adapter.mjs +364 -0
  346. package/tools/ralph-external/lib/omp-adapter.mjs +315 -0
  347. package/tools/ralph-external/lib/opencode-adapter.mjs +575 -0
  348. package/tools/ralph-external/lib/openhuman-adapter.mjs +75 -0
  349. package/tools/ralph-external/lib/pi-adapter.mjs +329 -0
  350. package/tools/ralph-external/lib/provider-adapter.mjs +748 -0
  351. package/tools/ralph-external/lib/recovery-probe.mjs +72 -0
  352. package/tools/ralph-external/lib/session-environment.mjs +28 -0
  353. package/tools/ralph-external/lib/systemd-client.mjs +49 -0
  354. package/tools/ralph-external/lib/worker-health.mjs +439 -0
  355. package/tools/ralph-external/lib/worker-isolation.mjs +717 -0
  356. package/tools/ralph-external/lib/worker-overflow.mjs +50 -0
  357. package/tools/ralph-external/process-monitor.mjs +1017 -0
  358. package/tools/ralph-external/session-launcher.mjs +5057 -0
  359. package/tools/scripts/resource-preflight.mjs +541 -0
  360. package/tools/skills/deploy-skills-codex.mjs +2 -1
  361. package/vscode-extension/schemas/aiwg.config.v1.json +2916 -0
@@ -1,8 +1,10 @@
1
+ import { aggregateModalAnswers } from './ensemble/aggregate.js';
2
+ import { readAiwgConfigCandidate } from '../config/aiwg-config.js';
1
3
  import { readFile } from 'node:fs/promises';
2
4
  import { JEV_ENDPOINT, JevDecisionAdapter } from './adapters/jev.js';
3
5
  import { evaluateDecisionRuleset } from './evaluate.js';
4
6
  import { artifactPin } from './validate.js';
5
- import { JEV_CREDENTIAL_REF, JEV_DEFAULT_REGION, lookupJevCredential, removeJevCredentialFile, resolveJevCredentialBytes, writeJevCredentialFile, } from './jev-credentials.js';
7
+ import { JEV_CREDENTIAL_REF, JEV_DEFAULT_REGION, lookupJevCredential, removeJevCredentialFile, writeJevCredentialFile, } from './jev-credentials.js';
6
8
  const CONTEXT_LIMIT_BYTES = 32 * 1024;
7
9
  const MODEL = 'jev-latest';
8
10
  const PURPOSE = 'agent-bounded-decision';
@@ -39,9 +41,22 @@ export function validateDecisionAskInput(input) {
39
41
  if (!Number.isFinite(threshold) || threshold < 0 || threshold > 1) {
40
42
  usageError('invalid-threshold', '--threshold must be a probability from 0 to 1');
41
43
  }
44
+ for (const key of ['minAgreement', 'agreementConfidenceFloor']) {
45
+ const value = input[key];
46
+ if (value !== undefined && (typeof value !== 'number' || !Number.isFinite(value) || value < 0 || value > 1)) {
47
+ usageError('invalid-agreement-policy', `${key} must be a probability from 0 to 1`);
48
+ }
49
+ }
42
50
  const timeoutMs = input.timeoutMs ?? 15_000;
43
51
  if (!Number.isInteger(timeoutMs) || timeoutMs <= 0)
44
52
  usageError('invalid-timeout', '--timeout-ms must be a positive integer');
53
+ const passes = input.passes ?? DEFAULT_DECISION_PASSES;
54
+ const escalationPasses = input.escalationPasses ?? defaultEscalationPasses(passes);
55
+ if (!Number.isSafeInteger(passes) || passes <= 0)
56
+ usageError('invalid-passes', '--passes must be a positive safe integer');
57
+ if (!Number.isSafeInteger(escalationPasses) || escalationPasses < passes) {
58
+ usageError('invalid-escalation-passes', '--escalation-passes must be a positive safe integer >= passes');
59
+ }
45
60
  if (input.mode.kind === 'choices') {
46
61
  if (input.mode.choices.length < 2 || input.mode.choices.length > 255)
47
62
  usageError('invalid-choices', '--choices requires 2..255 ids');
@@ -239,66 +254,209 @@ function publicAnswer(mode, value, distribution) {
239
254
  }
240
255
  return typeof value === 'string' ? { answer: value } : { answer: null };
241
256
  }
257
+ /** Product default: 5 passes, escalating to 10 when the first round is not decisive. */
258
+ export const DEFAULT_DECISION_PASSES = 5;
259
+ /** Escalation defaults to twice the initial pass count (5 -> 10, 10 -> 20). */
260
+ export function defaultEscalationPasses(passes) {
261
+ return passes * 2;
262
+ }
242
263
  export async function askDecision(input, options = {}) {
264
+ if (options.cwd) {
265
+ let config;
266
+ try {
267
+ config = await readAiwgConfigCandidate(options.cwd);
268
+ }
269
+ catch (error) {
270
+ usageError('invalid-config', error instanceof Error ? error.message : 'Invalid project config');
271
+ }
272
+ input = { ...input, passes: input.passes ?? config?.decision?.ask?.passes,
273
+ escalationPasses: input.escalationPasses ?? config?.decision?.ask?.escalation_passes,
274
+ minAgreement: input.minAgreement ?? config?.decision?.ask?.min_agreement,
275
+ agreementConfidenceFloor: input.agreementConfidenceFloor ?? config?.decision?.ask?.agreement_confidence_floor };
276
+ }
243
277
  validateDecisionAskInput(input);
244
278
  const started = options.now?.() ?? Date.now();
279
+ const wallStarted = Date.now();
280
+ const passesRequested = input.passes ?? DEFAULT_DECISION_PASSES;
281
+ const escalationPasses = input.escalationPasses ?? defaultEscalationPasses(passesRequested);
282
+ const threshold = input.threshold ?? 0.8;
283
+ const minAgreement = input.minAgreement;
284
+ const agreementConfidenceFloor = input.agreementConfidenceFloor ?? 0.55;
285
+ const timeoutMs = input.timeoutMs ?? 15_000;
286
+ const passes = [];
287
+ let escalated = false;
288
+ let earlyStop = null;
289
+ const labels = input.mode.kind === 'yes-no' ? ['false', 'true']
290
+ : input.mode.kind === 'choices' ? input.mode.choices.map(parseChoice).map(choice => choice.id)
291
+ : Array.from({ length: input.mode.high - input.mode.low + 1 }, (_, index) => String(input.mode.kind === 'scale' ? input.mode.low + index : index));
292
+ const aggregate = () => {
293
+ const successful = passes.filter(pass => pass.status === 'succeeded');
294
+ const modal = aggregateModalAnswers(labels, successful.map(pass => String(pass.answer)));
295
+ const confidences = successful.flatMap(pass => pass.confidence === null ? [] : [pass.confidence]);
296
+ return {
297
+ algorithm: 'majority-v1', semantics: 'stability-signal-not-correctness',
298
+ passesRequested, escalationPasses, passesRun: passes.length, escalated, earlyStop,
299
+ minAgreement: minAgreement ?? null, agreementConfidenceFloor,
300
+ // Failed/abstaining passes have no vote but remain in the denominator.
301
+ agreementRatio: passes.length ? Math.max(0, ...Object.values(modal.votes)) / passes.length : 0,
302
+ votes: modal.votes,
303
+ answer: modal.value === null ? null : input.mode.kind === 'yes-no' ? modal.value === 'true'
304
+ : input.mode.kind === 'scale' ? Number(modal.value) : modal.value,
305
+ confidenceSummary: { count: confidences.length, missing: passes.length - confidences.length,
306
+ mean: confidences.length ? confidences.reduce((sum, value) => sum + value, 0) / confidences.length : null,
307
+ min: confidences.length ? Math.min(...confidences) : null, max: confidences.length ? Math.max(...confidences) : null },
308
+ passes: [...passes],
309
+ };
310
+ };
311
+ // Sampling agreement is stability evidence, not calibrated correctness.
312
+ // Confidence acceptance retains the original requirement that every pass succeeds.
313
+ const answeredBy = (summary) => {
314
+ const successful = passes.filter(pass => pass.status === 'succeeded');
315
+ if (successful.length < passesRequested || summary.answer === null)
316
+ return null;
317
+ const mean = summary.confidenceSummary.mean;
318
+ if (successful.length === passes.length && !summary.confidenceSummary.missing
319
+ && summary.agreementRatio >= threshold && mean !== null && mean >= threshold)
320
+ return 'confidence';
321
+ if (minAgreement !== undefined && summary.agreementRatio >= minAgreement
322
+ && summary.confidenceSummary.count === successful.length && mean !== null
323
+ && mean + Number.EPSILON >= agreementConfidenceFloor)
324
+ return 'agreement';
325
+ return null;
326
+ };
327
+ const problem = (summary) => {
328
+ if (answeredBy(summary))
329
+ return null;
330
+ const failed = passes.find(pass => pass.status !== 'succeeded');
331
+ if (failed)
332
+ return failed.reason;
333
+ if (summary.agreementRatio < threshold)
334
+ return 'low-agreement';
335
+ return 'low-confidence';
336
+ };
337
+ const finish = (forcedReason) => {
338
+ const summary = aggregate();
339
+ const reason = forcedReason ?? problem(summary);
340
+ const latencyMs = Math.max(0, (options.now?.() ?? Date.now()) - started);
341
+ const model = passes.map(pass => pass.model).filter(model => model !== null).at(-1) ?? null;
342
+ const totalTokens = (key) => {
343
+ const values = passes.flatMap(pass => pass.usage?.[key] == null ? [] : [pass.usage[key]]);
344
+ return values.length ? values.reduce((sum, value) => sum + value, 0) : null;
345
+ };
346
+ const combinedUsage = passes.some(pass => pass.usage !== null)
347
+ ? { inputTokens: totalTokens('inputTokens'), outputTokens: totalTokens('outputTokens') } : null;
348
+ const result = reason ? fallback(reason, latencyMs) : {
349
+ schema: 'aiwg-decision-ask/v1', status: 'answered', answer: summary.answer,
350
+ confidence: summary.agreementRatio * summary.confidenceSummary.mean, fallback: null, reason: 'none',
351
+ model, usage: combinedUsage, latencyMs,
352
+ ...(input.mode.kind === 'yes-no' ? { probability: passes.reduce((sum, pass) => sum + (pass.probability ?? 0), 0)
353
+ / passes.filter(pass => pass.status === 'succeeded').length } : {}),
354
+ };
355
+ return { ...result, model, usage: combinedUsage, aggregate: summary, answeredBy: reason ? null : answeredBy(summary) };
356
+ };
245
357
  let credential;
246
358
  try {
247
359
  credential = await lookupJevCredential(options);
248
360
  }
249
361
  catch {
250
- return fallback('not-configured', 0);
362
+ return finish('not-configured');
251
363
  }
252
364
  if (!credential?.enabled || !credential.token)
253
- return fallback('not-configured', 0);
254
- const timeoutMs = input.timeoutMs ?? 15_000;
365
+ return finish('not-configured');
255
366
  const def = definition(input);
256
367
  const rules = ruleset(def);
257
- const bind = binding(rules, def.spec.answer.kind, timeoutMs);
258
368
  const adapter = options.adapter ?? options.createAdapter?.({ region: credential.region, endpoint: credential.endpoint })
259
369
  ?? new JevDecisionAdapter({ region: credential.region, ...(credential.endpoint ? {
260
- endpoint: credential.endpoint,
261
- allowedOrigins: [new URL(credential.endpoint).origin],
370
+ endpoint: credential.endpoint, allowedOrigins: [new URL(credential.endpoint).origin],
262
371
  } : {}) });
263
- let result;
264
- try {
265
- result = await (options.evaluate ?? evaluateDecisionRuleset)({
266
- ruleset: rules,
267
- binding: bind,
268
- definitions: { [def.metadata.id]: def },
269
- input: { message: contextMessage(input) },
270
- runId: options.runId ?? 'aiwg-decision-ask',
271
- invocationId: options.invocationId ?? `ask-${Date.now()}`,
272
- adapters: { jev: adapter },
273
- resolveCredential: options.resolveCredential ?? (logicalRef => resolveJevCredentialBytes(logicalRef, options)),
274
- projection: { resolve: () => projectionPolicy(credential.region, credential.endpoint) },
372
+ const controller = new AbortController();
373
+ const remainingMs = () => timeoutMs - (Date.now() - wallStarted);
374
+ const timer = setTimeout(() => controller.abort(), Math.max(0, remainingMs()));
375
+ const invocationId = options.invocationId ?? `ask-${Date.now()}`;
376
+ const runPass = async (index) => {
377
+ const passStarted = Date.now();
378
+ let removeListener = () => { };
379
+ const interrupted = new Promise((_resolve, reject) => {
380
+ const abort = () => reject(new Error('ask deadline'));
381
+ if (controller.signal.aborted)
382
+ abort();
383
+ else {
384
+ controller.signal.addEventListener('abort', abort, { once: true });
385
+ removeListener = () => controller.signal.removeEventListener('abort', abort);
386
+ }
275
387
  });
388
+ try {
389
+ const result = await Promise.race([interrupted, (options.evaluate ?? evaluateDecisionRuleset)({
390
+ ruleset: rules, binding: binding(rules, def.spec.answer.kind, Math.max(1, remainingMs())),
391
+ definitions: { [def.metadata.id]: def }, input: { message: contextMessage(input) },
392
+ runId: options.runId ?? 'aiwg-decision-ask', invocationId: `${invocationId}-pass-${index}`,
393
+ signal: controller.signal, adapters: { jev: adapter },
394
+ resolveCredential: options.resolveCredential ?? (() => Promise.resolve(new TextEncoder().encode(credential.token))),
395
+ projection: { resolve: () => projectionPolicy(credential.region, credential.endpoint) },
396
+ })]);
397
+ const evaluation = result.spec.evaluations.answer;
398
+ const attempt = evaluation?.spec.attempts.at(-1);
399
+ const succeeded = evaluation?.spec.status === 'success';
400
+ return { index, status: succeeded ? 'succeeded' : evaluation?.spec.status === 'abstained' ? 'abstained' : 'failed',
401
+ reason: succeeded ? 'none' : evaluation?.spec.reason ?? result.spec.reason,
402
+ ...(succeeded ? publicAnswer(input.mode, evaluation.spec.value, evaluation.spec.uncertainty?.distribution) : { answer: null }),
403
+ confidence: succeeded ? confidenceFor(input.mode, evaluation) : null,
404
+ model: attempt?.actualModel ?? null, usage: usage(attempt?.usage), latencyMs: Date.now() - passStarted };
405
+ }
406
+ catch {
407
+ return { index, status: 'failed', reason: controller.signal.aborted ? 'timeout' : 'jev-error', answer: null,
408
+ confidence: null, model: null, usage: null, latencyMs: Date.now() - passStarted };
409
+ }
410
+ finally {
411
+ removeListener();
412
+ }
413
+ };
414
+ const certainFailure = (total) => {
415
+ const summary = aggregate();
416
+ const remaining = total - passes.length;
417
+ const bestVotes = Math.max(0, ...Object.values(summary.votes));
418
+ const successful = passes.filter(pass => pass.status === 'succeeded');
419
+ const confidenceSum = successful.reduce((sum, pass) => sum + (pass.confidence ?? 0), 0);
420
+ // Give every unspent pass a matching vote and perfect confidence. Stop only
421
+ // when even that best case cannot answer, allowing roundoff at the boundary.
422
+ const bestAgreement = (bestVotes + remaining) / total;
423
+ const knownConfidence = successful.every(pass => pass.confidence !== null);
424
+ const confidenceReachable = successful.length === passes.length && knownConfidence
425
+ && bestAgreement + 1e-12 >= threshold && (confidenceSum + remaining) / total + 1e-12 >= threshold;
426
+ const agreementReachable = minAgreement !== undefined && knownConfidence
427
+ && successful.length + remaining >= passesRequested
428
+ && bestAgreement + 1e-12 >= minAgreement
429
+ && (confidenceSum + remaining) / (successful.length + remaining) + 1e-12 >= agreementConfidenceFloor;
430
+ return !confidenceReachable && !agreementReachable;
431
+ };
432
+ try {
433
+ let target = passesRequested;
434
+ while (passes.length < target) {
435
+ if (controller.signal.aborted || remainingMs() <= 0) {
436
+ earlyStop = 'timeout';
437
+ return finish('timeout');
438
+ }
439
+ const offset = passes.length;
440
+ if (offset >= passesRequested)
441
+ escalated = true;
442
+ passes.push(...await Promise.all(Array.from({ length: Math.min(4, target - offset) }, (_, index) => runPass(offset + index))));
443
+ if (controller.signal.aborted || remainingMs() <= 0) {
444
+ earlyStop = 'timeout';
445
+ return finish('timeout');
446
+ }
447
+ if (passes.length >= passesRequested && problem(aggregate()) && escalationPasses > passesRequested) {
448
+ target = escalationPasses;
449
+ }
450
+ if (passes.length < target && (passes.length >= passesRequested || certainFailure(passesRequested)) && certainFailure(escalationPasses)) {
451
+ earlyStop = 'certain-failure';
452
+ return finish();
453
+ }
454
+ }
455
+ return finish();
276
456
  }
277
- catch {
278
- return fallback('jev-error', Math.max(0, (options.now?.() ?? Date.now()) - started));
457
+ finally {
458
+ clearTimeout(timer);
279
459
  }
280
- const latencyMs = Math.max(0, (options.now?.() ?? Date.now()) - started);
281
- const evaluation = result.spec.evaluations.answer;
282
- const attempt = evaluation?.spec.attempts.at(-1);
283
- if (!evaluation || evaluation.spec.status !== 'success')
284
- return fallback(evaluation?.spec.reason ?? result.spec.reason, latencyMs, attempt);
285
- const confidence = confidenceFor(input.mode, evaluation);
286
- const threshold = input.threshold ?? 0.8;
287
- if (confidence === null || confidence < threshold)
288
- return fallback('low-confidence', latencyMs, attempt);
289
- const answer = publicAnswer(input.mode, evaluation.spec.value, evaluation.spec.uncertainty?.distribution);
290
- return {
291
- schema: 'aiwg-decision-ask/v1',
292
- status: 'answered',
293
- answer: answer.answer,
294
- confidence,
295
- ...(answer.probability === undefined ? {} : { probability: answer.probability }),
296
- fallback: null,
297
- reason: 'none',
298
- model: attempt?.actualModel ?? null,
299
- usage: usage(attempt?.usage),
300
- latencyMs,
301
- };
302
460
  }
303
461
  export async function readDecisionContextFile(filePath) {
304
462
  const content = await readFile(filePath, 'utf8');
@@ -342,6 +500,8 @@ export async function setupJev(input = {}, options = {}) {
342
500
  mode: { kind: 'yes-no' },
343
501
  threshold: 0,
344
502
  timeoutMs: 15_000,
503
+ passes: 1,
504
+ escalationPasses: 1,
345
505
  }, options);
346
506
  result.verified = verified.status === 'answered';
347
507
  result.model = verified.model;
@@ -223,7 +223,7 @@ export function validateContextLiveApproval(approval, corpus) {
223
223
  throw new Error('TV-12 collection margin is below the preregistered maximum');
224
224
  }
225
225
  export async function assertContextLiveSource(root, expected) {
226
- const git = (...args) => execFileSync('git', args, { cwd: root, encoding: 'utf8', timeout: 10_000 }).trim();
226
+ const git = (...args) => execFileSync('git', args, { cwd: root, encoding: 'utf8', timeout: 10_000, killSignal: 'SIGKILL' }).trim();
227
227
  if (git('rev-parse', 'HEAD') !== expected || git('status', '--porcelain', '--untracked-files=all'))
228
228
  throw new Error('TV-12 requires clean exact source commit');
229
229
  }
@@ -452,7 +452,7 @@ export async function collectContextLiveCase(item, corpus, approval, host, budge
452
452
  }
453
453
  /** `exact` (run evidence) requires the canonical root itself; `within` (prepared inputs) allows a descendant. */
454
454
  export async function assertContextArtifactRoot(sourceRoot, artifactRoot, mode = 'exact') {
455
- const route = JSON.parse(execFileSync('aiwg', ['artifacts', 'path', '--json', '--check-write'], { cwd: sourceRoot, encoding: 'utf8', timeout: 10_000 }));
455
+ const route = JSON.parse(execFileSync('aiwg', ['artifacts', 'path', '--json', '--check-write'], { cwd: sourceRoot, encoding: 'utf8', timeout: 10_000, killSignal: 'SIGKILL' }));
456
456
  if (typeof route.artifact_root !== 'string')
457
457
  throw new Error('Canonical artifact root unavailable');
458
458
  const delta = relative(await realpath(route.artifact_root), await realpath(artifactRoot));
@@ -1,10 +1,15 @@
1
+ import { resolveDecisionEnablement } from '../config/decision-config.js';
1
2
  import { existsSync } from 'node:fs';
2
3
  import { mkdir, readFile, writeFile } from 'node:fs/promises';
3
4
  import path from 'node:path';
4
5
  import { fileURLToPath, pathToFileURL } from 'node:url';
5
6
  import { askDecision, setupJev, DecisionAskUsageError, readDecisionContextFile, validateDecisionAskInput, } from './ask.js';
7
+ import { recordDecisionAsk, decisionLedgerHistory, decisionLedgerStats, resolveDecisionSessionId, resolveDecisionSession, DECISION_SESSION_ENV_VARS, parseDecisionLedgerSince, DecisionLedgerSinceError, DECISION_LEDGER_SCHEMA, DECISION_LEDGER_SCHEMA_V1, ATTRIBUTION_FIELD_PATTERN, MAX_ATTRIBUTION_FIELD_LENGTH, MAX_RECORDED_CHOICE_IDS, MAX_RECORD_TEXT_BYTES, MAX_LEDGER_RECORD_BYTES, DECISION_LEDGER_RECORD_TIMEOUT_MS, DECISION_SAVINGS_REASONING_ALLOWANCE_TOKENS, DECISION_SAVINGS_ESTIMATION_METHOD, buildDecisionLedgerRecord, appendDecisionLedgerRecord, filterDecisionLedgerRecords, } from './ledger.js';
8
+ export { DEFAULT_DECISION_SAVINGS_WEIGHTS, decisionSavingsWeights } from './savings.js';
9
+ import { resolveActiveProvider } from '../cli/provider-resolution.js';
10
+ export { appendDecisionOpportunity, buildDecisionOffloadSnapshot, decisionOffloadCategory, DECISION_OFFLOAD_CHECKPOINT_COVERAGE, DECISION_OFFLOAD_EXECUTABLE_COVERAGE, DECISION_OFFLOAD_SNAPSHOT_SCHEMA, DECISION_OPPORTUNITY_SCHEMA, runDecisionOffloadCheckpoint, } from './offload.js';
6
11
  import { assertDecisionEvaluateDispatcherConfig, decisionPatternPacks, getDecisionPatternPack, governedPatternArtifacts, listDecisionPatterns, patternInputSchema, patternOutputSchema, planLiveDecisionPattern, runOfflineDecisionPattern, validateAgainstSchema, validateDecisionDocument, validateDefinition, validateRuleset, } from './index.js';
7
- export { askDecision, setupJev, readDecisionContextFile, validateDecisionAskInput, DecisionAskUsageError, };
12
+ export { askDecision, setupJev, readDecisionContextFile, validateDecisionAskInput, DecisionAskUsageError, recordDecisionAsk, decisionLedgerHistory, decisionLedgerStats, resolveDecisionSessionId, resolveDecisionSession, DECISION_SESSION_ENV_VARS, parseDecisionLedgerSince, DecisionLedgerSinceError, DECISION_LEDGER_SCHEMA, DECISION_LEDGER_SCHEMA_V1, ATTRIBUTION_FIELD_PATTERN, MAX_ATTRIBUTION_FIELD_LENGTH, MAX_RECORDED_CHOICE_IDS, MAX_RECORD_TEXT_BYTES, MAX_LEDGER_RECORD_BYTES, DECISION_LEDGER_RECORD_TIMEOUT_MS, DECISION_SAVINGS_REASONING_ALLOWANCE_TOKENS, DECISION_SAVINGS_ESTIMATION_METHOD, buildDecisionLedgerRecord, appendDecisionLedgerRecord, filterDecisionLedgerRecords, resolveActiveProvider, };
8
13
  const DECISION_ROOT = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
9
14
  const VALIDATION_TARGETS = ['request', 'definition', 'ruleset', 'binding'];
10
15
  const OFFLINE_ADAPTER = 'agentic/code/addons/decision-engine/examples/fixture-jev-adapter.mjs';
@@ -222,9 +227,11 @@ export function decisionCapabilities(options = {}) {
222
227
  const env = driverEnv(options);
223
228
  const profiles = parseMcpRequestProfiles(env.AIWG_DECISION_MCP_REQUESTS, options.cwd ?? process.cwd());
224
229
  const packs = decisionPatternPacks;
230
+ const enablement = resolveDecisionEnablement(options);
225
231
  return {
226
232
  schema: 'aiwg-decision-driver-capabilities/v1',
227
- enabled: env.AIWG_DECISION_ENABLED === '1',
233
+ enabled: enablement.enabled,
234
+ enabledSource: enablement.source,
228
235
  offlineReady: true,
229
236
  primitives: [...new Set(packs.map(pack => pack.primitive))].sort(),
230
237
  patterns: {
@@ -236,19 +243,19 @@ export function decisionCapabilities(options = {}) {
236
243
  features: {
237
244
  patterns: ['list', 'show', 'offline-run', 'live-plan'],
238
245
  validation: [...VALIDATION_TARGETS],
239
- evaluate: { requiresEnv: 'AIWG_DECISION_ENABLED=1', mcpRequiresOptIn: true, mcpProfiles: profiles.map(profile => profile.name) },
246
+ evaluate: { requiresEnablement: true, mcpRequiresOptIn: true, mcpProfiles: profiles.map(profile => profile.name) },
240
247
  setup: ['synthetic-classification'],
241
248
  experimental: packs.filter(pack => pack.status === 'experimental').map(pack => pack.id),
242
249
  },
243
250
  backend: {
244
- configured: env.AIWG_DECISION_ENABLED === '1',
251
+ configured: enablement.enabled,
245
252
  probed: false,
246
253
  status: 'not-probed',
247
254
  note: 'Capabilities do not infer credential, network, dispatcher, or backend readiness.',
248
255
  },
249
256
  configuration: {
250
257
  env: {
251
- AIWG_DECISION_ENABLED: env.AIWG_DECISION_ENABLED === '1' ? 'set' : 'unset',
258
+ AIWG_DECISION_ENABLED: ['0', '1'].includes(env.AIWG_DECISION_ENABLED ?? '') ? 'set' : 'unset',
252
259
  AIWG_DECISION_MCP_REQUESTS: profiles.length ? 'configured' : 'unset',
253
260
  },
254
261
  },
@@ -321,7 +328,7 @@ export async function validateDecisionInput(target, filePath, options = {}) {
321
328
  export async function evaluateRequestPath(requestPath, options = {}, evaluateOptions = {}) {
322
329
  const env = driverEnv(options);
323
330
  const resolved = resolvePath(requestPath, options.cwd ?? process.cwd());
324
- if (env.AIWG_DECISION_ENABLED !== '1') {
331
+ if (!resolveDecisionEnablement(options).enabled) {
325
332
  return { schema: 'aiwg-decision-evaluate/v1', status: 'denied', reason: 'explicit-enable-required', exitCode: 2, result: null };
326
333
  }
327
334
  const validation = await validateDecisionInput('request', resolved, options);
@@ -336,7 +343,7 @@ export async function evaluateRequestPath(requestPath, options = {}, evaluateOpt
336
343
  argv.push('--host-policy-module', resolvePath(evaluateOptions.hostPolicyModulePath, options.cwd ?? process.cwd()));
337
344
  const exitCode = await dispatcher.runDecisionEvaluate({
338
345
  argv,
339
- env,
346
+ env: { ...env, AIWG_DECISION_ENABLED: '1' },
340
347
  runtime,
341
348
  stdout: { write(chunk) { stdout += String(chunk); return true; } },
342
349
  stderr: { write(chunk) { stderr += String(chunk); return true; } },
@@ -1,5 +1,19 @@
1
1
  import { admitEntry } from '../entry.js';
2
2
  import { compareEnsembleKeys, ensembleContractDigest, EnsembleContractError, validateEnsemblePolicy, } from './contract.js';
3
+ /** Modal buckets, with the existing locale-independent canonical tie break. */
4
+ export function aggregateModalAnswers(labels, answers) {
5
+ const ordered = [...labels].sort(compareEnsembleKeys);
6
+ const votes = Object.fromEntries(ordered.map(label => [label, 0]));
7
+ for (const answer of answers) {
8
+ if (!Object.hasOwn(votes, answer))
9
+ throw new EnsembleContractError('unknown modal answer', 'semantic');
10
+ votes[answer] += 1;
11
+ }
12
+ const top = Math.max(0, ...Object.values(votes));
13
+ const winners = ordered.filter(label => votes[label] === top);
14
+ return { value: answers.length ? winners[0] : null, votes, tie: winners.length > 1,
15
+ agreementRatio: answers.length ? top / answers.length : 0 };
16
+ }
3
17
  /** Deterministic reference aggregation over already-recorded member results. It never dispatches.
4
18
  * Member order does not matter: results are put in canonical (memberId, sampleIndex) order first,
5
19
  * so every sum and tie-break runs in the same sequence for any permutation of the input. */
@@ -24,12 +38,8 @@ export function aggregateEnsembleResults(policyInput, resultsInput, options = {}
24
38
  const { metric } = policy.disagreement;
25
39
  let candidate = null;
26
40
  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
- };
41
+ const modal = policy.primitive === 'ordinal-score' ? null
42
+ : aggregateModalAnswers(labels, succeeded.map(result => vote(policy, result)));
33
43
  const distributions = () => succeeded.map(result => distributionOf(policy, labels, result));
34
44
  const meanOf = (items) => Object.fromEntries(labels.map(label => [label, n ? round(items.reduce((sum, item) => sum + item[label], 0) / n) : 0]));
35
45
  const scores = () => succeeded.map(result => result.value);
@@ -40,7 +50,7 @@ export function aggregateEnsembleResults(policyInput, resultsInput, options = {}
40
50
  return n ? winners[0] : null;
41
51
  };
42
52
  if (algorithm === 'majority-v1' || metric === 'vote-share-v1' || metric === 'normalized-entropy-v1')
43
- statistics.votes = votes();
53
+ statistics.votes = modal.votes;
44
54
  if (policy.primitive === 'truth-probability' && (algorithm === 'mean-probability-v1' || metric === 'jensen-shannon-v1')) {
45
55
  statistics.meanProbability = n ? round(succeeded.reduce((sum, result) => sum + result.value, 0) / n) : null;
46
56
  }
@@ -55,8 +65,10 @@ export function aggregateEnsembleResults(policyInput, resultsInput, options = {}
55
65
  const middle = Math.floor(n / 2);
56
66
  statistics.medianScore = round(n % 2 ? sorted[middle] : (sorted[middle - 1] + sorted[middle]) / 2);
57
67
  }
58
- if (algorithm === 'majority-v1')
59
- candidate = pick(statistics.votes);
68
+ if (algorithm === 'majority-v1') {
69
+ candidate = modal.value;
70
+ tie = modal.tie;
71
+ }
60
72
  else if (algorithm === 'mean-probability-v1')
61
73
  candidate = policy.primitive === 'choice' ? pick(statistics.meanDistribution) : statistics.meanProbability;
62
74
  else if (algorithm === 'score-distribution-mean-v1')
@@ -68,7 +80,8 @@ export function aggregateEnsembleResults(policyInput, resultsInput, options = {}
68
80
  tie = n % 2 === 0 && sorted[middle - 1] !== sorted[middle];
69
81
  candidate = tie ? sorted[middle - 1] : statistics.medianScore;
70
82
  }
71
- const disagreementValue = n === 0 ? 1 : disagreement(policy, statistics, distributions, n);
83
+ const disagreementValue = n === 0 ? 1 : metric === 'vote-share-v1' ? 1 - modal.agreementRatio
84
+ : disagreement(policy, statistics, distributions, n);
72
85
  const valueBps = Math.round(Math.min(1, Math.max(0, disagreementValue)) * 10_000);
73
86
  const exceeded = valueBps > policy.disagreement.thresholdBps;
74
87
  let disposition = 'accept';
@@ -249,9 +249,13 @@ function dispatchOrder(rows, seed) {
249
249
  return [...development, ...test];
250
250
  }
251
251
  export function prepareD17MultifactStudy(seed, moduleDigest, sourceDigests) {
252
- if (!/^[a-z0-9][a-z0-9-]{0,31}$/.test(seed) || !/^sha256:[a-f0-9]{64}$/.test(moduleDigest)
252
+ if (typeof seed === 'string' && seed.length > 32)
253
+ refuse('seed-too-long');
254
+ if (typeof seed !== 'string' || !/^[a-z0-9][a-z0-9-]{0,31}$/.test(seed))
255
+ refuse('seed-invalid');
256
+ if (!/^sha256:[a-f0-9]{64}$/.test(moduleDigest)
253
257
  || !Object.keys(sourceDigests).length || Object.values(sourceDigests).some(v => !/^sha256:[a-f0-9]{64}$/.test(v)))
254
- throw new Error('D17-MF source or seed pins');
258
+ refuse('source-pin-mismatch');
255
259
  const generated = [];
256
260
  const gold = { schemaVersion: 'decision-d17mf-gold/v1', labels: {}, worlds: {} };
257
261
  for (let index = 0; index < D17MF_ROWS; index++) {
@@ -16,10 +16,29 @@ export class EntryAdmissionError extends Error {
16
16
  this.name = 'EntryAdmissionError';
17
17
  }
18
18
  }
19
+ /**
20
+ * Starts an admission-budget clock using per-thread CPU time.
21
+ *
22
+ * Per-thread CPU time measures only the work the admitting call itself did
23
+ * on this thread, so it is immune both to host scheduling contention
24
+ * pausing this thread between reads (the root cause of #3047's false
25
+ * `time-budget` rejections of legitimate input under host load) and to
26
+ * other threads' or background GC's CPU time landing on this same process
27
+ * (which a process-wide `process.cpuUsage()` clock would still charge here
28
+ * -- not a safe substitute for the same reason `performance.now()` was not).
29
+ */
30
+ export function admissionClockStart() {
31
+ return process.threadCpuUsage();
32
+ }
33
+ /** Elapsed time since `start` (from `admissionClockStart`), in milliseconds. */
34
+ export function elapsedAdmissionMs(start) {
35
+ const diff = process.threadCpuUsage(start);
36
+ return (diff.user + diff.system) / 1000;
37
+ }
19
38
  /** Check the object graph before JSON serialization, schema validation or digesting. */
20
39
  export function admitEntry(value, limits = DEFAULT_ENTRY_LIMITS) {
21
40
  const counts = { entries: 0, properties: 0, bytes: 0, estimatedMemoryBytes: 0 };
22
- const started = performance.now();
41
+ const started = admissionClockStart();
23
42
  const encoder = new TextEncoder();
24
43
  const stack = new Set();
25
44
  const fail = (reason) => { throw new EntryAdmissionError(reason, counts); };
@@ -32,7 +51,7 @@ export function admitEntry(value, limits = DEFAULT_ENTRY_LIMITS) {
32
51
  fail('memory-budget');
33
52
  };
34
53
  const visit = (item, depth) => {
35
- if (performance.now() - started > limits.timeMs)
54
+ if (elapsedAdmissionMs(started) > limits.timeMs)
36
55
  fail('time-budget');
37
56
  if (++counts.entries > limits.entries)
38
57
  fail('entry-count');
@@ -107,7 +126,7 @@ export function parseDecisionJson(source, limits = DEFAULT_ENTRY_LIMITS) {
107
126
  throw new EntryAdmissionError('serialized-bytes', { bytes });
108
127
  if (bytes * 4 > limits.memoryBytes)
109
128
  throw new EntryAdmissionError('memory-budget', { bytes });
110
- const started = performance.now();
129
+ const started = admissionClockStart();
111
130
  let value;
112
131
  try {
113
132
  value = JSON.parse(source);
@@ -118,7 +137,7 @@ export function parseDecisionJson(source, limits = DEFAULT_ENTRY_LIMITS) {
118
137
  const document = parseDocument(source, { uniqueKeys: true });
119
138
  if (document.errors.length)
120
139
  throw new EntryAdmissionError('duplicate-or-invalid-key', { bytes });
121
- if (performance.now() - started > limits.timeMs)
140
+ if (elapsedAdmissionMs(started) > limits.timeMs)
122
141
  throw new EntryAdmissionError('time-budget', { bytes });
123
142
  admitEntry(value, limits);
124
143
  return value;
@@ -130,7 +149,7 @@ export function parseDecisionYaml(source, limits = DEFAULT_ENTRY_LIMITS) {
130
149
  throw new EntryAdmissionError('serialized-bytes', { bytes });
131
150
  if (bytes * 4 > limits.memoryBytes)
132
151
  throw new EntryAdmissionError('memory-budget', { bytes });
133
- const started = performance.now();
152
+ const started = admissionClockStart();
134
153
  const document = parseDocument(source, { uniqueKeys: true });
135
154
  if (document.errors.length)
136
155
  throw new EntryAdmissionError('duplicate-or-invalid-key', { bytes });
@@ -160,7 +179,7 @@ export function parseDecisionYaml(source, limits = DEFAULT_ENTRY_LIMITS) {
160
179
  }
161
180
  };
162
181
  inspect(document.contents, 0);
163
- if (performance.now() - started > limits.timeMs)
182
+ if (elapsedAdmissionMs(started) > limits.timeMs)
164
183
  throw new EntryAdmissionError('time-budget', { bytes, entries });
165
184
  const value = document.toJS();
166
185
  admitEntry(value, limits);
@@ -796,7 +796,7 @@ export async function dagLivePriorSpend(artifactRoot) {
796
796
  export async function assertDagLiveArtifactRoot(sourceRoot, artifactRoot) {
797
797
  let route;
798
798
  try {
799
- route = JSON.parse(execFileSync('aiwg', ['artifacts', 'path', '--json', '--check-write'], { cwd: sourceRoot, encoding: 'utf8', timeout: 30_000 }));
799
+ route = JSON.parse(execFileSync('aiwg', ['artifacts', 'path', '--json', '--check-write'], { cwd: sourceRoot, encoding: 'utf8', timeout: 30_000, killSignal: 'SIGKILL' }));
800
800
  }
801
801
  catch {
802
802
  throw new Error('D12 requires the canonical artifact root');
@@ -99,6 +99,15 @@ export function finalizeDecisionGraphRun(graph, plan, report, records, ceilings
99
99
  report.realizedResources.nodeRuns !== flowRecords.length + approvedGates ||
100
100
  report.realizedResources.tokens !== flowRecords.reduce((sum, item) => sum + item.response.usage.tokens, 0) ||
101
101
  Math.abs(report.realizedResources.costUsd - flowRecords.reduce((sum, item) => sum + item.response.usage.costUsd, 0)) > 1e-9) {
102
+ // Admission or settlement can reject (for example after a wall-clock deadline)
103
+ // before the adapter emits an observation. Flow retains that failure in its
104
+ // trace; surface the cause instead of misdiagnosing it as lost evidence.
105
+ // Still reject the run: an unobserved invocation cannot produce a receipt.
106
+ const failure = report.status === 'failed' && report.trace.find(event => event.type === 'node-attempt-failed' && event.nodeId && !observed.has(event.nodeId) &&
107
+ graph.nodes.some(node => node.id === event.nodeId) &&
108
+ report.checkpoint.failed?.includes(event.nodeId) && typeof event.message === 'string');
109
+ if (failure)
110
+ throw new DecisionGraphError(`Flow graph node '${failure.nodeId}' failed before observation: ${failure.message}`);
102
111
  throw new DecisionGraphError('missing Flow graph observation');
103
112
  }
104
113
  const candidates = graph.terminals.filter(id => observed.get(id)?.status === 'ok');