blun-king-cli 9.1.566 → 9.1.569

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 (262) hide show
  1. package/agent-spine-plugin/.claude-plugin/marketplace.json +1 -1
  2. package/agent-spine-plugin/.claude-plugin/plugin.json +1 -1
  3. package/agent-spine-plugin/.codex-plugin/plugin.json +2 -1
  4. package/agent-spine-plugin/CHANGELOG.md +1581 -0
  5. package/agent-spine-plugin/README.md +30 -4
  6. package/agent-spine-plugin/blun.plugin.json +3 -3
  7. package/agent-spine-plugin/docs/acceptance.md +61 -0
  8. package/agent-spine-plugin/docs/assignment-continuation.md +48 -0
  9. package/agent-spine-plugin/docs/host-integration.md +178 -0
  10. package/agent-spine-plugin/docs/preflight-recall.md +69 -0
  11. package/agent-spine-plugin/docs/preservation-contract.md +53 -0
  12. package/agent-spine-plugin/docs/quality-gates.md +50 -0
  13. package/agent-spine-plugin/docs/releasing.md +85 -0
  14. package/agent-spine-plugin/docs/session-timeline.md +251 -0
  15. package/agent-spine-plugin/docs/source-roots.md +113 -0
  16. package/agent-spine-plugin/docs/structured-completion.md +67 -0
  17. package/agent-spine-plugin/docs/world-model.md +94 -0
  18. package/agent-spine-plugin/hooks/codex.json +2 -2
  19. package/agent-spine-plugin/hooks/hooks.json +1 -1
  20. package/agent-spine-plugin/hooks/version.json +1 -1
  21. package/agent-spine-plugin/package.json +13 -3
  22. package/agent-spine-plugin/scripts/check-codex-install.js +226 -0
  23. package/agent-spine-plugin/scripts/check-hosts.js +14 -5
  24. package/agent-spine-plugin/scripts/check-install-hook.js +226 -0
  25. package/agent-spine-plugin/scripts/check-install-selfstarter.js +154 -0
  26. package/agent-spine-plugin/scripts/check-install.js +478 -0
  27. package/agent-spine-plugin/scripts/check-line-budget.js +58 -0
  28. package/agent-spine-plugin/scripts/check-syntax.js +29 -0
  29. package/agent-spine-plugin/scripts/github-actions.js +11 -0
  30. package/agent-spine-plugin/scripts/hermetic-process.js +183 -0
  31. package/agent-spine-plugin/scripts/release-check.js +145 -0
  32. package/agent-spine-plugin/scripts/run-acceptance.js +19 -0
  33. package/agent-spine-plugin/scripts/run-checks.js +47 -0
  34. package/agent-spine-plugin/scripts/run-tests-hermetic.js +89 -0
  35. package/agent-spine-plugin/skills/agent-spine/SKILL.md +16 -2
  36. package/agent-spine-plugin/spine-example/1-identity.md +12 -0
  37. package/agent-spine-plugin/spine-example/2-voice.md +6 -0
  38. package/agent-spine-plugin/spine-example/3-conduct.md +8 -0
  39. package/agent-spine-plugin/spine-example/4-history.md +4 -0
  40. package/agent-spine-plugin/src/cli-agent.js +296 -0
  41. package/agent-spine-plugin/src/cli-attention.js +95 -0
  42. package/agent-spine-plugin/src/cli-autonomy.js +36 -0
  43. package/agent-spine-plugin/src/cli-common.js +71 -0
  44. package/agent-spine-plugin/src/cli-continuity.js +116 -0
  45. package/agent-spine-plugin/src/cli-core.js +128 -0
  46. package/agent-spine-plugin/src/cli-diagnostics.js +291 -0
  47. package/agent-spine-plugin/src/cli-host.js +21 -0
  48. package/agent-spine-plugin/src/cli-learning.js +305 -0
  49. package/agent-spine-plugin/src/cli-premortem.js +16 -0
  50. package/agent-spine-plugin/src/cli-sharing.js +230 -0
  51. package/agent-spine-plugin/src/cli.js +40 -1350
  52. package/agent-spine-plugin/src/codex-reader-launcher.js +182 -0
  53. package/agent-spine-plugin/src/hook.js +185 -567
  54. package/agent-spine-plugin/src/index.js +16 -2
  55. package/agent-spine-plugin/src/lib/acceptance.js +113 -11
  56. package/agent-spine-plugin/src/lib/action-lesson-recall.js +53 -0
  57. package/agent-spine-plugin/src/lib/attention-context.js +167 -0
  58. package/agent-spine-plugin/src/lib/attention-events.js +113 -0
  59. package/agent-spine-plugin/src/lib/attention-privacy.js +72 -0
  60. package/agent-spine-plugin/src/lib/attention-schema.js +164 -0
  61. package/agent-spine-plugin/src/lib/attention-storage.js +93 -0
  62. package/agent-spine-plugin/src/lib/attention.js +9 -600
  63. package/agent-spine-plugin/src/lib/audit-premortem.js +223 -0
  64. package/agent-spine-plugin/src/lib/audit.js +56 -6
  65. package/agent-spine-plugin/src/lib/autonomy-policy.js +110 -0
  66. package/agent-spine-plugin/src/lib/autonomy-store.js +202 -0
  67. package/agent-spine-plugin/src/lib/autonomy.js +8 -0
  68. package/agent-spine-plugin/src/lib/briefing.js +67 -4
  69. package/agent-spine-plugin/src/lib/catalog-document-read.js +51 -0
  70. package/agent-spine-plugin/src/lib/catalog.js +1 -1
  71. package/agent-spine-plugin/src/lib/codex-installation.js +231 -0
  72. package/agent-spine-plugin/src/lib/codex-skill-installation.js +211 -0
  73. package/agent-spine-plugin/src/lib/context.js +3 -1
  74. package/agent-spine-plugin/src/lib/delivery-agent-usage.js +224 -0
  75. package/agent-spine-plugin/src/lib/delivery-assignment.js +220 -0
  76. package/agent-spine-plugin/src/lib/delivery-command-actions.js +453 -0
  77. package/agent-spine-plugin/src/lib/delivery-knowledge.js +78 -0
  78. package/agent-spine-plugin/src/lib/delivery-premortem-binding.js +107 -0
  79. package/agent-spine-plugin/src/lib/delivery-premortem-closure.js +171 -0
  80. package/agent-spine-plugin/src/lib/delivery-premortem-codec.js +45 -0
  81. package/agent-spine-plugin/src/lib/delivery-premortem-correction.js +101 -0
  82. package/agent-spine-plugin/src/lib/delivery-premortem-file.js +21 -0
  83. package/agent-spine-plugin/src/lib/delivery-premortem-index.js +493 -0
  84. package/agent-spine-plugin/src/lib/delivery-premortem-inspection.js +37 -0
  85. package/agent-spine-plugin/src/lib/delivery-premortem-recovery.js +120 -0
  86. package/agent-spine-plugin/src/lib/delivery-premortem-rejection.js +65 -0
  87. package/agent-spine-plugin/src/lib/delivery-premortem-results.js +28 -0
  88. package/agent-spine-plugin/src/lib/delivery-premortem-session-guard.js +46 -0
  89. package/agent-spine-plugin/src/lib/delivery-premortem-write-ledger.js +285 -0
  90. package/agent-spine-plugin/src/lib/delivery-premortem.js +499 -0
  91. package/agent-spine-plugin/src/lib/delivery-shell-heredoc.js +115 -0
  92. package/agent-spine-plugin/src/lib/delivery-shell-substitutions.js +111 -0
  93. package/agent-spine-plugin/src/lib/delivery-shell-wrapper.js +64 -0
  94. package/agent-spine-plugin/src/lib/delivery-target.js +73 -0
  95. package/agent-spine-plugin/src/lib/delivery-verification.js +445 -0
  96. package/agent-spine-plugin/src/lib/documents.js +27 -5
  97. package/agent-spine-plugin/src/lib/filesystem-retry.js +2 -0
  98. package/agent-spine-plugin/src/lib/gateway-common.js +68 -0
  99. package/agent-spine-plugin/src/lib/gateway-control.js +302 -0
  100. package/agent-spine-plugin/src/lib/gateway-delivery.js +103 -0
  101. package/agent-spine-plugin/src/lib/gateway-execution.js +350 -0
  102. package/agent-spine-plugin/src/lib/gateway-host-lifecycle.js +185 -0
  103. package/agent-spine-plugin/src/lib/gateway-inspection.js +85 -0
  104. package/agent-spine-plugin/src/lib/gateway-knowledge.js +129 -0
  105. package/agent-spine-plugin/src/lib/gateway-policy-provenance.js +197 -0
  106. package/agent-spine-plugin/src/lib/gateway-premortem-disposition.js +82 -0
  107. package/agent-spine-plugin/src/lib/gateway-premortem.js +363 -0
  108. package/agent-spine-plugin/src/lib/gateway-runs.js +300 -0
  109. package/agent-spine-plugin/src/lib/gateway-runtime-identity.js +31 -0
  110. package/agent-spine-plugin/src/lib/gateway-runtime-records.js +21 -0
  111. package/agent-spine-plugin/src/lib/gateway-runtime.js +18 -1623
  112. package/agent-spine-plugin/src/lib/gateway-state-transaction.js +356 -0
  113. package/agent-spine-plugin/src/lib/gateway-state.js +342 -0
  114. package/agent-spine-plugin/src/lib/hook-artifact-guards.js +356 -0
  115. package/agent-spine-plugin/src/lib/hook-audit.js +16 -2
  116. package/agent-spine-plugin/src/lib/hook-briefing-use.js +68 -0
  117. package/agent-spine-plugin/src/lib/hook-context.js +424 -0
  118. package/agent-spine-plugin/src/lib/hook-final-message.js +26 -0
  119. package/agent-spine-plugin/src/lib/hook-input.js +27 -0
  120. package/agent-spine-plugin/src/lib/hook-output.js +140 -0
  121. package/agent-spine-plugin/src/lib/hook-premortem.js +257 -0
  122. package/agent-spine-plugin/src/lib/hook-process-advisory.js +37 -0
  123. package/agent-spine-plugin/src/lib/hook-protection.js +95 -0
  124. package/agent-spine-plugin/src/lib/hook-stop-verification.js +84 -0
  125. package/agent-spine-plugin/src/lib/hook-timeline.js +91 -0
  126. package/agent-spine-plugin/src/lib/identifier-analysis.js +446 -0
  127. package/agent-spine-plugin/src/lib/indexed-memory.js +23 -6
  128. package/agent-spine-plugin/src/lib/knowledge-evidence.js +431 -0
  129. package/agent-spine-plugin/src/lib/learning-applications.js +441 -0
  130. package/agent-spine-plugin/src/lib/learning-candidates.js +274 -0
  131. package/agent-spine-plugin/src/lib/learning-context.js +130 -0
  132. package/agent-spine-plugin/src/lib/learning-delivery-contracts.js +310 -0
  133. package/agent-spine-plugin/src/lib/learning-evaluation-contracts.js +350 -0
  134. package/agent-spine-plugin/src/lib/learning-evaluation-registration.js +372 -0
  135. package/agent-spine-plugin/src/lib/learning-evaluation-revocation.js +292 -0
  136. package/agent-spine-plugin/src/lib/learning-evidence-contracts.js +321 -0
  137. package/agent-spine-plugin/src/lib/learning-findings.js +458 -0
  138. package/agent-spine-plugin/src/lib/learning-measurement-contracts.js +285 -0
  139. package/agent-spine-plugin/src/lib/learning-measurements.js +265 -0
  140. package/agent-spine-plugin/src/lib/learning-outcome-contracts.js +165 -0
  141. package/agent-spine-plugin/src/lib/learning-outcomes.js +343 -0
  142. package/agent-spine-plugin/src/lib/learning-reconciliation.js +290 -0
  143. package/agent-spine-plugin/src/lib/learning-retry-contracts.js +188 -0
  144. package/agent-spine-plugin/src/lib/learning-schema.js +229 -0
  145. package/agent-spine-plugin/src/lib/learning-scope-targets.js +424 -0
  146. package/agent-spine-plugin/src/lib/learning-state-upgrade.js +477 -0
  147. package/agent-spine-plugin/src/lib/learning-status-configuration.js +476 -0
  148. package/agent-spine-plugin/src/lib/learning-storage.js +203 -0
  149. package/agent-spine-plugin/src/lib/learning-trial-recovery.js +218 -0
  150. package/agent-spine-plugin/src/lib/learning-validation-contracts.js +375 -0
  151. package/agent-spine-plugin/src/lib/learning-validation-renewal.js +298 -0
  152. package/agent-spine-plugin/src/lib/learning-validation-runtime.js +234 -0
  153. package/agent-spine-plugin/src/lib/learning.js +36 -6923
  154. package/agent-spine-plugin/src/lib/lesson-recall-session.js +172 -0
  155. package/agent-spine-plugin/src/lib/mcp-autonomy-tools.js +37 -0
  156. package/agent-spine-plugin/src/lib/mcp-delivery-completion.js +124 -0
  157. package/agent-spine-plugin/src/lib/mcp-delivery-tools.js +45 -0
  158. package/agent-spine-plugin/src/lib/mcp-premortem.js +68 -0
  159. package/agent-spine-plugin/src/lib/mcp-runtime.js +269 -0
  160. package/agent-spine-plugin/src/lib/mcp-source-context.js +51 -0
  161. package/agent-spine-plugin/src/lib/mcp-timeline-tools.js +164 -0
  162. package/agent-spine-plugin/src/lib/mcp-world-tools.js +52 -0
  163. package/agent-spine-plugin/src/lib/owned-file-lock.js +30 -10
  164. package/agent-spine-plugin/src/lib/project-portfolio.js +176 -0
  165. package/agent-spine-plugin/src/lib/selfstarter-core.js +382 -0
  166. package/agent-spine-plugin/src/lib/selfstarter-jobs.js +149 -0
  167. package/agent-spine-plugin/src/lib/selfstarter-lease.js +204 -0
  168. package/agent-spine-plugin/src/lib/selfstarter-policy.js +90 -0
  169. package/agent-spine-plugin/src/lib/selfstarter-workspace.js +111 -0
  170. package/agent-spine-plugin/src/lib/selfstarter.js +11 -911
  171. package/agent-spine-plugin/src/lib/session-timeline-auth.js +316 -0
  172. package/agent-spine-plugin/src/lib/session-timeline-codex.js +58 -0
  173. package/agent-spine-plugin/src/lib/session-timeline-contract.js +48 -0
  174. package/agent-spine-plugin/src/lib/session-timeline-enrollment-source.js +41 -0
  175. package/agent-spine-plugin/src/lib/session-timeline-enrollment-storage.js +132 -0
  176. package/agent-spine-plugin/src/lib/session-timeline-enrollment-transport.js +17 -0
  177. package/agent-spine-plugin/src/lib/session-timeline-enrollment.js +500 -0
  178. package/agent-spine-plugin/src/lib/session-timeline-event-extract.js +157 -0
  179. package/agent-spine-plugin/src/lib/session-timeline-host-origin.js +74 -0
  180. package/agent-spine-plugin/src/lib/session-timeline-host-receipt.js +117 -0
  181. package/agent-spine-plugin/src/lib/session-timeline-invocation.js +201 -0
  182. package/agent-spine-plugin/src/lib/session-timeline-king.js +79 -0
  183. package/agent-spine-plugin/src/lib/session-timeline-prior.js +59 -0
  184. package/agent-spine-plugin/src/lib/session-timeline-provider.js +34 -0
  185. package/agent-spine-plugin/src/lib/session-timeline-query.js +55 -0
  186. package/agent-spine-plugin/src/lib/session-timeline-results.js +31 -0
  187. package/agent-spine-plugin/src/lib/session-timeline-root.js +11 -0
  188. package/agent-spine-plugin/src/lib/session-timeline-search.js +82 -0
  189. package/agent-spine-plugin/src/lib/session-timeline-sid-acl.js +217 -0
  190. package/agent-spine-plugin/src/lib/session-timeline-source.js +83 -0
  191. package/agent-spine-plugin/src/lib/session-timeline-state.js +45 -0
  192. package/agent-spine-plugin/src/lib/session-timeline-transport.js +50 -0
  193. package/agent-spine-plugin/src/lib/session-timeline-windows-acl.js +148 -0
  194. package/agent-spine-plugin/src/lib/session-timeline.js +453 -0
  195. package/agent-spine-plugin/src/lib/source-roots.js +69 -136
  196. package/agent-spine-plugin/src/lib/source-tree-scan.js +178 -0
  197. package/agent-spine-plugin/src/lib/task-knowledge-context.js +78 -0
  198. package/agent-spine-plugin/src/lib/timeline-tool-guard.js +202 -0
  199. package/agent-spine-plugin/src/lib/world-knowledge.js +249 -0
  200. package/agent-spine-plugin/src/lib/world-model.js +278 -0
  201. package/agent-spine-plugin/src/mcp.js +20 -161
  202. package/agent-spine-plugin/src/version.js +1 -1
  203. package/agent-spine-plugin/src/worker.js +22 -5
  204. package/bin/agent-resume-snapshot.cjs +2 -2
  205. package/bin/agentspine-king-goal-inbox.mjs +111 -0
  206. package/bin/agentspine-king-goal-intake.mjs +106 -0
  207. package/bin/baseline-skill-performance-policy.cjs +1 -16
  208. package/bin/core-bootstrap.js +2 -0
  209. package/bin/curiosity-scout-policy.cjs +5 -1
  210. package/bin/input-draft-persistence.cjs +2 -2
  211. package/bin/king-tui-function-contract.json +33 -0
  212. package/bin/launcher-restart-policy.cjs +150 -0
  213. package/bin/launcher-runtime.js +56 -44
  214. package/bin/managed-context-startup-policy.cjs +27 -0
  215. package/bin/managed-plugin-selection.cjs +116 -0
  216. package/bin/mistake-relevance-policy.cjs +1 -1
  217. package/bin/observer-hooks.cjs +14 -0
  218. package/bin/oversized-context-offload-policy.cjs +86 -0
  219. package/bin/plugin-bootstrap.js +7 -40
  220. package/bin/proactive-compaction-policy.cjs +1 -1
  221. package/bin/provider-model-refresh-deadline.cjs +53 -0
  222. package/bin/provider-model-refresh-policy.cjs +107 -0
  223. package/bin/release-artifact-freeze-policy.cjs +30 -0
  224. package/bin/repeated-user-message-projection.cjs +3 -126
  225. package/bin/research-page-result.cjs +74 -0
  226. package/bin/runtime-exit-ledger.cjs +1 -0
  227. package/bin/session-compaction-policy.cjs +84 -0
  228. package/bin/skill-listing-performance-policy.cjs +2 -2
  229. package/bin/standard-tools-bootstrap.js +0 -37
  230. package/bin/subagent-skill-policy.cjs +3 -1
  231. package/bin/telegram-approval-relay.cjs +12 -7
  232. package/bin/telegram-private-conversation-policy.cjs +3 -2
  233. package/bin/telegram-queue-handoff-policy.cjs +24 -0
  234. package/bin/thinking-activity-status-policy.cjs +1 -1
  235. package/bin/thinking-only-guard.cjs +16 -12
  236. package/bin/tool-call-loop-policy.cjs +0 -2
  237. package/bin/tool-result-offload-policy.cjs +11 -2
  238. package/bin/tui-functional-contract.cjs +55 -0
  239. package/bin/update-notice.js +18 -14
  240. package/bin/user-message-offload-policy.cjs +1 -1
  241. package/bin/user-prompt-hook-origin-policy.cjs +34 -0
  242. package/bin/windows-node-crash-dump.cjs +110 -0
  243. package/blun.mjs +2517 -1048
  244. package/codebase-index/codebase_index.py +4 -3
  245. package/package.json +3 -17
  246. package/standard-skills/research-evidence/SKILL.md +39 -0
  247. package/standard-skills/research-evidence/references/evidence-format.md +82 -0
  248. package/standard-skills/research-evidence/scripts/evidence-collection.cjs +254 -0
  249. package/standard-skills/research-evidence/scripts/score-report.cjs +112 -0
  250. package/standard-skills/web-lesen/SKILL.md +37 -22
  251. package/standard-skills/web-lesen/scripts/crawl_public.py +376 -0
  252. package/telegram-plugin/DELIVERY.md +36 -0
  253. package/telegram-plugin/bin/telegram-approval-relay.cjs +13 -7
  254. package/telegram-plugin/bin/telegram-launcher-status-queue.cjs +122 -0
  255. package/telegram-plugin/bin/telegram-private-conversation-policy.cjs +3 -2
  256. package/telegram-plugin/bin/telegram-reply-parts.cjs +149 -0
  257. package/telegram-plugin/dist/bridge.mjs +11 -57
  258. package/telegram-plugin/dist/mcp-server.mjs +33 -4
  259. package/agent-spine-plugin/skill/SKILL.md +0 -76
  260. package/bin/mnemo-connect-heartbeat.cjs +0 -204
  261. package/bin/mnemo-tool-agent-policy.cjs +0 -22
  262. package/telegram-plugin/bin/telegram-mnemo-capture.cjs +0 -297
