blun-king-cli 9.1.567 → 9.1.570

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 (263) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/agent-spine-plugin/.claude-plugin/marketplace.json +1 -1
  3. package/agent-spine-plugin/.claude-plugin/plugin.json +1 -1
  4. package/agent-spine-plugin/.codex-plugin/plugin.json +2 -1
  5. package/agent-spine-plugin/CHANGELOG.md +1581 -0
  6. package/agent-spine-plugin/README.md +30 -4
  7. package/agent-spine-plugin/blun.plugin.json +3 -3
  8. package/agent-spine-plugin/docs/acceptance.md +61 -0
  9. package/agent-spine-plugin/docs/assignment-continuation.md +48 -0
  10. package/agent-spine-plugin/docs/host-integration.md +178 -0
  11. package/agent-spine-plugin/docs/preflight-recall.md +69 -0
  12. package/agent-spine-plugin/docs/preservation-contract.md +53 -0
  13. package/agent-spine-plugin/docs/quality-gates.md +50 -0
  14. package/agent-spine-plugin/docs/releasing.md +85 -0
  15. package/agent-spine-plugin/docs/session-timeline.md +251 -0
  16. package/agent-spine-plugin/docs/source-roots.md +113 -0
  17. package/agent-spine-plugin/docs/structured-completion.md +67 -0
  18. package/agent-spine-plugin/docs/world-model.md +94 -0
  19. package/agent-spine-plugin/hooks/codex.json +2 -2
  20. package/agent-spine-plugin/hooks/hooks.json +1 -1
  21. package/agent-spine-plugin/hooks/version.json +1 -1
  22. package/agent-spine-plugin/package.json +13 -3
  23. package/agent-spine-plugin/scripts/check-codex-install.js +226 -0
  24. package/agent-spine-plugin/scripts/check-hosts.js +14 -5
  25. package/agent-spine-plugin/scripts/check-install-hook.js +226 -0
  26. package/agent-spine-plugin/scripts/check-install-selfstarter.js +154 -0
  27. package/agent-spine-plugin/scripts/check-install.js +478 -0
  28. package/agent-spine-plugin/scripts/check-line-budget.js +58 -0
  29. package/agent-spine-plugin/scripts/check-syntax.js +29 -0
  30. package/agent-spine-plugin/scripts/github-actions.js +11 -0
  31. package/agent-spine-plugin/scripts/hermetic-process.js +183 -0
  32. package/agent-spine-plugin/scripts/release-check.js +145 -0
  33. package/agent-spine-plugin/scripts/run-acceptance.js +19 -0
  34. package/agent-spine-plugin/scripts/run-checks.js +47 -0
  35. package/agent-spine-plugin/scripts/run-tests-hermetic.js +89 -0
  36. package/agent-spine-plugin/skills/agent-spine/SKILL.md +16 -2
  37. package/agent-spine-plugin/spine-example/1-identity.md +12 -0
  38. package/agent-spine-plugin/spine-example/2-voice.md +6 -0
  39. package/agent-spine-plugin/spine-example/3-conduct.md +8 -0
  40. package/agent-spine-plugin/spine-example/4-history.md +4 -0
  41. package/agent-spine-plugin/src/cli-agent.js +296 -0
  42. package/agent-spine-plugin/src/cli-attention.js +95 -0
  43. package/agent-spine-plugin/src/cli-autonomy.js +36 -0
  44. package/agent-spine-plugin/src/cli-common.js +71 -0
  45. package/agent-spine-plugin/src/cli-continuity.js +116 -0
  46. package/agent-spine-plugin/src/cli-core.js +128 -0
  47. package/agent-spine-plugin/src/cli-diagnostics.js +291 -0
  48. package/agent-spine-plugin/src/cli-host.js +21 -0
  49. package/agent-spine-plugin/src/cli-learning.js +305 -0
  50. package/agent-spine-plugin/src/cli-premortem.js +16 -0
  51. package/agent-spine-plugin/src/cli-sharing.js +230 -0
  52. package/agent-spine-plugin/src/cli.js +40 -1350
  53. package/agent-spine-plugin/src/codex-reader-launcher.js +182 -0
  54. package/agent-spine-plugin/src/hook.js +185 -567
  55. package/agent-spine-plugin/src/index.js +16 -2
  56. package/agent-spine-plugin/src/lib/acceptance.js +113 -11
  57. package/agent-spine-plugin/src/lib/action-lesson-recall.js +53 -0
  58. package/agent-spine-plugin/src/lib/attention-context.js +167 -0
  59. package/agent-spine-plugin/src/lib/attention-events.js +113 -0
  60. package/agent-spine-plugin/src/lib/attention-privacy.js +72 -0
  61. package/agent-spine-plugin/src/lib/attention-schema.js +164 -0
  62. package/agent-spine-plugin/src/lib/attention-storage.js +93 -0
  63. package/agent-spine-plugin/src/lib/attention.js +9 -600
  64. package/agent-spine-plugin/src/lib/audit-premortem.js +223 -0
  65. package/agent-spine-plugin/src/lib/audit.js +56 -6
  66. package/agent-spine-plugin/src/lib/autonomy-policy.js +110 -0
  67. package/agent-spine-plugin/src/lib/autonomy-store.js +202 -0
  68. package/agent-spine-plugin/src/lib/autonomy.js +8 -0
  69. package/agent-spine-plugin/src/lib/briefing.js +67 -4
  70. package/agent-spine-plugin/src/lib/catalog-document-read.js +51 -0
  71. package/agent-spine-plugin/src/lib/catalog.js +1 -1
  72. package/agent-spine-plugin/src/lib/codex-installation.js +231 -0
  73. package/agent-spine-plugin/src/lib/codex-skill-installation.js +211 -0
  74. package/agent-spine-plugin/src/lib/context.js +3 -1
  75. package/agent-spine-plugin/src/lib/delivery-agent-usage.js +224 -0
  76. package/agent-spine-plugin/src/lib/delivery-assignment.js +220 -0
  77. package/agent-spine-plugin/src/lib/delivery-command-actions.js +453 -0
  78. package/agent-spine-plugin/src/lib/delivery-knowledge.js +78 -0
  79. package/agent-spine-plugin/src/lib/delivery-premortem-binding.js +107 -0
  80. package/agent-spine-plugin/src/lib/delivery-premortem-closure.js +171 -0
  81. package/agent-spine-plugin/src/lib/delivery-premortem-codec.js +45 -0
  82. package/agent-spine-plugin/src/lib/delivery-premortem-correction.js +101 -0
  83. package/agent-spine-plugin/src/lib/delivery-premortem-file.js +21 -0
  84. package/agent-spine-plugin/src/lib/delivery-premortem-index.js +493 -0
  85. package/agent-spine-plugin/src/lib/delivery-premortem-inspection.js +37 -0
  86. package/agent-spine-plugin/src/lib/delivery-premortem-recovery.js +120 -0
  87. package/agent-spine-plugin/src/lib/delivery-premortem-rejection.js +65 -0
  88. package/agent-spine-plugin/src/lib/delivery-premortem-results.js +28 -0
  89. package/agent-spine-plugin/src/lib/delivery-premortem-session-guard.js +46 -0
  90. package/agent-spine-plugin/src/lib/delivery-premortem-write-ledger.js +285 -0
  91. package/agent-spine-plugin/src/lib/delivery-premortem.js +499 -0
  92. package/agent-spine-plugin/src/lib/delivery-shell-heredoc.js +115 -0
  93. package/agent-spine-plugin/src/lib/delivery-shell-substitutions.js +111 -0
  94. package/agent-spine-plugin/src/lib/delivery-shell-wrapper.js +64 -0
  95. package/agent-spine-plugin/src/lib/delivery-target.js +73 -0
  96. package/agent-spine-plugin/src/lib/delivery-verification.js +445 -0
  97. package/agent-spine-plugin/src/lib/documents.js +27 -5
  98. package/agent-spine-plugin/src/lib/filesystem-retry.js +2 -0
  99. package/agent-spine-plugin/src/lib/gateway-common.js +68 -0
  100. package/agent-spine-plugin/src/lib/gateway-control.js +302 -0
  101. package/agent-spine-plugin/src/lib/gateway-delivery.js +103 -0
  102. package/agent-spine-plugin/src/lib/gateway-execution.js +350 -0
  103. package/agent-spine-plugin/src/lib/gateway-host-lifecycle.js +185 -0
  104. package/agent-spine-plugin/src/lib/gateway-inspection.js +85 -0
  105. package/agent-spine-plugin/src/lib/gateway-knowledge.js +129 -0
  106. package/agent-spine-plugin/src/lib/gateway-policy-provenance.js +197 -0
  107. package/agent-spine-plugin/src/lib/gateway-premortem-disposition.js +82 -0
  108. package/agent-spine-plugin/src/lib/gateway-premortem.js +363 -0
  109. package/agent-spine-plugin/src/lib/gateway-runs.js +300 -0
  110. package/agent-spine-plugin/src/lib/gateway-runtime-identity.js +31 -0
  111. package/agent-spine-plugin/src/lib/gateway-runtime-records.js +21 -0
  112. package/agent-spine-plugin/src/lib/gateway-runtime.js +18 -1623
  113. package/agent-spine-plugin/src/lib/gateway-state-transaction.js +356 -0
  114. package/agent-spine-plugin/src/lib/gateway-state.js +342 -0
  115. package/agent-spine-plugin/src/lib/hook-artifact-guards.js +356 -0
  116. package/agent-spine-plugin/src/lib/hook-audit.js +16 -2
  117. package/agent-spine-plugin/src/lib/hook-briefing-use.js +68 -0
  118. package/agent-spine-plugin/src/lib/hook-context.js +424 -0
  119. package/agent-spine-plugin/src/lib/hook-final-message.js +26 -0
  120. package/agent-spine-plugin/src/lib/hook-input.js +27 -0
  121. package/agent-spine-plugin/src/lib/hook-output.js +140 -0
  122. package/agent-spine-plugin/src/lib/hook-premortem.js +257 -0
  123. package/agent-spine-plugin/src/lib/hook-process-advisory.js +37 -0
  124. package/agent-spine-plugin/src/lib/hook-protection.js +95 -0
  125. package/agent-spine-plugin/src/lib/hook-stop-verification.js +84 -0
  126. package/agent-spine-plugin/src/lib/hook-timeline.js +91 -0
  127. package/agent-spine-plugin/src/lib/identifier-analysis.js +446 -0
  128. package/agent-spine-plugin/src/lib/indexed-memory.js +23 -6
  129. package/agent-spine-plugin/src/lib/knowledge-evidence.js +431 -0
  130. package/agent-spine-plugin/src/lib/learning-applications.js +441 -0
  131. package/agent-spine-plugin/src/lib/learning-candidates.js +274 -0
  132. package/agent-spine-plugin/src/lib/learning-context.js +130 -0
  133. package/agent-spine-plugin/src/lib/learning-delivery-contracts.js +310 -0
  134. package/agent-spine-plugin/src/lib/learning-evaluation-contracts.js +350 -0
  135. package/agent-spine-plugin/src/lib/learning-evaluation-registration.js +372 -0
  136. package/agent-spine-plugin/src/lib/learning-evaluation-revocation.js +292 -0
  137. package/agent-spine-plugin/src/lib/learning-evidence-contracts.js +321 -0
  138. package/agent-spine-plugin/src/lib/learning-findings.js +458 -0
  139. package/agent-spine-plugin/src/lib/learning-measurement-contracts.js +285 -0
  140. package/agent-spine-plugin/src/lib/learning-measurements.js +265 -0
  141. package/agent-spine-plugin/src/lib/learning-outcome-contracts.js +165 -0
  142. package/agent-spine-plugin/src/lib/learning-outcomes.js +343 -0
  143. package/agent-spine-plugin/src/lib/learning-reconciliation.js +290 -0
  144. package/agent-spine-plugin/src/lib/learning-retry-contracts.js +188 -0
  145. package/agent-spine-plugin/src/lib/learning-schema.js +229 -0
  146. package/agent-spine-plugin/src/lib/learning-scope-targets.js +424 -0
  147. package/agent-spine-plugin/src/lib/learning-state-upgrade.js +477 -0
  148. package/agent-spine-plugin/src/lib/learning-status-configuration.js +476 -0
  149. package/agent-spine-plugin/src/lib/learning-storage.js +203 -0
  150. package/agent-spine-plugin/src/lib/learning-trial-recovery.js +218 -0
  151. package/agent-spine-plugin/src/lib/learning-validation-contracts.js +375 -0
  152. package/agent-spine-plugin/src/lib/learning-validation-renewal.js +298 -0
  153. package/agent-spine-plugin/src/lib/learning-validation-runtime.js +234 -0
  154. package/agent-spine-plugin/src/lib/learning.js +36 -6923
  155. package/agent-spine-plugin/src/lib/lesson-recall-session.js +172 -0
  156. package/agent-spine-plugin/src/lib/mcp-autonomy-tools.js +37 -0
  157. package/agent-spine-plugin/src/lib/mcp-delivery-completion.js +124 -0
  158. package/agent-spine-plugin/src/lib/mcp-delivery-tools.js +45 -0
  159. package/agent-spine-plugin/src/lib/mcp-premortem.js +68 -0
  160. package/agent-spine-plugin/src/lib/mcp-runtime.js +269 -0
  161. package/agent-spine-plugin/src/lib/mcp-source-context.js +51 -0
  162. package/agent-spine-plugin/src/lib/mcp-timeline-tools.js +164 -0
  163. package/agent-spine-plugin/src/lib/mcp-world-tools.js +52 -0
  164. package/agent-spine-plugin/src/lib/owned-file-lock.js +30 -10
  165. package/agent-spine-plugin/src/lib/project-portfolio.js +176 -0
  166. package/agent-spine-plugin/src/lib/selfstarter-core.js +382 -0
  167. package/agent-spine-plugin/src/lib/selfstarter-jobs.js +149 -0
  168. package/agent-spine-plugin/src/lib/selfstarter-lease.js +204 -0
  169. package/agent-spine-plugin/src/lib/selfstarter-policy.js +90 -0
  170. package/agent-spine-plugin/src/lib/selfstarter-workspace.js +111 -0
  171. package/agent-spine-plugin/src/lib/selfstarter.js +11 -911
  172. package/agent-spine-plugin/src/lib/session-timeline-auth.js +316 -0
  173. package/agent-spine-plugin/src/lib/session-timeline-codex.js +58 -0
  174. package/agent-spine-plugin/src/lib/session-timeline-contract.js +48 -0
  175. package/agent-spine-plugin/src/lib/session-timeline-enrollment-source.js +41 -0
  176. package/agent-spine-plugin/src/lib/session-timeline-enrollment-storage.js +132 -0
  177. package/agent-spine-plugin/src/lib/session-timeline-enrollment-transport.js +17 -0
  178. package/agent-spine-plugin/src/lib/session-timeline-enrollment.js +500 -0
  179. package/agent-spine-plugin/src/lib/session-timeline-event-extract.js +157 -0
  180. package/agent-spine-plugin/src/lib/session-timeline-host-origin.js +74 -0
  181. package/agent-spine-plugin/src/lib/session-timeline-host-receipt.js +117 -0
  182. package/agent-spine-plugin/src/lib/session-timeline-invocation.js +201 -0
  183. package/agent-spine-plugin/src/lib/session-timeline-king.js +79 -0
  184. package/agent-spine-plugin/src/lib/session-timeline-prior.js +59 -0
  185. package/agent-spine-plugin/src/lib/session-timeline-provider.js +34 -0
  186. package/agent-spine-plugin/src/lib/session-timeline-query.js +55 -0
  187. package/agent-spine-plugin/src/lib/session-timeline-results.js +31 -0
  188. package/agent-spine-plugin/src/lib/session-timeline-root.js +11 -0
  189. package/agent-spine-plugin/src/lib/session-timeline-search.js +82 -0
  190. package/agent-spine-plugin/src/lib/session-timeline-sid-acl.js +217 -0
  191. package/agent-spine-plugin/src/lib/session-timeline-source.js +83 -0
  192. package/agent-spine-plugin/src/lib/session-timeline-state.js +45 -0
  193. package/agent-spine-plugin/src/lib/session-timeline-transport.js +50 -0
  194. package/agent-spine-plugin/src/lib/session-timeline-windows-acl.js +148 -0
  195. package/agent-spine-plugin/src/lib/session-timeline.js +453 -0
  196. package/agent-spine-plugin/src/lib/source-roots.js +69 -136
  197. package/agent-spine-plugin/src/lib/source-tree-scan.js +178 -0
  198. package/agent-spine-plugin/src/lib/task-knowledge-context.js +78 -0
  199. package/agent-spine-plugin/src/lib/timeline-tool-guard.js +202 -0
  200. package/agent-spine-plugin/src/lib/world-knowledge.js +249 -0
  201. package/agent-spine-plugin/src/lib/world-model.js +278 -0
  202. package/agent-spine-plugin/src/mcp.js +20 -161
  203. package/agent-spine-plugin/src/version.js +1 -1
  204. package/agent-spine-plugin/src/worker.js +22 -5
  205. package/bin/agent-resume-snapshot.cjs +2 -2
  206. package/bin/agentspine-king-goal-inbox.mjs +111 -0
  207. package/bin/agentspine-king-goal-intake.mjs +106 -0
  208. package/bin/baseline-skill-performance-policy.cjs +1 -16
  209. package/bin/core-bootstrap.js +3 -0
  210. package/bin/curiosity-scout-policy.cjs +5 -1
  211. package/bin/input-draft-persistence.cjs +2 -2
  212. package/bin/king-tui-function-contract.json +33 -0
  213. package/bin/launcher-restart-policy.cjs +150 -0
  214. package/bin/launcher-runtime.js +56 -44
  215. package/bin/managed-context-startup-policy.cjs +27 -0
  216. package/bin/managed-plugin-selection.cjs +116 -0
  217. package/bin/mistake-relevance-policy.cjs +1 -1
  218. package/bin/observer-hooks.cjs +14 -0
  219. package/bin/oversized-context-offload-policy.cjs +86 -0
  220. package/bin/plugin-bootstrap.js +7 -40
  221. package/bin/proactive-compaction-policy.cjs +1 -1
  222. package/bin/provider-model-refresh-deadline.cjs +53 -0
  223. package/bin/provider-model-refresh-policy.cjs +107 -0
  224. package/bin/release-artifact-freeze-policy.cjs +30 -0
  225. package/bin/repeated-user-message-projection.cjs +3 -126
  226. package/bin/research-page-result.cjs +74 -0
  227. package/bin/runtime-exit-ledger.cjs +1 -0
  228. package/bin/session-compaction-policy.cjs +84 -0
  229. package/bin/skill-listing-performance-policy.cjs +2 -2
  230. package/bin/standard-tools-bootstrap.js +0 -37
  231. package/bin/subagent-skill-policy.cjs +3 -1
  232. package/bin/telegram-approval-relay.cjs +12 -7
  233. package/bin/telegram-private-conversation-policy.cjs +3 -2
  234. package/bin/telegram-queue-handoff-policy.cjs +24 -0
  235. package/bin/thinking-activity-status-policy.cjs +1 -1
  236. package/bin/thinking-only-guard.cjs +16 -12
  237. package/bin/tool-call-loop-policy.cjs +0 -2
  238. package/bin/tool-result-offload-policy.cjs +11 -2
  239. package/bin/tui-functional-contract.cjs +55 -0
  240. package/bin/update-notice.js +18 -14
  241. package/bin/user-message-offload-policy.cjs +1 -1
  242. package/bin/user-prompt-hook-origin-policy.cjs +34 -0
  243. package/bin/windows-node-crash-dump.cjs +110 -0
  244. package/blun.mjs +2861 -1173
  245. package/codebase-index/codebase_index.py +4 -3
  246. package/package.json +5 -18
  247. package/standard-skills/research-evidence/SKILL.md +39 -0
  248. package/standard-skills/research-evidence/references/evidence-format.md +82 -0
  249. package/standard-skills/research-evidence/scripts/evidence-collection.cjs +254 -0
  250. package/standard-skills/research-evidence/scripts/score-report.cjs +112 -0
  251. package/standard-skills/web-lesen/SKILL.md +37 -22
  252. package/standard-skills/web-lesen/scripts/crawl_public.py +376 -0
  253. package/telegram-plugin/DELIVERY.md +36 -0
  254. package/telegram-plugin/bin/telegram-approval-relay.cjs +13 -7
  255. package/telegram-plugin/bin/telegram-launcher-status-queue.cjs +122 -0
  256. package/telegram-plugin/bin/telegram-private-conversation-policy.cjs +3 -2
  257. package/telegram-plugin/bin/telegram-reply-parts.cjs +149 -0
  258. package/telegram-plugin/dist/bridge.mjs +7 -56
  259. package/telegram-plugin/dist/mcp-server.mjs +33 -4
  260. package/agent-spine-plugin/skill/SKILL.md +0 -76
  261. package/bin/mnemo-connect-heartbeat.cjs +0 -204
  262. package/bin/mnemo-tool-agent-policy.cjs +0 -22
  263. package/telegram-plugin/bin/telegram-mnemo-capture.cjs +0 -297
