@aiwg/cli 2026.9.19 → 2026.9.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (272) hide show
  1. package/agentic/code/providers/capability-matrix.yaml +58 -6
  2. package/agentic/code/providers/model-capabilities.v1.json +34 -0
  3. package/agentic/code/providers/model-catalog.v1.json +55 -13
  4. package/bin/aiwg.mjs +2 -0
  5. package/dist/src/api/index.d.ts +1 -0
  6. package/dist/src/api/index.js +1 -0
  7. package/dist/src/artifacts/enrichment/cli.js +1 -1
  8. package/dist/src/artifacts/views/cli.js +1 -1
  9. package/dist/src/audit/operator-decision.js +10 -0
  10. package/dist/src/auth/credential-store.js +127 -27
  11. package/dist/src/catalog/builtin-models.json +272 -40
  12. package/dist/src/catalog/cli.js +1 -1
  13. package/dist/src/catalog/cli.mjs +1 -1
  14. package/dist/src/cli/handlers/effect.js +872 -0
  15. package/dist/src/cli/handlers/help.js +2 -0
  16. package/dist/src/cli/handlers/index.js +4 -2
  17. package/dist/src/cli/handlers/models.js +41 -10
  18. package/dist/src/cli/handlers/ralph.js +1 -1
  19. package/dist/src/cli/handlers/sessions.js +113 -37
  20. package/dist/src/cli/handlers/subcommands.js +31 -0
  21. package/dist/src/cli/handlers/use.js +46 -9
  22. package/dist/src/cli/handlers/utilities.js +10 -0
  23. package/dist/src/cli/handlers/workspace.js +3 -1
  24. package/dist/src/cli/scope-resolver.js +112 -9
  25. package/dist/src/cli/services/deployment-verification.js +5 -0
  26. package/dist/src/config/aiwg-config.js +3 -0
  27. package/dist/src/decision/acceptance-rollout.js +93 -0
  28. package/dist/src/decision/acceptance.js +175 -0
  29. package/dist/src/decision/adapters/jev.js +234 -13
  30. package/dist/src/decision/adapters/llm-subagent.js +60 -12
  31. package/dist/src/decision/admission.js +550 -0
  32. package/dist/src/decision/batch-benchmark.js +47 -0
  33. package/dist/src/decision/batch-receipts/accounting.js +75 -0
  34. package/dist/src/decision/batch-receipts/export.js +40 -0
  35. package/dist/src/decision/batch-receipts/index.js +9 -0
  36. package/dist/src/decision/batch-receipts/protection.js +40 -0
  37. package/dist/src/decision/batch-receipts/receipt.js +44 -0
  38. package/dist/src/decision/batch-receipts/result-store.js +341 -0
  39. package/dist/src/decision/batch-receipts/store.js +362 -0
  40. package/dist/src/decision/batch-receipts/types.js +2 -0
  41. package/dist/src/decision/batch-receipts/validate.js +127 -0
  42. package/dist/src/decision/batch.js +68 -0
  43. package/dist/src/decision/calibration/index.js +4 -0
  44. package/dist/src/decision/calibration/receipts.js +130 -0
  45. package/dist/src/decision/calibration/registry.js +258 -0
  46. package/dist/src/decision/calibration/types.js +2 -0
  47. package/dist/src/decision/compile-cache/benchmark.js +62 -0
  48. package/dist/src/decision/compile-cache/compatibility.js +65 -0
  49. package/dist/src/decision/compile-cache/file-store.js +280 -0
  50. package/dist/src/decision/compile-cache/identity.js +26 -0
  51. package/dist/src/decision/compile-cache/index.js +9 -0
  52. package/dist/src/decision/compile-cache/prefix.js +21 -0
  53. package/dist/src/decision/compile-cache/records.js +86 -0
  54. package/dist/src/decision/compile-cache/runtime.js +68 -0
  55. package/dist/src/decision/compile-cache/store.js +108 -0
  56. package/dist/src/decision/compile-cache/types.js +2 -0
  57. package/dist/src/decision/context-plan.js +234 -0
  58. package/dist/src/decision/context-qualification.js +60 -0
  59. package/dist/src/decision/ensemble/aggregate.js +215 -0
  60. package/dist/src/decision/ensemble/contract.js +316 -0
  61. package/dist/src/decision/ensemble/drift.js +51 -0
  62. package/dist/src/decision/ensemble/index.js +6 -0
  63. package/dist/src/decision/ensemble/report.js +71 -0
  64. package/dist/src/decision/ensemble/types.js +2 -0
  65. package/dist/src/decision/evaluate.js +1024 -78
  66. package/dist/src/decision/file-lifecycle-store.js +168 -0
  67. package/dist/src/decision/graph-beam.js +40 -0
  68. package/dist/src/decision/graph-budget.js +110 -0
  69. package/dist/src/decision/graph-decision-bridge.js +93 -0
  70. package/dist/src/decision/graph-evidence.js +123 -0
  71. package/dist/src/decision/graph-flow-adapter.js +56 -0
  72. package/dist/src/decision/graph-flow.js +105 -0
  73. package/dist/src/decision/graph-parallel.js +79 -0
  74. package/dist/src/decision/graph-patterns.js +20 -0
  75. package/dist/src/decision/graph-run-store.js +66 -0
  76. package/dist/src/decision/graph-run.js +166 -0
  77. package/dist/src/decision/graph-skill-bridge.js +91 -0
  78. package/dist/src/decision/graph-templates.js +56 -0
  79. package/dist/src/decision/graph.js +115 -0
  80. package/dist/src/decision/index.js +35 -0
  81. package/dist/src/decision/job-accounting.js +93 -0
  82. package/dist/src/decision/job-contract.js +186 -0
  83. package/dist/src/decision/job-effects.js +95 -0
  84. package/dist/src/decision/job-evaluate.js +80 -0
  85. package/dist/src/decision/job-gateway.js +121 -0
  86. package/dist/src/decision/job-http.js +194 -0
  87. package/dist/src/decision/job-payload-store.js +258 -0
  88. package/dist/src/decision/job-poll-limiter.js +92 -0
  89. package/dist/src/decision/job-quota.js +158 -0
  90. package/dist/src/decision/job-runtime.js +240 -0
  91. package/dist/src/decision/job-scheduler.js +26 -0
  92. package/dist/src/decision/job-service.js +117 -0
  93. package/dist/src/decision/job-store.js +264 -0
  94. package/dist/src/decision/job-worker.js +141 -0
  95. package/dist/src/decision/lifecycle.js +148 -0
  96. package/dist/src/decision/patterns/artifacts.js +173 -0
  97. package/dist/src/decision/patterns/catalog.js +108 -0
  98. package/dist/src/decision/patterns/durable-review.js +237 -0
  99. package/dist/src/decision/patterns/index.js +8 -0
  100. package/dist/src/decision/patterns/playground.js +401 -0
  101. package/dist/src/decision/patterns/recorded.js +43 -0
  102. package/dist/src/decision/patterns/specs.js +174 -0
  103. package/dist/src/decision/patterns/types.js +2 -0
  104. package/dist/src/decision/portable-secrets.js +53 -0
  105. package/dist/src/decision/projection.js +210 -0
  106. package/dist/src/decision/qualification/capture.js +67 -0
  107. package/dist/src/decision/qualification/drift.js +68 -0
  108. package/dist/src/decision/qualification/gate-evidence.js +172 -0
  109. package/dist/src/decision/qualification/gates.js +83 -0
  110. package/dist/src/decision/qualification/index.js +12 -0
  111. package/dist/src/decision/qualification/load.js +275 -0
  112. package/dist/src/decision/qualification/manifest.js +37 -0
  113. package/dist/src/decision/qualification/privacy.js +41 -0
  114. package/dist/src/decision/qualification/quality.js +213 -0
  115. package/dist/src/decision/qualification/release.js +116 -0
  116. package/dist/src/decision/qualification/runner.js +408 -0
  117. package/dist/src/decision/qualification/types.js +2 -0
  118. package/dist/src/decision/receipts.js +30 -7
  119. package/dist/src/decision/result-cache/index.js +6 -0
  120. package/dist/src/decision/result-cache/integrity.js +28 -0
  121. package/dist/src/decision/result-cache/key.js +28 -0
  122. package/dist/src/decision/result-cache/service.js +92 -0
  123. package/dist/src/decision/result-cache/store.js +327 -0
  124. package/dist/src/decision/result-cache/types.js +3 -0
  125. package/dist/src/decision/review/authorization.js +110 -0
  126. package/dist/src/decision/review/effect-ledger-adapter.js +321 -0
  127. package/dist/src/decision/review/index.js +12 -0
  128. package/dist/src/decision/review/migrate.js +34 -0
  129. package/dist/src/decision/review/operator-audit.js +59 -0
  130. package/dist/src/decision/review/recovery-adapters.js +252 -0
  131. package/dist/src/decision/review/recovery.js +33 -0
  132. package/dist/src/decision/review/service.js +498 -0
  133. package/dist/src/decision/review/store.js +219 -0
  134. package/dist/src/decision/review/types.js +2 -0
  135. package/dist/src/decision/review/validate.js +205 -0
  136. package/dist/src/decision/scheduler.js +115 -0
  137. package/dist/src/decision/telemetry/context.js +42 -0
  138. package/dist/src/decision/telemetry/debug-sidecar.js +152 -0
  139. package/dist/src/decision/telemetry/exporter.js +77 -0
  140. package/dist/src/decision/telemetry/file-debug-backend.js +112 -0
  141. package/dist/src/decision/telemetry/index.js +13 -0
  142. package/dist/src/decision/telemetry/mapping.js +146 -0
  143. package/dist/src/decision/telemetry/metrics.js +164 -0
  144. package/dist/src/decision/telemetry/otlp-http.js +183 -0
  145. package/dist/src/decision/telemetry/redaction.js +93 -0
  146. package/dist/src/decision/telemetry/retention.js +109 -0
  147. package/dist/src/decision/telemetry/runtime.js +310 -0
  148. package/dist/src/decision/telemetry/trace.js +158 -0
  149. package/dist/src/decision/telemetry/types.js +3 -0
  150. package/dist/src/decision/validate.js +135 -5
  151. package/dist/src/effects/checkpoint-sinks.js +94 -0
  152. package/dist/src/effects/checkpoint.js +76 -0
  153. package/dist/src/effects/decision-links.js +54 -0
  154. package/dist/src/effects/errors.js +48 -0
  155. package/dist/src/effects/identity.js +178 -0
  156. package/dist/src/effects/index.js +25 -0
  157. package/dist/src/effects/keyring.js +160 -0
  158. package/dist/src/effects/keys.js +130 -0
  159. package/dist/src/effects/ledger.js +435 -0
  160. package/dist/src/effects/lifecycle.js +110 -0
  161. package/dist/src/effects/lock-recovery.js +269 -0
  162. package/dist/src/effects/reader.js +103 -0
  163. package/dist/src/effects/records.js +114 -0
  164. package/dist/src/effects/redaction.js +51 -0
  165. package/dist/src/effects/schema.js +73 -0
  166. package/dist/src/effects/store.js +239 -0
  167. package/dist/src/effects/types.js +49 -0
  168. package/dist/src/effects/verifiers/decision.js +168 -0
  169. package/dist/src/effects/verifiers/file.js +106 -0
  170. package/dist/src/effects/verifiers/git.js +206 -0
  171. package/dist/src/effects/verifiers/index.js +90 -0
  172. package/dist/src/effects/verifiers/registry.js +148 -0
  173. package/dist/src/effects/verifiers/review.js +123 -0
  174. package/dist/src/effects/verifiers/tracker.js +299 -0
  175. package/dist/src/effects/verifiers/types.js +21 -0
  176. package/dist/src/effects/verify.js +295 -0
  177. package/dist/src/evidence/bundle.js +2 -0
  178. package/dist/src/extensions/commands/definitions.js +28 -0
  179. package/dist/src/extensions/manifest.js +1 -0
  180. package/dist/src/marketplace/provenance.js +25 -27
  181. package/dist/src/mcp/cli.mjs +11 -1
  182. package/dist/src/mcp/grok-build-config.mjs +189 -0
  183. package/dist/src/mcp/registry.js +21 -1
  184. package/dist/src/mcp/registry.mjs +20 -1
  185. package/dist/src/models/grok-build-models.js +356 -0
  186. package/dist/src/models/index.js +1 -0
  187. package/dist/src/models/model-capabilities.v1.json +34 -0
  188. package/dist/src/models/model-catalog.v1.json +55 -13
  189. package/dist/src/models/model-discovery.js +41 -5
  190. package/dist/src/models/provider-policy.js +3 -2
  191. package/dist/src/plugin/skill-command-translator.js +1 -0
  192. package/dist/src/providers/capability-matrix.yaml +58 -6
  193. package/dist/src/providers/grok-build-acp.js +192 -0
  194. package/dist/src/providers/grok-build-dispatch.js +251 -0
  195. package/dist/src/providers/grok-build-headless.js +170 -0
  196. package/dist/src/providers/muse-paths.js +105 -0
  197. package/dist/src/providers/provider-definitions.js +93 -7
  198. package/dist/src/providers/provider-definitions.mjs +21 -1
  199. package/dist/src/providers/provider-inventory.js +3 -0
  200. package/dist/src/security/artifact-attestation.js +4 -8
  201. package/dist/src/security/signing.js +119 -0
  202. package/dist/src/sessions/adapters/grok-build.js +187 -0
  203. package/dist/src/sessions/adapters/muse.js +412 -0
  204. package/dist/src/sessions/contracts.js +1 -1
  205. package/dist/src/sessions/grok-build-cli.js +196 -0
  206. package/dist/src/sessions/index.js +3 -0
  207. package/dist/src/sessions/workspace-discovery.js +9 -5
  208. package/dist/src/skills/deployer.js +33 -5
  209. package/dist/src/smiths/context-pipeline/claude-hook.js +2 -0
  210. package/dist/src/smiths/context-pipeline/context-file-safety.js +18 -0
  211. package/dist/src/smiths/context-pipeline/finalization.js +1 -32
  212. package/dist/src/smiths/context-pipeline/generator.js +12 -0
  213. package/dist/src/smiths/context-pipeline/index.js +1 -1
  214. package/dist/src/smiths/context-pipeline/managed-hook.js +2 -0
  215. package/dist/src/smiths/context-pipeline/muse-bridge.js +50 -0
  216. package/dist/src/smiths/context-pipeline/provider-policy.js +5 -0
  217. package/dist/src/smiths/context-pipeline/workspace-context.js +22 -16
  218. package/dist/src/smiths/hook-bridge/grok-build-translator.js +64 -0
  219. package/dist/src/smiths/hook-bridge/index.js +3 -0
  220. package/dist/src/smiths/hook-bridge/shim.js +2 -0
  221. package/dist/src/smiths/hook-bridge/types.js +5 -0
  222. package/dist/src/smiths/skillsmith/collision-detector.js +1 -0
  223. package/dist/src/storage/protected-files.js +102 -0
  224. package/dist/src/tracker/forge-http.js +371 -0
  225. package/dist/src/tracker/project-inputs.js +41 -0
  226. package/package.json +2 -1
  227. package/schemas/decision/BatchReceipt.v1.schema.json +50 -0
  228. package/schemas/decision/CalibrationArtifact.v1.schema.json +76 -0
  229. package/schemas/decision/CalibrationCompatibility.v1.schema.json +18 -0
  230. package/schemas/decision/CalibrationGovernanceReceipt.v1.schema.json +44 -0
  231. package/schemas/decision/DecisionAdmissionEvidence.v1.schema.json +43 -0
  232. package/schemas/decision/DecisionBinding.schema.json +309 -0
  233. package/schemas/decision/DecisionBinding.v1alpha2.schema.json +369 -0
  234. package/schemas/decision/DecisionCacheTelemetry.v1.schema.json +20 -0
  235. package/schemas/decision/DecisionChampionChallenger.v1.schema.json +100 -0
  236. package/schemas/decision/DecisionCompileCacheEntry.v1.schema.json +35 -0
  237. package/schemas/decision/DecisionCompileCacheIdentity.v1.schema.json +26 -0
  238. package/schemas/decision/DecisionCompileCacheTombstone.v1.schema.json +31 -0
  239. package/schemas/decision/DecisionContextEvidence.v1.schema.json +136 -0
  240. package/schemas/decision/DecisionDefinition.schema.json +159 -0
  241. package/schemas/decision/DecisionDefinition.v1alpha2.schema.json +203 -0
  242. package/schemas/decision/DecisionDriftResponse.v1.schema.json +55 -0
  243. package/schemas/decision/DecisionEnsembleAggregate.v1.schema.json +86 -0
  244. package/schemas/decision/DecisionEnsembleIntegrityReport.v1.schema.json +64 -0
  245. package/schemas/decision/DecisionEnsemblePolicy.v1.schema.json +130 -0
  246. package/schemas/decision/DecisionGraph.v1.schema.json +77 -0
  247. package/schemas/decision/DecisionJob.v1.schema.json +71 -0
  248. package/schemas/decision/DecisionLifecyclePolicy.v1.schema.json +52 -0
  249. package/schemas/decision/DecisionProjectionPolicy.v1.schema.json +65 -0
  250. package/schemas/decision/DecisionProviderPrefixCompatibility.v1.schema.json +45 -0
  251. package/schemas/decision/DecisionProviderPrefixEvidence.v1.schema.json +17 -0
  252. package/schemas/decision/DecisionProviderPrefixIdentity.v1.schema.json +36 -0
  253. package/schemas/decision/DecisionResult.schema.json +524 -0
  254. package/schemas/decision/DecisionResult.v1alpha2.schema.json +619 -0
  255. package/schemas/decision/DecisionResultCacheEntry.v1.schema.json +53 -0
  256. package/schemas/decision/DecisionReview.schema.json +29 -0
  257. package/schemas/decision/DecisionRuleset.schema.json +313 -0
  258. package/schemas/decision/DecisionRuleset.v1alpha2.schema.json +313 -0
  259. package/schemas/decision/DecisionTelemetryTrace.v1.schema.json +95 -0
  260. package/schemas/decision/RulesetResult.schema.json +721 -0
  261. package/schemas/decision/RulesetResult.v1alpha2.schema.json +891 -0
  262. package/schemas/security/pr-evidence-receipt.v1.schema.json +77 -0
  263. package/schemas/security/threat-assessment-report.v1.schema.json +71 -1
  264. package/tools/agents/deploy-agents.mjs +21 -6
  265. package/tools/agents/providers/base.mjs +7 -4
  266. package/tools/agents/providers/codex.mjs +1 -1
  267. package/tools/agents/providers/grok-build.d.mts +7 -0
  268. package/tools/agents/providers/grok-build.mjs +249 -9
  269. package/tools/agents/providers/model-role.mjs +6 -3
  270. package/tools/agents/providers/muse-hooks.mjs +785 -0
  271. package/tools/agents/providers/muse-paths.mjs +112 -0
  272. package/tools/agents/providers/muse.mjs +336 -0
