@astrosheep/keiyaku 4.5.11 → 4.5.13

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 (272) hide show
  1. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +135 -377
  2. package/build/integrations/pi/keiyaku.ts +11 -116
  3. package/build/src/akuma/akuma-errors.d.ts +1 -0
  4. package/build/src/akuma/akuma-errors.js +1 -0
  5. package/build/src/akuma/akuma-handle.d.ts +1 -0
  6. package/build/src/akuma/akuma-handle.js +6 -7
  7. package/build/src/akuma/akuma-product.d.ts +6 -8
  8. package/build/src/akuma/akuma-product.js +141 -57
  9. package/build/src/akuma/akuma.d.ts +5 -7
  10. package/build/src/akuma/akuma.js +1 -22
  11. package/build/src/akuma/archetype.d.ts +6 -5
  12. package/build/src/akuma/archetype.js +137 -38
  13. package/build/src/akuma/body.d.ts +5 -5
  14. package/build/src/akuma/body.js +223 -63
  15. package/build/src/akuma/call-input.d.ts +4 -0
  16. package/build/src/akuma/call-input.js +21 -0
  17. package/build/src/akuma/call-request.d.ts +5 -8
  18. package/build/src/akuma/call-request.js +28 -73
  19. package/build/src/akuma/fleet-execution.d.ts +26 -0
  20. package/build/src/akuma/fleet-execution.js +109 -0
  21. package/build/src/{library/fleet-result.d.ts → akuma/fleet-observation.d.ts} +13 -57
  22. package/build/src/{library/fleet-result.js → akuma/fleet-observation.js} +4 -25
  23. package/build/src/akuma/fleet-request.d.ts +122 -0
  24. package/build/src/akuma/fleet-request.js +168 -0
  25. package/build/src/akuma/heart/facts.d.ts +23 -0
  26. package/build/src/akuma/heart/facts.js +7 -0
  27. package/build/src/akuma/heart/index.d.ts +43 -4
  28. package/build/src/akuma/heart/index.js +107 -7
  29. package/build/src/akuma/heart/request-authority.d.ts +2 -0
  30. package/build/src/akuma/heart/request-authority.js +28 -3
  31. package/build/src/akuma/heart/request-rows.d.ts +2 -0
  32. package/build/src/akuma/heart/request-rows.js +24 -5
  33. package/build/src/akuma/heart/rows.d.ts +4 -1
  34. package/build/src/akuma/heart/rows.js +46 -19
  35. package/build/src/akuma/heart/schema.d.ts +1 -1
  36. package/build/src/akuma/heart/schema.js +28 -6
  37. package/build/src/akuma/heart/storage.d.ts +1 -1
  38. package/build/src/akuma/heart/tells.d.ts +29 -0
  39. package/build/src/akuma/heart/tells.js +136 -5
  40. package/build/src/akuma/heart/timeline.js +9 -3
  41. package/build/src/akuma/projection-read.js +36 -3
  42. package/build/src/akuma/projection.js +3 -4
  43. package/build/src/akuma/provider.d.ts +4 -0
  44. package/build/src/akuma/provider.js +56 -3
  45. package/build/src/akuma/providers/claude/index.js +3 -0
  46. package/build/src/akuma/providers/codex-app-server/index.js +14 -12
  47. package/build/src/akuma/providers/pi/index.js +7 -1
  48. package/build/src/akuma/publication.js +65 -49
  49. package/build/src/akuma/request-lifecycle.d.ts +5 -4
  50. package/build/src/akuma/request-lifecycle.js +29 -16
  51. package/build/src/akuma/request-rendezvous.d.ts +15 -12
  52. package/build/src/akuma/request-rendezvous.js +80 -51
  53. package/build/src/akuma/request-serve.d.ts +1 -2
  54. package/build/src/akuma/request-serve.js +94 -39
  55. package/build/src/akuma/request-wire.d.ts +57 -26
  56. package/build/src/akuma/request-wire.js +64 -25
  57. package/build/src/akuma/turn-drive.d.ts +5 -4
  58. package/build/src/akuma/turn-drive.js +27 -11
  59. package/build/src/akuma-body.d.ts +4 -6
  60. package/build/src/akuma-body.js +19 -17
  61. package/build/src/bounded-list.d.ts +8 -0
  62. package/build/src/bounded-list.js +14 -0
  63. package/build/src/cli/accepted.d.ts +1 -1
  64. package/build/src/cli/accepted.js +1 -0
  65. package/build/src/cli/commands/akuma-invoke.js +1 -25
  66. package/build/src/cli/commands/contract-help.d.ts +5 -2
  67. package/build/src/cli/commands/contract-help.js +62 -12
  68. package/build/src/cli/commands/contract-invoke.d.ts +5 -0
  69. package/build/src/cli/commands/contract-invoke.js +26 -1
  70. package/build/src/cli/commands/contract.js +50 -18
  71. package/build/src/cli/commands/task-invoke.js +10 -2
  72. package/build/src/cli/commands/task-query.js +3 -4
  73. package/build/src/cli/coordinates.d.ts +13 -0
  74. package/build/src/cli/coordinates.js +38 -24
  75. package/build/src/cli/draft.d.ts +1 -1
  76. package/build/src/cli/invoke.js +14 -1
  77. package/build/src/cli/render/akuma-activity.d.ts +0 -1
  78. package/build/src/cli/render/akuma-activity.js +5 -38
  79. package/build/src/cli/render/akuma.js +4 -1
  80. package/build/src/cli/render/audit.js +4 -1
  81. package/build/src/cli/render/catalog.js +13 -39
  82. package/build/src/cli/render/contract.js +4 -14
  83. package/build/src/cli/render/kanshi-akuma.js +3 -8
  84. package/build/src/cli/render/kanshi.js +8 -34
  85. package/build/src/cli/render/nuke.js +7 -3
  86. package/build/src/cli/render/receipt.d.ts +1 -0
  87. package/build/src/cli/render/receipt.js +8 -0
  88. package/build/src/cli/render/refusal.js +4 -3
  89. package/build/src/cli/render/task.js +4 -37
  90. package/build/src/cli/render/terminal.d.ts +2 -6
  91. package/build/src/cli/render/terminal.js +4 -9
  92. package/build/src/cli/result.d.ts +2 -1
  93. package/build/src/contract-guidance.js +13 -3
  94. package/build/src/contract-worktree.d.ts +1 -0
  95. package/build/src/contract-worktree.js +26 -2
  96. package/build/src/core/facts/codec.d.ts +2 -1
  97. package/build/src/core/facts/codec.js +3 -0
  98. package/build/src/core/facts/gate.d.ts +2 -1
  99. package/build/src/core/facts/gate.js +63 -0
  100. package/build/src/core/verbs/abandon.d.ts +2 -1
  101. package/build/src/core/verbs/abandon.js +16 -0
  102. package/build/src/core/verbs/amend.d.ts +2 -1
  103. package/build/src/core/verbs/amend.js +20 -0
  104. package/build/src/core/verbs/arc.d.ts +2 -1
  105. package/build/src/core/verbs/arc.js +16 -0
  106. package/build/src/core/verbs/attestation.d.ts +2 -1
  107. package/build/src/core/verbs/attestation.js +16 -0
  108. package/build/src/core/verbs/bind.d.ts +2 -1
  109. package/build/src/core/verbs/bind.js +16 -0
  110. package/build/src/core/verbs/deliver.d.ts +2 -1
  111. package/build/src/core/verbs/deliver.js +16 -0
  112. package/build/src/core/verbs/placement.d.ts +2 -1
  113. package/build/src/core/verbs/placement.js +58 -1
  114. package/build/src/dispatch/association.d.ts +13 -0
  115. package/build/src/dispatch/association.js +20 -0
  116. package/build/src/dispatch/index.d.ts +6 -0
  117. package/build/src/dispatch/index.js +27 -3
  118. package/build/src/git/hooks.d.ts +9 -1
  119. package/build/src/git/hooks.js +79 -1
  120. package/build/src/git/nuke.d.ts +2 -1
  121. package/build/src/git/nuke.js +6 -11
  122. package/build/src/git/private-state-seat.d.ts +26 -1
  123. package/build/src/git/private-state-seat.js +78 -13
  124. package/build/src/git/process.d.ts +2 -0
  125. package/build/src/git/reconcile.d.ts +1 -2
  126. package/build/src/git/reconcile.js +0 -6
  127. package/build/src/git/repository.d.ts +0 -2
  128. package/build/src/git/repository.js +27 -25
  129. package/build/src/git/result-codec.d.ts +29 -0
  130. package/build/src/git/result-codec.js +306 -0
  131. package/build/src/git/target-placement.js +2 -8
  132. package/build/src/git/workspace.d.ts +2 -0
  133. package/build/src/git/workspace.js +4 -0
  134. package/build/src/index.d.ts +1 -0
  135. package/build/src/kanshi/fleet.js +1 -1
  136. package/build/src/kanshi/read.js +55 -28
  137. package/build/src/kanshi/report.d.ts +4 -3
  138. package/build/src/kanshi/select.js +14 -1
  139. package/build/src/library/address.d.ts +1 -0
  140. package/build/src/library/address.js +4 -7
  141. package/build/src/library/akuma-creation.d.ts +5 -3
  142. package/build/src/library/akuma-creation.js +90 -33
  143. package/build/src/library/audit.js +2 -1
  144. package/build/src/library/catalog.d.ts +10 -2
  145. package/build/src/library/catalog.js +65 -25
  146. package/build/src/library/composition.d.ts +1 -0
  147. package/build/src/library/composition.js +1 -0
  148. package/build/src/library/configuration.d.ts +4 -21
  149. package/build/src/library/configuration.js +2 -158
  150. package/build/src/library/continuation.d.ts +20 -1
  151. package/build/src/library/continuation.js +36 -0
  152. package/build/src/library/contract-bind.d.ts +1 -0
  153. package/build/src/library/contract-bind.js +16 -5
  154. package/build/src/library/contract-forwarding.d.ts +2 -2
  155. package/build/src/library/contract-forwarding.js +18 -4
  156. package/build/src/library/contract-handle.js +4 -3
  157. package/build/src/library/contract-operations.d.ts +5 -9
  158. package/build/src/library/contract-operations.js +34 -47
  159. package/build/src/library/contract.d.ts +11 -2
  160. package/build/src/library/contract.js +21 -1
  161. package/build/src/library/delivery.d.ts +4 -1
  162. package/build/src/library/delivery.js +47 -0
  163. package/build/src/library/fleet.d.ts +4 -136
  164. package/build/src/library/fleet.js +31 -315
  165. package/build/src/library/keiyaku.d.ts +7 -3
  166. package/build/src/library/keiyaku.js +1 -0
  167. package/build/src/library/mutation.d.ts +431 -3
  168. package/build/src/library/mutation.js +192 -19
  169. package/build/src/library/nuke.d.ts +7 -1
  170. package/build/src/library/nuke.js +18 -4
  171. package/build/src/library/reconcile.d.ts +14 -0
  172. package/build/src/library/reconcile.js +22 -8
  173. package/build/src/library/refusal.d.ts +9 -1
  174. package/build/src/library/refusal.js +76 -0
  175. package/build/src/library/region.d.ts +0 -8
  176. package/build/src/library/region.js +1 -29
  177. package/build/src/library/result-codec.d.ts +2 -0
  178. package/build/src/library/result-codec.js +12 -0
  179. package/build/src/markdown/lex.js +9 -2
  180. package/build/src/markdown/types.d.ts +2 -1
  181. package/build/src/markdown/types.js +3 -1
  182. package/build/src/plugin/akuma-signals.d.ts +12 -0
  183. package/build/src/plugin/akuma-signals.js +14 -0
  184. package/build/src/plugin/public.d.ts +51 -0
  185. package/build/src/plugin/public.js +1 -0
  186. package/build/src/plugin/runtime.d.ts +14 -0
  187. package/build/src/plugin/runtime.js +357 -0
  188. package/build/src/protocol/amend.js +3 -3
  189. package/build/src/protocol/attempt.d.ts +2 -1
  190. package/build/src/protocol/attempt.js +1 -1
  191. package/build/src/protocol/audit.d.ts +1 -1
  192. package/build/src/protocol/audit.js +1 -1
  193. package/build/src/protocol/bind.d.ts +3 -1
  194. package/build/src/protocol/bind.js +26 -0
  195. package/build/src/protocol/completion.js +0 -1
  196. package/build/src/protocol/deliver.d.ts +1 -1
  197. package/build/src/protocol/deliver.js +136 -57
  198. package/build/src/protocol/intent.d.ts +0 -1
  199. package/build/src/protocol/intent.js +0 -1
  200. package/build/src/protocol/operations.d.ts +9 -2
  201. package/build/src/protocol/operations.js +16 -1
  202. package/build/src/protocol/outcome.d.ts +3 -0
  203. package/build/src/protocol/outcome.js +17 -1
  204. package/build/src/protocol/placement.js +10 -3
  205. package/build/src/protocol/read/status.d.ts +8 -0
  206. package/build/src/protocol/read/status.js +49 -0
  207. package/build/src/protocol/reintegrate.js +10 -3
  208. package/build/src/protocol/result-codec.d.ts +22 -0
  209. package/build/src/protocol/result-codec.js +442 -0
  210. package/build/src/protocol/review.d.ts +1 -1
  211. package/build/src/protocol/review.js +81 -46
  212. package/build/src/protocol/run.d.ts +22 -2
  213. package/build/src/protocol/run.js +119 -29
  214. package/build/src/runtime/proc/lifecycle.js +1 -1
  215. package/build/src/runtime/proc/line-rpc.d.ts +7 -1
  216. package/build/src/runtime/proc/line-rpc.js +49 -9
  217. package/build/src/runtime/proc/run.d.ts +0 -1
  218. package/build/src/runtime/proc/run.js +1 -11
  219. package/build/src/runtime/proc/termination.d.ts +1 -0
  220. package/build/src/runtime/proc/termination.js +63 -18
  221. package/build/src/runtime/proc/windows-launch.exe +0 -0
  222. package/build/src/settings.d.ts +10 -0
  223. package/build/src/settings.js +88 -2
  224. package/build/src/settlement/result-codec.d.ts +2 -0
  225. package/build/src/settlement/result-codec.js +56 -0
  226. package/build/src/settlement/settle.d.ts +5 -12
  227. package/build/src/settlement/settle.js +60 -55
  228. package/build/src/task/board.d.ts +3 -0
  229. package/build/src/task/board.js +22 -4
  230. package/build/src/task/catalog.d.ts +13 -2
  231. package/build/src/task/catalog.js +59 -5
  232. package/build/src/task/compose.d.ts +4 -2
  233. package/build/src/task/compose.js +56 -30
  234. package/build/src/task/created-observation.d.ts +39 -0
  235. package/build/src/task/created-observation.js +26 -0
  236. package/build/src/task/identity.d.ts +6 -0
  237. package/build/src/task/identity.js +48 -7
  238. package/build/src/task/index.d.ts +4 -3
  239. package/build/src/task/index.js +19 -16
  240. package/build/src/task/input.d.ts +3 -1
  241. package/build/src/task/input.js +7 -4
  242. package/build/src/task/lifecycle-operations.d.ts +6 -0
  243. package/build/src/task/lifecycle-operations.js +116 -0
  244. package/build/src/task/mutation-result.d.ts +762 -20
  245. package/build/src/task/mutation-result.js +34 -9
  246. package/build/src/task/mutation.d.ts +4 -9
  247. package/build/src/task/mutation.js +17 -52
  248. package/build/src/task/operation-types.d.ts +114 -0
  249. package/build/src/task/operation-types.js +1 -0
  250. package/build/src/task/operations.d.ts +6 -113
  251. package/build/src/task/operations.js +75 -117
  252. package/build/src/task/query.d.ts +8 -11
  253. package/build/src/task/query.js +7 -14
  254. package/build/src/task/store.d.ts +11 -1
  255. package/build/src/task/store.js +169 -55
  256. package/build/src/task/view.d.ts +4 -0
  257. package/build/src/task/view.js +7 -0
  258. package/build/src/verification/declaration.d.ts +1 -0
  259. package/build/src/verification/declaration.js +18 -1
  260. package/build/src/verification/execution.d.ts +0 -1
  261. package/build/src/verification/execution.js +5 -5
  262. package/build/src/world.d.ts +0 -2
  263. package/build/src/world.js +3 -7
  264. package/package.json +17 -5
  265. package/build/src/cli/square-edge.d.ts +0 -7
  266. package/build/src/cli/square-edge.js +0 -159
  267. package/build/src/git/ref-migration.d.ts +0 -22
  268. package/build/src/git/ref-migration.js +0 -55
  269. package/build/src/library/contract-forwarding-reconciliation-result.d.ts +0 -102
  270. package/build/src/library/contract-forwarding-reconciliation-result.js +0 -92
  271. package/build/src/library/contract-forwarding-result.d.ts +0 -4270
  272. package/build/src/library/contract-forwarding-result.js +0 -521