@@ -0,0 +1,356 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ import { createHash, randomUUID } from "node:crypto";
3
+ import { readdir, readFile, stat, unlink, writeFile } from "node:fs/promises";
4
+ import { basename, join } from "node:path";
5
+ import { replaceFileWithRetry } from "./filesystem-retry.js";
6
+ import { readGatewayJson, writeGatewayJson } from "./gateway-policy-provenance.js";
7
+ import { withOwnedFileLock } from "./owned-file-lock.js";
8
+
9
+ const TRANSACTION_SCHEMA = "agentspine.gateway-state-transaction/v1";
10
+ const AUTHORITY = "state-coordination-only";
11
+ const MAX_STATE_BYTES = 8 * 1024 * 1024;
12
+ const MAX_TRANSACTION_BYTES = 16 * 1024;
13
+ const DIGEST_RE = /^[a-f0-9]{64}$/;
14
+ const UUID_RE = /^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/;
15
+ const STAGE_RE = /^gateway-pair-[a-f0-9-]{36}-(?:policy|runtime)\.next\.json$/;
16
+ const contextStore = new AsyncLocalStorage();
17
+ const testHookStore = new AsyncLocalStorage();
18
+
19
+ function canonical(value) {
20
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
21
+ if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
22
+ return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`).join(",")}}`;
23
+ }
24
+
25
+ function sha256(value) {
26
+ return createHash("sha256").update(value).digest("hex");
27
+ }
28
+
29
+ function stateContent(value) {
30
+ const content = `${JSON.stringify(value, null, 2)}\n`;
31
+ if (Buffer.byteLength(content) > MAX_STATE_BYTES) throw new Error("gateway state exceeds 8 MiB");
32
+ return content;
33
+ }
34
+
35
+ async function fileDigest(path, maximum = MAX_STATE_BYTES) {
36
+ try {
37
+ const metadata = await stat(path);
38
+ if (metadata.size > maximum) throw new Error("gateway transaction file exceeds its byte limit");
39
+ return sha256(await readFile(path));
40
+ } catch (error) {
41
+ if (error.code === "ENOENT") return null;
42
+ throw error;
43
+ }
44
+ }
45
+
46
+ async function readBounded(path, maximum) {
47
+ const metadata = await stat(path);
48
+ if (metadata.size > maximum) throw new Error("gateway transaction file exceeds its byte limit");
49
+ const content = await readFile(path, "utf8");
50
+ if (Buffer.byteLength(content) > maximum) throw new Error("gateway transaction file exceeds its byte limit");
51
+ return content;
52
+ }
53
+
54
+ function transactionPaths(paths, transactionId = null) {
55
+ const prefix = transactionId ? `gateway-pair-${transactionId}` : null;
56
+ return {
57
+ lockPath: join(paths.directory, "gateway-runtime.lock"),
58
+ journalPath: join(paths.directory, "gateway-state-transaction.json"),
59
+ policyStagePath: prefix ? join(paths.directory, `${prefix}-policy.next.json`) : null,
60
+ runtimeStagePath: prefix ? join(paths.directory, `${prefix}-runtime.next.json`) : null
61
+ };
62
+ }
63
+
64
+ function transactionMaterial(value) {
65
+ return {
66
+ schema: value.schema,
67
+ transactionId: value.transactionId,
68
+ projectRootDigest: value.projectRootDigest,
69
+ before: value.before,
70
+ after: value.after,
71
+ stages: value.stages,
72
+ authority: value.authority
73
+ };
74
+ }
75
+
76
+ function exactKeys(value, keys) {
77
+ return value && canonical(Object.keys(value).sort()) === canonical([...keys].sort());
78
+ }
79
+
80
+ function validDigestPair(value, nullable) {
81
+ return exactKeys(value, ["policy", "runtime"])
82
+ && [value.policy, value.runtime].every((digest) => (nullable && digest === null) || DIGEST_RE.test(digest || ""));
83
+ }
84
+
85
+ function validTransaction(value, paths) {
86
+ if (!exactKeys(value, ["schema", "transactionId", "projectRootDigest", "before", "after", "stages", "authority", "digest"])
87
+ || value.schema !== TRANSACTION_SCHEMA || !UUID_RE.test(value.transactionId || "")
88
+ || value.projectRootDigest !== sha256(paths.catalog.root) || value.authority !== AUTHORITY
89
+ || !validDigestPair(value.before, true) || !validDigestPair(value.after, false)
90
+ || !exactKeys(value.stages, ["policy", "runtime"])) return false;
91
+ const expected = transactionPaths(paths, value.transactionId);
92
+ return value.stages.policy === basename(expected.policyStagePath)
93
+ && value.stages.runtime === basename(expected.runtimeStagePath)
94
+ && value.digest === sha256(canonical(transactionMaterial(value)));
95
+ }
96
+
97
+ async function atomicWrite(path, content, { assertOwned, beforeFinalCheck, expectedDigest, maximum }) {
98
+ if (Buffer.byteLength(content) > maximum) throw new Error("gateway transaction file exceeds its byte limit");
99
+ const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`;
100
+ try {
101
+ await writeFile(temporary, content, { mode: 0o600 });
102
+ await replaceFileWithRetry(temporary, path, { beforeAttempt: async () => {
103
+ await assertOwned();
104
+ if (await fileDigest(path, maximum) !== expectedDigest) {
105
+ throw new Error("gateway transaction predecessor changed before atomic replace");
106
+ }
107
+ await beforeFinalCheck?.();
108
+ await assertOwned();
109
+ } });
110
+ } finally {
111
+ await unlink(temporary).catch((error) => { if (error.code !== "ENOENT") throw error; });
112
+ }
113
+ }
114
+
115
+ async function writeStage(path, content, assertOwned) {
116
+ await assertOwned();
117
+ await writeFile(path, content, { flag: "wx", mode: 0o600 });
118
+ }
119
+
120
+ async function readTransaction(paths) {
121
+ const { journalPath } = transactionPaths(paths);
122
+ try {
123
+ const value = JSON.parse(await readBounded(journalPath, MAX_TRANSACTION_BYTES));
124
+ if (!validTransaction(value, paths)) throw new Error("gateway state transaction journal is invalid");
125
+ return value;
126
+ } catch (error) {
127
+ if (error.code === "ENOENT") return null;
128
+ if (error instanceof SyntaxError) throw new Error("gateway state transaction journal is invalid");
129
+ throw error;
130
+ }
131
+ }
132
+
133
+ async function readStage(path, expectedDigest, schema, root) {
134
+ const content = await readBounded(path, MAX_STATE_BYTES);
135
+ if (sha256(content) !== expectedDigest) throw new Error("gateway state transaction stage digest mismatch");
136
+ let value;
137
+ try { value = JSON.parse(content); }
138
+ catch { throw new Error("gateway state transaction stage is invalid"); }
139
+ if (value?.schema !== schema || value.root !== root) throw new Error("gateway state transaction stage scope mismatch");
140
+ return { content, value };
141
+ }
142
+
143
+ async function currentPair(paths) {
144
+ return {
145
+ policy: await fileDigest(paths.gatewayPolicyPath),
146
+ runtime: await fileDigest(paths.gatewayRuntimePath)
147
+ };
148
+ }
149
+
150
+ function pairMatches(pair, expected) {
151
+ return pair.policy === expected.policy && pair.runtime === expected.runtime;
152
+ }
153
+
154
+ function pairWithinTransaction(pair, transaction) {
155
+ return ["policy", "runtime"].every((key) =>
156
+ pair[key] === transaction.before[key] || pair[key] === transaction.after[key]);
157
+ }
158
+
159
+ async function removeTransactionFiles(paths, transaction, assertOwned) {
160
+ const names = transactionPaths(paths, transaction.transactionId);
161
+ await assertOwned();
162
+ await unlink(names.journalPath);
163
+ await Promise.all([names.policyStagePath, names.runtimeStagePath].map((path) =>
164
+ unlink(path).catch((error) => { if (error.code !== "ENOENT") throw error; })));
165
+ }
166
+
167
+ async function runTestHook(context, phase) {
168
+ if (typeof context.testHook === "function") await context.testHook(phase, {
169
+ paths: context.paths,
170
+ transactionId: context.transactionId || null
171
+ });
172
+ }
173
+
174
+ async function validateStatePair(context, policy, runtime) {
175
+ if (typeof context.validatePair !== "function") {
176
+ throw new Error("gateway state pair validation is unavailable");
177
+ }
178
+ await context.validatePair(structuredClone(policy), structuredClone(runtime));
179
+ }
180
+
181
+ async function installTransaction(context, transaction) {
182
+ const paths = context.paths;
183
+ const names = transactionPaths(paths, transaction.transactionId);
184
+ const [policyStage, runtimeStage] = await Promise.all([
185
+ readStage(names.policyStagePath, transaction.after.policy, "agentspine.gateway-policy/v2", paths.catalog.root),
186
+ readStage(names.runtimeStagePath, transaction.after.runtime, "agentspine.gateway-runtime/v1", paths.catalog.root)
187
+ ]);
188
+ await validateStatePair(context, policyStage.value, runtimeStage.value);
189
+ const current = await currentPair(paths);
190
+ if (!pairWithinTransaction(current, transaction)) {
191
+ throw new Error("gateway state changed outside the prepared transaction");
192
+ }
193
+ await writeGatewayJson(paths.gatewayPolicyPath, policyStage.value, {
194
+ assertOwned: context.assertOwned,
195
+ expectedDigest: current.policy
196
+ });
197
+ await runTestHook(context, "after-policy-install");
198
+ const runtimeBase = await fileDigest(paths.gatewayRuntimePath);
199
+ if (![transaction.before.runtime, transaction.after.runtime].includes(runtimeBase)) {
200
+ throw new Error("gateway runtime changed outside the prepared transaction");
201
+ }
202
+ await writeGatewayJson(paths.gatewayRuntimePath, runtimeStage.value, {
203
+ assertOwned: context.assertOwned,
204
+ expectedDigest: runtimeBase
205
+ });
206
+ await runTestHook(context, "after-runtime-install");
207
+ if (!pairMatches(await currentPair(paths), transaction.after)) {
208
+ throw new Error("gateway state transaction did not install the exact pair");
209
+ }
210
+ await removeTransactionFiles(paths, transaction, context.assertOwned);
211
+ context.expected = { ...transaction.after };
212
+ }
213
+
214
+ async function cleanOrphanStages(paths, assertOwned) {
215
+ let entries;
216
+ try { entries = await readdir(paths.directory, { withFileTypes: true }); }
217
+ catch (error) { if (error.code === "ENOENT") return; throw error; }
218
+ for (const entry of entries.filter((item) => item.isFile() && STAGE_RE.test(item.name)).slice(0, 64)) {
219
+ await assertOwned();
220
+ await unlink(join(paths.directory, entry.name)).catch((error) => {
221
+ if (error.code !== "ENOENT") throw error;
222
+ });
223
+ }
224
+ }
225
+
226
+ async function recoverPreparedTransaction(context) {
227
+ const transaction = await readTransaction(context.paths);
228
+ if (!transaction) {
229
+ await cleanOrphanStages(context.paths, context.assertOwned);
230
+ return;
231
+ }
232
+ context.transactionId = transaction.transactionId;
233
+ await installTransaction(context, transaction);
234
+ context.transactionId = null;
235
+ }
236
+
237
+ export async function withGatewayStateLock(paths, task, options = {}) {
238
+ if (!paths?.catalog?.root || !paths.directory || typeof task !== "function") {
239
+ throw new Error("gateway state lock requires exact paths and a task");
240
+ }
241
+ const names = transactionPaths(paths);
242
+ const lockOptions = options.lockOptions || {
243
+ staleAfterMs: 120000,
244
+ heartbeatIntervalMs: 1000,
245
+ retryDelayMs: 25,
246
+ maxAttempts: 160
247
+ };
248
+ return withOwnedFileLock(names.lockPath, async ({ assertOwned, token }) => {
249
+ const context = { paths, assertOwned, token, expected: null, validatePair: options.validatePair || null,
250
+ transactionId: null, testHook: options.testHook || testHookStore.getStore() || null };
251
+ return contextStore.run(context, async () => {
252
+ await recoverPreparedTransaction(context);
253
+ context.expected = await currentPair(paths);
254
+ const result = await task();
255
+ await assertOwned();
256
+ if (!pairMatches(await currentPair(paths), context.expected)
257
+ || await fileDigest(names.journalPath, MAX_TRANSACTION_BYTES) !== null) {
258
+ throw new Error("gateway state changed outside its owned transaction");
259
+ }
260
+ return result;
261
+ });
262
+ }, lockOptions);
263
+ }
264
+
265
+ export async function withGatewayStateTestHook(testHook, task) {
266
+ if (typeof testHook !== "function" || typeof task !== "function") {
267
+ throw new Error("gateway state test hook requires two functions");
268
+ }
269
+ return testHookStore.run(testHook, task);
270
+ }
271
+
272
+ function activeContext() {
273
+ const context = contextStore.getStore();
274
+ if (!context) throw new Error("gateway state write requires the owned gateway lock");
275
+ return context;
276
+ }
277
+
278
+ function stateKey(context, path) {
279
+ if (path === context.paths.gatewayPolicyPath) return "policy";
280
+ if (path === context.paths.gatewayRuntimePath) return "runtime";
281
+ throw new Error("gateway state write target is outside the bound pair");
282
+ }
283
+
284
+ export async function writeGatewayStateJson(path, value) {
285
+ const context = activeContext();
286
+ const key = stateKey(context, path);
287
+ await writeGatewayJson(path, value, {
288
+ assertOwned: context.assertOwned,
289
+ expectedDigest: context.expected[key]
290
+ });
291
+ context.expected[key] = sha256(stateContent(value));
292
+ }
293
+
294
+ export async function readGatewayStateJson(path, root, normalize, empty) {
295
+ const context = activeContext();
296
+ const key = stateKey(context, path);
297
+ return readGatewayJson(path, root, normalize, empty, { assertOwned: context.assertOwned,
298
+ afterWrite: (digest) => { context.expected[key] = digest; } });
299
+ }
300
+
301
+ export async function writeGatewayStatePair(policy, runtime) {
302
+ const context = activeContext();
303
+ const paths = context.paths;
304
+ if (policy?.root !== paths.catalog.root || runtime?.root !== paths.catalog.root
305
+ || policy?.schema !== "agentspine.gateway-policy/v2"
306
+ || runtime?.schema !== "agentspine.gateway-runtime/v1") {
307
+ throw new Error("gateway state transaction pair has an invalid scope or schema");
308
+ }
309
+ await validateStatePair(context, policy, runtime);
310
+ if (!pairMatches(await currentPair(paths), context.expected)) {
311
+ throw new Error("gateway state predecessor changed before transaction prepare");
312
+ }
313
+ const transactionId = randomUUID();
314
+ const names = transactionPaths(paths, transactionId);
315
+ const policyContent = stateContent(policy);
316
+ const runtimeContent = stateContent(runtime);
317
+ await writeStage(names.policyStagePath, policyContent, context.assertOwned);
318
+ try {
319
+ await writeStage(names.runtimeStagePath, runtimeContent, context.assertOwned);
320
+ const material = {
321
+ schema: TRANSACTION_SCHEMA,
322
+ transactionId,
323
+ projectRootDigest: sha256(paths.catalog.root),
324
+ before: { ...context.expected },
325
+ after: { policy: sha256(policyContent), runtime: sha256(runtimeContent) },
326
+ stages: { policy: basename(names.policyStagePath), runtime: basename(names.runtimeStagePath) },
327
+ authority: AUTHORITY
328
+ };
329
+ const transaction = { ...material, digest: sha256(canonical(material)) };
330
+ await atomicWrite(names.journalPath, `${JSON.stringify(transaction, null, 2)}\n`, {
331
+ assertOwned: context.assertOwned,
332
+ beforeFinalCheck: () => runTestHook(context, "after-journal-predecessor-check"),
333
+ expectedDigest: null,
334
+ maximum: MAX_TRANSACTION_BYTES
335
+ });
336
+ context.transactionId = transactionId;
337
+ await runTestHook(context, "after-prepare");
338
+ await installTransaction(context, transaction);
339
+ context.transactionId = null;
340
+ } catch (error) {
341
+ if (!await fileDigest(names.journalPath, MAX_TRANSACTION_BYTES)) {
342
+ await Promise.all([names.policyStagePath, names.runtimeStagePath].map((path) =>
343
+ unlink(path).catch((cleanupError) => { if (cleanupError.code !== "ENOENT") throw cleanupError; })));
344
+ }
345
+ throw error;
346
+ }
347
+ }
348
+
349
+ export async function inspectGatewayStateTransaction(paths) {
350
+ try {
351
+ const transaction = await readTransaction(paths);
352
+ return { status: transaction ? "prepared" : "none", transaction };
353
+ } catch (error) {
354
+ return { status: "invalid", transaction: null, error: error.message };
355
+ }
356
+ }