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,499 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { mkdir, readFile, unlink } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { withOwnedFileLock } from "./owned-file-lock.js";
5
+ import { canonicalPath, projectStateDir } from "./paths.js";
6
+ import { finalizePremortemScope,
7
+ registerPremortemLaneIndex, removePremortemLaneIndex } from "./delivery-premortem-index.js";
8
+ import { writePremortemFile } from "./delivery-premortem-file.js";
9
+ import { appendPremortemWrite, appendPremortemWriteIndex, findPremortemWrite, readPremortemWriteIndex,
10
+ validPremortemWriteLedger, verifyPremortemWriteIndex } from "./delivery-premortem-write-ledger.js";
11
+ import { closedPremortemResult as closedResult, degradedPremortem as degraded,
12
+ premortemBlock as block, premortemBoundary as boundary } from "./delivery-premortem-results.js";
13
+ import { normalizePremortemBinding as normalizeBinding, premortemGoalBindingSummary as goalBindingSummary,
14
+ formatPremortemRequirementId as requirementId, parsePremortemRequirementId,
15
+ premortemLaneDigest as laneDigest,
16
+ premortemWriteIdentity, validPremortemStateBinding as validStateBinding } from "./delivery-premortem-binding.js";
17
+ import { createPremortemArtifact, createPremortemAttachment, createPremortemClosure,
18
+ reviewPremortemClosure, validPremortemArtifact, validPremortemClosure } from "./delivery-premortem-closure.js";
19
+ import { inspectActiveDeliveryPremortems,
20
+ verifyEmptyPremortemGoalScope } from "./delivery-premortem-inspection.js";
21
+ import { premortemGoalWriterScopeCheck } from "./delivery-premortem-session-guard.js";
22
+ import { recordPremortemRegistrationRejection } from "./delivery-premortem-rejection.js";
23
+ import { removePremortemStateFile,
24
+ resolvePremortemGoalScope } from "./delivery-premortem-recovery.js";
25
+ import { canonicalPremortem as canonical, premortemSha256 as sha256, premortemTime as at,
26
+ premortemMismatchError as mismatchError, sealPremortem as sealed, validPremortemSeal as validSeal,
27
+ validPremortemTime as validTime } from "./delivery-premortem-codec.js";
28
+ export const DELIVERY_PREMORTEM_SCHEMA = "agentspine.delivery-premortem/v1";
29
+ const REQUIREMENT_SCHEMA = "agentspine.delivery-premortem-requirement/v1";
30
+ const EVENT_SCHEMA = "agentspine.delivery-premortem-event/v1";
31
+ const AUTHORITY = "context-only";
32
+ const MAX_BYTES = 64 * 1024;
33
+ const MAX_EVENTS = 48;
34
+ const MAX_FILES = 256;
35
+ const DIGEST_RE = /^[a-f0-9]{64}$/;
36
+ export const PREMORTEM_REQUIREMENT_TEXT = [
37
+ "Before the first Write/Edit/apply_patch or recognized common shell-mediated mutation, register exactly three premortem items.",
38
+ "Use categories baseline-environment, contract-tests, and delivery-path exactly once.",
39
+ "Each failure must start `this delivery fails because ` and include one concrete check.",
40
+ "Registration returns the immutable checkId used for closure.",
41
+ "Premortem content is context only and grants no permissions or tool access.",
42
+ "Close a written delivery with `Premortem closure sha256 <64hex>` and one line per category:",
43
+ "Bind it with `Premortem latest write sha256 <64hex>` from the latest write receipt.",
44
+ "- <category> <checkId>: PASS — <nonempty result>"
45
+ ].join("\n");
46
+ function requirementFor(binding, digest, now) {
47
+ const generationDigest = sha256({ laneDigest: digest, nonce: randomUUID() });
48
+ return sealed({ schema: REQUIREMENT_SCHEMA,
49
+ requirementId: requirementId(digest, generationDigest), laneDigest: digest,
50
+ generationDigest, binding, createdAt: at(now), authority: AUTHORITY });
51
+ }
52
+ function stateMaterial(state) {
53
+ const material = { ...state };
54
+ delete material.integrityDigest;
55
+ return material;
56
+ }
57
+ function sealState(state) {
58
+ state.integrityDigest = sha256(stateMaterial(state));
59
+ return state;
60
+ }
61
+ function emptyState(binding, digest) {
62
+ return sealState({ schema: DELIVERY_PREMORTEM_SCHEMA, laneDigest: digest, binding, revision: 0,
63
+ requirement: null, artifact: null, firstWrite: null, lastWrite: null, late: false, conflict: null,
64
+ closure: null, writeIndexRoot: null, writeLedger: [], events: [], authority: AUTHORITY,
65
+ integrityDigest: null });
66
+ }
67
+ function validRequirement(value, state) {
68
+ let parsed = null;
69
+ try { parsed = parsePremortemRequirementId(value?.requirementId); } catch {}
70
+ return value && value.schema === REQUIREMENT_SCHEMA && value.authority === AUTHORITY
71
+ && value.laneDigest === state.laneDigest && parsed?.laneDigest === state.laneDigest
72
+ && parsed?.generationDigest === value.generationDigest
73
+ && DIGEST_RE.test(value.generationDigest || "")
74
+ && canonical(value.binding) === canonical(state.binding)
75
+ && validTime(value.createdAt) && validSeal(value);
76
+ }
77
+ function validWrite(value, state) {
78
+ return value && value.schema === "agentspine.delivery-premortem-write/v1" && value.authority === AUTHORITY
79
+ && value.laneDigest === state.laneDigest && value.requirementId === state.requirement?.requirementId
80
+ && new Set(["intent", "post"]).has(value.phase)
81
+ && DIGEST_RE.test(value.idDigest || "") && DIGEST_RE.test(value.inputDigest || "")
82
+ && (value.inputKnown === undefined || typeof value.inputKnown === "boolean")
83
+ && validTime(value.recordedAt) && validSeal(value);
84
+ }
85
+ function validEvents(events, revision) {
86
+ const types = new Set(["requirement-prepared", "artifact-recorded", "artifact-recorded-late",
87
+ "artifact-conflict", "write-recorded", "write-conflict", "closure-invalidated",
88
+ "premortem-closed", "premortem-consumed"]);
89
+ return events.every((event, index) => event.schema === EVENT_SCHEMA && event.authority === AUTHORITY
90
+ && types.has(event.type) && validTime(event.at) && Number.isInteger(event.sequence)
91
+ && event.sequence > 0 && event.sequence <= revision && (!index || event.sequence > events[index - 1].sequence)
92
+ && DIGEST_RE.test(event.payloadDigest || "")
93
+ && (event.type !== "write-recorded" || event.writeIdDigest === undefined
94
+ || (DIGEST_RE.test(event.writeIdDigest) && DIGEST_RE.test(event.inputDigest)
95
+ && typeof event.inputKnown === "boolean" && DIGEST_RE.test(event.writeDigest)))
96
+ && validSeal(event));
97
+ }
98
+ function validateState(state, expectedDigest) {
99
+ if (!state || state.schema !== DELIVERY_PREMORTEM_SCHEMA || state.laneDigest !== expectedDigest
100
+ || !validStateBinding(state, expectedDigest) || state.authority !== AUTHORITY
101
+ || !Number.isInteger(state.revision) || state.revision < 0
102
+ || !Array.isArray(state.events) || state.events.length > MAX_EVENTS
103
+ || !validEvents(state.events, state.revision)
104
+ || (state.requirement !== null && !validRequirement(state.requirement, state))
105
+ || (state.artifact !== null && !validPremortemArtifact(state.artifact, state))
106
+ || Boolean(state.artifact) > Boolean(state.requirement) || typeof state.late !== "boolean"
107
+ || (state.firstWrite !== null && !validWrite(state.firstWrite, state))
108
+ || (state.lastWrite !== null && !validWrite(state.lastWrite, state))
109
+ || Boolean(state.firstWrite) !== Boolean(state.lastWrite)
110
+ || (state.firstWrite ? !DIGEST_RE.test(state.writeIndexRoot || "") : state.writeIndexRoot !== null)
111
+ || !validPremortemWriteLedger(state.writeLedger, state.firstWrite, state.lastWrite)
112
+ || (state.closure !== null && !validPremortemClosure(state.closure, state))
113
+ || (state.conflict !== null && !DIGEST_RE.test(state.conflict || ""))
114
+ || !DIGEST_RE.test(state.integrityDigest || "")
115
+ || state.integrityDigest !== sha256(stateMaterial(state))) {
116
+ throw mismatchError("delivery premortem state failed integrity validation");
117
+ }
118
+ return state;
119
+ }
120
+ async function directoryFor(root) {
121
+ const directory = join(await projectStateDir(await canonicalPath(root)), "delivery-premortem");
122
+ await mkdir(directory, { recursive: true, mode: 0o700 });
123
+ return directory;
124
+ }
125
+ async function pathsFor(root, digest) {
126
+ const directory = await directoryFor(root);
127
+ const path = join(directory, `${digest}.json`);
128
+ return { directory, path, lockPath: `${path}.lock` };
129
+ }
130
+ async function readState(path, digest) {
131
+ try {
132
+ const text = await readFile(path, "utf8");
133
+ if (Buffer.byteLength(text) > MAX_BYTES) throw mismatchError("delivery premortem state exceeds 64 KiB");
134
+ return validateState(JSON.parse(text), digest);
135
+ } catch (error) {
136
+ if (error.code === "ENOENT") return null;
137
+ if (error instanceof SyntaxError) throw new Error("delivery premortem state is not valid JSON");
138
+ throw error;
139
+ }
140
+ }
141
+ async function saveState(path, state, assertOwned, scopeCheck = null) {
142
+ sealState(state);
143
+ await registerPremortemLaneIndex({ statePath: path, state, scopeCheck,
144
+ commit: ({ assertOwned: assertScopeOwned } = {}) =>
145
+ writePremortemFile(path, state, async () => {
146
+ await assertOwned();
147
+ await assertScopeOwned?.();
148
+ }, MAX_BYTES) });
149
+ }
150
+ function appendEvent(state, type, payload, now, details = {}) {
151
+ const event = sealed({ ...details, schema: EVENT_SCHEMA, type, at: at(now), sequence: state.revision + 1,
152
+ payloadDigest: sha256(payload), authority: AUTHORITY });
153
+ state.events.push(event);
154
+ state.events = state.events.slice(-MAX_EVENTS);
155
+ state.revision += 1;
156
+ return event;
157
+ }
158
+ const parseRequirement = (id) => parsePremortemRequirementId(id).laneDigest;
159
+ export async function inspectPremortemState({ root, binding: rawBinding = null,
160
+ requirementId: id = null }) {
161
+ try {
162
+ if (Boolean(rawBinding) === Boolean(id)) throw new Error("inspect premortem requires one exact selector");
163
+ const binding = rawBinding ? normalizeBinding(rawBinding) : null;
164
+ const digest = binding ? laneDigest(binding) : parseRequirement(id);
165
+ const state = await readState((await pathsFor(root, digest)).path, digest);
166
+ if (!state) return { status: "absent", blocked: false, laneDigest: digest,
167
+ requirementId: id };
168
+ if (id && state.requirement?.requirementId !== id) {
169
+ return block("stale", `AgentSpine cannot find current premortem requirement ${id}.`,
170
+ { requirementId: id });
171
+ }
172
+ return { status: state.firstWrite ? "written" : "read-only", blocked: false,
173
+ laneDigest: digest, requirementId: state.requirement?.requirementId || null,
174
+ requirement: structuredClone(state.requirement),
175
+ binding: structuredClone(state.binding), hasWrite: Boolean(state.firstWrite),
176
+ closed: Boolean(state.closure),
177
+ completionSource: state.closure?.completionSource || null,
178
+ consumed: state.events.some((event) => event.type === "premortem-consumed"),
179
+ conflicted: Boolean(state.conflict), artifactDigest: state.artifact?.digest || null };
180
+ } catch (error) {
181
+ return boundary(error);
182
+ }
183
+ }
184
+ export function premortemRequirementText(requirement) {
185
+ const id = typeof requirement === "string" ? requirement : requirement?.requirementId;
186
+ return `${PREMORTEM_REQUIREMENT_TEXT}\nRequirement: ${id || "<unavailable; retry registration>"}`;
187
+ }
188
+ export async function preparePremortemRequirement({ root, binding: rawBinding, now = new Date() }) {
189
+ try {
190
+ const binding = normalizeBinding(rawBinding);
191
+ const digest = laneDigest(binding);
192
+ const paths = await pathsFor(root, digest);
193
+ return await withOwnedFileLock(paths.lockPath, async ({ assertOwned }) => {
194
+ const state = await readState(paths.path, digest) || emptyState(binding, digest);
195
+ if (!state.requirement) {
196
+ state.requirement = requirementFor(binding, digest, now);
197
+ appendEvent(state, "requirement-prepared", { requirementId: state.requirement.requirementId }, now);
198
+ await saveState(paths.path, state, assertOwned);
199
+ }
200
+ return { status: "required", blocked: false, requirementId: state.requirement.requirementId,
201
+ requirement: structuredClone(state.requirement), laneDigest: digest, path: paths.path };
202
+ });
203
+ } catch (error) {
204
+ return boundary(error);
205
+ }
206
+ }
207
+ export async function recordDeliveryPremortem({ root, requirementId: id, items, now = new Date() }) {
208
+ try {
209
+ const digest = parseRequirement(id);
210
+ const paths = await pathsFor(root, digest);
211
+ return await withOwnedFileLock(paths.lockPath, async ({ assertOwned }) => {
212
+ const state = await readState(paths.path, digest);
213
+ if (!state?.requirement || state.requirement.requirementId !== id) {
214
+ return block("stale", `AgentSpine cannot find current premortem requirement ${id}.`, { requirementId: id });
215
+ }
216
+ const proposed = createPremortemArtifact({ requirement: state.requirement,
217
+ items, recordedAt: at(now) });
218
+ if (state.conflict) return block("conflict", `AgentSpine premortem ${id} has conflicting registrations.`,
219
+ { requirementId: id, digest: state.artifact?.digest || null });
220
+ if (state.artifact) {
221
+ if (canonical(state.artifact.items) !== canonical(proposed.items)) {
222
+ const rejection = await recordPremortemRegistrationRejection({ root, state, proposed, now });
223
+ return block("conflict", `AgentSpine premortem ${id} conflicts with its first registration.`,
224
+ { requirementId: id, digest: state.artifact.digest, rejection });
225
+ }
226
+ return { status: state.late ? "late" : "duplicate", blocked: state.late,
227
+ reason: state.late ? `AgentSpine premortem ${id} was recorded after the first write.` : undefined,
228
+ requirementId: id, artifact: structuredClone(state.artifact), digest: state.artifact.digest };
229
+ }
230
+ state.artifact = proposed;
231
+ state.late = Boolean(state.firstWrite);
232
+ appendEvent(state, state.late ? "artifact-recorded-late" : "artifact-recorded",
233
+ { artifactDigest: proposed.digest }, now);
234
+ await saveState(paths.path, state, assertOwned);
235
+ if (state.late) return block("late", `AgentSpine premortem ${id} was recorded after the first write.`,
236
+ { requirementId: id, artifact: structuredClone(proposed), digest: proposed.digest });
237
+ return { status: "recorded", blocked: false, requirementId: id,
238
+ artifact: structuredClone(proposed), digest: proposed.digest };
239
+ });
240
+ } catch (error) {
241
+ if (error.code === "AGENTSPINE_PREMORTEM_SECRET") {
242
+ return block("unsafe", error.message, { requirementId: id });
243
+ }
244
+ return boundary(error);
245
+ }
246
+ }
247
+ function premortemProblem(state, id) {
248
+ if (!state?.requirement || !state.artifact) {
249
+ return block("missing", `AgentSpine is missing premortem ${id} before the first write.`, { requirementId: id });
250
+ }
251
+ if (state.conflict) return block("conflict", `AgentSpine premortem ${id} has conflicting registrations.`, { requirementId: id });
252
+ if (state.late) return block("late", `AgentSpine premortem ${id} was recorded after the first write.`, { requirementId: id });
253
+ if (state.events.some((event) => event.type === "premortem-consumed")) {
254
+ return block("finalized", `AgentSpine premortem ${id} was already consumed.`, { requirementId: id });
255
+ }
256
+ return null;
257
+ }
258
+ export async function verifyPremortemBeforeWrite({ root, binding: rawBinding }) {
259
+ try {
260
+ const binding = normalizeBinding(rawBinding);
261
+ const digest = laneDigest(binding);
262
+ const state = await readState((await pathsFor(root, digest)).path, digest);
263
+ const problem = premortemProblem(state, state?.requirement?.requirementId || requirementId(digest));
264
+ return problem || { status: "verified", blocked: false, requirementId: state.requirement.requirementId,
265
+ artifact: structuredClone(state.artifact), digest: state.artifact.digest };
266
+ } catch (error) {
267
+ return boundary(error);
268
+ }
269
+ }
270
+ export async function recordPremortemWrite({ root, binding: rawBinding, input, phase = "post", now = new Date() }) {
271
+ try {
272
+ if (!new Set(["intent", "post"]).has(phase)) throw new Error("premortem write phase is invalid");
273
+ const binding = normalizeBinding(rawBinding);
274
+ const digest = laneDigest(binding);
275
+ const paths = await pathsFor(root, digest);
276
+ return await withOwnedFileLock(paths.lockPath, async ({ assertOwned }) => {
277
+ const state = await readState(paths.path, digest) || emptyState(binding, digest);
278
+ if (state.events.some((event) => event.type === "premortem-consumed")) {
279
+ return block("finalized", "The goal-step premortem was already consumed; later writes are blocked.");
280
+ }
281
+ const write = premortemWriteIdentity(input, phase);
282
+ if (phase === "intent") {
283
+ const problem = premortemProblem(state,
284
+ state.requirement?.requirementId || requirementId(digest));
285
+ if (problem) return problem;
286
+ }
287
+ if (!state.requirement) {
288
+ state.requirement = requirementFor(binding, digest, now);
289
+ appendEvent(state, "requirement-prepared", { requirementId: state.requirement.requirementId }, now);
290
+ }
291
+ const recentWrite = findPremortemWrite(state.writeLedger, write.idDigest);
292
+ const storedWrite = await readPremortemWriteIndex({ statePath: paths.path,
293
+ laneDigest: digest, rootDigest: state.writeIndexRoot, idDigest: write.idDigest });
294
+ if (storedWrite && recentWrite && ["inputDigest", "inputKnown", "writeDigest"]
295
+ .some((key) => storedWrite[key] !== recentWrite[key])) {
296
+ throw mismatchError("premortem write receipt conflicts with recent state");
297
+ }
298
+ if (recentWrite && !storedWrite) throw mismatchError("premortem write index lost a recent write");
299
+ const priorWrite = storedWrite || recentWrite;
300
+ if (priorWrite && write.inputKnown && (priorWrite.inputKnown === false
301
+ || priorWrite.inputDigest !== write.inputDigest)) {
302
+ state.conflict = sha256({ writeIdDigest: write.idDigest,
303
+ priorInputDigest: priorWrite.inputDigest, inputDigest: write.inputDigest });
304
+ appendEvent(state, "write-conflict", { conflict: state.conflict }, now);
305
+ await saveState(paths.path, state, assertOwned);
306
+ return block("conflict", "A repeated write receipt has different tool input.");
307
+ }
308
+ if (priorWrite) {
309
+ return { status: "duplicate", blocked: false, writeDigest: priorWrite.writeDigest };
310
+ }
311
+ const boundWrite = sealed({ ...write, laneDigest: digest,
312
+ requirementId: state.requirement.requirementId, recordedAt: at(now) });
313
+ state.writeIndexRoot = (await appendPremortemWriteIndex({ statePath: paths.path,
314
+ laneDigest: digest, rootDigest: state.writeIndexRoot, write: boundWrite, assertOwned })).rootDigest;
315
+ appendPremortemWrite(state.writeLedger, boundWrite);
316
+ if (!state.firstWrite) state.firstWrite = boundWrite;
317
+ state.lastWrite = boundWrite;
318
+ appendEvent(state, "write-recorded", { writeIdDigest: write.idDigest,
319
+ inputDigest: write.inputDigest }, now, { writeIdDigest: write.idDigest,
320
+ inputDigest: write.inputDigest, inputKnown: write.inputKnown, writeDigest: boundWrite.digest });
321
+ if (state.closure) {
322
+ appendEvent(state, "closure-invalidated", { closureDigest: state.closure.digest,
323
+ lastWriteDigest: boundWrite.digest }, now);
324
+ state.closure = null;
325
+ }
326
+ await saveState(paths.path, state, assertOwned, premortemGoalWriterScopeCheck({
327
+ stateDirectory: paths.directory, binding, laneDigest: digest, readState }));
328
+ return { status: "write-recorded", blocked: false, writeDigest: boundWrite.digest,
329
+ premortemDigest: state.artifact?.digest || null };
330
+ });
331
+ } catch (error) {
332
+ return boundary(error);
333
+ }
334
+ }
335
+ export async function recordPremortemWriteIntent(input) {
336
+ const result = await recordPremortemWrite({ ...input, phase: "intent" });
337
+ return result.status === "write-recorded" ? { ...result, status: "write-intent-recorded" } : result;
338
+ }
339
+ export async function verifyPremortemStop({ root, binding: rawBinding, message, now = new Date(),
340
+ testStateDigest = null, allowStoredCompletion = false }) {
341
+ try {
342
+ const binding = normalizeBinding(rawBinding);
343
+ const digest = laneDigest(binding);
344
+ const paths = await pathsFor(root, digest);
345
+ return await withOwnedFileLock(paths.lockPath, async ({ assertOwned }) => {
346
+ const state = await readState(paths.path, digest);
347
+ if (!state) return { status: "no-write", blocked: false };
348
+ if (!state.firstWrite) {
349
+ const status = state.binding.goalId ? "read-only" : "no-write";
350
+ const removed = await removePremortemLaneIndex({ statePath: paths.path, state,
351
+ commit: ({ assertOwned: assertIndexOwned }) =>
352
+ removePremortemStateFile(paths.path, assertOwned, assertIndexOwned) });
353
+ if (removed.status === "degraded") return { ...removed, blocked: false };
354
+ if (removed.blocked) return removed;
355
+ return { status, blocked: false };
356
+ }
357
+ const id = state.requirement?.requirementId || requirementId(digest);
358
+ const problem = premortemProblem(state, id);
359
+ if (problem) return problem;
360
+ await verifyPremortemWriteIndex({ statePath: paths.path, state });
361
+ if (allowStoredCompletion && state.closure?.completionSource === "mcp") {
362
+ return closedResult(state, id);
363
+ }
364
+ const reviewed = reviewPremortemClosure(message, state.artifact, state.lastWrite.digest);
365
+ if (reviewed.missing.length) return block("unchecked",
366
+ `AgentSpine premortem closure is incomplete: ${reviewed.missing.join(", ")}.`,
367
+ { requirementId: id, unchecked: reviewed.missing });
368
+ const proposed = createPremortemClosure({ requirementId: id,
369
+ artifactDigest: state.artifact.digest, lastWriteDigest: state.lastWrite.digest,
370
+ checks: reviewed.checks, closedAt: at(now), testStateDigest });
371
+ if (state.closure && canonical(state.closure.checks) !== canonical(proposed.checks)) {
372
+ return block("conflict", `AgentSpine premortem ${id} has conflicting closure results.`, { requirementId: id });
373
+ }
374
+ if (!state.closure) {
375
+ state.closure = proposed;
376
+ appendEvent(state, "premortem-closed", { closureDigest: proposed.digest }, now);
377
+ await saveState(paths.path, state, assertOwned);
378
+ }
379
+ return closedResult(state, id);
380
+ });
381
+ } catch (error) {
382
+ return boundary(error);
383
+ }
384
+ }
385
+ export async function inspectDeliveryPremortems(root) {
386
+ try {
387
+ return inspectActiveDeliveryPremortems({ stateDirectory: await directoryFor(root),
388
+ readState, maxFiles: MAX_FILES });
389
+ } catch (error) {
390
+ return { states: [], paths: [], errors: [{ path: String(root),
391
+ reason: String(error.message).slice(0, 400) }], truncations: [], directory: null };
392
+ }
393
+ }
394
+ async function removeReadOnlyGoalLane(root, state) {
395
+ const paths = await pathsFor(root, state.laneDigest);
396
+ return withOwnedFileLock(paths.lockPath, async ({ assertOwned }) => {
397
+ const current = await readState(paths.path, state.laneDigest);
398
+ if (!current || current.firstWrite) return { removed: !current, state: current };
399
+ const removed = await removePremortemLaneIndex({ statePath: paths.path, state: current,
400
+ commit: ({ assertOwned: assertIndexOwned }) =>
401
+ removePremortemStateFile(paths.path, assertOwned, assertIndexOwned) });
402
+ if (removed.status === "degraded") throw new Error(removed.reason);
403
+ if (removed.blocked) throw mismatchError(removed.reason);
404
+ return { removed: true, state: null };
405
+ });
406
+ }
407
+ export async function closedPremortemForGoal({ root, goalId, goalStepId, queueId = null,
408
+ gatewayAttempt = null }) {
409
+ try {
410
+ if (gatewayAttempt !== null && (!Number.isInteger(gatewayAttempt) || gatewayAttempt < 1)) {
411
+ throw new Error("premortem gatewayAttempt must be a positive integer");
412
+ }
413
+ const stateDirectory = await directoryFor(root);
414
+ const indexed = await resolvePremortemGoalScope({ stateDirectory,
415
+ expected: { goalId, goalStepId, queueId, gatewayAttempt }, readState,
416
+ inspectStates: () => inspectDeliveryPremortems(root, { includeHistory: false }),
417
+ removeReadOnlyState: (state) => removeReadOnlyGoalLane(root, state) });
418
+ if (indexed.blocked) return { ...indexed, attachment: null };
419
+ if (indexed.status === "degraded") return { ...indexed, blocked: false, attachment: null };
420
+ if (indexed.status === "finalized" && indexed.finalization?.status === "read-only") return {
421
+ status: "read-only-finalized", blocked: false, attachment: null,
422
+ finalization: structuredClone(indexed.finalization) };
423
+ if (indexed.status === "unavailable") return { ...indexed, blocked: false, attachment: null };
424
+ const states = indexed.states;
425
+ const invalid = states.find((state) => state.conflict || state.late);
426
+ if (invalid) return block(invalid.conflict ? "conflict" : "late",
427
+ "The exact goal-step premortem is conflicted or late.", { attachment: null });
428
+ const written = states.filter((state) => state.firstWrite);
429
+ for (const state of states.filter((entry) => !entry.firstWrite)) {
430
+ const cleaned = await removeReadOnlyGoalLane(root, state);
431
+ if (cleaned.state?.firstWrite) written.push(cleaned.state);
432
+ }
433
+ if (!written.length) return { status: "read-only", blocked: false, attachment: null,
434
+ bindings: states.map((state) => goalBindingSummary(state)) };
435
+ if (written.length > 1) return block("conflict", "Multiple premortems use the same goal-step attempt.", { attachment: null });
436
+ const open = written.filter((state) => !state.closure);
437
+ if (open.length) return block("missing", "A written goal-step premortem has no valid closure.", { attachment: null });
438
+ const target = written[0];
439
+ const paths = await pathsFor(root, target.laneDigest);
440
+ return await withOwnedFileLock(paths.lockPath, async ({ assertOwned }) => {
441
+ const current = await readState(paths.path, target.laneDigest);
442
+ const problem = premortemProblem(current,
443
+ current?.requirement?.requirementId || requirementId(target.laneDigest));
444
+ if ((problem && problem.status !== "finalized") || !current.firstWrite || !current.closure) return problem
445
+ || block("mismatch", "The goal-step premortem changed before consumption.", { attachment: null });
446
+ await verifyPremortemWriteIndex({ statePath: paths.path, state: current });
447
+ const attachment = createPremortemAttachment(current);
448
+ const summary = goalBindingSummary(current);
449
+ const payload = { attachmentDigest: attachment.attachmentDigest };
450
+ const consumed = current.events.find((event) => event.type === "premortem-consumed");
451
+ if (consumed && consumed.payloadDigest !== sha256(payload)) {
452
+ return block("conflict", "The goal-step premortem has conflicting consumption.", { attachment: null });
453
+ }
454
+ if (!consumed) {
455
+ appendEvent(current, "premortem-consumed", payload, new Date());
456
+ sealState(current);
457
+ }
458
+ const finalized = await finalizePremortemScope({ stateDirectory, goalId,
459
+ goalStepId, queueId, gatewayAttempt, status: "closed",
460
+ laneDigest: target.laneDigest, attachmentDigest: attachment.attachmentDigest,
461
+ context: current.binding, bindingSummaryDigests: [summary.digest],
462
+ commit: consumed ? null : ({ assertOwned: assertScopeOwned }) =>
463
+ writePremortemFile(paths.path, current, async () => {
464
+ await assertOwned();
465
+ await assertScopeOwned();
466
+ }, MAX_BYTES) });
467
+ if (finalized.blocked || finalized.status === "degraded") {
468
+ return { ...finalized, attachment: null };
469
+ }
470
+ return { status: "closed", blocked: false, attachment,
471
+ finalization: finalized.finalization };
472
+ });
473
+ } catch (error) {
474
+ return { ...boundary(error), attachment: null };
475
+ }
476
+ }
477
+ export async function finalizeReadOnlyPremortemForGoal({ root, goalId, goalStepId,
478
+ queueId = null, gatewayAttempt = null, dispositionDigest, context,
479
+ bindingSummaryDigests = [] }) {
480
+ try {
481
+ if (!DIGEST_RE.test(dispositionDigest || "")) throw new Error("premortem disposition digest is invalid");
482
+ const stateDirectory = await directoryFor(root);
483
+ return await finalizePremortemScope({ stateDirectory, goalId, goalStepId,
484
+ queueId, gatewayAttempt, status: "read-only", attachmentDigest: dispositionDigest,
485
+ context, bindingSummaryDigests,
486
+ verifyEmpty: () => verifyEmptyPremortemGoalScope({ stateDirectory, readState,
487
+ maxFiles: MAX_FILES, expected: { goalId, goalStepId, queueId, gatewayAttempt } }) });
488
+ } catch (error) {
489
+ return boundary(error);
490
+ }
491
+ }
492
+ export async function deliveryPremortemPath({ root, binding: rawBinding }) {
493
+ try {
494
+ const binding = normalizeBinding(rawBinding);
495
+ return (await pathsFor(root, laneDigest(binding))).path;
496
+ } catch {
497
+ return null;
498
+ }
499
+ }
@@ -0,0 +1,115 @@
1
+ function delimiterWord(line, start) {
2
+ let value = "";
3
+ let quote = null;
4
+ let escaped = false;
5
+ let cursor = start;
6
+ for (; cursor < line.length; cursor += 1) {
7
+ const character = line[cursor];
8
+ if (escaped) { value += character; escaped = false; continue; }
9
+ if (character === "\\" && quote !== "'") { escaped = true; continue; }
10
+ if (quote) {
11
+ if (character === quote) quote = null;
12
+ else value += character;
13
+ continue;
14
+ }
15
+ if (character === "'" || character === '"') { quote = character; continue; }
16
+ if (/\s/.test(character) || /[;&|<>()]/.test(character)) break;
17
+ value += character;
18
+ }
19
+ return !quote && value ? { delimiter: value, end: cursor } : null;
20
+ }
21
+
22
+ function lineDeclarations(line) {
23
+ const declarations = [];
24
+ let quote = null;
25
+ let escaped = false;
26
+ let arithmeticDepth = 0;
27
+ let commandStart = 0;
28
+ for (let index = 0; index < line.length; index += 1) {
29
+ const character = line[index];
30
+ const next = line[index + 1];
31
+ if (escaped) { escaped = false; continue; }
32
+ if (character === "\\" && quote !== "'") { escaped = true; continue; }
33
+ if (quote) {
34
+ if (character === quote) quote = null;
35
+ continue;
36
+ }
37
+ if (character === "'" || character === '"') { quote = character; continue; }
38
+ if (character === "#" && (index === 0 || /[\s;&|()]/.test(line[index - 1]))) break;
39
+ if (character === "$" && next === "(" && line[index + 2] === "(") {
40
+ arithmeticDepth += 1; index += 2; continue;
41
+ }
42
+ if (character === "(" && next === "(") { arithmeticDepth += 1; index += 1; continue; }
43
+ if (arithmeticDepth && character === ")" && next === ")") {
44
+ arithmeticDepth -= 1; index += 1; continue;
45
+ }
46
+ if (!arithmeticDepth && /[;&|]/.test(character)) {
47
+ commandStart = index + 1;
48
+ continue;
49
+ }
50
+ if (arithmeticDepth || character !== "<" || next !== "<") continue;
51
+ if (line[index + 2] === "<") { index += 2; continue; }
52
+ let cursor = index + 2;
53
+ let stripTabs = false;
54
+ if (line[cursor] === "-") { stripTabs = true; cursor += 1; }
55
+ while (line[cursor] === " " || line[cursor] === "\t") cursor += 1;
56
+ const word = delimiterWord(line, cursor);
57
+ if (!word) continue;
58
+ declarations.push({
59
+ delimiter: word.delimiter,
60
+ stripTabs,
61
+ opener: line.slice(commandStart, index)
62
+ });
63
+ index = word.end - 1;
64
+ }
65
+ return declarations;
66
+ }
67
+
68
+ function nextLine(command, start) {
69
+ const newline = command.indexOf("\n", start);
70
+ return { end: newline < 0 ? command.length : newline, next: newline < 0 ? command.length : newline + 1 };
71
+ }
72
+
73
+ function maskLine(output, start, end) {
74
+ for (let index = start; index < end; index += 1) output[index] = " ";
75
+ }
76
+
77
+ export function maskHeredocBodies(command) {
78
+ if (!command.includes("<<") || !command.includes("\n")) return { command, bodies: [] };
79
+ const output = [...command];
80
+ const bodies = [];
81
+ let cursor = 0;
82
+ while (cursor < command.length) {
83
+ const opener = nextLine(command, cursor);
84
+ const declarations = lineDeclarations(command.slice(cursor, opener.end));
85
+ if (!declarations.length) { cursor = opener.next; continue; }
86
+ let bodyStart = opener.next;
87
+ for (const declaration of declarations) {
88
+ let found = false;
89
+ const contentStart = bodyStart;
90
+ let contentEnd = bodyStart;
91
+ while (bodyStart <= command.length) {
92
+ const lineStart = bodyStart;
93
+ const bodyLine = nextLine(command, bodyStart);
94
+ const raw = command.slice(bodyStart, bodyLine.end).replace(/\r$/, "");
95
+ const candidate = declaration.stripTabs ? raw.replace(/^\t+/, "") : raw;
96
+ maskLine(output, bodyStart, bodyLine.end);
97
+ bodyStart = bodyLine.next;
98
+ if (candidate === declaration.delimiter) {
99
+ found = true;
100
+ contentEnd = lineStart;
101
+ break;
102
+ }
103
+ if (bodyLine.end === command.length) break;
104
+ }
105
+ if (!found) return { command: output.join(""), bodies };
106
+ bodies.push({
107
+ text: command.slice(contentStart, contentEnd),
108
+ index: contentStart,
109
+ opener: declaration.opener
110
+ });
111
+ }
112
+ cursor = bodyStart;
113
+ }
114
+ return { command: output.join(""), bodies };
115
+ }