@@ -1,19 +1,74 @@
1
1
  import { extendAdmissionPathsAt, observeContractsForAdmissionAt } from "../git/observe.js";
2
- import { withPrivateStatePublicationSeat } from "../git/private-state-seat.js";
2
+ import { appendPrivateStateSeatClose, isPrivateStateSeatContention, mergePrivateStateSeatClose, withPrivateStatePublicationSeat, } from "../git/private-state-seat.js";
3
+ import { GIT_REF, readRefs } from "../git/repository.js";
3
4
  import { admitDecidedOffer } from "./attempt.js";
4
- /** Form one decided offer without admitting or reinterpreting it. */
5
- export async function prepareProtocolAttempt(input, baseAttempt) {
6
- const decisionObservation = input.observe === undefined
5
+ export const STALE_PRIVATE_STATE_PREPARATION = {
6
+ kind: "publication-failed",
7
+ diagnostic: "stale private-state preparation",
8
+ };
9
+ export function privateRootCommit(observation) {
10
+ return observation.admission.snapshot.commit;
11
+ }
12
+ export function samePrivateRootObservation(prepared, fresh) {
13
+ return privateRootCommit(prepared) === privateRootCommit(fresh);
14
+ }
15
+ export function sameSpeculativeWorktreeInput(prepared, fresh) {
16
+ if (prepared === undefined || fresh === undefined)
17
+ return prepared === fresh;
18
+ return (prepared.tree === fresh.tree &&
19
+ prepared.head === fresh.head &&
20
+ prepared.mergeHead === fresh.mergeHead &&
21
+ prepared.dirty === fresh.dirty &&
22
+ prepared.changeId === fresh.changeId);
23
+ }
24
+ export async function privateStateAssertionsMatch(repository, assertions) {
25
+ if (assertions.length === 0)
26
+ return true;
27
+ const refs = await readRefs(repository, [GIT_REF, ...assertions.map((assertion) => assertion.ref)]);
28
+ return assertions.every((assertion) => refs.get(assertion.ref) === assertion.oid);
29
+ }
30
+ export function publicationFailedFromSeatError(error) {
31
+ if (isPrivateStateSeatContention(error))
32
+ return { kind: "publication-failed", diagnostic: error.message };
33
+ throw error;
34
+ }
35
+ export async function matchingPrivateRootObservation(repository, channel, contractId, prepared, revalidate) {
36
+ const fresh = await observeContractsForAdmissionAt(repository, channel, [contractId]);
37
+ if (!samePrivateRootObservation(prepared, fresh))
38
+ return STALE_PRIVATE_STATE_PREPARATION;
39
+ if (revalidate !== undefined && !(await revalidate(fresh)))
40
+ return STALE_PRIVATE_STATE_PREPARATION;
41
+ return fresh;
42
+ }
43
+ export async function privateStateSeatAttempt(repository, action, wrap) {
44
+ try {
45
+ return wrap(await withPrivateStatePublicationSeat(repository, action));
46
+ }
47
+ catch (error) {
48
+ return publicationFailedFromSeatError(error);
49
+ }
50
+ }
51
+ async function observeProtocolDecision(input) {
52
+ return input.observe === undefined
7
53
  ? await observeContractsForAdmissionAt(input.repository, input.channel, input.contracts, input.observationSelection)
8
54
  : await input.observe(input.repository, input.channel, input.contracts);
9
- const observation = decisionObservation.decision;
10
- const preparedInput = "prepareInput" in input && input.prepareInput !== undefined
11
- ? await input.prepareInput(decisionObservation, input.input)
12
- : { kind: "prepared", input: input.input, assertions: [] };
13
- if (preparedInput.kind === "refused")
14
- return preparedInput;
15
- const attempt = input.extendAttempt === undefined ? baseAttempt : input.extendAttempt(baseAttempt, observation.size);
16
- const decision = input.decide({ input: preparedInput.input, attempt, observation });
55
+ }
56
+ async function prepareProtocolSeed(input, observation) {
57
+ if (!("prepareInput" in input) || input.prepareInput === undefined) {
58
+ return { kind: "prepared", input: input.input, assertions: [] };
59
+ }
60
+ const prepared = await input.prepareInput(observation, input.input);
61
+ return prepared.kind === "refused" ? prepared : { ...prepared, assertions: prepared.assertions ?? [] };
62
+ }
63
+ async function decideDecoratedProtocolOffer(input, baseAttempt, decisionObservation, preparedInput) {
64
+ const attempt = input.extendAttempt === undefined
65
+ ? baseAttempt
66
+ : input.extendAttempt(baseAttempt, decisionObservation.decision.size);
67
+ const decision = input.decide({
68
+ input: preparedInput.input,
69
+ attempt,
70
+ observation: decisionObservation.decision,
71
+ });
17
72
  if (decision.kind === "refused")
18
73
  return decision;
19
74
  const companions = input.decorateOffer === undefined
@@ -35,29 +90,64 @@ export async function prepareProtocolAttempt(input, baseAttempt) {
35
90
  observation: admissionObservation,
36
91
  attempt,
37
92
  offer,
38
- assertions: preparedInput.assertions ?? [],
93
+ assertions: preparedInput.assertions,
39
94
  };
40
95
  }
96
+ /** Form one decided offer without admitting or reinterpreting it. */
97
+ export async function prepareProtocolAttempt(input, baseAttempt) {
98
+ const decisionObservation = await observeProtocolDecision(input);
99
+ const preparedInput = await prepareProtocolSeed(input, decisionObservation);
100
+ if (preparedInput.kind === "refused")
101
+ return preparedInput;
102
+ return await decideDecoratedProtocolOffer(input, baseAttempt, decisionObservation, preparedInput);
103
+ }
104
+ function protocolAttemptWithSeatClose(outcome) {
105
+ return mergePrivateStateSeatClose(outcome, (value, closeLag) => {
106
+ if (value.kind !== "accepted")
107
+ throw new Error(closeLag.diagnostic);
108
+ return { ...value, seatClose: appendPrivateStateSeatClose(value.seatClose, closeLag) };
109
+ });
110
+ }
111
+ async function speculateProtocolAttempt(input) {
112
+ const observation = await observeProtocolDecision(input);
113
+ const prepared = await prepareProtocolSeed(input, observation);
114
+ return prepared.kind === "refused" ? prepared : { observation, ...prepared };
115
+ }
116
+ async function admitSpeculativeProtocolAttempt(input, seat, speculated, attempt) {
117
+ const fresh = await observeProtocolDecision(input);
118
+ if (!samePrivateRootObservation(speculated.observation, fresh) ||
119
+ !(await privateStateAssertionsMatch(input.repository, speculated.assertions))) {
120
+ return STALE_PRIVATE_STATE_PREPARATION;
121
+ }
122
+ const decided = await decideDecoratedProtocolOffer(input, attempt, fresh, {
123
+ kind: "prepared",
124
+ input: speculated.input,
125
+ assertions: speculated.assertions,
126
+ });
127
+ if (decided.kind === "refused")
128
+ return decided;
129
+ return await admitDecidedOffer({
130
+ channel: input.channel,
131
+ repository: input.repository,
132
+ seat,
133
+ decisionObservation: decided.observation,
134
+ attempt: decided.attempt,
135
+ offer: decided.offer,
136
+ primaryContract: input.input.contractId,
137
+ assertions: decided.assertions,
138
+ ...(input.validateAdmission === undefined ? {} : { validateAdmission: input.validateAdmission }),
139
+ });
140
+ }
141
+ async function runSpeculativeProtocolAttempt(input, speculated, attempt) {
142
+ if (speculated.kind === "refused")
143
+ return speculated;
144
+ return await privateStateSeatAttempt(input.repository, async (seat) => await admitSpeculativeProtocolAttempt(input, seat, speculated, attempt), protocolAttemptWithSeatClose);
145
+ }
41
146
  /** Run bounded, verb-neutral attempts and return one real admission on acceptance. */
42
147
  export async function runProtocol(input) {
43
148
  const attempts = input.attempts;
44
149
  for (let index = 0; index < attempts.length; index += 1) {
45
- const result = await withPrivateStatePublicationSeat(input.repository, async (seat) => {
46
- const prepared = await prepareProtocolAttempt(input, attempts[index]);
47
- if (prepared.kind === "refused")
48
- return prepared;
49
- return await admitDecidedOffer({
50
- channel: input.channel,
51
- repository: input.repository,
52
- seat,
53
- decisionObservation: prepared.observation,
54
- attempt: prepared.attempt,
55
- offer: prepared.offer,
56
- primaryContract: input.input.contractId,
57
- assertions: prepared.assertions,
58
- ...(input.validateAdmission === undefined ? {} : { validateAdmission: input.validateAdmission }),
59
- });
60
- });
150
+ const result = await runSpeculativeProtocolAttempt(input, await speculateProtocolAttempt(input), attempts[index]);
61
151
  if (result.kind === "refused" || result.kind === "accepted" || result.kind === "publication-failed")
62
152
  return result;
63
153
  if (result.kind === "collision" && index + 1 === attempts.length)
@@ -17,7 +17,7 @@ export function createProcessLifecycle(runTermination, releaseCustody) {
17
17
  return termination;
18
18
  },
19
19
  release() {
20
- if (state !== "active")
20
+ if (state === "inert")
21
21
  return;
22
22
  state = "inert";
23
23
  releaseCustody();
@@ -12,6 +12,7 @@ export declare class LineRpcProcess {
12
12
  private readonly pending;
13
13
  private readonly notifications;
14
14
  private readonly serverRequests;
15
+ private readonly requestTimeoutMs;
15
16
  private nextId;
16
17
  private stdout;
17
18
  private closed;
@@ -19,6 +20,8 @@ export declare class LineRpcProcess {
19
20
  argv: readonly [string, ...string[]];
20
21
  cwd: string;
21
22
  env?: NodeJS.ProcessEnv;
23
+ requestTimeoutMs?: number;
24
+ timeoutMs?: number;
22
25
  }>);
23
26
  onNotification(listener: (value: LineRpcNotification) => void): void;
24
27
  onServerRequest(listener: (value: LineRpcServerRequest) => void): void;
@@ -26,7 +29,10 @@ export declare class LineRpcProcess {
26
29
  private consume;
27
30
  private receive;
28
31
  private fail;
29
- request(method: string, params?: Readonly<Record<string, unknown>>): Promise<unknown>;
32
+ private failAndClose;
33
+ request(method: string, params?: Readonly<Record<string, unknown>>, options?: Readonly<{
34
+ timeoutMs?: number;
35
+ }>): Promise<unknown>;
30
36
  notify(method: string): void;
31
37
  endInputAndDrain(timeoutMs?: number): Promise<void>;
32
38
  close(force?: boolean): Promise<void>;
@@ -1,4 +1,5 @@
1
1
  import { spawnStdioProcess } from "./stdio.js";
2
+ const DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
2
3
  function object(value) {
3
4
  return value !== null && typeof value === "object" && !Array.isArray(value)
4
5
  ? value
@@ -12,10 +13,15 @@ export class LineRpcProcess {
12
13
  pending = new Map();
13
14
  notifications = new Set();
14
15
  serverRequests = new Set();
16
+ requestTimeoutMs;
15
17
  nextId = 1;
16
18
  stdout = "";
17
19
  closed = false;
18
20
  constructor(input) {
21
+ this.requestTimeoutMs = input.requestTimeoutMs ?? input.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
22
+ if (!Number.isFinite(this.requestTimeoutMs) || this.requestTimeoutMs < 0) {
23
+ throw new TypeError("line RPC request timeout must be a nonnegative finite millisecond duration");
24
+ }
19
25
  this.process = spawnStdioProcess(input);
20
26
  this.process.output.setEncoding("utf8");
21
27
  this.process.output.on("data", (chunk) => this.consume(chunk));
@@ -34,15 +40,22 @@ export class LineRpcProcess {
34
40
  void this.process.exited.then(listener);
35
41
  }
36
42
  consume(chunk) {
37
- this.stdout += chunk;
38
- for (;;) {
39
- const newline = this.stdout.indexOf("\n");
40
- if (newline < 0)
43
+ let offset = 0;
44
+ while (offset < chunk.length) {
45
+ const newline = chunk.indexOf("\n", offset);
46
+ if (newline < 0) {
47
+ this.stdout += chunk.slice(offset);
41
48
  return;
42
- const line = this.stdout.slice(0, newline).trim();
43
- this.stdout = this.stdout.slice(newline + 1);
49
+ }
50
+ this.stdout += chunk.slice(offset, newline);
51
+ const rawLine = this.stdout;
52
+ this.stdout = "";
53
+ offset = newline + 1;
54
+ const line = rawLine.trim();
44
55
  if (line.length > 0)
45
56
  this.receive(line);
57
+ if (this.closed)
58
+ return;
46
59
  }
47
60
  }
48
61
  receive(line) {
@@ -51,6 +64,7 @@ export class LineRpcProcess {
51
64
  decoded = JSON.parse(line);
52
65
  }
53
66
  catch {
67
+ this.failAndClose(new Error("line RPC protocol received malformed JSON"));
54
68
  return;
55
69
  }
56
70
  const message = object(decoded);
@@ -61,6 +75,8 @@ export class LineRpcProcess {
61
75
  if (pending === undefined)
62
76
  return;
63
77
  this.pending.delete(message.id);
78
+ if (pending.timer !== undefined)
79
+ clearTimeout(pending.timer);
64
80
  const error = object(message.error);
65
81
  if (error !== undefined)
66
82
  pending.reject(new Error(text(error.message) ?? "line RPC error"));
@@ -81,20 +97,44 @@ export class LineRpcProcess {
81
97
  listener({ method, ...(params === undefined ? {} : { params }) });
82
98
  }
83
99
  fail(error) {
84
- for (const pending of this.pending.values())
100
+ for (const pending of this.pending.values()) {
101
+ if (pending.timer !== undefined)
102
+ clearTimeout(pending.timer);
85
103
  pending.reject(error);
104
+ }
86
105
  this.pending.clear();
87
106
  }
88
- request(method, params) {
107
+ failAndClose(error) {
108
+ if (this.closed)
109
+ return;
110
+ this.closed = true;
111
+ this.fail(error);
112
+ void this.process.close(true).catch(() => undefined);
113
+ }
114
+ request(method, params, options = {}) {
89
115
  if (this.closed)
90
116
  return Promise.reject(new Error("line RPC process is closed"));
117
+ const timeoutMs = options.timeoutMs ?? this.requestTimeoutMs;
118
+ if (!Number.isFinite(timeoutMs) || timeoutMs < 0) {
119
+ return Promise.reject(new TypeError("line RPC request timeout must be a nonnegative finite millisecond duration"));
120
+ }
91
121
  const id = this.nextId++;
92
122
  return new Promise((resolve, reject) => {
93
- this.pending.set(id, { resolve, reject });
123
+ const pending = { resolve, reject };
124
+ pending.timer = setTimeout(() => {
125
+ if (this.pending.get(id) !== pending)
126
+ return;
127
+ this.pending.delete(id);
128
+ reject(new Error(`line RPC request timed out after ${timeoutMs}ms`));
129
+ }, timeoutMs);
130
+ pending.timer.unref?.();
131
+ this.pending.set(id, pending);
94
132
  this.process.input.write(`${JSON.stringify({ id, method, ...(params === undefined ? {} : { params }) })}\n`, (error) => {
95
133
  if (error === null || error === undefined)
96
134
  return;
97
135
  this.pending.delete(id);
136
+ if (pending.timer !== undefined)
137
+ clearTimeout(pending.timer);
98
138
  reject(error);
99
139
  });
100
140
  });
@@ -53,5 +53,4 @@ export { terminateOwnedProcess } from "./termination.js";
53
53
  export declare function spawnDetachedProcess(input: DetachedProcessInput): Promise<OwnedProcess>;
54
54
  export declare function runProcess(input: ProcessInput): Promise<ProcessOutcome>;
55
55
  export declare function runCrossPlatformProcess(input: ProcessInput): Promise<ProcessOutcome>;
56
- export declare function runProcessToExit(input: ProcessLaunch): Promise<ProcessOutcome>;
57
56
  export declare function consumeProcessStdout(input: ProcessInput, consume: (chunk: Buffer) => void): Promise<ProcessConsumption>;
@@ -87,14 +87,10 @@ export async function spawnDetachedProcess(input) {
87
87
  const from = (await log.stat()).size;
88
88
  const child = spawn(input.argv[0], input.argv.slice(1), spawnOptionsFor(input, ["ignore", log.fd, log.fd]));
89
89
  const lifecycle = createProcessLifecycle((force) => terminateOwnedProcess(child, force), () => child.unref());
90
- const invalidate = () => {
91
- lifecycle.markInert();
92
- };
93
- child.once("exit", invalidate);
94
- child.once("close", invalidate);
95
90
  const exited = new Promise((resolve, reject) => {
96
91
  child.once("close", (code, signal) => {
97
92
  void (async () => {
93
+ await lifecycle.terminate();
98
94
  const status = detachedExitStatus(code, signal);
99
95
  let failure;
100
96
  let result;
@@ -239,12 +235,6 @@ export async function runCrossPlatformProcess(input) {
239
235
  throw new Error("buffered process produced a stream consumer error");
240
236
  return result.outcome;
241
237
  }
242
- export async function runProcessToExit(input) {
243
- const result = await executeProcess(input, undefined);
244
- if (result.outcome.kind === "stream-error")
245
- throw new Error("buffered process produced a stream consumer error");
246
- return result.outcome;
247
- }
248
238
  export function consumeProcessStdout(input, consume) {
249
239
  return executeProcess(input, input.timeoutMs, consume);
250
240
  }
@@ -1,4 +1,5 @@
1
1
  import { type ChildProcess } from "node:child_process";
2
+ export declare function ownedProcessGroupExists(pid: number): boolean;
2
3
  export declare function terminateWindowsTree(pid: number): Promise<void>;
3
4
  export declare function settleWindowsTermination(exit: Promise<void>): Promise<void>;
4
5
  export declare function terminateOwnedProcess(child: ChildProcess, force?: boolean): Promise<void>;
@@ -4,6 +4,7 @@ import { promisify } from "node:util";
4
4
  const TERMINATION_GRACE_MS = 250;
5
5
  const WINDOWS_TERMINATION_SETTLE_MS = 250;
6
6
  const WINDOWS_TERMINATION_TIMEOUT_MS = 1_000;
7
+ const PROCESS_GROUP_POLL_MS = 10;
7
8
  const execFileAsync = promisify(execFile);
8
9
  function isMissingProcess(error) {
9
10
  return error.code === "ESRCH";
@@ -25,6 +26,29 @@ async function signalOwnedProcessGroup(pid, signal, exit) {
25
26
  throw error;
26
27
  }
27
28
  }
29
+ export function ownedProcessGroupExists(pid) {
30
+ try {
31
+ process.kill(-pid, 0);
32
+ return true;
33
+ }
34
+ catch (error) {
35
+ if (isMissingProcess(error))
36
+ return false;
37
+ if (error.code === "EPERM")
38
+ return true;
39
+ throw error;
40
+ }
41
+ }
42
+ async function waitForOwnedProcessGroupExit(pid) {
43
+ const attempts = Math.ceil(TERMINATION_GRACE_MS / PROCESS_GROUP_POLL_MS);
44
+ for (let attempt = 0; attempt < attempts; attempt += 1) {
45
+ if (!ownedProcessGroupExists(pid))
46
+ return;
47
+ await delay(PROCESS_GROUP_POLL_MS, undefined, { ref: false });
48
+ }
49
+ if (ownedProcessGroupExists(pid))
50
+ throw new Error("owned process group did not exit after termination");
51
+ }
28
52
  export async function terminateWindowsTree(pid) {
29
53
  try {
30
54
  await execFileAsync("taskkill", ["/PID", String(pid), "/T", "/F"], {
@@ -54,24 +78,41 @@ function closeWindowsStreams(child) {
54
78
  child.stdout?.destroy();
55
79
  child.stderr?.destroy();
56
80
  }
57
- export async function terminateOwnedProcess(child, force = false) {
58
- const pid = child.pid;
59
- if (pid === undefined || child.exitCode !== null || child.signalCode !== null)
60
- return;
61
- let exited = false;
62
- const exit = new Promise((resolve) => {
63
- child.once("exit", () => {
64
- exited = true;
81
+ function waitForOwnedChildExit(child, pid) {
82
+ return new Promise((resolve) => {
83
+ if (child.exitCode !== null || child.signalCode !== null) {
65
84
  resolve();
66
- });
67
- child.once("close", () => {
68
- exited = true;
85
+ return;
86
+ }
87
+ let settled = false;
88
+ const settle = () => {
89
+ if (settled)
90
+ return;
91
+ settled = true;
69
92
  resolve();
93
+ };
94
+ child.once("exit", settle);
95
+ child.once("close", settle);
96
+ // Unknown exit can leave exitCode/signalCode both null after close; settle if the leader pid is gone.
97
+ process.nextTick(() => {
98
+ if (child.exitCode !== null || child.signalCode !== null) {
99
+ settle();
100
+ return;
101
+ }
102
+ try {
103
+ process.kill(pid, 0);
104
+ }
105
+ catch {
106
+ settle();
107
+ }
70
108
  });
71
109
  });
72
- await new Promise((resolve) => setImmediate(resolve));
73
- if (exited || child.exitCode !== null || child.signalCode !== null)
110
+ }
111
+ export async function terminateOwnedProcess(child, force = false) {
112
+ const pid = child.pid;
113
+ if (pid === undefined)
74
114
  return;
115
+ const exit = waitForOwnedChildExit(child, pid);
75
116
  if (process.platform === "win32") {
76
117
  try {
77
118
  await terminateWindowsTree(pid);
@@ -84,15 +125,19 @@ export async function terminateOwnedProcess(child, force = false) {
84
125
  }
85
126
  if (force) {
86
127
  await signalOwnedProcessGroup(pid, "SIGKILL", exit);
128
+ await waitForOwnedProcessGroupExit(pid);
87
129
  await exit;
88
130
  return;
89
131
  }
90
132
  await signalOwnedProcessGroup(pid, "SIGTERM", exit);
91
- if (exited || child.exitCode !== null || child.signalCode !== null)
92
- return;
93
- await Promise.race([exit, delay(TERMINATION_GRACE_MS)]);
94
- if (exited || child.exitCode !== null || child.signalCode !== null)
133
+ if (!ownedProcessGroupExists(pid)) {
134
+ await exit;
95
135
  return;
96
- await signalOwnedProcessGroup(pid, "SIGKILL", exit);
136
+ }
137
+ await delay(TERMINATION_GRACE_MS);
138
+ if (ownedProcessGroupExists(pid)) {
139
+ await signalOwnedProcessGroup(pid, "SIGKILL", exit);
140
+ await waitForOwnedProcessGroupExit(pid);
141
+ }
97
142
  await exit;
98
143
  }
@@ -46,6 +46,16 @@ export declare class SettingsError extends Error {
46
46
  readonly kind = "settings";
47
47
  constructor(message: string);
48
48
  }
49
+ export type Gate = string;
50
+ export type GatesFromInput = Readonly<{
51
+ settings: Settings;
52
+ names?: readonly string[];
53
+ }>;
54
+ export type RequireBranchesToBeUpToDateFromInput = Readonly<{
55
+ settings: Settings;
56
+ }>;
49
57
  export declare function settings(input?: SettingsInput): Promise<Settings>;
50
58
  /** Read only the project scope owned by a materialized candidate snapshot. */
51
59
  export declare function projectSettings(root: string): Promise<Settings>;
60
+ export declare function gatesFrom(input: GatesFromInput): readonly Gate[];
61
+ export declare function requireBranchesToBeUpToDateFrom(input: RequireBranchesToBeUpToDateFromInput): boolean;
@@ -1,6 +1,7 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import { homedir } from "node:os";
3
3
  import { join, resolve } from "node:path";
4
+ import { gateWord } from "./core/facts/types.js";
4
5
  export class SettingsError extends Error {
5
6
  kind = "settings";
6
7
  constructor(message) {
@@ -52,9 +53,10 @@ function namespaceEntries(name, scope, loaded) {
52
53
  if (loaded.state.kind === "failed") {
53
54
  return { entries: [], failure: { scope, diagnostic: loaded.state.diagnostic } };
54
55
  }
55
- const value = loaded.root?.[name];
56
- if (value === undefined)
56
+ if (loaded.root === undefined || !Object.prototype.hasOwnProperty.call(loaded.root, name)) {
57
57
  return { entries: [] };
58
+ }
59
+ const value = loaded.root[name];
58
60
  if (!object(value)) {
59
61
  return { entries: [], failure: { scope, diagnostic: `${name} must be an object of named values` } };
60
62
  }
@@ -116,3 +118,87 @@ export async function projectSettings(root) {
116
118
  state: { kind: "absent" },
117
119
  });
118
120
  }
121
+ function namespaceFailure(view) {
122
+ if (view.kind !== "failed")
123
+ throw new Error("settings namespace failure expected");
124
+ throw new SettingsError(view.failures.map((failure) => `${failure.scope}: ${failure.diagnostic}`).join("; "));
125
+ }
126
+ function bundleGates(name, value) {
127
+ if (!object(value))
128
+ throw new SettingsError(`gate bundle '${name}' must be an object`);
129
+ if (value.kind !== "bundle") {
130
+ throw new SettingsError(`gate bundle '${name}' has unsupported kind: ${String(value.kind)}`);
131
+ }
132
+ for (const field of Object.keys(value)) {
133
+ if (field !== "kind" && field !== "gates") {
134
+ throw new SettingsError(`gate bundle '${name}' has unknown field: ${field}`);
135
+ }
136
+ }
137
+ if (!Array.isArray(value.gates)) {
138
+ throw new SettingsError(`gate bundle '${name}'.gates must be an array`);
139
+ }
140
+ return value.gates.map((gate) => {
141
+ if (!gateWord(gate)) {
142
+ throw new SettingsError(`gate bundle '${name}' contains an invalid gate word`);
143
+ }
144
+ if (gate !== "reviewed" && gate !== "verified") {
145
+ throw new SettingsError(`gate bundle '${name}' contains a gate without a producer: ${gate}`);
146
+ }
147
+ return gate;
148
+ });
149
+ }
150
+ export function gatesFrom(input) {
151
+ if (!object(input))
152
+ throw new TypeError("gatesFrom input must be an object");
153
+ if (input.names !== undefined && !Array.isArray(input.names)) {
154
+ throw new TypeError("gatesFrom names must be an array");
155
+ }
156
+ const names = input.names ?? ["default"];
157
+ for (const name of names) {
158
+ if (!gateWord(name))
159
+ throw new SettingsError("gate bundle name must match ^[a-z][a-z0-9-]{0,63}$");
160
+ }
161
+ const view = input.settings.namespace("gates");
162
+ if (view.kind === "failed")
163
+ namespaceFailure(view);
164
+ const expanded = [];
165
+ const seen = new Set();
166
+ for (const name of names) {
167
+ const selected = view.entries.find((entry) => entry.name === name);
168
+ if (selected === undefined) {
169
+ if (input.names === undefined) {
170
+ if (!seen.has("reviewed"))
171
+ expanded.push("reviewed");
172
+ seen.add("reviewed");
173
+ continue;
174
+ }
175
+ throw new SettingsError(`unknown gate bundle: ${name}`);
176
+ }
177
+ for (const gate of bundleGates(name, selected.value)) {
178
+ if (seen.has(gate))
179
+ continue;
180
+ seen.add(gate);
181
+ expanded.push(gate);
182
+ }
183
+ }
184
+ return Object.freeze(expanded);
185
+ }
186
+ export function requireBranchesToBeUpToDateFrom(input) {
187
+ if (!object(input))
188
+ throw new TypeError("requireBranchesToBeUpToDateFrom input must be an object");
189
+ const view = input.settings.namespace("git");
190
+ if (view.kind === "failed")
191
+ namespaceFailure(view);
192
+ for (const entry of view.entries) {
193
+ if (entry.name !== "requireBranchesToBeUpToDate") {
194
+ throw new SettingsError(`git has unknown entry: ${entry.name}`);
195
+ }
196
+ }
197
+ const selected = view.entries.find((entry) => entry.name === "requireBranchesToBeUpToDate");
198
+ if (selected === undefined)
199
+ return false;
200
+ if (typeof selected.value !== "boolean") {
201
+ throw new SettingsError("git.requireBranchesToBeUpToDate must be a boolean");
202
+ }
203
+ return selected.value;
204
+ }
@@ -0,0 +1,2 @@
1
+ import type { SettlementLag } from "./settle.js";
2
+ export declare function decodeSettlementLag(value: unknown): SettlementLag;
@@ -0,0 +1,56 @@
1
+ import { contractId } from "../core/facts/types.js";
2
+ import { canonicalTaskId } from "../task/identity.js";
3
+ function fail() {
4
+ throw new Error("malformed settlement lag");
5
+ }
6
+ function object(value, required, optional = []) {
7
+ if (value === null || typeof value !== "object" || Array.isArray(value))
8
+ fail();
9
+ const record = value;
10
+ const allowed = new Set([...required, ...optional]);
11
+ for (const key of Object.keys(record))
12
+ if (!allowed.has(key))
13
+ fail();
14
+ for (const key of required)
15
+ if (!(key in record))
16
+ fail();
17
+ return record;
18
+ }
19
+ function nonblank(value) {
20
+ if (typeof value !== "string" || value.trim() === "")
21
+ fail();
22
+ return value;
23
+ }
24
+ export function decodeSettlementLag(value) {
25
+ const record = object(value, ["kind", "surface", "contractId", "diagnostic"], ["taskId", "path"]);
26
+ if (record.kind !== "settlement-failed")
27
+ fail();
28
+ if (record.surface !== "task-holder" && record.surface !== "task")
29
+ fail();
30
+ let id;
31
+ try {
32
+ id = contractId(nonblank(record.contractId));
33
+ }
34
+ catch {
35
+ fail();
36
+ }
37
+ const lag = {
38
+ kind: "settlement-failed",
39
+ surface: record.surface,
40
+ contractId: id,
41
+ diagnostic: nonblank(record.diagnostic),
42
+ };
43
+ if (record.taskId !== undefined) {
44
+ try {
45
+ return {
46
+ ...lag,
47
+ taskId: canonicalTaskId(nonblank(record.taskId)),
48
+ ...(record.path === undefined ? {} : { path: nonblank(record.path) }),
49
+ };
50
+ }
51
+ catch {
52
+ fail();
53
+ }
54
+ }
55
+ return record.path === undefined ? lag : { ...lag, path: nonblank(record.path) };
56
+ }