@@ -0,0 +1,350 @@
1
+ import {
2
+ EXECUTION_ATTEMPT_SCHEMA, EXECUTION_OUTCOME_SCHEMA, ID_RE, METRIC_OPERATORS,
3
+ STRATEGY_TRANSFER_PROOF_SCHEMA, exactId, sha256, timestamp
4
+ } from "./gateway-common.js";
5
+
6
+ function executionDecisionMaterial(execution) {
7
+ return {
8
+ requiredCapabilities: execution.requiredCapabilities, strategies: execution.strategies,
9
+ verification: execution.verification, selectedStrategyId: execution.selectedStrategyId,
10
+ ...(execution.explorationMaxAttempts === undefined ? {} : {
11
+ explorationMaxAttempts: execution.explorationMaxAttempts,
12
+ explorationOrder: execution.explorationOrder
13
+ }),
14
+ ...(execution.transferKey === undefined ? {} : {
15
+ transferKey: execution.transferKey, transferMaxAgeDays: execution.transferMaxAgeDays,
16
+ transferProof: execution.transferProof
17
+ }),
18
+ authority: "context-only-decision"
19
+ };
20
+ }
21
+
22
+ function sufficientExecutionStrategies(execution) {
23
+ const required = new Set(execution.requiredCapabilities);
24
+ return execution.strategies.filter((strategy) => strategy.capabilities.every((capability) => ID_RE.test(capability))
25
+ && [...required].every((capability) => strategy.capabilities.includes(capability)))
26
+ .sort((left, right) => left.risk - right.risk || left.cost - right.cost
27
+ || left.strategyId.localeCompare(right.strategyId));
28
+ }
29
+
30
+ function selectedExecutionStrategy(execution) {
31
+ const sufficient = sufficientExecutionStrategies(execution);
32
+ const minimumRisk = sufficient[0]?.risk;
33
+ const transferred = execution.transferProof && sufficient.find((strategy) =>
34
+ strategy.strategyId === execution.transferProof.strategyId && strategy.risk === minimumRisk);
35
+ return transferred || sufficient[0] || null;
36
+ }
37
+
38
+ function expectedExplorationOrder(execution) {
39
+ const sufficient = sufficientExecutionStrategies(execution);
40
+ const selected = selectedExecutionStrategy(execution);
41
+ if (!selected) return [];
42
+ return [selected, ...sufficient.filter((strategy) => strategy.risk === selected.risk
43
+ && strategy.strategyId !== selected.strategyId)].map((strategy) => strategy.strategyId);
44
+ }
45
+
46
+ function strategyTransferProofMaterial(proof) {
47
+ return {
48
+ transferKey: proof.transferKey, strategyId: proof.strategyId, maxAgeDays: proof.maxAgeDays,
49
+ evidence: proof.evidence, authority: "context-only-transfer"
50
+ };
51
+ }
52
+
53
+ function validStrategyTransferProof(proof) {
54
+ if (!(proof && proof.schema === STRATEGY_TRANSFER_PROOF_SCHEMA
55
+ && ID_RE.test(proof.proofId || "") && ID_RE.test(proof.transferKey || "")
56
+ && ID_RE.test(proof.strategyId || "") && Number.isInteger(proof.maxAgeDays)
57
+ && proof.maxAgeDays >= 1 && proof.maxAgeDays <= 90 && Array.isArray(proof.evidence)
58
+ && proof.evidence.length >= 2 && proof.evidence.length <= 8
59
+ && new Set(proof.evidence.map((item) => item.sourceGoalId)).size === proof.evidence.length
60
+ && new Set(proof.evidence.map((item) => item.sourceDigest)).size === proof.evidence.length
61
+ && proof.evidence.every((item) => item && ID_RE.test(item.sourceGoalId || "")
62
+ && ID_RE.test(item.sourceStepId || "") && ID_RE.test(item.outcomeId || "")
63
+ && /^[a-f0-9]{64}$/.test(item.outcomeDigest || "")
64
+ && /^[a-f0-9]{64}$/.test(item.sourceDigest || "")
65
+ && Number.isFinite(new Date(item.completedAt).getTime()))
66
+ && /^[a-f0-9]{64}$/.test(proof.proofDigest || "")
67
+ && proof.authority === "context-only-transfer")) return false;
68
+ const digest = sha256(JSON.stringify(strategyTransferProofMaterial(proof)));
69
+ return proof.proofDigest === digest && proof.proofId === "strategy-transfer:" + digest.slice(0, 32);
70
+ }
71
+
72
+ function sameStrategy(left, right) {
73
+ return JSON.stringify(left) === JSON.stringify(right);
74
+ }
75
+
76
+ function sameVerification(left, right) {
77
+ return JSON.stringify(left) === JSON.stringify(right);
78
+ }
79
+
80
+ function collectStrategyTransferEvidence(goals, execution, strategy, scope) {
81
+ if (!execution.transferKey) return { evidence: [], regressed: false };
82
+ const before = new Date(scope.before);
83
+ const cutoff = new Date(before.getTime() - execution.transferMaxAgeDays * 86400000);
84
+ const evidence = []; let regressed = false;
85
+ for (const goal of goals) {
86
+ if (goal.goalId === scope.goalId || goal.projectId !== scope.projectId || goal.groupId !== scope.groupId || !goal.plan) continue;
87
+ for (const step of goal.plan.steps) {
88
+ const prior = step.execution;
89
+ const completedAt = step.completedAt && new Date(step.completedAt);
90
+ const updatedAt = new Date(step.updatedAt);
91
+ if (!prior || prior.transferKey !== execution.transferKey
92
+ || !sameVerification(prior.verification, execution.verification)
93
+ || !sameStrategy(prior.strategies.find((item) => item.strategyId === strategy.strategyId), strategy)
94
+ || updatedAt > before) continue;
95
+ for (const outcome of step.executionOutcomes || []) {
96
+ if (outcome.strategyId !== strategy.strategyId) continue;
97
+ const observedAt = new Date(outcome.observedAt);
98
+ if (observedAt < cutoff || observedAt > before || observedAt < new Date(goal.createdAt)) continue;
99
+ if (!outcome.passed || outcome.blockingDefect) { regressed = true; continue; }
100
+ if (step.status !== "completed" || !completedAt || completedAt < cutoff || completedAt > before
101
+ || observedAt > completedAt) continue;
102
+ evidence.push({ sourceGoalId: goal.goalId, sourceStepId: step.stepId, outcomeId: outcome.outcomeId,
103
+ outcomeDigest: outcome.digest, sourceDigest: outcome.sourceDigest, completedAt: step.completedAt });
104
+ }
105
+ }
106
+ }
107
+ const unique = [];
108
+ for (const item of evidence.sort((left, right) => left.completedAt.localeCompare(right.completedAt)
109
+ || left.outcomeId.localeCompare(right.outcomeId))) {
110
+ if (!unique.some((entry) => entry.sourceGoalId === item.sourceGoalId || entry.sourceDigest === item.sourceDigest)) unique.push(item);
111
+ }
112
+ return { evidence: unique.slice(-8), regressed };
113
+ }
114
+
115
+ function createStrategyTransferProof(goals, execution, scope) {
116
+ if (!execution.transferKey) return null;
117
+ const required = new Set(execution.requiredCapabilities);
118
+ const sufficient = execution.strategies.filter((strategy) =>
119
+ [...required].every((capability) => strategy.capabilities.includes(capability)));
120
+ const minimumRisk = Math.min(...sufficient.map((strategy) => strategy.risk));
121
+ const proven = sufficient.filter((strategy) => strategy.risk === minimumRisk).map((strategy) => ({
122
+ strategy, ...collectStrategyTransferEvidence(goals, execution, strategy, scope)
123
+ })).filter((item) => !item.regressed && item.evidence.length >= 2)
124
+ .sort((left, right) => right.evidence.length - left.evidence.length
125
+ || left.strategy.cost - right.strategy.cost || left.strategy.strategyId.localeCompare(right.strategy.strategyId));
126
+ if (!proven.length) return null;
127
+ const proof = { schema: STRATEGY_TRANSFER_PROOF_SCHEMA, proofId: null,
128
+ transferKey: execution.transferKey, strategyId: proven[0].strategy.strategyId,
129
+ maxAgeDays: execution.transferMaxAgeDays, evidence: proven[0].evidence,
130
+ proofDigest: null, authority: "context-only-transfer" };
131
+ proof.proofDigest = sha256(JSON.stringify(strategyTransferProofMaterial(proof)));
132
+ proof.proofId = "strategy-transfer:" + proof.proofDigest.slice(0, 32);
133
+ return proof;
134
+ }
135
+
136
+ export function validGoalTransferProofs(goal, goals) {
137
+ if (!goal.plan) return true;
138
+ return goal.plan.steps.every((step) => {
139
+ if (!step.execution?.transferProof) return true;
140
+ const expected = createStrategyTransferProof(goals, step.execution, {
141
+ goalId: goal.goalId, projectId: goal.projectId, groupId: goal.groupId, before: goal.createdAt
142
+ });
143
+ return JSON.stringify(expected) === JSON.stringify(step.execution.transferProof);
144
+ });
145
+ }
146
+
147
+ export function validExecutionDecision(execution) {
148
+ if (!(execution && execution.authority === "context-only-decision"
149
+ && Array.isArray(execution.requiredCapabilities) && execution.requiredCapabilities.length > 0
150
+ && execution.requiredCapabilities.length <= 16
151
+ && new Set(execution.requiredCapabilities).size === execution.requiredCapabilities.length
152
+ && execution.requiredCapabilities.every((capability) => ID_RE.test(capability || ""))
153
+ && Array.isArray(execution.strategies) && execution.strategies.length >= 2 && execution.strategies.length <= 8
154
+ && new Set(execution.strategies.map((strategy) => strategy?.strategyId)).size === execution.strategies.length
155
+ && execution.strategies.every((strategy) => strategy && ID_RE.test(strategy.strategyId || "")
156
+ && Array.isArray(strategy.capabilities) && strategy.capabilities.length > 0 && strategy.capabilities.length <= 16
157
+ && new Set(strategy.capabilities).size === strategy.capabilities.length
158
+ && strategy.capabilities.every((capability) => ID_RE.test(capability || ""))
159
+ && Number.isInteger(strategy.risk) && strategy.risk >= 0 && strategy.risk <= 100
160
+ && Number.isInteger(strategy.cost) && strategy.cost >= 0 && strategy.cost <= 100)
161
+ && execution.verification && ID_RE.test(execution.verification.evaluatorId || "")
162
+ && ID_RE.test(execution.verification.metric || "") && METRIC_OPERATORS.has(execution.verification.operator)
163
+ && Number.isFinite(execution.verification.threshold)
164
+ && Number.isInteger(execution.verification.minCases) && execution.verification.minCases >= 1
165
+ && execution.verification.minCases <= 100000 && ID_RE.test(execution.selectedStrategyId || "")
166
+ && (execution.explorationMaxAttempts === undefined || (Number.isInteger(execution.explorationMaxAttempts)
167
+ && execution.explorationMaxAttempts >= 2 && execution.explorationMaxAttempts <= 4
168
+ && Array.isArray(execution.explorationOrder)
169
+ && execution.explorationOrder.length === execution.explorationMaxAttempts
170
+ && new Set(execution.explorationOrder).size === execution.explorationOrder.length
171
+ && execution.explorationOrder.every((strategyId) => ID_RE.test(strategyId || ""))))
172
+ && (execution.transferKey === undefined || (ID_RE.test(execution.transferKey || "")
173
+ && Number.isInteger(execution.transferMaxAgeDays) && execution.transferMaxAgeDays >= 1
174
+ && execution.transferMaxAgeDays <= 90
175
+ && (execution.transferProof === null || (validStrategyTransferProof(execution.transferProof)
176
+ && execution.transferProof.transferKey === execution.transferKey
177
+ && execution.transferProof.maxAgeDays === execution.transferMaxAgeDays))))
178
+ && /^[a-f0-9]{64}$/.test(execution.decisionDigest || ""))) return false;
179
+ const selected = selectedExecutionStrategy(execution);
180
+ return selected?.strategyId === execution.selectedStrategyId
181
+ && (execution.explorationMaxAttempts === undefined
182
+ || JSON.stringify(execution.explorationOrder)
183
+ === JSON.stringify(expectedExplorationOrder(execution).slice(0, execution.explorationMaxAttempts)))
184
+ && execution.decisionDigest === sha256(JSON.stringify(executionDecisionMaterial(execution)));
185
+ }
186
+
187
+ export function createExecutionDecision(value, field, transferContext) {
188
+ if (value === null || value === undefined) return null;
189
+ const execution = {
190
+ requiredCapabilities: Array.isArray(value.requiredCapabilities)
191
+ ? value.requiredCapabilities.map((capability) => exactId(capability, `${field}.requiredCapabilities`)) : [],
192
+ strategies: Array.isArray(value.strategies) ? value.strategies.map((strategy, index) => ({
193
+ strategyId: exactId(strategy?.strategyId, `${field}.strategies[${index}].strategyId`),
194
+ capabilities: Array.isArray(strategy?.capabilities)
195
+ ? strategy.capabilities.map((capability) => exactId(capability, `${field}.strategies[${index}].capabilities`)) : [],
196
+ risk: Number(strategy?.risk), cost: Number(strategy?.cost)
197
+ })) : [],
198
+ verification: {
199
+ evaluatorId: exactId(value.verification?.evaluatorId, `${field}.verification.evaluatorId`),
200
+ metric: exactId(value.verification?.metric, `${field}.verification.metric`),
201
+ operator: value.verification?.operator,
202
+ threshold: Number(value.verification?.threshold), minCases: Number(value.verification?.minCases)
203
+ },
204
+ selectedStrategyId: null, decisionDigest: null, authority: "context-only-decision"
205
+ };
206
+ if (value.transfer !== undefined && value.transfer !== null) {
207
+ execution.transferKey = exactId(value.transfer?.transferKey, `${field}.transfer.transferKey`);
208
+ execution.transferMaxAgeDays = Number(value.transfer?.maxAgeDays);
209
+ execution.transferProof = null;
210
+ execution.transferProof = createStrategyTransferProof(transferContext.goals, execution, transferContext.scope);
211
+ }
212
+ execution.selectedStrategyId = selectedExecutionStrategy(execution)?.strategyId || null;
213
+ if (value.exploration !== undefined && value.exploration !== null) {
214
+ execution.explorationMaxAttempts = Number(value.exploration?.maxAttempts);
215
+ execution.explorationOrder = expectedExplorationOrder(execution).slice(0, execution.explorationMaxAttempts);
216
+ }
217
+ execution.decisionDigest = sha256(JSON.stringify(executionDecisionMaterial(execution)));
218
+ if (!validExecutionDecision(execution)) {
219
+ throw new Error(`${field} requires 2-8 bounded strategies and one objective verification gate`);
220
+ }
221
+ return execution;
222
+ }
223
+
224
+ function executionOutcomeMaterial(outcome) {
225
+ return {
226
+ queueId: outcome.queueId, decisionDigest: outcome.decisionDigest, strategyId: outcome.strategyId,
227
+ ...(outcome.attempt === undefined ? {} : {
228
+ attempt: outcome.attempt, previousOutcomeDigest: outcome.previousOutcomeDigest
229
+ }),
230
+ capabilitiesUsed: outcome.capabilitiesUsed, evaluatorId: outcome.evaluatorId, metric: outcome.metric,
231
+ value: outcome.value, cases: outcome.cases, blockingDefect: outcome.blockingDefect,
232
+ sourceDigest: outcome.sourceDigest, observedAt: outcome.observedAt, passed: outcome.passed,
233
+ authority: "objective-evidence-only"
234
+ };
235
+ }
236
+
237
+ export function currentExecutionAttempt(execution, outcomes = []) {
238
+ if (execution.explorationMaxAttempts === undefined) return null;
239
+ if (outcomes.length >= execution.explorationMaxAttempts || outcomes.some((outcome) => outcome.passed || outcome.blockingDefect)) {
240
+ return null;
241
+ }
242
+ return {
243
+ schema: EXECUTION_ATTEMPT_SCHEMA,
244
+ attempt: outcomes.length + 1, maxAttempts: execution.explorationMaxAttempts,
245
+ strategyId: execution.explorationOrder[outcomes.length],
246
+ previousOutcomeDigest: outcomes.at(-1)?.digest || null,
247
+ decisionDigest: execution.decisionDigest, authority: "context-only-attempt"
248
+ };
249
+ }
250
+
251
+ export function executionAttemptForStep(step) {
252
+ if (!step?.execution || !Array.isArray(step.executionOutcomes)) return null;
253
+ return currentExecutionAttempt(step.execution, step.executionOutcomes);
254
+ }
255
+
256
+ function metricPassed(operator, value, threshold) {
257
+ if (operator === "gte") return value >= threshold;
258
+ if (operator === "lte") return value <= threshold;
259
+ return value === threshold;
260
+ }
261
+
262
+ function validExecutionOutcome(outcome, execution, expectedStrategyId = execution.selectedStrategyId,
263
+ expectedAttempt = null, previousOutcomeDigest = null) {
264
+ if (!(outcome && outcome.schema === EXECUTION_OUTCOME_SCHEMA && ID_RE.test(outcome.outcomeId || "")
265
+ && ID_RE.test(outcome.queueId || "") && outcome.decisionDigest === execution.decisionDigest
266
+ && outcome.strategyId === expectedStrategyId
267
+ && (execution.explorationMaxAttempts === undefined
268
+ ? outcome.attempt === undefined && outcome.previousOutcomeDigest === undefined
269
+ : outcome.attempt === expectedAttempt && outcome.previousOutcomeDigest === previousOutcomeDigest)
270
+ && Array.isArray(outcome.capabilitiesUsed)
271
+ && outcome.capabilitiesUsed.length <= 16 && new Set(outcome.capabilitiesUsed).size === outcome.capabilitiesUsed.length
272
+ && outcome.capabilitiesUsed.every((capability) => ID_RE.test(capability || ""))
273
+ && outcome.evaluatorId === execution.verification.evaluatorId && outcome.metric === execution.verification.metric
274
+ && Number.isFinite(outcome.value) && Number.isInteger(outcome.cases) && outcome.cases >= 0
275
+ && typeof outcome.blockingDefect === "boolean" && /^[a-f0-9]{64}$/.test(outcome.sourceDigest || "")
276
+ && Number.isFinite(new Date(outcome.observedAt).getTime()) && typeof outcome.passed === "boolean"
277
+ && /^[a-f0-9]{64}$/.test(outcome.digest || "") && outcome.authority === "objective-evidence-only")) return false;
278
+ const strategy = execution.strategies.find((item) => item.strategyId === expectedStrategyId);
279
+ const used = new Set(outcome.capabilitiesUsed);
280
+ const expectedPass = execution.requiredCapabilities.every((capability) => used.has(capability))
281
+ && outcome.capabilitiesUsed.every((capability) => strategy.capabilities.includes(capability))
282
+ && outcome.cases >= execution.verification.minCases && !outcome.blockingDefect
283
+ && metricPassed(execution.verification.operator, outcome.value, execution.verification.threshold);
284
+ const digest = sha256(JSON.stringify(executionOutcomeMaterial(outcome)));
285
+ return outcome.passed === expectedPass && outcome.digest === digest
286
+ && outcome.outcomeId === "execution-outcome:" + digest.slice(0, 32);
287
+ }
288
+
289
+ export function validExecutionOutcomeSequence(execution, outcomes) {
290
+ if (execution.explorationMaxAttempts === undefined) {
291
+ return outcomes.every((outcome) => validExecutionOutcome(outcome, execution));
292
+ }
293
+ if (outcomes.length > execution.explorationMaxAttempts
294
+ || new Set(outcomes.map((outcome) => outcome.sourceDigest)).size !== outcomes.length) return false;
295
+ let previous = null;
296
+ for (let index = 0; index < outcomes.length; index += 1) {
297
+ const outcome = outcomes[index];
298
+ if (!validExecutionOutcome(outcome, execution, execution.explorationOrder[index], index + 1, previous)) return false;
299
+ if (index < outcomes.length - 1 && (outcome.passed || outcome.blockingDefect)) return false;
300
+ previous = outcome.digest;
301
+ }
302
+ return true;
303
+ }
304
+
305
+ export function reviewExecutionResult(execution, priorOutcomes, queueId, report, now) {
306
+ const attempt = currentExecutionAttempt(execution, priorOutcomes);
307
+ const expectedStrategyId = attempt?.strategyId || execution.selectedStrategyId;
308
+ if (!report || report.strategyId !== expectedStrategyId || !Array.isArray(report.capabilitiesUsed)
309
+ || !report.outcome || report.outcome.evaluatorId !== execution.verification.evaluatorId
310
+ || report.outcome.metric !== execution.verification.metric) {
311
+ return { passed: false, outcome: null, reason: "Objective execution evidence is missing or invalid." };
312
+ }
313
+ let outcome;
314
+ try {
315
+ outcome = {
316
+ schema: EXECUTION_OUTCOME_SCHEMA, outcomeId: null, queueId,
317
+ decisionDigest: execution.decisionDigest, strategyId: report.strategyId,
318
+ ...(attempt === null ? {} : { attempt: attempt.attempt, previousOutcomeDigest: attempt.previousOutcomeDigest }),
319
+ capabilitiesUsed: report.capabilitiesUsed.map((capability) => exactId(capability, "execution.capabilitiesUsed")),
320
+ evaluatorId: report.outcome.evaluatorId, metric: report.outcome.metric,
321
+ value: Number(report.outcome.value), cases: Number(report.outcome.cases),
322
+ blockingDefect: report.outcome.blockingDefect === true, sourceDigest: String(report.outcome.sourceDigest || ""),
323
+ observedAt: timestamp(report.outcome.observedAt || now), passed: false, digest: null,
324
+ authority: "objective-evidence-only"
325
+ };
326
+ if (new Date(outcome.observedAt) > new Date(now)
327
+ || (attempt && priorOutcomes.some((prior) => prior.sourceDigest === outcome.sourceDigest))) {
328
+ return { passed: false, outcome: null, reason: "Objective execution evidence is missing or invalid." };
329
+ }
330
+ const strategy = execution.strategies.find((item) => item.strategyId === expectedStrategyId);
331
+ const used = new Set(outcome.capabilitiesUsed);
332
+ outcome.passed = execution.requiredCapabilities.every((capability) => used.has(capability))
333
+ && outcome.capabilitiesUsed.every((capability) => strategy.capabilities.includes(capability))
334
+ && outcome.cases >= execution.verification.minCases && !outcome.blockingDefect
335
+ && metricPassed(execution.verification.operator, outcome.value, execution.verification.threshold);
336
+ outcome.digest = sha256(JSON.stringify(executionOutcomeMaterial(outcome)));
337
+ outcome.outcomeId = "execution-outcome:" + outcome.digest.slice(0, 32);
338
+ } catch {
339
+ return { passed: false, outcome: null, reason: "Objective execution evidence is missing or invalid." };
340
+ }
341
+ if (!validExecutionOutcome(outcome, execution, expectedStrategyId, attempt?.attempt ?? null,
342
+ attempt?.previousOutcomeDigest ?? null)
343
+ || !validExecutionOutcomeSequence(execution, [...priorOutcomes, outcome])) {
344
+ return { passed: false, outcome: null, reason: "Objective execution evidence is missing or invalid." };
345
+ }
346
+ const nextAttempt = !outcome.passed && !outcome.blockingDefect
347
+ ? currentExecutionAttempt(execution, [...priorOutcomes, outcome]) : null;
348
+ return { passed: outcome.passed, outcome, nextAttempt,
349
+ reason: outcome.passed ? null : "The objective execution gate did not pass." };
350
+ }
@@ -0,0 +1,185 @@
1
+ export const AMBIGUOUS_HOST_OUTCOME =
2
+ "Host execution may have produced an effect before restart; manual owner review is required.";
3
+
4
+ const EXECUTION_MODES = new Set(["host-effect", "read-only"]);
5
+
6
+ function validTimestamp(value) {
7
+ return typeof value === "string" && Number.isFinite(new Date(value).getTime());
8
+ }
9
+
10
+ export function normalizeGatewayExecutionMode(value = "host-effect") {
11
+ if (!EXECUTION_MODES.has(value)) {
12
+ throw new Error("executionMode must be host-effect or read-only");
13
+ }
14
+ return value;
15
+ }
16
+
17
+ export function newGatewayLease(workerId, current, seconds, executionMode) {
18
+ return { workerId, claimedAt: current, executionMode,
19
+ expiresAt: new Date(new Date(current).getTime() + seconds * 1000).toISOString(),
20
+ ...(executionMode === "host-effect" ? { effectMayStartAt: current } : {}) };
21
+ }
22
+
23
+ export function assertGatewayCompletionMode(lease, result) {
24
+ if (lease.executionMode === "read-only") {
25
+ if (result?.readOnly !== true) {
26
+ throw new Error("read-only gateway lease completion requires result.readOnly: true");
27
+ }
28
+ return false;
29
+ }
30
+ if (!validTimestamp(lease?.hostStartedAt)) {
31
+ throw new Error("gateway lease with possible host effects requires markGatewayHostStarted before completion");
32
+ }
33
+ return true;
34
+ }
35
+
36
+ export function matchesGatewayLaneLease(lane, queue) {
37
+ return queue?.status === "leased" && queue.lease?.workerId === lane.workerId
38
+ && queue.lease.claimedAt === lane.claimedAt && queue.lease.expiresAt === lane.expiresAt;
39
+ }
40
+
41
+ export function exactGatewayLeaseGeneration(claimedAt, attempt, action) {
42
+ if (!validTimestamp(claimedAt) || !Number.isInteger(attempt) || attempt < 1) {
43
+ throw new Error(`${action} requires claimedAt and attempt from the exact active queue lease`);
44
+ }
45
+ return new Date(claimedAt).toISOString();
46
+ }
47
+
48
+ export function requireExactGatewayLease(runtime, item, { workerId, claimedAt, attempt, current, action }) {
49
+ if (!item || item.status !== "leased" || item.attempts !== attempt
50
+ || item.lease?.workerId !== workerId || item.lease.claimedAt !== claimedAt
51
+ || new Date(item.lease.expiresAt) <= new Date(current)) {
52
+ throw new Error(`${action} requires the exact active queue lease`);
53
+ }
54
+ const lane = runtime.lanes.find((entry) => entry.queueId === item.queueId && entry.workerId === workerId
55
+ && entry.claimedAt === claimedAt && entry.expiresAt === item.lease.expiresAt && entry.status === "leased");
56
+ if (!lane) throw new Error("agent lane lease is missing");
57
+ return lane;
58
+ }
59
+
60
+ export function validGatewayLeaseExecution(lease) {
61
+ if (lease?.executionMode === undefined) {
62
+ return lease?.effectMayStartAt === undefined
63
+ && (lease.hostStartedAt === undefined || validTimestamp(lease.hostStartedAt));
64
+ }
65
+ if (lease.executionMode === "host-effect") {
66
+ return validTimestamp(lease.effectMayStartAt)
67
+ && (lease.hostStartedAt === undefined || validTimestamp(lease.hostStartedAt));
68
+ }
69
+ return lease.executionMode === "read-only" && lease.effectMayStartAt === undefined
70
+ && lease.hostStartedAt === undefined;
71
+ }
72
+
73
+ export function leaseMayHaveHostEffect(lease) {
74
+ return lease?.executionMode !== "read-only";
75
+ }
76
+
77
+ export function hasAmbiguousHostOutcome(runtime) {
78
+ return runtime.receipts.some((receipt) => receipt.kind === "host-outcome-ambiguous");
79
+ }
80
+
81
+ export function gatewayHostHealthFindings(runtime) {
82
+ if (runtime.health.host !== "failed") return [];
83
+ return ["host-not-healthy", ...(hasAmbiguousHostOutcome(runtime) ? ["host-outcome-ambiguous"] : [])];
84
+ }
85
+
86
+ function markAmbiguousHostHealth(runtime, current) {
87
+ runtime.health.host = "failed";
88
+ runtime.health.worker = "degraded";
89
+ runtime.health.lastTickAt = current;
90
+ }
91
+
92
+ export function requireExactHostLease(runtime, item, { workerId, claimedAt, attempt, current }) {
93
+ const lane = requireExactGatewayLease(runtime, item,
94
+ { workerId, claimedAt, attempt, current, action: "host start" });
95
+ if (item.lease.executionMode !== "host-effect") {
96
+ throw new Error("host start requires a host-effect lease");
97
+ }
98
+ if (item.lease.hostStartedAt) throw new Error("the exact queue lease already started host execution");
99
+ return lane;
100
+ }
101
+
102
+ export function markHostStarted(runtime, item, lane, workerId, current, { preserve, appendReceipt }) {
103
+ preserve(runtime, "queue", item, "host-started", current);
104
+ item.lease.hostStartedAt = current;
105
+ item.updatedAt = current;
106
+ lane.updatedAt = current;
107
+ runtime.revision += 1;
108
+ return appendReceipt(runtime, "host-started", item.queueId, current,
109
+ { workerId, attempt: item.attempts });
110
+ }
111
+
112
+ function blockBoundGoal(policy, item, current) {
113
+ const goal = item.goalId ? policy.goals.find((entry) => entry.goalId === item.goalId) : null;
114
+ if (!goal || goal.status !== "active") return false;
115
+ const step = item.goalStepId && goal.plan
116
+ ? goal.plan.steps.find((entry) => entry.stepId === item.goalStepId) : null;
117
+ if (goal.plan && (step?.stepId !== goal.plan.currentStepId || step.status !== "active")) return false;
118
+ policy.history.push({ kind: "goal", at: current, value: structuredClone(goal),
119
+ authority: "authenticated-goal-policy" });
120
+ goal.status = "blocked";
121
+ goal.blocker = AMBIGUOUS_HOST_OUTCOME;
122
+ goal.heartbeatAt = current;
123
+ goal.updatedAt = current;
124
+ if (step) {
125
+ step.status = "blocked";
126
+ step.blocker = AMBIGUOUS_HOST_OUTCOME;
127
+ step.updatedAt = current;
128
+ goal.plan.revision += 1;
129
+ }
130
+ policy.revision += 1;
131
+ return true;
132
+ }
133
+
134
+ export function expireGatewayLane(policy, runtime, lane, current, { preserve, appendReceipt }) {
135
+ const item = runtime.queue.find((entry) => entry.queueId === lane.queueId && entry.status === "leased");
136
+ if (item?.lease && leaseMayHaveHostEffect(item.lease)) {
137
+ const { hostStartedAt = null, effectMayStartAt = null, executionMode = "legacy-unknown" } = item.lease;
138
+ preserve(runtime, "queue", item, "host-outcome-ambiguous", current);
139
+ item.status = "blocked";
140
+ item.lease = null;
141
+ item.lastError = AMBIGUOUS_HOST_OUTCOME;
142
+ item.completedAt = current;
143
+ item.updatedAt = current;
144
+ appendReceipt(runtime, "host-outcome-ambiguous", item.queueId, current,
145
+ { attempt: item.attempts, hostStartedAt, effectMayStartAt, executionMode });
146
+ lane.status = "expired";
147
+ lane.updatedAt = current;
148
+ markAmbiguousHostHealth(runtime, current);
149
+ return blockBoundGoal(policy, item, current);
150
+ }
151
+ if (item) {
152
+ preserve(runtime, "queue", item, "lease-expired", current);
153
+ item.status = item.attempts >= 3 ? "dead-letter" : "pending";
154
+ item.lease = null;
155
+ item.updatedAt = current;
156
+ }
157
+ lane.status = "expired";
158
+ lane.updatedAt = current;
159
+ return false;
160
+ }
161
+
162
+ export function failGatewayLane(policy, runtime, item, lane, current,
163
+ { error, retryAfterMs, preserve, appendReceipt }) {
164
+ if (leaseMayHaveHostEffect(item.lease)) {
165
+ expireGatewayLane(policy, runtime, lane, current, { preserve, appendReceipt });
166
+ runtime.revision += 1;
167
+ return { ambiguous: true, receipt: runtime.receipts.at(-1) };
168
+ }
169
+ preserve(runtime, "queue", item, "run-failed", current);
170
+ item.status = item.attempts >= 3 ? "dead-letter" : "pending";
171
+ item.lease = null;
172
+ item.lastError = error;
173
+ item.updatedAt = current;
174
+ item.availableAt = new Date(new Date(current).getTime() + retryAfterMs).toISOString();
175
+ if (item.status === "dead-letter") item.completedAt = current;
176
+ lane.status = "completed";
177
+ lane.updatedAt = current;
178
+ runtime.health.host = "failed";
179
+ runtime.health.worker = "degraded";
180
+ runtime.health.lastTickAt = current;
181
+ runtime.revision += 1;
182
+ return { ambiguous: false, receipt: appendReceipt(runtime,
183
+ item.status === "dead-letter" ? "run-dead-letter" : "run-retry",
184
+ item.queueId, current, { attempt: item.attempts }) };
185
+ }
@@ -0,0 +1,85 @@
1
+ import { gatewayHostHealthFindings } from "./gateway-host-lifecycle.js";
2
+ import { emptyPolicy, emptyRuntime, exactId } from "./gateway-common.js";
3
+ import { executionAttemptForStep } from "./gateway-execution.js";
4
+ import {
5
+ conflictingResources, currentPlanStep, gatewayRuntimeFindings, normalizePolicy,
6
+ normalizeRuntime, pathsFor, planStepAgentId, readJson, withLock
7
+ } from "./gateway-state.js";
8
+
9
+ export async function loadGatewayRuntime(root = process.cwd(), catalog = null) {
10
+ const paths = await pathsFor(root, catalog);
11
+ return withLock(paths, async () => {
12
+ const [policy, runtime] = await Promise.all([
13
+ readJson(paths.gatewayPolicyPath, paths.catalog.root, normalizePolicy, emptyPolicy),
14
+ readJson(paths.gatewayRuntimePath, paths.catalog.root, normalizeRuntime, emptyRuntime)
15
+ ]);
16
+ return { policy, runtime, ...paths };
17
+ });
18
+ }
19
+
20
+ export async function inspectGatewayRuntime(root = process.cwd(), catalog = null) {
21
+ const paths = await pathsFor(root, catalog); const errors = [];
22
+ let policy = emptyPolicy(paths.catalog.root); let runtime = emptyRuntime(paths.catalog.root);
23
+ try {
24
+ await withLock(paths, async () => {
25
+ try { policy = await readJson(paths.gatewayPolicyPath, paths.catalog.root, normalizePolicy, emptyPolicy); }
26
+ catch (error) { errors.push("policy:" + error.message); }
27
+ try { runtime = await readJson(paths.gatewayRuntimePath, paths.catalog.root, normalizeRuntime, emptyRuntime); }
28
+ catch (error) { errors.push("runtime:" + error.message); }
29
+ });
30
+ } catch (error) { errors.push("transaction:" + error.message); }
31
+ return { policy, runtime, errors, ...paths };
32
+ }
33
+
34
+
35
+ export { gatewayRuntimeFindings } from "./gateway-state.js";
36
+
37
+ export function gatewayHealthFindings(policy, runtime, { now = new Date(), staleAfterMs = 180000 } = {}) {
38
+ if (!policy.enabled || policy.killSwitch) return [];
39
+ const findings = [];
40
+ if (runtime.health.gateway !== "running") findings.push("gateway-not-running");
41
+ if (runtime.health.scheduler !== "healthy") findings.push("scheduler-not-healthy");
42
+ if (runtime.health.queue !== "healthy") findings.push("queue-not-healthy");
43
+ if (!new Set(["healthy", "degraded"]).has(runtime.health.worker)) findings.push("worker-not-healthy");
44
+ if (!new Set(["healthy", "degraded"]).has(runtime.health.adapter)) findings.push("adapter-not-healthy");
45
+ findings.push(...gatewayHostHealthFindings(runtime));
46
+ const current = now instanceof Date ? now : new Date(now);
47
+ const tick = runtime.health.lastTickAt === null ? null : new Date(runtime.health.lastTickAt);
48
+ const reconciliation = runtime.health.lastReconciledAt === null ? null : new Date(runtime.health.lastReconciledAt);
49
+ if (!tick || current.getTime() - tick.getTime() > staleAfterMs) findings.push("worker-heartbeat-stale");
50
+ if (!reconciliation || current.getTime() - reconciliation.getTime() > staleAfterMs) findings.push("scheduler-heartbeat-stale");
51
+ return findings;
52
+ }
53
+
54
+ export async function gatewayContext({ root = process.cwd(), agentId = null } = {}) {
55
+ const { policy, runtime } = await loadGatewayRuntime(root);
56
+ const findings = gatewayRuntimeFindings(policy, runtime);
57
+ if (findings.length) throw new Error("gateway runtime failed closed: " + findings.join(", "));
58
+ const exactAgentId = agentId === null ? null : exactId(agentId, "agentId");
59
+ const goals = policy.goals.filter((item) => exactAgentId === null || item.agentId === exactAgentId
60
+ || item.plan?.steps.some((step) => planStepAgentId(item, step) === exactAgentId));
61
+ const executionAttempts = goals.map((goal) => {
62
+ const step = currentPlanStep(goal); const attempt = executionAttemptForStep(step);
63
+ return attempt && (exactAgentId === null || planStepAgentId(goal, step) === exactAgentId)
64
+ ? { goalId: goal.goalId, goalStepId: step.stepId, ...attempt } : null;
65
+ }).filter(Boolean);
66
+ const queue = runtime.queue.filter((item) => exactAgentId === null || item.agentId === exactAgentId);
67
+ const queueIds = new Set(queue.map((item) => item.queueId));
68
+ const leased = runtime.queue.filter((item) => item.status === "leased");
69
+ const resourceWaits = queue.filter((item) => item.status === "pending").map((item) => {
70
+ const blockers = leased.map((entry) => ({ entry, resources: conflictingResources(policy, item, entry) }))
71
+ .filter(({ resources }) => resources.length > 0);
72
+ return blockers.length ? {
73
+ queueId: item.queueId,
74
+ resources: [...new Set(blockers.flatMap(({ resources }) => resources))].sort(),
75
+ blockedByQueueIds: blockers.map(({ entry }) => entry.queueId).sort(),
76
+ authority: "execution-state-only"
77
+ } : null;
78
+ }).filter(Boolean);
79
+ return { schema: "agentspine.gateway-context/v1", enabled: policy.enabled, killSwitch: policy.killSwitch,
80
+ goals: structuredClone(goals), queue: structuredClone(queue),
81
+ outbox: structuredClone(runtime.outbox.filter((item) => queueIds.has(item.queueId))),
82
+ resourceWaits, executionAttempts,
83
+ health: structuredClone(runtime.health), healthFindings: gatewayHealthFindings(policy, runtime),
84
+ authority: "execution-state-only" };
85
+ }