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,129 @@
1
+ import {
2
+ createKnowledgeGap, createSelfHelpRequirement, createSelfHelpResolution,
3
+ pendingSelfHelpRequirement
4
+ } from "./knowledge-evidence.js";
5
+
6
+ function blockKnowledgeLimit({ goal, step, item, runtime, current, appendReceipt }, kind) {
7
+ const reason = `Goal step reached the bounded limit of 16 ${kind}; local review is required.`;
8
+ item.status = "blocked"; item.lastError = reason; item.completedAt = current;
9
+ step.status = "blocked"; step.blocker = reason; step.updatedAt = current;
10
+ goal.status = "blocked"; goal.blocker = reason; goal.updatedAt = current;
11
+ appendReceipt(runtime, "self-help-limit-exhausted", item.queueId, current,
12
+ { goalStepId: step.stepId, limit: 16, category: kind });
13
+ return null;
14
+ }
15
+
16
+ function resolvedSelfHelp({ goal, step, item, runtime, current, request,
17
+ appendReceipt, newGoalQueue, planQueueKey }) {
18
+ if (!Array.isArray(step.knowledgeGaps)) step.knowledgeGaps = [];
19
+ if (!Array.isArray(step.selfHelpReports)) step.selfHelpReports = [];
20
+ const resolved = createSelfHelpResolution({ goal, step, queueId: item.queueId,
21
+ report: request, agentId: item.agentId, now: current });
22
+ const existingGap = step.knowledgeGaps.find((gap) => gap.gapId === resolved.gap.gapId);
23
+ if (existingGap) {
24
+ goal.blocker = "The host repeated an already resolved self-help question.";
25
+ step.status = "blocked"; step.blocker = goal.blocker; goal.status = "blocked";
26
+ appendReceipt(runtime, "self-help-research-regression", item.queueId, current,
27
+ { goalStepId: step.stepId, reportId: resolved.report.reportId, gapId: existingGap.gapId });
28
+ return null;
29
+ }
30
+ if (step.knowledgeGaps.length >= 16 || step.selfHelpReports.length >= 16) {
31
+ return blockKnowledgeLimit({ goal, step, item, runtime, current, appendReceipt },
32
+ "self-help research resolutions");
33
+ }
34
+ step.knowledgeGaps.push(resolved.gap); step.selfHelpReports.push(resolved.report);
35
+ if (resolved.status === "needs-owner-input") {
36
+ item.status = "blocked"; step.status = "blocked"; step.blocker = resolved.gap.question;
37
+ goal.status = "blocked"; goal.blocker = resolved.gap.question;
38
+ appendReceipt(runtime, "self-help-research-exhausted", item.queueId, current,
39
+ { goalStepId: step.stepId, reportId: resolved.report.reportId, gapId: resolved.gap.gapId });
40
+ appendReceipt(runtime, "knowledge-gap-opened", item.queueId, current,
41
+ { goalStepId: step.stepId, gapId: resolved.gap.gapId, requiredEvidence: "owner-input" });
42
+ return { clarification: structuredClone(resolved.gap), report: structuredClone(resolved.report) };
43
+ }
44
+ step.status = "active"; step.blocker = null; goal.status = "active"; goal.blocker = null;
45
+ goal.nextSafeStep = step.title;
46
+ const key = planQueueKey(goal.goalId, step.stepId, "self-help", resolved.report.reportDigest.slice(0, 20));
47
+ let queued = runtime.queue.find((entry) => entry.dedupeKey === key);
48
+ if (!queued) { queued = newGoalQueue(goal, step, "follow-up", key, current); runtime.queue.push(queued); }
49
+ appendReceipt(runtime, "self-help-research-resolved", queued.queueId, current,
50
+ { goalStepId: step.stepId, reportId: resolved.report.reportId, gapId: resolved.gap.gapId });
51
+ return { report: structuredClone(resolved.report), queueId: queued.queueId };
52
+ }
53
+
54
+ function requireSelfHelp({ goal, step, item, runtime, current, request,
55
+ appendReceipt, newGoalQueue, planQueueKey }) {
56
+ if (!Array.isArray(step.selfHelpRequirements)) step.selfHelpRequirements = [];
57
+ const pending = pendingSelfHelpRequirement(step);
58
+ if (pending) {
59
+ const same = pending.question === request.question?.trim().replace(/\s+/g, " ")
60
+ && pending.reason === request.reason?.trim().replace(/\s+/g, " ");
61
+ goal.blocker = same
62
+ ? "The host repeated a question before completing required repository-first self-help."
63
+ : "The host changed an objective question before completing required repository-first self-help.";
64
+ item.status = "blocked"; step.status = "blocked"; step.blocker = goal.blocker; goal.status = "blocked";
65
+ appendReceipt(runtime, "self-help-requirement-regression", item.queueId, current,
66
+ { goalStepId: step.stepId, requirementId: pending.requirementId, repeated: same });
67
+ return null;
68
+ }
69
+ if (step.selfHelpRequirements.length >= 16) {
70
+ return blockKnowledgeLimit({ goal, step, item, runtime, current, appendReceipt },
71
+ "self-help requirements");
72
+ }
73
+ const requirement = createSelfHelpRequirement(goal, step, item.queueId, request, current);
74
+ step.selfHelpRequirements.push(requirement); item.status = "completed";
75
+ step.status = "active"; step.blocker = null; goal.status = "active"; goal.blocker = null;
76
+ goal.nextSafeStep = step.title;
77
+ const key = planQueueKey(goal.goalId, step.stepId, "self-help-required",
78
+ requirement.requirementDigest.slice(0, 20));
79
+ let queued = runtime.queue.find((entry) => entry.dedupeKey === key);
80
+ if (!queued) { queued = newGoalQueue(goal, step, "follow-up", key, current); runtime.queue.push(queued); }
81
+ appendReceipt(runtime, "self-help-required", queued.queueId, current,
82
+ { goalStepId: step.stepId, requirementId: requirement.requirementId });
83
+ return { requirement: structuredClone(requirement), queueId: queued.queueId };
84
+ }
85
+
86
+ function openOwnerGap({ goal, step, item, runtime, current, request, appendReceipt }) {
87
+ const pending = pendingSelfHelpRequirement(step);
88
+ if (pending) {
89
+ goal.blocker = "The host asked the owner before completing required repository-first self-help.";
90
+ item.status = "blocked"; step.status = "blocked"; step.blocker = goal.blocker; goal.status = "blocked";
91
+ appendReceipt(runtime, "self-help-requirement-regression", item.queueId, current,
92
+ { goalStepId: step.stepId, requirementId: pending.requirementId, prematureOwnerQuestion: true });
93
+ return null;
94
+ }
95
+ if (!Array.isArray(step.knowledgeGaps)) step.knowledgeGaps = [];
96
+ const proposed = createKnowledgeGap(goal, step, item.queueId, request, current);
97
+ const existing = step.knowledgeGaps.find((gap) => gap.gapId === proposed.gapId);
98
+ if (!existing && step.knowledgeGaps.length >= 16) {
99
+ return blockKnowledgeLimit({ goal, step, item, runtime, current, appendReceipt }, "knowledge gaps");
100
+ }
101
+ if (existing?.status === "resolved") {
102
+ goal.blocker = "The host repeated an already resolved knowledge gap.";
103
+ step.status = "blocked"; step.blocker = goal.blocker; goal.status = "blocked";
104
+ appendReceipt(runtime, "knowledge-gap-regression", item.queueId, current,
105
+ { goalStepId: step.stepId, gapId: existing.gapId });
106
+ return null;
107
+ }
108
+ const gap = existing || proposed;
109
+ if (!existing) step.knowledgeGaps.push(gap);
110
+ goal.blocker = gap.question; step.status = "blocked"; step.blocker = gap.question; goal.status = "blocked";
111
+ appendReceipt(runtime, "knowledge-gap-opened", item.queueId, current,
112
+ { goalStepId: step.stepId, gapId: gap.gapId, requiredEvidence: gap.requiredEvidence });
113
+ return structuredClone(gap);
114
+ }
115
+
116
+ export function handlePlanKnowledge(options) {
117
+ const { goal, step, item, runtime, current, selfHelpRequest, knowledgeGapRequest } = options;
118
+ if (!selfHelpRequest && !knowledgeGapRequest) return null;
119
+ let result;
120
+ if (selfHelpRequest) {
121
+ const handled = resolvedSelfHelp({ ...options, request: selfHelpRequest });
122
+ result = handled?.clarification ? handled : { selfHelp: handled };
123
+ }
124
+ else if (knowledgeGapRequest.requiredEvidence === "objective-observation") {
125
+ result = { selfHelpRequired: requireSelfHelp({ ...options, request: knowledgeGapRequest }) };
126
+ } else result = { clarification: openOwnerGap({ ...options, request: knowledgeGapRequest }) };
127
+ goal.plan.revision += 1;
128
+ return result;
129
+ }
@@ -0,0 +1,197 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { readFile, stat, unlink, writeFile } from "node:fs/promises";
3
+ import { basename, dirname, join } from "node:path";
4
+ import { replaceFileWithRetry } from "./filesystem-retry.js";
5
+
6
+ const PROVENANCE_SCHEMA = "agentspine.gateway-policy-provenance/v1";
7
+ const REGISTRY_AUTHORITY = "context-only-contract-provenance";
8
+ const POLICY_SCHEMA = "agentspine.gateway-policy/v2";
9
+ const POLICY_BYTES = 8 * 1024 * 1024;
10
+ const PROVENANCE_BYTES = 8 * 1024;
11
+ const DIGEST_RE = /^[a-f0-9]{64}$/;
12
+ const PROVENANCE_FILE_DIGEST = Symbol("gateway-policy-provenance-file-digest");
13
+ export const GOAL_PREMORTEM_CONTRACT = "agentspine.goal-premortem/v1";
14
+
15
+ function canonical(value) {
16
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
17
+ if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
18
+ return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`).join(",")}}`;
19
+ }
20
+ function sha256(value) {
21
+ return createHash("sha256").update(typeof value === "string" ? value : canonical(value)).digest("hex");
22
+ }
23
+ function exactKeys(value, keys) {
24
+ return value && canonical(Object.keys(value).sort()) === canonical([...keys].sort());
25
+ }
26
+ function seal(value) {
27
+ return { ...value, digest: sha256(value) };
28
+ }
29
+ function validSeal(value) {
30
+ if (!value || !DIGEST_RE.test(value.digest || "")) return false;
31
+ const material = { ...value };
32
+ delete material.digest;
33
+ return value.digest === sha256(material);
34
+ }
35
+ function policyProvenance(root, policySchema, state, previousPolicyDigest, nextPolicyDigest) {
36
+ return seal({ schema: PROVENANCE_SCHEMA, projectRootDigest: sha256(root), policySchema,
37
+ contract: GOAL_PREMORTEM_CONTRACT, state, previousPolicyDigest, nextPolicyDigest,
38
+ authority: REGISTRY_AUTHORITY });
39
+ }
40
+
41
+ export function validGatewayPolicyProvenance(value, root, policySchema) {
42
+ return exactKeys(value, ["schema", "projectRootDigest", "policySchema", "contract", "state",
43
+ "previousPolicyDigest", "nextPolicyDigest", "authority", "digest"])
44
+ && value.schema === PROVENANCE_SCHEMA && value.projectRootDigest === sha256(root)
45
+ && value.policySchema === policySchema && value.contract === GOAL_PREMORTEM_CONTRACT
46
+ && new Set(["prepared", "committed"]).has(value.state)
47
+ && (value.previousPolicyDigest === null || DIGEST_RE.test(value.previousPolicyDigest || ""))
48
+ && DIGEST_RE.test(value.nextPolicyDigest || "")
49
+ && value.authority === REGISTRY_AUTHORITY && validSeal(value);
50
+ }
51
+
52
+ async function readPolicyProvenance(path) {
53
+ const provenancePath = join(dirname(path), "gateway-policy-provenance.json");
54
+ try {
55
+ const metadata = await stat(provenancePath);
56
+ if (metadata.size > PROVENANCE_BYTES) throw new Error("gateway policy provenance exceeds 8 KiB");
57
+ const text = await readFile(provenancePath, "utf8");
58
+ if (Buffer.byteLength(text) > PROVENANCE_BYTES) throw new Error("gateway policy provenance exceeds 8 KiB");
59
+ const value = JSON.parse(text);
60
+ if (value && typeof value === "object") {
61
+ Object.defineProperty(value, PROVENANCE_FILE_DIGEST, { value: sha256(text) });
62
+ }
63
+ return value;
64
+ } catch (error) {
65
+ if (error.code === "ENOENT") return null;
66
+ throw error;
67
+ }
68
+ }
69
+ async function currentGatewayDigest(path) {
70
+ try { return sha256(await readFile(path, "utf8")); }
71
+ catch (error) { if (error.code === "ENOENT") return null; throw error; }
72
+ }
73
+ async function atomicGatewayWrite(path, content, maximum = POLICY_BYTES, guard = {}) {
74
+ if (Buffer.byteLength(content) > maximum) throw new Error("gateway state exceeds its byte limit");
75
+ const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`;
76
+ try {
77
+ await writeFile(temporary, content, { mode: 0o600 });
78
+ await replaceFileWithRetry(temporary, path, { beforeAttempt: async () => {
79
+ await guard.assertOwned?.();
80
+ if (Object.hasOwn(guard, "expectedDigest")
81
+ && await currentGatewayDigest(path) !== guard.expectedDigest) {
82
+ throw new Error("gateway state predecessor changed before atomic replace");
83
+ }
84
+ await guard.assertRelated?.();
85
+ await guard.assertOwned?.();
86
+ } });
87
+ } finally {
88
+ await unlink(temporary).catch((error) => { if (error.code !== "ENOENT") throw error; });
89
+ }
90
+ }
91
+ async function commitObservedPolicyProvenance(path, root, provenance, match, policyDigest, assertOwned) {
92
+ if (provenance.state !== "prepared" || typeof assertOwned !== "function") return;
93
+ const provenancePath = join(dirname(path), "gateway-policy-provenance.json");
94
+ const priorDigest = match === "next" ? provenance.previousPolicyDigest : provenance.nextPolicyDigest;
95
+ const committed = policyProvenance(root, provenance.policySchema, "committed", priorDigest, policyDigest);
96
+ const assertObserved = async () => {
97
+ if (await currentGatewayDigest(path) !== policyDigest) {
98
+ throw new Error("gateway policy changed before prepared provenance recovery");
99
+ }
100
+ };
101
+ await atomicGatewayWrite(provenancePath, `${JSON.stringify(committed, null, 2)}\n`, PROVENANCE_BYTES, {
102
+ assertOwned, assertRelated: assertObserved, expectedDigest: provenance[PROVENANCE_FILE_DIGEST]
103
+ });
104
+ }
105
+
106
+ export async function readGatewayJson(path, root, normalize, empty, guard = {}) {
107
+ const policyFile = basename(path) === "gateway-policy.json";
108
+ for (let attempt = 0; attempt < (policyFile ? 4 : 1); attempt += 1) {
109
+ const provenance = policyFile ? await readPolicyProvenance(path) : null;
110
+ let text;
111
+ try {
112
+ const metadata = await stat(path);
113
+ if (metadata.size > POLICY_BYTES) throw new Error("gateway state exceeds 8 MiB");
114
+ text = await readFile(path, "utf8");
115
+ if (Buffer.byteLength(text) > POLICY_BYTES) throw new Error("gateway state exceeds 8 MiB");
116
+ } catch (error) {
117
+ if (error.code !== "ENOENT") throw error;
118
+ const after = policyFile ? await readPolicyProvenance(path) : null;
119
+ if (canonical(provenance) !== canonical(after)) continue;
120
+ if (provenance && (!validGatewayPolicyProvenance(provenance, root, POLICY_SCHEMA)
121
+ || provenance.state !== "prepared" || provenance.previousPolicyDigest !== null)) {
122
+ throw new Error("gateway policy is missing while provenance exists");
123
+ }
124
+ if (provenance && typeof guard.assertOwned === "function") {
125
+ const value = normalize(empty(root), root, { record: provenance, match: "previous" });
126
+ await writeGatewayJson(path, value, { assertOwned: guard.assertOwned,
127
+ afterWrite: guard.afterWrite, expectedDigest: null });
128
+ return value;
129
+ }
130
+ return empty(root);
131
+ }
132
+ const after = policyFile ? await readPolicyProvenance(path) : null;
133
+ if (canonical(provenance) !== canonical(after)) continue;
134
+ let anchored = null;
135
+ if (provenance) {
136
+ if (!validGatewayPolicyProvenance(provenance, root, POLICY_SCHEMA)) {
137
+ throw new Error("gateway policy provenance is invalid");
138
+ }
139
+ const policyDigest = sha256(text);
140
+ const match = policyDigest === provenance.nextPolicyDigest ? "next"
141
+ : policyDigest === provenance.previousPolicyDigest ? "previous" : null;
142
+ if (!match) throw new Error("gateway policy does not match its persisted provenance");
143
+ anchored = { record: provenance, match };
144
+ }
145
+ const parsed = JSON.parse(text);
146
+ const legacyPolicy = policyFile && parsed?.schema === "agentspine.gateway-policy/v1";
147
+ const value = normalize(parsed, root, anchored);
148
+ if (legacyPolicy && typeof guard.assertOwned === "function") {
149
+ await writeGatewayJson(path, value, { assertOwned: guard.assertOwned,
150
+ afterWrite: guard.afterWrite, expectedDigest: sha256(text) });
151
+ return value;
152
+ }
153
+ if (anchored) await commitObservedPolicyProvenance(path, root, provenance,
154
+ anchored.match, sha256(text), guard.assertOwned);
155
+ return value;
156
+ }
157
+ throw new Error("gateway policy changed during bounded provenance read");
158
+ }
159
+
160
+ export async function writeGatewayJson(path, value, guard = {}) {
161
+ const content = `${JSON.stringify(value, null, 2)}\n`;
162
+ if (basename(path) === "gateway-policy.json" && value.schema === POLICY_SCHEMA) {
163
+ const provenancePath = join(dirname(path), "gateway-policy-provenance.json");
164
+ const previousPolicyDigest = await currentGatewayDigest(path);
165
+ if (Object.hasOwn(guard, "expectedDigest") && previousPolicyDigest !== guard.expectedDigest) {
166
+ throw new Error("gateway state predecessor changed before policy provenance");
167
+ }
168
+ const nextPolicyDigest = sha256(content);
169
+ const prepared = policyProvenance(value.root, value.schema, "prepared",
170
+ previousPolicyDigest, nextPolicyDigest);
171
+ const preparedContent = `${JSON.stringify(prepared, null, 2)}\n`;
172
+ const previousProvenanceDigest = await currentGatewayDigest(provenancePath);
173
+ const assertPreviousPolicy = async () => {
174
+ if (await currentGatewayDigest(path) !== previousPolicyDigest) {
175
+ throw new Error("gateway state predecessor changed before policy provenance");
176
+ }
177
+ };
178
+ await atomicGatewayWrite(provenancePath, preparedContent, PROVENANCE_BYTES,
179
+ { assertOwned: guard.assertOwned, assertRelated: assertPreviousPolicy,
180
+ expectedDigest: previousProvenanceDigest });
181
+ await atomicGatewayWrite(path, content, POLICY_BYTES, guard);
182
+ const committed = policyProvenance(value.root, value.schema, "committed",
183
+ previousPolicyDigest, nextPolicyDigest);
184
+ const assertNextPolicy = async () => {
185
+ if (await currentGatewayDigest(path) !== nextPolicyDigest) {
186
+ throw new Error("gateway policy changed before provenance commit");
187
+ }
188
+ };
189
+ await atomicGatewayWrite(provenancePath, `${JSON.stringify(committed, null, 2)}\n`, PROVENANCE_BYTES,
190
+ { assertOwned: guard.assertOwned, assertRelated: assertNextPolicy,
191
+ expectedDigest: sha256(preparedContent) });
192
+ await guard.afterWrite?.(nextPolicyDigest);
193
+ return;
194
+ }
195
+ await atomicGatewayWrite(path, content, POLICY_BYTES, guard);
196
+ await guard.afterWrite?.(sha256(content));
197
+ }
@@ -0,0 +1,82 @@
1
+ import { createHash } from "node:crypto";
2
+ import { finalizeReadOnlyPremortemForGoal } from "./delivery-premortem.js";
3
+ import { validPremortemScopeFinalization,
4
+ validPremortemScopeFinalizationContext } from "./delivery-premortem-index.js";
5
+
6
+ const CHECKPOINT_SCHEMA = "agentspine.goal-delivery-checkpoint/v1";
7
+ const OUTCOME_SCHEMA = "agentspine.goal-outcome-receipt/v1";
8
+ const AUTHORITY = "context-only";
9
+
10
+ function canonical(value) {
11
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
12
+ if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
13
+ return `{${Object.keys(value).sort().map((key) =>
14
+ `${JSON.stringify(key)}:${canonical(value[key])}`).join(",")}}`;
15
+ }
16
+
17
+ function sha256(value) {
18
+ return createHash("sha256").update(typeof value === "string" ? value : canonical(value)).digest("hex");
19
+ }
20
+
21
+ function seal(value) {
22
+ return { ...value, digest: sha256(value) };
23
+ }
24
+
25
+ export function readOnlyDispositionDigest(expected) {
26
+ return sha256({ status: "read-only", goalId: expected.goalId,
27
+ goalStepId: expected.goalStepId, queueId: expected.queueId,
28
+ gatewayAttempt: expected.gatewayAttempt, planDefinitionsDigest: expected.planDefinitionsDigest,
29
+ projectRootDigest: sha256(expected.root), host: expected.host, projectId: expected.projectId,
30
+ entityId: expected.entityId, groupId: expected.groupId, taskId: null, authority: AUTHORITY });
31
+ }
32
+
33
+ export function dispositionAttachments(status, expected, checkpoint, completedAt,
34
+ reason = status, finalization = null) {
35
+ const common = { status, queueId: expected.queueId, gatewayAttempt: expected.gatewayAttempt,
36
+ goalId: expected.goalId, goalStepId: expected.goalStepId,
37
+ planDefinitionsDigest: expected.planDefinitionsDigest, projectRootDigest: sha256(expected.root),
38
+ checkpointDigest: sha256(checkpoint ?? null), host: expected.host,
39
+ projectId: expected.projectId, entityId: expected.entityId, groupId: expected.groupId,
40
+ scopeFinalizationDigest: finalization?.digest ?? null, diagnosticDigest: sha256(reason),
41
+ authority: AUTHORITY };
42
+ const deliveryCheckpoint = seal({ schema: CHECKPOINT_SCHEMA, ...common });
43
+ const outcomeReceipt = seal({ schema: OUTCOME_SCHEMA, ...common, completedAt,
44
+ deliveryCheckpointDigest: deliveryCheckpoint.digest });
45
+ return { deliveryCheckpoint, outcomeReceipt };
46
+ }
47
+
48
+ export function finalizedReadOnlyAttachments(expected, checkpoint, completedAt, finalization) {
49
+ if (!validPremortemScopeFinalizationContext(finalization, expected)
50
+ || finalization.status !== "read-only"
51
+ || finalization.attachmentDigest !== readOnlyDispositionDigest(expected)) return null;
52
+ return dispositionAttachments("read-only", expected, checkpoint, completedAt,
53
+ "host-confirmed-read-only", finalization);
54
+ }
55
+
56
+ export function validClosedFinalization(expected, attachment, finalization) {
57
+ return validPremortemScopeFinalizationContext(finalization, expected)
58
+ && finalization.status === "closed"
59
+ && finalization.attachmentDigest === attachment.attachmentDigest;
60
+ }
61
+
62
+ export async function finalizeReadOnlyReview(expected, checkpoint, completedAt, bindings = []) {
63
+ const dispositionDigest = readOnlyDispositionDigest(expected);
64
+ const summaryDigests = bindings.map((binding) => binding.digest);
65
+ const fenced = await finalizeReadOnlyPremortemForGoal({ root: expected.root,
66
+ goalId: expected.goalId, goalStepId: expected.goalStepId, queueId: expected.queueId,
67
+ gatewayAttempt: expected.gatewayAttempt, dispositionDigest, context: expected,
68
+ bindingSummaryDigests: summaryDigests });
69
+ if (!fenced || fenced.status === "degraded") return { status: "degraded", blocked: false,
70
+ attachments: dispositionAttachments("degraded-fail-open", expected, checkpoint,
71
+ completedAt, fenced?.reason) };
72
+ if (fenced.blocked || fenced.status !== "finalized"
73
+ || !validPremortemScopeFinalization(fenced.finalization, expected, summaryDigests)
74
+ || fenced.finalization.attachmentDigest !== dispositionDigest) {
75
+ return { status: fenced.status || "mismatch", blocked: true,
76
+ reason: fenced.reason || "Read-only premortem finalization does not match the goal step.",
77
+ attachments: null };
78
+ }
79
+ return { status: "read-only", blocked: false,
80
+ attachments: dispositionAttachments("read-only", expected, checkpoint, completedAt,
81
+ "host-confirmed-read-only", fenced.finalization) };
82
+ }