@@ -0,0 +1,105 @@
1
+ import { planDecisionGraph, DecisionGraphError } from './graph.js';
2
+ import { effectiveGraphCeilings } from './graph-evidence.js';
3
+ /** Flow gate ID guarding one node of an approval stage. The gate is an existing
4
+ * Flow `gate` node: Flow refuses to invoke it until the host approves it. */
5
+ export function decisionGraphApprovalGateId(nodeId) { return `approve-${nodeId}`; }
6
+ /** Compile an authorized decision DAG into existing Flow skill nodes. No new executor/node kind. */
7
+ export function decisionGraphToFlow(graph, options) {
8
+ const plan = planDecisionGraph(graph, options.resolvedPins);
9
+ const approvalStages = new Set(options.approvalStages ?? []);
10
+ if (approvalStages.size !== (options.approvalStages ?? []).length || [...approvalStages].some(stage => !Number.isSafeInteger(stage) || stage < 1 || !plan.stages.some(s => s.stage === stage))) {
11
+ throw new DecisionGraphError('invalid approval stage');
12
+ }
13
+ if (!/^aiwg:skill:[a-f0-9]{16}$/.test(options.decisionSkillId) || !graph.terminals.includes(options.terminal)) {
14
+ throw new DecisionGraphError('unauthorized Flow skill or terminal');
15
+ }
16
+ // Flow's v1alpha1 IDs are lowercase DNS labels. Never silently rename identities.
17
+ if ([graph.id, ...graph.nodes.map(n => n.id), ...graph.nodes.flatMap(n => [...n.input, ...n.output])]
18
+ .some(id => !/^[a-z][a-z0-9-]*$/.test(id)))
19
+ throw new DecisionGraphError('graph identity incompatible with Flow');
20
+ const reserved = new Set(['subject', 'target', 'model', 'egress', 'state-digest', 'definition-pin', 'binding-pin']);
21
+ if (graph.nodes.some(n => n.input.some(name => reserved.has(name)) || n.output.length === 0) ||
22
+ graph.id.length > 63 || graph.id.endsWith('-'))
23
+ throw new DecisionGraphError('graph incompatible with Flow bindings');
24
+ const ceilings = effectiveGraphCeilings(graph, ...(options.ceilings ?? []));
25
+ const names = new Map(graph.nodes.map(n => [n.id, n]));
26
+ const guards = new Map();
27
+ for (const edge of plan.edges) {
28
+ if (!edge.when)
29
+ continue;
30
+ const name = `condition-${edge.from}-${edge.when.source}`;
31
+ guards.set(`${edge.from}.${edge.when.source}`, name);
32
+ }
33
+ const gated = new Set(plan.stages.filter(stage => approvalStages.has(stage.stage)).flatMap(stage => stage.nodes));
34
+ for (const id of gated) {
35
+ const gate = decisionGraphApprovalGateId(id);
36
+ if (names.has(gate) || gate.length > 63)
37
+ throw new DecisionGraphError('approval gate identity incompatible with Flow');
38
+ }
39
+ const nodes = plan.stages.flatMap(stage => stage.nodes.flatMap(id => {
40
+ const n = names.get(id);
41
+ const incoming = plan.edges.filter(edge => edge.to === id);
42
+ const predecessors = [...new Set(incoming.map(e => e.from))].sort();
43
+ // A gate mirrors its node's readiness (same predecessors and guarded routes),
44
+ // so approval is requested only for a node that would otherwise run.
45
+ const gate = gated.has(id) ? [{ id: decisionGraphApprovalGateId(id), kind: 'gate',
46
+ phase: `stage-${stage.stage}`, dependsOn: predecessors, inputs: [], outputs: [],
47
+ capabilities: [], permissions: [], sideEffectMode: 'none' }] : [];
48
+ const dependsOn = [...predecessors, ...gate.map(g => g.id)].sort();
49
+ return [...gate, {
50
+ id, kind: 'skill', ref: options.decisionSkillId,
51
+ phase: `stage-${stage.stage}`,
52
+ ...(dependsOn.length ? { dependsOn } : {}),
53
+ inputs: [
54
+ { name: 'subject', schema: { type: 'string' }, value: n.subject },
55
+ { name: 'target', schema: { type: 'string' }, value: n.target },
56
+ { name: 'model', schema: { type: 'string' }, value: n.model },
57
+ { name: 'egress', schema: { type: 'string' }, value: n.egress },
58
+ { name: 'state-digest', schema: { type: 'string' }, value: n.stateDigest },
59
+ { name: 'definition-pin', schema: { type: 'object' }, value: n.definition },
60
+ { name: 'binding-pin', schema: { type: 'object' }, value: n.binding },
61
+ ...incoming.map(e => ({ name: e.destination, schema: { type: ['object', 'array', 'string', 'number', 'boolean', 'null'] }, from: `${e.from}.${e.source}` })),
62
+ ],
63
+ outputs: n.output.map(name => {
64
+ const guard = guards.get(`${id}.${name}`);
65
+ return { name, schema: guard ? { type: 'boolean' } : { type: ['object', 'array', 'string', 'number', 'boolean', 'null'] },
66
+ ...(guard ? { state: guard } : {}) };
67
+ }),
68
+ capabilities: [], permissions: [], sideEffectMode: 'none',
69
+ retry: { limit: 0, backoff: 'none', on: ['failure'] },
70
+ }];
71
+ }));
72
+ const terminalNode = names.get(options.terminal);
73
+ if (terminalNode.output.length < 1)
74
+ throw new DecisionGraphError('terminal requires an output');
75
+ // Existing Flow tracks run/node/activation/invocation identity and enforces its
76
+ // own permission, retry, approval, trace and resource policies. These are ceilings,
77
+ // not a grant for inference: the host adapter still controls actual transport.
78
+ // Each approval wave costs one Flow activation but no decision attempt.
79
+ return {
80
+ apiVersion: 'flow.aiwg.io/v1alpha1', kind: 'FlowGraph',
81
+ metadata: { name: graph.id },
82
+ spec: {
83
+ entry: [graph.entry], candidates: [{ id: options.decisionSkillId, kind: 'skill' }],
84
+ state: { fields: [...guards.values()].sort().map(name => ({ name, schema: { type: 'boolean' }, reducer: 'replace' })) },
85
+ permissions: [], capabilities: [],
86
+ ceilings: { activations: ceilings.attempts + approvalStages.size, tokens: ceilings.tokens,
87
+ costUsd: ceilings.costMicros / 1_000_000, timeMs: ceilings.deadlineMs, concurrency: ceilings.concurrency },
88
+ nodes, routes: [...new Set(plan.edges.map(e => `${e.from}:${e.to}`))].sort().flatMap(pair => {
89
+ const [from, to] = pair.split(':');
90
+ const matches = plan.edges.filter(e => e.from === from && e.to === to);
91
+ const predicates = [...new Set(matches.map(e => e.when ? `${guards.get(`${e.from}.${e.when.source}`)} == ${e.when.equals}` : 'always'))];
92
+ if (predicates.length !== 1)
93
+ throw new DecisionGraphError('conflicting guards on Flow route');
94
+ const when = predicates[0] === 'always' ? {} : { when: { expression: `state.${predicates[0]}` } };
95
+ return [{ from: from, to: to, ...when },
96
+ ...(gated.has(to) ? [{ from: from, to: decisionGraphApprovalGateId(to), ...when }] : [])];
97
+ }),
98
+ joins: [], failure: { onNodeFailure: 'fail', maxFailures: 0 },
99
+ output: { mode: 'final-only', from: `${options.terminal}.${terminalNode.output[0]}`,
100
+ schema: { type: ['object', 'array', 'string', 'number', 'boolean', 'null'] } },
101
+ trace: { level: 'metadata', redact: [] },
102
+ },
103
+ };
104
+ }
105
+ //# sourceMappingURL=graph-flow.js.map
@@ -0,0 +1,79 @@
1
+ import { canonicalJson } from '../security/artifact-trust.js';
2
+ import { correlateAtomicBatch, decisionBatchQuestionId } from './batch.js';
3
+ import { DecisionGraphError, planDecisionGraph } from './graph.js';
4
+ /** Flow parallelDispatch seam. Native batching is strictly opt-in and atomic;
5
+ * otherwise the existing admitted invokeNode handles bounded fan-out.
6
+ */
7
+ export function decisionGraphParallelDispatch(graph, plan, options) {
8
+ const pins = new Set(graph.nodes.flatMap(n => [n.definition.digest, n.binding.digest]));
9
+ if (canonicalJson(plan) !== canonicalJson(planDecisionGraph(graph, pins)))
10
+ throw new DecisionGraphError('graph/plan mismatch');
11
+ return async (requests, invokeNode) => {
12
+ const ids = new Set(requests.map(r => r.node.id));
13
+ if (ids.size !== requests.length)
14
+ throw new DecisionGraphError('duplicate Flow parallel node');
15
+ const groups = [];
16
+ const assigned = new Set();
17
+ for (const stage of plan.stages)
18
+ for (const batch of stage.batches) {
19
+ const group = requests.filter(r => batch.includes(r.node.id)).sort((a, b) => a.node.id.localeCompare(b.node.id));
20
+ if (group.length > 1) {
21
+ groups.push(group);
22
+ group.forEach(r => assigned.add(r.node.id));
23
+ }
24
+ }
25
+ for (const request of requests)
26
+ if (!assigned.has(request.node.id))
27
+ groups.push([request]);
28
+ const settled = new Map();
29
+ await Promise.all(groups.map(async (group) => {
30
+ if (group.length === 1 || !options.canBatch(group) ||
31
+ new Set(group.map(r => canonicalJson(r.inputs))).size !== 1) {
32
+ const results = await Promise.all(group.map(r => invokeNode(r)));
33
+ group.forEach((r, i) => settled.set(r.node.id, results[i]));
34
+ return;
35
+ }
36
+ const stages = new Set(group.map(r => r.node.phase));
37
+ if (stages.size !== 1 || group.some(r => r.node.kind !== 'skill' || r.node.retry?.limit !== 0 ||
38
+ r.node.sideEffectMode !== 'none' || !/^stage-(0|[1-9]\d*)$/.test(r.node.phase ?? ''))) {
39
+ throw new DecisionGraphError('invalid native batch stage');
40
+ }
41
+ const stage = Number(group[0].node.phase.slice(6));
42
+ const estimates = group.map(r => options.estimate(r));
43
+ if (estimates.some(e => !e || !Number.isSafeInteger(e.costMicros) || e.costMicros < 1 ||
44
+ !Number.isSafeInteger(e.tokens) || e.tokens < 1))
45
+ throw new DecisionGraphError('missing native batch estimate');
46
+ const finish = options.ledger.reserveBatch(stage, estimates);
47
+ try {
48
+ const ids = group.map(r => decisionBatchQuestionId(r.node.id));
49
+ const correlated = correlateAtomicBatch(ids, await options.invokeNativeBatch(group));
50
+ const values = ids.map(id => correlated.get(id));
51
+ values.forEach((value, index) => {
52
+ const bound = estimates[index];
53
+ // An abstention inside an atomic batch fails the whole batch closed.
54
+ if (!value || !value.usage || (value.status ?? 'ok') !== 'ok' || !Number.isSafeInteger(value.attempts) || value.attempts < 1 ||
55
+ !Number.isSafeInteger(value.usage.tokens) || value.usage.tokens < 0 ||
56
+ !Number.isFinite(value.usage.costUsd) || value.usage.costUsd < 0 ||
57
+ !Number.isSafeInteger(value.usage.timeMs) || value.usage.timeMs < 0 ||
58
+ value.attempts > bound.attempts || value.usage.tokens > bound.tokens ||
59
+ Math.ceil(value.usage.costUsd * 1_000_000) > bound.costMicros) {
60
+ throw new DecisionGraphError('invalid native batch usage');
61
+ }
62
+ });
63
+ values.forEach((value, index) => finish[index]({ attempts: value.attempts,
64
+ tokens: value.usage.tokens, costMicros: Math.ceil(value.usage.costUsd * 1_000_000) }));
65
+ group.forEach((r, index) => {
66
+ settled.set(r.node.id, values[index]);
67
+ options.onObservation?.({ request: r, response: values[index] });
68
+ });
69
+ options.onNativeBatch?.(group.map(r => r.node.id));
70
+ }
71
+ catch (error) {
72
+ options.ledger.cancel();
73
+ throw error; // Never retry an uncertain native batch as individual calls.
74
+ }
75
+ }));
76
+ return requests.map(r => settled.get(r.node.id));
77
+ };
78
+ }
79
+ //# sourceMappingURL=graph-parallel.js.map
@@ -0,0 +1,20 @@
1
+ import { DecisionGraphError } from './graph.js';
2
+ /** Deterministic, bounded ranking primitive for shortlist/rerank and taxonomy beams.
3
+ * The trusted caller supplies candidate IDs and scores; model evidence cannot widen
4
+ * the graph or confer action authority. This primitive performs no dispatch.
5
+ */
6
+ export function selectDecisionBeam(candidates, width, ceiling) {
7
+ if (!Number.isSafeInteger(width) || width < 1 || !Number.isSafeInteger(ceiling) || ceiling < 1 || width > ceiling) {
8
+ throw new DecisionGraphError('invalid beam width');
9
+ }
10
+ const ids = new Set();
11
+ for (const candidate of candidates) {
12
+ if (!/^[a-zA-Z][a-zA-Z0-9_-]{0,63}$/.test(candidate.id) || ids.has(candidate.id) ||
13
+ typeof candidate.score !== 'number' || !Number.isFinite(candidate.score))
14
+ throw new DecisionGraphError('invalid beam candidate');
15
+ ids.add(candidate.id);
16
+ }
17
+ // Explicit secondary key: binary code-point order, never locale or completion order.
18
+ return [...candidates].sort((a, b) => b.score - a.score || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)).slice(0, width);
19
+ }
20
+ //# sourceMappingURL=graph-patterns.js.map
@@ -0,0 +1,66 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { mkdir, open, link, unlink, readFile } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { canonicalJson } from '../security/artifact-trust.js';
5
+ import { admitEntry } from './entry.js';
6
+ import { DecisionGraphError } from './graph.js';
7
+ /** Immutable create-once graph receipt store. Digest detects accidental changes;
8
+ * without a host-supplied authenticated storage layer it does not prove origin.
9
+ * Flow checkpoint and dispatcher receipts remain their own durable authorities.
10
+ */
11
+ export class FileGraphRunReceiptStore {
12
+ directory;
13
+ constructor(directory) {
14
+ this.directory = directory;
15
+ }
16
+ path(id) {
17
+ if (!/^[a-zA-Z][a-zA-Z0-9_-]{0,63}$/.test(id))
18
+ throw new DecisionGraphError('invalid graph receipt id');
19
+ return join(this.directory, `${id}.json`);
20
+ }
21
+ async create(id, receipt) {
22
+ const destination = this.path(id);
23
+ this.verify(receipt);
24
+ await mkdir(this.directory, { recursive: true, mode: 0o700 });
25
+ const temporary = join(this.directory, `.${randomUUID()}.tmp`);
26
+ const handle = await open(temporary, 'wx', 0o600);
27
+ try {
28
+ await handle.writeFile(canonicalJson(receipt));
29
+ await handle.sync();
30
+ }
31
+ catch (error) {
32
+ await handle.close();
33
+ await unlink(temporary);
34
+ throw error;
35
+ }
36
+ await handle.close();
37
+ try {
38
+ // link is exclusive: another writer cannot replace the existing receipt.
39
+ await link(temporary, destination);
40
+ }
41
+ finally {
42
+ await unlink(temporary);
43
+ }
44
+ }
45
+ async read(id) {
46
+ const value = JSON.parse(await readFile(this.path(id), 'utf8'));
47
+ this.verify(value);
48
+ return value;
49
+ }
50
+ verify(value) {
51
+ try {
52
+ admitEntry(value);
53
+ }
54
+ catch {
55
+ throw new DecisionGraphError('invalid graph receipt');
56
+ }
57
+ if (value.schemaVersion !== 'decision-graph-run/v1' || typeof value.receiptDigest !== 'string') {
58
+ throw new DecisionGraphError('invalid graph receipt');
59
+ }
60
+ const { receiptDigest, ...body } = value;
61
+ if (receiptDigest !== `sha256:${createHash('sha256').update(canonicalJson(body)).digest('hex')}`) {
62
+ throw new DecisionGraphError('graph receipt digest mismatch');
63
+ }
64
+ }
65
+ }
66
+ //# sourceMappingURL=graph-run-store.js.map
@@ -0,0 +1,166 @@
1
+ import { canonicalJson } from '../security/artifact-trust.js';
2
+ import { createHash } from 'node:crypto';
3
+ import { admitEntry } from './entry.js';
4
+ import { DecisionGraphError } from './graph.js';
5
+ import { auditGraphEvidence } from './graph-evidence.js';
6
+ import { selectDecisionBeam } from './graph-patterns.js';
7
+ import { decisionGraphApprovalGateId } from './graph-flow.js';
8
+ /** Cross-check Flow results against the host's append-only invocation observations.
9
+ * If an observed Flow invocation is absent, incomplete, or tampered with, fail closed.
10
+ * The host must persist this receipt atomically alongside its Flow checkpoint.
11
+ */
12
+ export function finalizeDecisionGraphRun(graph, plan, report, records, ceilings = [], unknownCostBoundMicros, nativeBatches = [], approvalStages = []) {
13
+ try {
14
+ admitEntry({ graph, plan, ceilings, runId: report.runId, status: report.status,
15
+ results: Object.fromEntries(Object.entries(report.results).map(([id, value]) => [id, value.outputs])),
16
+ completed: report.checkpoint.completed, skipped: report.checkpoint.skipped,
17
+ routes: report.trace.filter(item => item.type === 'route-evaluated').map(item => ({ from: item.from ?? '', to: item.to ?? '', active: item.active === true })),
18
+ records: records.map(({ request, response }) => ({ node: request.node.id, runId: request.runId,
19
+ outputs: response.outputs, attempts: response.attempts, usage: response.usage })),
20
+ nativeBatches: nativeBatches.map(group => [...group].sort()), approvalStages: [...approvalStages],
21
+ ...(unknownCostBoundMicros === undefined ? {} : { unknownCostBoundMicros }) });
22
+ }
23
+ catch {
24
+ throw new DecisionGraphError('graph run admission denied');
25
+ }
26
+ if (new Set(approvalStages).size !== approvalStages.length ||
27
+ approvalStages.some(stage => stage < 1 || !plan.stages.some(s => s.stage === stage)))
28
+ throw new DecisionGraphError('invalid approval stage');
29
+ // A paused run is waiting for an approval; it has no outcome yet.
30
+ if (report.status === 'paused')
31
+ throw new DecisionGraphError('Flow graph run is not final');
32
+ const gates = new Map(plan.stages.filter(stage => approvalStages.includes(stage.stage))
33
+ .flatMap(stage => stage.nodes.map(id => [decisionGraphApprovalGateId(id), { nodeId: id, stage: stage.stage }])));
34
+ if (report.checkpoint.completed.some(id => !gates.has(id) && !graph.nodes.some(n => n.id === id))) {
35
+ throw new DecisionGraphError('unknown Flow graph node');
36
+ }
37
+ const completed = report.checkpoint.completed.filter(id => !gates.has(id));
38
+ if ([...gates].some(([gate, { nodeId }]) => completed.includes(nodeId) && !report.checkpoint.completed.includes(gate))) {
39
+ throw new DecisionGraphError('gated node ran without approval');
40
+ }
41
+ const observed = new Map();
42
+ for (const { request, response } of records) {
43
+ const status = response.status ?? 'ok';
44
+ if (observed.has(request.node.id) || request.runId !== report.runId || !graph.nodes.some(n => n.id === request.node.id) ||
45
+ (status === 'ok' ? !completed.includes(request.node.id) || !report.results[request.node.id] ||
46
+ canonicalJson(report.results[request.node.id].outputs) !== canonicalJson(response.outputs)
47
+ : completed.includes(request.node.id) || !report.checkpoint.failed?.includes(request.node.id) ||
48
+ (status !== 'abstained' && status !== 'unsupported') || Object.keys(response.outputs).length)) {
49
+ throw new DecisionGraphError('Flow graph observation mismatch');
50
+ }
51
+ if (!Number.isSafeInteger(response.attempts) || response.attempts < 1 ||
52
+ !Number.isSafeInteger(response.usage.tokens) || response.usage.tokens < 0 ||
53
+ !Number.isSafeInteger(response.usage.timeMs) || response.usage.timeMs < 0 ||
54
+ !Number.isFinite(response.usage.costUsd) || response.usage.costUsd < 0)
55
+ throw new DecisionGraphError('invalid Flow graph usage');
56
+ observed.set(request.node.id, { node: request.node.id, status, output: response.outputs,
57
+ attempts: response.attempts, tokens: response.usage.tokens, costMicros: Math.ceil(response.usage.costUsd * 1_000_000),
58
+ durationMs: response.usage.timeMs, used: status === 'ok',
59
+ flow: { runId: request.runId, nodeRunId: request.nodeRunId,
60
+ activationId: request.activationId, invocationKey: request.invocationKey } });
61
+ }
62
+ // Flow v1alpha1 reports guarded-off destinations as NO_RUNNABLE_NODES rather
63
+ // than skipped. Only interpret such nodes as unused if every incoming route
64
+ // has an explicit inactive trace from a completed predecessor (or a skipped
65
+ // predecessor). Never normalize an unrelated failed Flow run to success.
66
+ const inactive = new Set(report.checkpoint.skipped);
67
+ for (const stage of plan.stages) {
68
+ for (const id of stage.nodes) {
69
+ if (completed.includes(id) || inactive.has(id))
70
+ continue;
71
+ const incoming = plan.edges.filter(e => e.to === id);
72
+ if (incoming.length && incoming.every(e => inactive.has(e.from) ||
73
+ report.trace.some(event => event.type === 'route-evaluated' && event.from === e.from && event.to === id && event.active === false))) {
74
+ inactive.add(id);
75
+ }
76
+ }
77
+ }
78
+ for (const id of inactive) {
79
+ if (observed.has(id))
80
+ throw new DecisionGraphError('Flow node both skipped and observed');
81
+ observed.set(id, { node: id, status: 'skipped', output: {}, attempts: 0, tokens: 0,
82
+ costMicros: 0, durationMs: 0, used: false });
83
+ }
84
+ // A node that abstained or was unsupported stopped the graph (as does a cancelled
85
+ // run): nodes it prevented from dispatching are recorded with zero usage.
86
+ const stopped = report.status === 'cancelled' ? 'cancelled' :
87
+ [...observed.values()].some(obs => obs.status !== 'ok' && obs.status !== 'skipped') ? 'skipped' : null;
88
+ if (stopped)
89
+ for (const node of graph.nodes) {
90
+ if (observed.has(node.id))
91
+ continue;
92
+ observed.set(node.id, { node: node.id, status: stopped, output: {}, attempts: 0, tokens: 0,
93
+ costMicros: 0, durationMs: 0, used: false });
94
+ }
95
+ // Flow counts only successful node runs, including approved zero-cost gates.
96
+ const flowRecords = records.filter(item => (item.response.status ?? 'ok') === 'ok');
97
+ const approvedGates = report.checkpoint.completed.filter(id => gates.has(id)).length;
98
+ if (completed.length !== flowRecords.length || observed.size !== graph.nodes.length ||
99
+ report.realizedResources.nodeRuns !== flowRecords.length + approvedGates ||
100
+ report.realizedResources.tokens !== flowRecords.reduce((sum, item) => sum + item.response.usage.tokens, 0) ||
101
+ Math.abs(report.realizedResources.costUsd - flowRecords.reduce((sum, item) => sum + item.response.usage.costUsd, 0)) > 1e-9) {
102
+ throw new DecisionGraphError('missing Flow graph observation');
103
+ }
104
+ const candidates = graph.terminals.filter(id => observed.get(id)?.status === 'ok');
105
+ // Prefer the deepest completed terminal, with stable ID tie ordering.
106
+ // An invoked fallback supersedes its verifier; a guarded-off fallback does not.
107
+ const deepest = Math.max(-1, ...candidates.map(id => graph.nodes.find(n => n.id === id).stage));
108
+ const terminal = candidates.length ? selectDecisionBeam(candidates.map(id => ({ id, score: graph.nodes.find(n => n.id === id).stage === deepest ? 1 : 0 })), 1, 1)[0].id : null;
109
+ // Only the chosen terminal's transitive predecessors contributed to the
110
+ // selected evidence. Every speculative observation still retains its usage.
111
+ const used = new Set();
112
+ if (terminal) {
113
+ const pending = [terminal];
114
+ while (pending.length) {
115
+ const id = pending.pop();
116
+ if (used.has(id))
117
+ continue;
118
+ used.add(id);
119
+ pending.push(...plan.edges.filter(edge => edge.to === id).map(edge => edge.from));
120
+ }
121
+ }
122
+ for (const obs of observed.values())
123
+ if (obs.status === 'ok')
124
+ obs.used = used.has(obs.node);
125
+ const evidence = auditGraphEvidence(graph, plan, [...observed.values()], ceilings, unknownCostBoundMicros);
126
+ let outcome = evidence.outcome;
127
+ if (report.status === 'cancelled')
128
+ outcome = 'cancelled';
129
+ else if (report.status !== 'completed' && outcome === 'complete' &&
130
+ !(report.status === 'failed' && report.stopReason.startsWith('no runnable nodes remain:') &&
131
+ completed.length + inactive.size === graph.nodes.length))
132
+ outcome = 'error';
133
+ else if (outcome === 'complete' && !terminal)
134
+ outcome = graph.pattern === 'shortlist-rerank' ? 'empty-shortlist' : 'incomplete-evidence';
135
+ const value = outcome === 'complete' && terminal ? observed.get(terminal).output : null;
136
+ const declared = nativeBatches.map(group => canonicalJson([...group].sort()));
137
+ const permitted = new Set(plan.stages.flatMap(stage => stage.batches.map(group => canonicalJson(group))));
138
+ if (declared.length !== new Set(declared).size || declared.some(group => !permitted.has(group))) {
139
+ throw new DecisionGraphError('unrecognized native batch receipt');
140
+ }
141
+ const batches = plan.stages.flatMap(stage => stage.batches.map(nodes => ({ stage: stage.stage, nodes,
142
+ resultDigests: nodes.map(id => evidence.stages[stage.stage].nodes.find(node => node.id === id).resultDigest),
143
+ executedNative: declared.includes(canonicalJson(nodes)) })));
144
+ const started = report.trace.filter(event => event.type === 'node-started');
145
+ const approvals = started.filter(event => event.nodeId && gates.has(event.nodeId)).map(event => {
146
+ if (!event.nodeRunId || !Number.isSafeInteger(event.activation))
147
+ throw new DecisionGraphError('invalid Flow approval lineage');
148
+ const gate = gates.get(event.nodeId);
149
+ return { gate: event.nodeId, nodeId: gate.nodeId, stage: gate.stage, nodeRunId: event.nodeRunId, activation: event.activation };
150
+ }).sort((a, b) => a.gate.localeCompare(b.gate) || a.activation - b.activation);
151
+ const attempts = started.filter(event => !event.nodeId || !gates.has(event.nodeId)).map(event => {
152
+ if (!event.nodeId || !event.nodeRunId || !event.invocationKey ||
153
+ !Number.isSafeInteger(event.attempt) || !Number.isSafeInteger(event.activation)) {
154
+ throw new DecisionGraphError('invalid Flow attempt lineage');
155
+ }
156
+ return { nodeId: event.nodeId, nodeRunId: event.nodeRunId, invocationKey: event.invocationKey,
157
+ attempt: event.attempt, activation: event.activation };
158
+ }).sort((a, b) => a.nodeId.localeCompare(b.nodeId) || a.attempt - b.attempt);
159
+ if (attempts.length < records.length)
160
+ throw new DecisionGraphError('missing Flow attempt lineage');
161
+ const receipt = { schemaVersion: 'decision-graph-run/v1', graphDigest: plan.graphDigest,
162
+ flowRunId: report.runId, flowStatus: report.status, flowStopReason: report.stopReason,
163
+ evidence, terminal, outcome, value, batches, attempts, approvals };
164
+ return { ...receipt, receiptDigest: `sha256:${createHash('sha256').update(canonicalJson(receipt)).digest('hex')}` };
165
+ }
166
+ //# sourceMappingURL=graph-run.js.map
@@ -0,0 +1,91 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { chmod, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
3
+ import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
4
+ import { resolveRoutableCapability } from '../artifacts/capability-resolver.js';
5
+ import { parseDecisionJson } from './entry.js';
6
+ import { DecisionGraphError } from './graph.js';
7
+ import { assertDecisionFlowPins, assertUnknownCostBound, decisionFlowNode, decisionFlowResponse } from './graph-decision-bridge.js';
8
+ /** Resolve the packaged `decision-evaluate` skill: its stable catalog ID becomes the
9
+ * Flow node ref, and its declared script entrypoint is the only program invoked.
10
+ * Neither comes from graph evidence.
11
+ */
12
+ export async function resolveDecisionEvaluateSkill(frameworkRoot) {
13
+ const skill = await resolveRoutableCapability(frameworkRoot, 'skill', 'decision-evaluate');
14
+ if (!/^aiwg:skill:[a-f0-9]{16}$/.test(skill.id))
15
+ throw new DecisionGraphError('invalid decision-evaluate skill ID');
16
+ const manifest = resolve(frameworkRoot, skill.source.path);
17
+ const frontmatter = /^---\n([\s\S]*?)\n---/.exec(await readFile(manifest, 'utf8'))?.[1] ?? '';
18
+ const entrypoint = /^\s+entrypoint:\s*(\S+)\s*$/m.exec(frontmatter)?.[1];
19
+ const scriptPath = entrypoint ? resolve(dirname(manifest), entrypoint) : '';
20
+ const inside = relative(dirname(manifest), scriptPath);
21
+ if (!entrypoint || !inside || inside.startsWith('..') || isAbsolute(inside))
22
+ throw new DecisionGraphError('invalid decision-evaluate entrypoint');
23
+ return { id: skill.id, scriptPath };
24
+ }
25
+ /** Default runner: executes the skill entrypoint with a minimal environment.
26
+ * Only PATH, the opt-in flag and host-listed variables reach the child process.
27
+ */
28
+ export function runDecisionEvaluateSkill(run) {
29
+ return new Promise(done => {
30
+ execFile(process.execPath, [run.scriptPath, '--request', run.requestPath], {
31
+ cwd: dirname(run.requestPath), timeout: run.timeoutMs, maxBuffer: 16 * 1024 * 1024,
32
+ env: { PATH: process.env.PATH ?? '', ...run.env, AIWG_DECISION_ENABLED: '1' },
33
+ }, (error, stdout) => done({ code: error ? (typeof error.code === 'number' ? error.code : -1) : 0, stdout }));
34
+ });
35
+ }
36
+ /** Flow skill invoker that runs the shipped decision-evaluate skill for every graph node.
37
+ * Each call writes a private dispatcher request (and, for a dependent node, only its
38
+ * declared projected evidence as input), runs the skill script, and accepts only a
39
+ * RulesetResult for the same Flow run/invocation and binding pin. The skill's own
40
+ * stderr is never propagated into graph errors.
41
+ */
42
+ export function decisionEvaluateSkillFlowInvoker(graph, host) {
43
+ assertUnknownCostBound(host);
44
+ if (!isAbsolute(host.workDirectory))
45
+ throw new DecisionGraphError('skill work directory must be absolute');
46
+ return async (flow) => {
47
+ const { node, input } = decisionFlowNode(graph, flow);
48
+ const base = host.request(node.id);
49
+ const paths = [base.rulesetPath, base.bindingPath, ...base.definitionPaths,
50
+ ...(base.inputPath === undefined ? [] : [base.inputPath]), ...Object.values(base.adapterModules ?? {}),
51
+ ...(base.receiptDirectory === undefined ? [] : [base.receiptDirectory])];
52
+ if (!base.definitionPaths.length || paths.some(path => typeof path !== 'string' || !isAbsolute(path)) ||
53
+ (node.id === graph.entry && base.inputPath === undefined))
54
+ throw new DecisionGraphError('invalid decision-evaluate request');
55
+ const load = async (path) => parseDecisionJson(await readFile(path, 'utf8'));
56
+ assertDecisionFlowPins(node, await load(base.bindingPath), await Promise.all(base.definitionPaths.map(load)));
57
+ const directory = await mkdtemp(join(host.workDirectory, 'decision-evaluate-'));
58
+ try {
59
+ await chmod(directory, 0o700);
60
+ let inputPath = base.inputPath;
61
+ if (node.id !== graph.entry) {
62
+ inputPath = join(directory, 'input.json');
63
+ await writeFile(inputPath, JSON.stringify(input), { mode: 0o600, flag: 'wx' });
64
+ }
65
+ const requestPath = join(directory, 'request.json');
66
+ await writeFile(requestPath, JSON.stringify({ ...base, inputPath, runId: flow.runId, invocationId: flow.invocationKey }), { mode: 0o600, flag: 'wx' });
67
+ const outcome = await (host.run ?? runDecisionEvaluateSkill)({ scriptPath: host.skill.scriptPath, requestPath,
68
+ env: { ...(host.env ?? {}) }, timeoutMs: host.timeoutMs ?? 60_000 });
69
+ // Exit 1 still carries an error/cancelled RulesetResult; anything else is a failure.
70
+ if (outcome.code !== 0 && outcome.code !== 1)
71
+ throw new DecisionGraphError('decision-evaluate skill failed');
72
+ let result;
73
+ try {
74
+ result = parseDecisionJson(outcome.stdout);
75
+ }
76
+ catch {
77
+ throw new DecisionGraphError('invalid decision-evaluate output');
78
+ }
79
+ if (!result || result.kind !== 'RulesetResult' || !result.spec || result.spec.runId !== flow.runId ||
80
+ result.spec.invocationId !== flow.invocationKey || result.spec.binding?.digest !== node.binding.digest ||
81
+ !result.spec.evaluations || typeof result.spec.evaluations !== 'object') {
82
+ throw new DecisionGraphError('decision-evaluate result does not match its Flow invocation');
83
+ }
84
+ return decisionFlowResponse(graph, node, result, host);
85
+ }
86
+ finally {
87
+ await rm(directory, { recursive: true, force: true });
88
+ }
89
+ };
90
+ }
91
+ //# sourceMappingURL=graph-skill-bridge.js.map
@@ -0,0 +1,56 @@
1
+ import { planDecisionGraph } from './graph.js';
2
+ function validated(graph, pins) {
3
+ return { graph, plan: planDecisionGraph(graph, pins) };
4
+ }
5
+ /** Entry obtains an explicit shortlist; only its declared candidates reach reranking.
6
+ * The host projector must set `has-candidates` from the shortlist it projects: when it
7
+ * is false, reranking never runs and the graph outcome is `empty-shortlist`.
8
+ */
9
+ export function shortlistRerankTemplate(base) {
10
+ return validated({ schemaVersion: 'decision-graph/v1', id: base.id, pattern: 'shortlist-rerank',
11
+ entry: 'shortlist', terminals: ['rerank'], budget: base.budget,
12
+ nodes: [
13
+ { ...base.shortlist, id: 'shortlist', stage: 0, input: [], output: ['candidates', 'has-candidates'] },
14
+ { ...base.rerank, id: 'rerank', stage: 1, input: ['candidates'], output: ['result'] },
15
+ ], edges: [{ from: 'shortlist', to: 'rerank', source: 'candidates', destination: 'candidates',
16
+ when: { source: 'has-candidates', equals: true } }] }, base.resolvedPins);
17
+ }
18
+ /** Fixed, caller-authored taxonomy candidates fan out; no model may create new nodes. */
19
+ export function taxonomyBeamTemplate(base) {
20
+ const ordered = [...base.branches].sort((a, b) => a.id.localeCompare(b.id));
21
+ if (base.details && base.select.target !== 'beam')
22
+ throw new Error('guarded beam requires a deterministic selector target');
23
+ const terminals = base.details ? ordered.map(branch => `detail-${branch.id}`) : ['select'];
24
+ return validated({ schemaVersion: 'decision-graph/v1', id: base.id, pattern: 'taxonomy-beam',
25
+ entry: 'taxonomy', terminals, budget: base.budget,
26
+ nodes: [
27
+ { ...base.taxonomy, id: 'taxonomy', stage: 0, input: [], output: ['children'] },
28
+ ...ordered.map(branch => ({ ...branch.config, id: branch.id, stage: 1, input: ['children'], output: ['score'] })),
29
+ { ...base.select, id: 'select', stage: 2, input: ordered.map(branch => branch.id),
30
+ output: ['result', ...(base.details ? ordered.map(branch => `chosen-${branch.id}`) : [])] },
31
+ ...(base.details ? ordered.map(branch => ({ ...base.details, id: `detail-${branch.id}`,
32
+ stage: 3, input: ['seed'], output: ['result'] })) : []),
33
+ ], edges: [
34
+ ...ordered.map(branch => ({ from: 'taxonomy', to: branch.id, source: 'children', destination: 'children' })),
35
+ ...ordered.map(branch => ({ from: branch.id, to: 'select', source: 'score', destination: branch.id })),
36
+ ...(base.details ? ordered.map(branch => ({ from: 'select', to: `detail-${branch.id}`,
37
+ source: 'result', destination: 'seed', when: { source: `chosen-${branch.id}`, equals: true } })) : []),
38
+ ] }, base.resolvedPins);
39
+ }
40
+ /** A declared boolean from the verifier controls the ordinary-LLM fallback.
41
+ * If false, verifier is terminal; if true, the fallback result supersedes it.
42
+ */
43
+ export function extractorVerifierFallbackTemplate(base) {
44
+ return validated({ schemaVersion: 'decision-graph/v1', id: base.id, pattern: 'extractor-verifier-fallback',
45
+ entry: 'extractor', terminals: ['verifier', 'fallback'], budget: base.budget,
46
+ nodes: [
47
+ { ...base.extractor, id: 'extractor', stage: 0, input: [], output: ['evidence'] },
48
+ { ...base.verifier, id: 'verifier', stage: 1, input: ['evidence'], output: ['result', 'needs-fallback'] },
49
+ { ...base.fallback, id: 'fallback', stage: 2, input: ['prior'], output: ['result'] },
50
+ ], edges: [
51
+ { from: 'extractor', to: 'verifier', source: 'evidence', destination: 'evidence' },
52
+ { from: 'verifier', to: 'fallback', source: 'result', destination: 'prior',
53
+ when: { source: 'needs-fallback', equals: true } },
54
+ ] }, base.resolvedPins);
55
+ }
56
+ //# sourceMappingURL=graph-templates.js.map