blun-king-cli 9.1.567 → 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 +7 -56
  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,65 @@
1
+ import { mkdir, readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { withOwnedFileLock } from "./owned-file-lock.js";
4
+ import { canonicalPath, projectStateDir } from "./paths.js";
5
+ import { writePremortemFile } from "./delivery-premortem-file.js";
6
+ import { premortemSha256 as sha256, premortemTime as at,
7
+ sealPremortem as seal, validPremortemSeal as validSeal,
8
+ validPremortemTime as validTime } from "./delivery-premortem-codec.js";
9
+
10
+ const SCHEMA = "agentspine.delivery-premortem-registration-rejection/v1";
11
+ const AUTHORITY = "context-only";
12
+ const MAX_BYTES = 8 * 1024;
13
+ const DIGEST_RE = /^[a-f0-9]{64}$/;
14
+
15
+ function valid(value) {
16
+ return value && value.schema === SCHEMA && value.authority === AUTHORITY
17
+ && typeof value.requirementId === "string"
18
+ && DIGEST_RE.test(value.priorArtifactDigest || "")
19
+ && DIGEST_RE.test(value.proposedArtifactDigest || "")
20
+ && DIGEST_RE.test(value.laneDigest || "")
21
+ && validTime(value.rejectedAt) && validSeal(value);
22
+ }
23
+
24
+ async function read(path) {
25
+ try {
26
+ const text = await readFile(path, "utf8");
27
+ if (Buffer.byteLength(text) > MAX_BYTES) throw new Error("premortem rejection exceeds 8 KiB");
28
+ const value = JSON.parse(text);
29
+ if (!valid(value)) throw new Error("premortem rejection failed integrity validation");
30
+ return value;
31
+ } catch (error) {
32
+ if (error.code === "ENOENT") return null;
33
+ if (error instanceof SyntaxError) throw new Error("premortem rejection is not valid JSON");
34
+ throw error;
35
+ }
36
+ }
37
+
38
+ export async function recordPremortemRegistrationRejection({ root, state, proposed, now = new Date() }) {
39
+ const receipt = seal({ schema: SCHEMA,
40
+ requirementId: state.requirement.requirementId,
41
+ laneDigest: state.laneDigest,
42
+ priorArtifactDigest: state.artifact.digest,
43
+ proposedArtifactDigest: proposed.digest,
44
+ reason: "conflicts-with-first-registration",
45
+ rejectedAt: at(now), authority: AUTHORITY });
46
+ const digest = sha256({ laneDigest: state.laneDigest,
47
+ priorArtifactDigest: state.artifact.digest, proposedArtifactDigest: proposed.digest });
48
+ const directory = join(await projectStateDir(await canonicalPath(root)),
49
+ "delivery-premortem-rejections", state.laneDigest);
50
+ await mkdir(directory, { recursive: true, mode: 0o700 });
51
+ const path = join(directory, `${digest}.json`);
52
+ return withOwnedFileLock(`${path}.lock`, async ({ assertOwned }) => {
53
+ const existing = await read(path);
54
+ if (existing) return { status: "duplicate", digest,
55
+ receiptDigest: existing.digest, receipt: existing };
56
+ await writePremortemFile(path, receipt, assertOwned, MAX_BYTES);
57
+ return { status: "recorded", digest, receiptDigest: receipt.digest, receipt };
58
+ });
59
+ }
60
+
61
+ export async function inspectPremortemRegistrationRejection({ root, laneDigest, digest }) {
62
+ const directory = join(await projectStateDir(await canonicalPath(root)),
63
+ "delivery-premortem-rejections", laneDigest);
64
+ return read(join(directory, `${digest}.json`));
65
+ }
@@ -0,0 +1,28 @@
1
+ import { createPremortemAttachment } from "./delivery-premortem-closure.js";
2
+
3
+ export function premortemBlock(status, reason, extra = {}) {
4
+ return { status, blocked: true, reason, ...extra };
5
+ }
6
+
7
+ export function degradedPremortem(error) {
8
+ return { status: "degraded", blocked: false,
9
+ reason: String(error?.message || error).slice(0, 400) };
10
+ }
11
+
12
+ export function premortemBoundary(error) {
13
+ if (error?.code === "AGENTSPINE_PREMORTEM_MISMATCH") {
14
+ return premortemBlock("mismatch", String(error.message).slice(0, 400));
15
+ }
16
+ if (error?.code === "AGENTSPINE_PREMORTEM_FINALIZED") {
17
+ return premortemBlock("finalized", String(error.message).slice(0, 400));
18
+ }
19
+ return degradedPremortem(error);
20
+ }
21
+
22
+ export function closedPremortemResult(state, requirementId) {
23
+ const attachment = state.binding.goalId ? createPremortemAttachment(state) : null;
24
+ return { status: "closed", blocked: false, requirementId,
25
+ artifact: structuredClone(state.artifact), digest: state.artifact.digest,
26
+ closure: structuredClone(state.closure), checkpointAttachment: attachment,
27
+ outcomeReceiptAttachment: attachment ? structuredClone(attachment) : null };
28
+ }
@@ -0,0 +1,46 @@
1
+ import { join } from "node:path";
2
+ import { lookupPremortemLaneIndex } from "./delivery-premortem-index.js";
3
+ import { premortemSha256 as sha256,
4
+ premortemMismatchError as mismatchError } from "./delivery-premortem-codec.js";
5
+
6
+ function sameIndexedLane(state, pointer) {
7
+ const binding = state?.binding;
8
+ return state?.laneDigest === pointer.laneDigest
9
+ && binding?.goalId === pointer.goalId
10
+ && binding?.goalStepId === pointer.goalStepId
11
+ && binding?.queueId === pointer.queueId
12
+ && binding?.gatewayAttempt === pointer.gatewayAttempt
13
+ && binding?.planDefinitionsDigest === pointer.planDefinitionsDigest
14
+ && sha256(binding?.sessionId || "") === pointer.sessionDigest;
15
+ }
16
+
17
+ function sessionConflict(binding) {
18
+ return mismatchError(
19
+ `A different host session already wrote in ${binding.goalId}/${binding.goalStepId}, `
20
+ + `queue ${binding.queueId}, gateway attempt ${binding.gatewayAttempt}. This session cannot `
21
+ + "continue that attempt; fail or expire the lease, reconcile, and reclaim the queue so "
22
+ + "AGENTSPINE_GATEWAY_ATTEMPT advances before retrying."
23
+ );
24
+ }
25
+
26
+ export function premortemGoalWriterScopeCheck({ stateDirectory, binding, laneDigest, readState }) {
27
+ if (!binding.goalId || !binding.goalStepId || !binding.queueId || binding.gatewayAttempt === null) {
28
+ return null;
29
+ }
30
+ return async () => {
31
+ const indexed = await lookupPremortemLaneIndex({ stateDirectory,
32
+ goalId: binding.goalId, goalStepId: binding.goalStepId,
33
+ queueId: binding.queueId, gatewayAttempt: binding.gatewayAttempt });
34
+ if (indexed.blocked) throw mismatchError(indexed.reason);
35
+ if (indexed.status === "degraded") throw new Error(indexed.reason);
36
+ for (const pointer of indexed.pointers || []) {
37
+ if (pointer.laneDigest === laneDigest) continue;
38
+ const state = await readState(join(stateDirectory, `${pointer.laneDigest}.json`),
39
+ pointer.laneDigest);
40
+ if (!state || !sameIndexedLane(state, pointer)) {
41
+ throw mismatchError("A premortem session pointer does not match its exact lane state.");
42
+ }
43
+ if (state.firstWrite) throw sessionConflict(binding);
44
+ }
45
+ };
46
+ }
@@ -0,0 +1,285 @@
1
+ import { mkdir, readFile } from "node:fs/promises";
2
+ import { dirname, join } from "node:path";
3
+ import { writePremortemFile } from "./delivery-premortem-file.js";
4
+ import { canonicalPremortem as canonical, premortemMismatchError as mismatchError,
5
+ premortemSha256 as sha256 } from "./delivery-premortem-codec.js";
6
+
7
+ const SCHEMA = "agentspine.delivery-premortem-write-index/v2";
8
+ const AUTHORITY = "context-only";
9
+ const DIGEST_RE = /^[a-f0-9]{64}$/;
10
+ const EDGE_RE = /^[a-f0-9]{2}$/;
11
+ const MAX_RECENT_WRITE_IDS = 64;
12
+ const MAX_NODE_BYTES = 64 * 1024;
13
+ const BUCKET_KEYS = ["authority", "depth", "digest", "entries", "kind", "prefix", "schema"];
14
+ const BRANCH_KEYS = ["authority", "children", "depth", "digest", "kind", "prefix", "schema"];
15
+
16
+ function validEntry(entry) {
17
+ return entry && DIGEST_RE.test(entry.idDigest || "")
18
+ && DIGEST_RE.test(entry.inputDigest || "") && DIGEST_RE.test(entry.writeDigest || "")
19
+ && typeof entry.inputKnown === "boolean"
20
+ && Object.keys(entry).sort().join(",") === "idDigest,inputDigest,inputKnown,writeDigest";
21
+ }
22
+
23
+ function writeEntry(write) {
24
+ const entry = { idDigest: write?.idDigest, inputDigest: write?.inputDigest,
25
+ inputKnown: write?.inputKnown, writeDigest: write?.writeDigest || write?.digest };
26
+ if (!validEntry(entry)) throw new Error("premortem write identity is invalid");
27
+ return entry;
28
+ }
29
+
30
+ export function findPremortemWrite(entries, idDigest) {
31
+ return entries.find((entry) => entry.idDigest === idDigest) || null;
32
+ }
33
+
34
+ export function premortemWriteIsLedgered(entries, write) {
35
+ const entry = findPremortemWrite(entries, write?.idDigest);
36
+ return entry?.inputDigest === write?.inputDigest && entry?.inputKnown === write?.inputKnown
37
+ && entry?.writeDigest === write?.digest;
38
+ }
39
+
40
+ export function validPremortemWriteLedger(entries, firstWrite = null, lastWrite = null) {
41
+ return Array.isArray(entries) && entries.length <= MAX_RECENT_WRITE_IDS
42
+ && new Set(entries.map((entry) => entry?.idDigest)).size === entries.length
43
+ && entries.every(validEntry) && (!lastWrite || premortemWriteIsLedgered(entries, lastWrite))
44
+ && (!firstWrite || entries.length === MAX_RECENT_WRITE_IDS
45
+ || premortemWriteIsLedgered(entries, firstWrite));
46
+ }
47
+
48
+ export function appendPremortemWrite(entries, write) {
49
+ if (entries.length >= MAX_RECENT_WRITE_IDS) entries.shift();
50
+ entries.push(writeEntry(write));
51
+ }
52
+
53
+ function indexDirectory(statePath, laneDigest) {
54
+ if (!DIGEST_RE.test(laneDigest || "")) throw new Error("premortem write lane is invalid");
55
+ return join(dirname(dirname(statePath)), "delivery-premortem-write-index", laneDigest, "nodes");
56
+ }
57
+
58
+ export function deliveryPremortemWriteNodePath(statePath, laneDigest, nodeDigest) {
59
+ if (!DIGEST_RE.test(nodeDigest || "")) throw new Error("premortem write node digest is invalid");
60
+ return join(indexDirectory(statePath, laneDigest), `${nodeDigest}.json`);
61
+ }
62
+
63
+ function nodeMaterial(kind, depth, prefix, payload) {
64
+ return { schema: SCHEMA, kind, depth, prefix, ...payload, authority: AUTHORITY };
65
+ }
66
+
67
+ function sealedNode(kind, depth, prefix, payload) {
68
+ const material = nodeMaterial(kind, depth, prefix, payload);
69
+ return { ...material, digest: sha256(material) };
70
+ }
71
+
72
+ function nodeBytes(node) {
73
+ return Buffer.byteLength(`${JSON.stringify(node, null, 2)}\n`);
74
+ }
75
+
76
+ function validDepth(depth) {
77
+ return Number.isInteger(depth) && depth >= 0 && depth <= 64 && depth % 2 === 0;
78
+ }
79
+
80
+ function validNode(value, expectedDigest, expectedDepth, expectedPrefix) {
81
+ if (!value || typeof value !== "object" || Array.isArray(value)
82
+ || value.schema !== SCHEMA || value.authority !== AUTHORITY
83
+ || value.digest !== expectedDigest || value.depth !== expectedDepth
84
+ || value.prefix !== expectedPrefix || !validDepth(value.depth)
85
+ || typeof value.prefix !== "string" || value.prefix.length !== value.depth
86
+ || !/^[a-f0-9]*$/.test(value.prefix)) return false;
87
+ const keys = value.kind === "bucket" ? BUCKET_KEYS : value.kind === "branch" ? BRANCH_KEYS : [];
88
+ if (canonical(Object.keys(value).sort()) !== canonical(keys)) return false;
89
+ if (value.kind === "bucket") {
90
+ if (!Array.isArray(value.entries) || !value.entries.length
91
+ || !value.entries.every(validEntry)
92
+ || !value.entries.every((entry) => entry.idDigest.startsWith(value.prefix))
93
+ || new Set(value.entries.map((entry) => entry.idDigest)).size !== value.entries.length
94
+ || canonical(value.entries) !== canonical([...value.entries]
95
+ .sort((left, right) => left.idDigest.localeCompare(right.idDigest)))) return false;
96
+ } else {
97
+ if (value.depth >= 64 || !value.children || typeof value.children !== "object"
98
+ || Array.isArray(value.children)) return false;
99
+ const edges = Object.keys(value.children);
100
+ if (!edges.length || edges.length > 256 || !edges.every((edge) => EDGE_RE.test(edge)
101
+ && DIGEST_RE.test(value.children[edge] || ""))
102
+ || new Set(Object.values(value.children)).size !== edges.length) return false;
103
+ }
104
+ const material = { ...value };
105
+ delete material.digest;
106
+ return value.digest === sha256(material) && nodeBytes(value) <= MAX_NODE_BYTES;
107
+ }
108
+
109
+ async function readNode(statePath, laneDigest, nodeDigest, depth, prefix) {
110
+ const path = deliveryPremortemWriteNodePath(statePath, laneDigest, nodeDigest);
111
+ try {
112
+ const text = await readFile(path, "utf8");
113
+ if (Buffer.byteLength(text) > MAX_NODE_BYTES) {
114
+ throw mismatchError("premortem write index node exceeds 64 KiB");
115
+ }
116
+ let value;
117
+ try { value = JSON.parse(text); } catch { throw new Error("premortem write index node is not valid JSON"); }
118
+ if (!validNode(value, nodeDigest, depth, prefix)) {
119
+ throw mismatchError("premortem write index node failed integrity validation");
120
+ }
121
+ return { path, value };
122
+ } catch (error) {
123
+ if (error.code === "ENOENT") {
124
+ throw mismatchError("premortem write index references a missing node");
125
+ }
126
+ throw error;
127
+ }
128
+ }
129
+
130
+ async function saveNode(statePath, laneDigest, node, assertOwned) {
131
+ const directory = indexDirectory(statePath, laneDigest);
132
+ const path = deliveryPremortemWriteNodePath(statePath, laneDigest, node.digest);
133
+ await assertOwned();
134
+ await mkdir(directory, { recursive: true, mode: 0o700 });
135
+ try {
136
+ const existing = await readNode(statePath, laneDigest, node.digest, node.depth, node.prefix);
137
+ if (canonical(existing.value) !== canonical(node)) {
138
+ throw mismatchError("premortem write index node conflicts with its digest");
139
+ }
140
+ return node.digest;
141
+ } catch (error) {
142
+ if (error.code !== "AGENTSPINE_PREMORTEM_MISMATCH"
143
+ || !/references a missing node/.test(error.message)) throw error;
144
+ }
145
+ await writePremortemFile(path, node, assertOwned, MAX_NODE_BYTES);
146
+ return node.digest;
147
+ }
148
+
149
+ function bucket(depth, prefix, entries) {
150
+ return sealedNode("bucket", depth, prefix, { entries: [...entries]
151
+ .sort((left, right) => left.idDigest.localeCompare(right.idDigest)) });
152
+ }
153
+
154
+ function branch(depth, prefix, children) {
155
+ return sealedNode("branch", depth, prefix, { children: Object.fromEntries(
156
+ Object.entries(children).sort(([left], [right]) => left.localeCompare(right))) });
157
+ }
158
+
159
+ async function buildNode(statePath, laneDigest, depth, prefix, entries, assertOwned) {
160
+ const candidate = bucket(depth, prefix, entries);
161
+ if (nodeBytes(candidate) <= MAX_NODE_BYTES) {
162
+ await saveNode(statePath, laneDigest, candidate, assertOwned);
163
+ return candidate.digest;
164
+ }
165
+ if (depth >= 64) throw new Error("premortem write index bucket could not be split");
166
+ const grouped = new Map();
167
+ for (const entry of entries) {
168
+ const edge = entry.idDigest.slice(depth, depth + 2);
169
+ if (!grouped.has(edge)) grouped.set(edge, []);
170
+ grouped.get(edge).push(entry);
171
+ }
172
+ const children = {};
173
+ for (const [edge, group] of grouped) {
174
+ children[edge] = await buildNode(statePath, laneDigest, depth + 2,
175
+ `${prefix}${edge}`, group, assertOwned);
176
+ }
177
+ const candidateBranch = branch(depth, prefix, children);
178
+ await saveNode(statePath, laneDigest, candidateBranch, assertOwned);
179
+ return candidateBranch.digest;
180
+ }
181
+
182
+ async function insertNode(statePath, laneDigest, nodeDigest, depth, prefix, entry, assertOwned) {
183
+ if (!nodeDigest) return buildNode(statePath, laneDigest, depth, prefix, [entry], assertOwned);
184
+ const { value } = await readNode(statePath, laneDigest, nodeDigest, depth, prefix);
185
+ if (value.kind === "bucket") {
186
+ const prior = findPremortemWrite(value.entries, entry.idDigest);
187
+ if (prior) {
188
+ if (canonical(prior) !== canonical(entry)) {
189
+ throw mismatchError("premortem write index has conflicting input for an existing ID");
190
+ }
191
+ return nodeDigest;
192
+ }
193
+ return buildNode(statePath, laneDigest, depth, prefix, [...value.entries, entry], assertOwned);
194
+ }
195
+ const edge = entry.idDigest.slice(depth, depth + 2);
196
+ const next = await insertNode(statePath, laneDigest, value.children[edge] || null,
197
+ depth + 2, `${prefix}${edge}`, entry, assertOwned);
198
+ return saveNode(statePath, laneDigest,
199
+ branch(depth, prefix, { ...value.children, [edge]: next }), assertOwned);
200
+ }
201
+
202
+ export async function inspectPremortemWriteProof({ statePath, laneDigest, rootDigest, idDigest }) {
203
+ if (!DIGEST_RE.test(idDigest || "")) throw new Error("premortem write ID is invalid");
204
+ if (rootDigest === null) return { entry: null, paths: [] };
205
+ if (!DIGEST_RE.test(rootDigest || "")) throw mismatchError("premortem write index root is invalid");
206
+ let digest = rootDigest;
207
+ let depth = 0;
208
+ let prefix = "";
209
+ const paths = [];
210
+ while (digest) {
211
+ const loaded = await readNode(statePath, laneDigest, digest, depth, prefix);
212
+ paths.push(loaded.path);
213
+ if (loaded.value.kind === "bucket") {
214
+ return { entry: findPremortemWrite(loaded.value.entries, idDigest), paths };
215
+ }
216
+ const edge = idDigest.slice(depth, depth + 2);
217
+ digest = loaded.value.children[edge] || null;
218
+ prefix += edge;
219
+ depth += 2;
220
+ }
221
+ return { entry: null, paths };
222
+ }
223
+
224
+ export async function readPremortemWriteIndex(input) {
225
+ return (await inspectPremortemWriteProof(input)).entry;
226
+ }
227
+
228
+ export async function verifyPremortemWriteIndex({ statePath, state }) {
229
+ const writes = [state.firstWrite, state.lastWrite]
230
+ .filter((write, index, all) => write && all.findIndex((item) => item?.idDigest === write.idDigest) === index);
231
+ for (const write of writes) {
232
+ const entry = await readPremortemWriteIndex({ statePath, laneDigest: state.laneDigest,
233
+ rootDigest: state.writeIndexRoot, idDigest: write.idDigest });
234
+ if (!entry || entry.inputDigest !== write.inputDigest || entry.inputKnown !== write.inputKnown
235
+ || entry.writeDigest !== write.digest) {
236
+ throw mismatchError("premortem write index does not prove a bound write");
237
+ }
238
+ }
239
+ return true;
240
+ }
241
+
242
+ export async function inspectPremortemWriteIndexes({ statePath, state, maxNodes = 512 }) {
243
+ const directory = indexDirectory(statePath, state.laneDigest);
244
+ const result = { directory, paths: [], nodes: [], errors: [], truncations: [] };
245
+ if (!state.writeIndexRoot) return result;
246
+ const pending = [{ digest: state.writeIndexRoot, depth: 0, prefix: "" }];
247
+ const seen = new Set();
248
+ while (pending.length && seen.size < maxNodes) {
249
+ const current = pending.shift();
250
+ if (seen.has(current.digest)) continue;
251
+ seen.add(current.digest);
252
+ const path = deliveryPremortemWriteNodePath(statePath, state.laneDigest, current.digest);
253
+ result.paths.push(path);
254
+ try {
255
+ const loaded = await readNode(statePath, state.laneDigest, current.digest,
256
+ current.depth, current.prefix);
257
+ result.nodes.push(structuredClone(loaded.value));
258
+ if (loaded.value.kind === "branch") {
259
+ for (const [edge, digest] of Object.entries(loaded.value.children)) {
260
+ pending.push({ digest, depth: current.depth + 2, prefix: `${current.prefix}${edge}` });
261
+ }
262
+ }
263
+ } catch (error) {
264
+ result.errors.push({ path, reason: String(error.message).slice(0, 400),
265
+ code: typeof error.code === "string" ? error.code : null });
266
+ }
267
+ }
268
+ if (pending.length) result.truncations.push({ path: directory,
269
+ reason: `limited to ${maxNodes} reachable write-index nodes` });
270
+ try { await verifyPremortemWriteIndex({ statePath, state }); } catch (error) {
271
+ result.errors.push({ path: statePath, reason: String(error.message).slice(0, 400),
272
+ code: typeof error.code === "string" ? error.code : null });
273
+ }
274
+ return result;
275
+ }
276
+
277
+ export async function appendPremortemWriteIndex({ statePath, laneDigest, rootDigest,
278
+ write, assertOwned }) {
279
+ if (rootDigest !== null && !DIGEST_RE.test(rootDigest || "")) {
280
+ throw mismatchError("premortem write index root is invalid");
281
+ }
282
+ const entry = writeEntry(write);
283
+ const nextRoot = await insertNode(statePath, laneDigest, rootDigest, 0, "", entry, assertOwned);
284
+ return { rootDigest: nextRoot, entry };
285
+ }