@astrosheep/keiyaku 4.0.0 → 4.0.1

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 (241) hide show
  1. package/README.md +2 -9
  2. package/build/integrations/marketplace/plugins/keiyaku/.claude-plugin/plugin.json +2 -2
  3. package/build/integrations/marketplace/plugins/keiyaku/.codex-plugin/plugin.json +4 -4
  4. package/build/integrations/marketplace/plugins/keiyaku/opencode.js +2 -2
  5. package/build/integrations/marketplace/plugins/keiyaku/package.json +2 -2
  6. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +11 -3
  7. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +9 -6
  8. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +94 -0
  9. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/agents/openai.yaml +4 -0
  10. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-task/SKILL.md +41 -6
  11. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +162 -45
  12. package/build/src/akuma/akuma.d.ts +33 -25
  13. package/build/src/akuma/akuma.js +93 -61
  14. package/build/src/akuma/archetype.d.ts +8 -0
  15. package/build/src/akuma/archetype.js +28 -9
  16. package/build/src/akuma/body.js +196 -84
  17. package/build/src/akuma/coordinate.d.ts +9 -0
  18. package/build/src/akuma/coordinate.js +22 -0
  19. package/build/src/akuma/heart/facts.d.ts +67 -15
  20. package/build/src/akuma/heart/facts.js +3 -3
  21. package/build/src/akuma/heart/index.d.ts +41 -29
  22. package/build/src/akuma/heart/index.js +72 -193
  23. package/build/src/akuma/heart/rows.d.ts +30 -53
  24. package/build/src/akuma/heart/rows.js +91 -111
  25. package/build/src/akuma/heart/schema.d.ts +1 -1
  26. package/build/src/akuma/heart/schema.js +55 -8
  27. package/build/src/akuma/heart/storage.d.ts +33 -0
  28. package/build/src/akuma/heart/storage.js +164 -0
  29. package/build/src/akuma/heart/tells.d.ts +12 -0
  30. package/build/src/akuma/heart/tells.js +79 -0
  31. package/build/src/akuma/heart/timeline.d.ts +15 -0
  32. package/build/src/akuma/heart/timeline.js +86 -0
  33. package/build/src/akuma/index.d.ts +4 -2
  34. package/build/src/akuma/index.js +1 -1
  35. package/build/src/akuma/projection.d.ts +97 -0
  36. package/build/src/akuma/projection.js +145 -0
  37. package/build/src/akuma/provider.d.ts +57 -11
  38. package/build/src/akuma/provider.js +57 -37
  39. package/build/src/akuma/providers/{claude.d.ts → claude/events.d.ts} +9 -41
  40. package/build/src/akuma/providers/claude/events.js +185 -0
  41. package/build/src/akuma/providers/claude/index.d.ts +46 -0
  42. package/build/src/akuma/providers/claude/index.js +265 -0
  43. package/build/src/akuma/providers/claude/input.d.ts +13 -0
  44. package/build/src/akuma/providers/claude/input.js +109 -0
  45. package/build/src/akuma/providers/codex-app-server/events.d.ts +1 -1
  46. package/build/src/akuma/providers/codex-app-server/events.js +12 -12
  47. package/build/src/akuma/providers/codex-app-server/index.d.ts +13 -11
  48. package/build/src/akuma/providers/codex-app-server/index.js +73 -22
  49. package/build/src/akuma/providers/index.js +22 -8
  50. package/build/src/akuma/providers/opencode-sdk/events.d.ts +48 -0
  51. package/build/src/akuma/providers/opencode-sdk/events.js +116 -0
  52. package/build/src/akuma/providers/opencode-sdk/index.d.ts +32 -0
  53. package/build/src/akuma/providers/opencode-sdk/index.js +203 -0
  54. package/build/src/akuma/providers/opencode-sdk/session.d.ts +41 -0
  55. package/build/src/akuma/providers/opencode-sdk/session.js +62 -0
  56. package/build/src/akuma/providers/pi/events.d.ts +33 -0
  57. package/build/src/akuma/providers/pi/events.js +128 -0
  58. package/build/src/akuma/providers/pi/index.d.ts +11 -0
  59. package/build/src/akuma/providers/pi/index.js +162 -0
  60. package/build/src/akuma/requests.d.ts +1 -0
  61. package/build/src/akuma/requests.js +5 -2
  62. package/build/src/alias/index.d.ts +4 -3
  63. package/build/src/alias/index.js +2 -3
  64. package/build/src/body/render.js +2 -1
  65. package/build/src/body/shape.js +7 -1
  66. package/build/src/body/verification.js +19 -4
  67. package/build/src/cli/accepted.d.ts +2 -0
  68. package/build/src/cli/accepted.js +2 -1
  69. package/build/src/cli/commands/akuma-invoke.d.ts +42 -18
  70. package/build/src/cli/commands/akuma-invoke.js +94 -50
  71. package/build/src/cli/commands/akuma.d.ts +15 -7
  72. package/build/src/cli/commands/akuma.js +94 -56
  73. package/build/src/cli/commands/contract.d.ts +142 -0
  74. package/build/src/cli/commands/contract.js +14 -0
  75. package/build/src/cli/commands/install.js +2 -0
  76. package/build/src/cli/commands/task-invoke.d.ts +3 -1
  77. package/build/src/cli/commands/task-invoke.js +27 -2
  78. package/build/src/cli/commands/task.js +2 -2
  79. package/build/src/cli/invoke.d.ts +6 -1
  80. package/build/src/cli/invoke.js +241 -43
  81. package/build/src/cli/main.js +20 -1
  82. package/build/src/cli/parse.d.ts +13 -116
  83. package/build/src/cli/parse.js +51 -78
  84. package/build/src/cli/render/akuma-tool.d.ts +2 -0
  85. package/build/src/cli/render/akuma-tool.js +6 -2
  86. package/build/src/cli/render/akuma.d.ts +2 -1
  87. package/build/src/cli/render/akuma.js +157 -217
  88. package/build/src/cli/render/catalog.d.ts +2 -0
  89. package/build/src/cli/render/catalog.js +16 -0
  90. package/build/src/cli/render/contract.d.ts +1 -1
  91. package/build/src/cli/render/contract.js +38 -5
  92. package/build/src/cli/render/kanshi.js +53 -5
  93. package/build/src/cli/render/refusal.js +17 -0
  94. package/build/src/cli/render/terminal.d.ts +13 -0
  95. package/build/src/cli/render/terminal.js +75 -0
  96. package/build/src/cli/render/text.js +5 -0
  97. package/build/src/cli/result.d.ts +15 -3
  98. package/build/src/contract-worktree.d.ts +43 -0
  99. package/build/src/contract-worktree.js +213 -0
  100. package/build/src/coordination/durable-file.d.ts +3 -0
  101. package/build/src/coordination/durable-file.js +44 -1
  102. package/build/src/core/facts/codec.js +23 -4
  103. package/build/src/core/facts/eligibility.d.ts +1 -10
  104. package/build/src/core/facts/eligibility.js +0 -49
  105. package/build/src/core/facts/observation.d.ts +3 -0
  106. package/build/src/core/facts/observation.js +12 -0
  107. package/build/src/core/facts/types.d.ts +10 -3
  108. package/build/src/core/subject.js +2 -2
  109. package/build/src/core/verbs/amend.js +12 -16
  110. package/build/src/core/verbs/bind.js +2 -16
  111. package/build/src/core/verbs/deliver.js +20 -4
  112. package/build/src/core/verbs/placement.js +3 -8
  113. package/build/src/dispatch/index.d.ts +3 -1
  114. package/build/src/dispatch/index.js +14 -5
  115. package/build/src/duration.d.ts +10 -0
  116. package/build/src/duration.js +22 -0
  117. package/build/src/git/admission.d.ts +2 -2
  118. package/build/src/git/admission.js +26 -16
  119. package/build/src/git/hooks.d.ts +15 -0
  120. package/build/src/git/hooks.js +57 -5
  121. package/build/src/git/identity.d.ts +5 -1
  122. package/build/src/git/identity.js +23 -4
  123. package/build/src/git/integration.d.ts +49 -0
  124. package/build/src/git/integration.js +214 -0
  125. package/build/src/git/observe.d.ts +25 -13
  126. package/build/src/git/observe.js +175 -72
  127. package/build/src/git/read-observation.d.ts +41 -0
  128. package/build/src/git/read-observation.js +361 -0
  129. package/build/src/git/reconcile.d.ts +15 -5
  130. package/build/src/git/reconcile.js +238 -85
  131. package/build/src/git/repository.d.ts +19 -4
  132. package/build/src/git/repository.js +77 -43
  133. package/build/src/git/scratch.d.ts +13 -0
  134. package/build/src/git/scratch.js +66 -0
  135. package/build/src/git/target-placement.d.ts +56 -0
  136. package/build/src/git/target-placement.js +255 -0
  137. package/build/src/git/tender.d.ts +48 -0
  138. package/build/src/git/tender.js +82 -0
  139. package/build/src/git/terminal-seal.d.ts +24 -0
  140. package/build/src/git/terminal-seal.js +87 -0
  141. package/build/src/git/tree.d.ts +2 -2
  142. package/build/src/git/tree.js +4 -0
  143. package/build/src/git/workspace.d.ts +21 -0
  144. package/build/src/git/workspace.js +104 -0
  145. package/build/src/identity/selector.d.ts +6 -0
  146. package/build/src/identity/selector.js +19 -0
  147. package/build/src/index.d.ts +4 -2
  148. package/build/src/index.js +2 -1
  149. package/build/src/kanshi/index.d.ts +1 -1
  150. package/build/src/kanshi/read.d.ts +5 -2
  151. package/build/src/kanshi/read.js +154 -45
  152. package/build/src/kanshi/report.d.ts +31 -6
  153. package/build/src/kanshi/select.js +1 -1
  154. package/build/src/library/address.d.ts +48 -0
  155. package/build/src/library/address.js +134 -0
  156. package/build/src/library/akuma-creation.d.ts +10 -6
  157. package/build/src/library/akuma-creation.js +12 -11
  158. package/build/src/library/bind.d.ts +21 -0
  159. package/build/src/library/bind.js +56 -0
  160. package/build/src/library/catalog.d.ts +58 -0
  161. package/build/src/library/catalog.js +72 -0
  162. package/build/src/library/configuration.d.ts +6 -15
  163. package/build/src/library/configuration.js +14 -17
  164. package/build/src/library/contract.d.ts +21 -46
  165. package/build/src/library/contract.js +193 -183
  166. package/build/src/library/delivery.d.ts +23 -0
  167. package/build/src/library/delivery.js +21 -0
  168. package/build/src/library/fleet.d.ts +65 -0
  169. package/build/src/library/fleet.js +189 -0
  170. package/build/src/library/input.d.ts +3 -0
  171. package/build/src/library/input.js +19 -0
  172. package/build/src/library/keiyaku.d.ts +16 -2
  173. package/build/src/library/keiyaku.js +11 -1
  174. package/build/src/library/mutation.d.ts +38 -0
  175. package/build/src/library/mutation.js +39 -0
  176. package/build/src/library/refusal.d.ts +21 -0
  177. package/build/src/library/refusal.js +29 -0
  178. package/build/src/library/region.d.ts +2 -1
  179. package/build/src/library/region.js +3 -3
  180. package/build/src/library/repo.d.ts +4 -1
  181. package/build/src/library/repo.js +31 -16
  182. package/build/src/protocol/attempt.d.ts +16 -3
  183. package/build/src/protocol/attempt.js +39 -8
  184. package/build/src/protocol/bind.d.ts +9 -2
  185. package/build/src/protocol/bind.js +47 -39
  186. package/build/src/protocol/intent.d.ts +42 -18
  187. package/build/src/protocol/intent.js +36 -75
  188. package/build/src/protocol/operations.d.ts +93 -35
  189. package/build/src/protocol/operations.js +235 -123
  190. package/build/src/protocol/outcome.d.ts +7 -2
  191. package/build/src/protocol/outcome.js +9 -3
  192. package/build/src/protocol/placement.d.ts +30 -0
  193. package/build/src/protocol/placement.js +114 -0
  194. package/build/src/protocol/read/audit.d.ts +9 -3
  195. package/build/src/protocol/read/audit.js +25 -9
  196. package/build/src/protocol/read/documents.d.ts +2 -2
  197. package/build/src/protocol/read/documents.js +4 -4
  198. package/build/src/protocol/read/status.d.ts +11 -5
  199. package/build/src/protocol/read/status.js +28 -15
  200. package/build/src/protocol/run.d.ts +49 -7
  201. package/build/src/protocol/run.js +53 -25
  202. package/build/src/runtime/proc/line-rpc.d.ts +1 -0
  203. package/build/src/runtime/proc/line-rpc.js +21 -0
  204. package/build/src/runtime/proc/run.d.ts +18 -2
  205. package/build/src/runtime/proc/run.js +34 -8
  206. package/build/src/settings.d.ts +6 -0
  207. package/build/src/settings.js +29 -14
  208. package/build/src/settlement/fence.d.ts +9 -0
  209. package/build/src/settlement/fence.js +23 -0
  210. package/build/src/settlement/holder.d.ts +25 -4
  211. package/build/src/settlement/holder.js +102 -28
  212. package/build/src/settlement/settle.d.ts +8 -0
  213. package/build/src/settlement/settle.js +102 -28
  214. package/build/src/task/board.d.ts +20 -2
  215. package/build/src/task/board.js +11 -1
  216. package/build/src/task/compose.d.ts +2 -2
  217. package/build/src/task/compose.js +2 -2
  218. package/build/src/task/context.js +2 -7
  219. package/build/src/task/index.d.ts +9 -9
  220. package/build/src/task/index.js +15 -12
  221. package/build/src/task/operations.d.ts +31 -12
  222. package/build/src/task/operations.js +8 -4
  223. package/build/src/task/store.d.ts +4 -6
  224. package/build/src/task/store.js +3 -3
  225. package/build/src/verification/declaration.d.ts +1 -0
  226. package/build/src/verification/execution.d.ts +49 -0
  227. package/build/src/verification/execution.js +104 -0
  228. package/build/src/world.d.ts +13 -0
  229. package/build/src/world.js +103 -0
  230. package/package.json +7 -4
  231. package/build/src/akuma/activity.d.ts +0 -66
  232. package/build/src/akuma/activity.js +0 -139
  233. package/build/src/akuma/providers/claude.js +0 -305
  234. package/build/src/context-root.d.ts +0 -4
  235. package/build/src/context-root.js +0 -15
  236. package/build/src/git/delivery.d.ts +0 -20
  237. package/build/src/git/delivery.js +0 -109
  238. package/build/src/git/verification.d.ts +0 -14
  239. package/build/src/git/verification.js +0 -56
  240. package/build/src/verification/producer.d.ts +0 -23
  241. package/build/src/verification/producer.js +0 -54
@@ -1,16 +1,7 @@
1
- import type { Offer } from "./offer.js";
2
1
  import { type ContractsObservation } from "./observation.js";
3
- import type { ContractId, EntryUlid } from "./types.js";
2
+ import type { ContractId } from "./types.js";
4
3
  type EligibilityObservation = ContractsObservation;
5
- type EligibilityAttempt = Readonly<{
6
- entryUlids: readonly EntryUlid[];
7
- }>;
8
- type PrerequisiteStatus = "unknown" | "pending" | "claimed";
9
- /** Resolve prerequisite existence and eligibility from one decision snapshot. */
10
- export declare function prerequisiteStatus(prerequisites: readonly ContractId[], observation: EligibilityObservation, offeredClaims?: ReadonlySet<ContractId>): PrerequisiteStatus;
11
4
  /** Decide whether resulting prerequisites reach the amended contract. */
12
5
  export declare function prerequisitesReach(contract: ContractId, prerequisites: readonly ContractId[], observation: EligibilityObservation): boolean;
13
6
  export declare function samePrerequisites(left: readonly ContractId[], right: readonly ContractId[]): boolean;
14
- /** Append bounds made eligible by a claimed offer from its full snapshot. */
15
- export declare function placeEligibleBounds(offer: Offer, observation: EligibilityObservation, attempt: EligibilityAttempt): Offer;
16
7
  export {};
@@ -1,16 +1,4 @@
1
1
  import { contractState } from "./observation.js";
2
- /** Resolve prerequisite existence and eligibility from one decision snapshot. */
3
- export function prerequisiteStatus(prerequisites, observation, offeredClaims) {
4
- let pending = false;
5
- for (const dependency of prerequisites) {
6
- const state = contractState(observation, dependency);
7
- if (state === null)
8
- return "unknown";
9
- if (state.terminal?.kind !== "claimed" && offeredClaims?.has(dependency) !== true)
10
- pending = true;
11
- }
12
- return pending ? "pending" : "claimed";
13
- }
14
2
  /** Decide whether resulting prerequisites reach the amended contract. */
15
3
  export function prerequisitesReach(contract, prerequisites, observation) {
16
4
  const pending = [...prerequisites];
@@ -36,40 +24,3 @@ export function samePrerequisites(left, right) {
36
24
  return false;
37
25
  return left.every((id, index) => id === right[index]);
38
26
  }
39
- /** Append bounds made eligible by a claimed offer from its full snapshot. */
40
- export function placeEligibleBounds(offer, observation, attempt) {
41
- const claimedEntry = offer.facts[0]?.entries[0];
42
- if (offer.facts.length !== 1 || offer.facts[0]?.entries.length !== 1 || claimedEntry?.kind !== "claimed") {
43
- throw new Error("eligible-bound placement requires exactly one claimed fact");
44
- }
45
- const claimed = new Set([claimedEntry.contract]);
46
- const used = new Set([claimedEntry.entry]);
47
- const available = attempt.entryUlids.filter((entry) => !used.has(entry));
48
- let availableIndex = 0;
49
- const additions = [];
50
- for (const id of observation.keys()) {
51
- const state = contractState(observation, id);
52
- if (!state || state.bound || state.terminal)
53
- continue;
54
- if (prerequisiteStatus(state.terms.after, observation, claimed) !== "claimed")
55
- continue;
56
- const entry = available[availableIndex];
57
- if (entry === undefined)
58
- throw new Error("placement attempt lacks an entry ULID for an eligible contract");
59
- availableIndex += 1;
60
- additions.push({
61
- contractId: id,
62
- expectedHead: state.head,
63
- entries: [{
64
- v: 1,
65
- kind: "bound",
66
- contract: id,
67
- entry,
68
- at: claimedEntry.at,
69
- ...(claimedEntry.actor === undefined ? {} : { actor: claimedEntry.actor }),
70
- data: {},
71
- }],
72
- });
73
- }
74
- return additions.length === 0 ? offer : { ...offer, facts: [...additions, ...offer.facts] };
75
- }
@@ -1,8 +1,11 @@
1
1
  import type { ContractId, ContractState, DocumentKey } from "./types.js";
2
2
  /** The complete state projection consumed by one pure pact decision. */
3
3
  export type ContractsObservation = ReadonlyMap<ContractId, ContractState | null>;
4
+ export type PrerequisiteStatus = "unknown" | "pending" | "claimed";
4
5
  /** Read one requested identity; a missing key violates the observation contract. */
5
6
  export declare function contractState(observation: ContractsObservation, contract: ContractId): ContractState | null;
7
+ /** Project prerequisite existence and terminal satisfaction from one observation. */
8
+ export declare function prerequisiteStatus(prerequisites: readonly ContractId[], observation: ContractsObservation): PrerequisiteStatus;
6
9
  export type ActiveContractRefusal = Readonly<{
7
10
  kind: "contract-missing" | "terminal";
8
11
  contractId: ContractId;
@@ -9,6 +9,18 @@ export function contractState(observation, contract) {
9
9
  }
10
10
  return state;
11
11
  }
12
+ /** Project prerequisite existence and terminal satisfaction from one observation. */
13
+ export function prerequisiteStatus(prerequisites, observation) {
14
+ let pending = false;
15
+ for (const dependency of prerequisites) {
16
+ const state = contractState(observation, dependency);
17
+ if (state === null)
18
+ return "unknown";
19
+ if (state.terminal?.kind !== "claimed")
20
+ pending = true;
21
+ }
22
+ return pending ? "pending" : "claimed";
23
+ }
12
24
  /** The single lifecycle guard shared by every operation on an existing contract. */
13
25
  export function activeContract(observation, contract) {
14
26
  const state = contractState(observation, contract);
@@ -74,9 +74,16 @@ export type BindData = Readonly<{
74
74
  export type AmendData = ContractTerms;
75
75
  export type BoundData = Readonly<Record<string, never>>;
76
76
  export type DeliverData = Readonly<{
77
- expectedPredecessor: SnapshotId;
78
- candidate: SnapshotId;
79
- deliveryPatchId: ChangeId;
77
+ tenderSnapshot: SnapshotId;
78
+ integration: Readonly<{
79
+ predecessor: SnapshotId;
80
+ snapshot: SnapshotId;
81
+ changeId: ChangeId;
82
+ }>;
83
+ method: "squash";
84
+ policy: Readonly<{
85
+ requireBranchesToBeUpToDate: boolean;
86
+ }>;
80
87
  }>;
81
88
  export type AttestationData = Readonly<{
82
89
  gate: Gate;
@@ -69,8 +69,8 @@ function currentKeys(state) {
69
69
  encodeKey({ kind: "document", value: state.terms.document.key }),
70
70
  ...state.terms.segments.map((value) => encodeKey({ kind: "segment", value })),
71
71
  ...(state.delivery === null ? [] : [
72
- encodeKey({ kind: "snapshot", value: state.delivery.data.candidate }),
73
- encodeKey({ kind: "change", value: state.delivery.data.deliveryPatchId }),
72
+ encodeKey({ kind: "snapshot", value: state.delivery.data.integration.snapshot }),
73
+ encodeKey({ kind: "change", value: state.delivery.data.integration.changeId }),
74
74
  ]),
75
75
  ]);
76
76
  }
@@ -1,5 +1,13 @@
1
- import { prerequisiteStatus, prerequisitesReach, samePrerequisites } from "../facts/eligibility.js";
2
- import { activeContract } from "../facts/observation.js";
1
+ import { prerequisitesReach, samePrerequisites } from "../facts/eligibility.js";
2
+ import { activeContract, prerequisiteStatus } from "../facts/observation.js";
3
+ function prerequisitesConsumed(current, next, observation) {
4
+ if (samePrerequisites(current.terms.after, next.after))
5
+ return false;
6
+ const status = prerequisiteStatus(current.terms.after, observation);
7
+ if (status === "unknown")
8
+ throw new Error("current prerequisite is absent from the decision observation");
9
+ return current.bound !== null || status === "claimed";
10
+ }
3
11
  export function decideAmend({ input, attempt, observation }) {
4
12
  const id = input.contractId;
5
13
  const current = activeContract(observation, id);
@@ -21,7 +29,7 @@ export function decideAmend({ input, attempt, observation }) {
21
29
  if (input.preparation.kind === "refused")
22
30
  return { kind: "refused", refusal: input.preparation.refusal };
23
31
  const { data } = input.preparation;
24
- if (current.bound !== null && !samePrerequisites(current.terms.after, data.after)) {
32
+ if (prerequisitesConsumed(current, data, observation)) {
25
33
  return { kind: "refused", refusal: { kind: "prerequisites-already-consumed", contractId: id } };
26
34
  }
27
35
  const prerequisites = prerequisiteStatus(data.after, observation);
@@ -40,17 +48,5 @@ export function decideAmend({ input, attempt, observation }) {
40
48
  ...(input.actor === undefined ? {} : { actor: input.actor }),
41
49
  data,
42
50
  };
43
- const entries = [entry];
44
- if (current.bound === null && prerequisites === "claimed") {
45
- entries.push({
46
- v: 1,
47
- kind: "bound",
48
- contract: id,
49
- entry: attempt.entryUlids[1],
50
- at: input.at,
51
- ...(input.actor === undefined ? {} : { actor: input.actor }),
52
- data: {},
53
- });
54
- }
55
- return { kind: "offer", offer: { facts: [{ contractId: id, expectedHead: current.head, entries }] } };
51
+ return { kind: "offer", offer: { facts: [{ contractId: id, expectedHead: current.head, entries: [entry] }] } };
56
52
  }
@@ -1,5 +1,4 @@
1
- import { prerequisiteStatus } from "../facts/eligibility.js";
2
- import { contractState } from "../facts/observation.js";
1
+ import { contractState, prerequisiteStatus } from "../facts/observation.js";
3
2
  export function decideBind({ input, attempt, observation }) {
4
3
  const id = input.contractId;
5
4
  const current = contractState(observation, id);
@@ -23,18 +22,5 @@ export function decideBind({ input, attempt, observation }) {
23
22
  ...(input.actor === undefined ? {} : { actor: input.actor }),
24
23
  data,
25
24
  };
26
- const entries = [bind];
27
- if (prerequisites === "claimed") {
28
- const bound = {
29
- v: 1,
30
- kind: "bound",
31
- contract: id,
32
- entry: attempt.entryUlids[1],
33
- at: input.at,
34
- ...(input.actor === undefined ? {} : { actor: input.actor }),
35
- data: {},
36
- };
37
- entries.push(bound);
38
- }
39
- return { kind: "offer", offer: { facts: [{ contractId: id, expectedHead: null, entries }] } };
25
+ return { kind: "offer", offer: { facts: [{ contractId: id, expectedHead: null, entries: [bind] }] } };
40
26
  }
@@ -1,10 +1,14 @@
1
- import { activeContract, documentIsCurrent } from "../facts/observation.js";
1
+ import { activeContract, documentIsCurrent, prerequisiteStatus } from "../facts/observation.js";
2
2
  export function decideDeliver({ input, attempt, observation, }) {
3
3
  const state = activeContract(observation, input.contractId);
4
4
  if ("kind" in state)
5
5
  return { kind: "refused", refusal: state };
6
- if (state.bound === null)
6
+ const prerequisites = state.bound === null
7
+ ? prerequisiteStatus(state.terms.after, observation)
8
+ : "claimed";
9
+ if (prerequisites !== "claimed") {
7
10
  return { kind: "refused", refusal: { kind: "not-bound", contractId: input.contractId } };
11
+ }
8
12
  if (input.preparation.kind === "unavailable") {
9
13
  throw new Error("existing contract requires a stamped delivery preparation");
10
14
  }
@@ -13,14 +17,26 @@ export function decideDeliver({ input, attempt, observation, }) {
13
17
  }
14
18
  if (input.preparation.kind === "refused")
15
19
  return { kind: "refused", refusal: input.preparation.refusal };
20
+ const entries = [];
21
+ if (state.bound === null)
22
+ entries.push({
23
+ v: 1,
24
+ kind: "bound",
25
+ contract: input.contractId,
26
+ entry: attempt.entryUlids[0],
27
+ at: input.at,
28
+ ...(input.actor === undefined ? {} : { actor: input.actor }),
29
+ data: {},
30
+ });
16
31
  const deliver = {
17
32
  v: 1,
18
33
  kind: "deliver",
19
34
  contract: input.contractId,
20
- entry: attempt.entryUlids[0],
35
+ entry: attempt.entryUlids[state.bound === null ? 1 : 0],
21
36
  at: input.at,
22
37
  ...(input.actor === undefined ? {} : { actor: input.actor }),
23
38
  data: input.preparation.data,
24
39
  };
25
- return { kind: "offer", offer: { facts: [{ contractId: input.contractId, expectedHead: state.head, entries: [deliver] }] } };
40
+ entries.push(deliver);
41
+ return { kind: "offer", offer: { facts: [{ contractId: input.contractId, expectedHead: state.head, entries }] } };
26
42
  }
@@ -1,5 +1,4 @@
1
1
  import { gatesSatisfied } from "../facts/gate.js";
2
- import { placeEligibleBounds } from "../facts/eligibility.js";
3
2
  import { activeContract } from "../facts/observation.js";
4
3
  export function decidePlacement({ input, attempt, observation }) {
5
4
  const id = input.contractId;
@@ -22,21 +21,17 @@ export function decidePlacement({ input, attempt, observation }) {
22
21
  ...(input.actor === undefined ? {} : { actor: input.actor }),
23
22
  data: { delivery: delivery.entry },
24
23
  };
25
- const offer = {
24
+ return {
26
25
  kind: "offer",
27
26
  offer: {
28
27
  facts: [{ contractId: id, expectedHead: current.head, entries: [claimed] }],
29
28
  ...(current.coordinates.target === undefined ? {} : {
30
29
  target: {
31
30
  target: current.coordinates.target,
32
- expectedOid: delivery.data.expectedPredecessor,
33
- newOid: delivery.data.candidate,
31
+ expectedOid: delivery.data.integration.predecessor,
32
+ newOid: delivery.data.integration.snapshot,
34
33
  },
35
34
  }),
36
35
  },
37
36
  };
38
- return {
39
- ...offer,
40
- offer: placeEligibleBounds(offer.offer, observation, attempt),
41
- };
42
37
  }
@@ -1,6 +1,7 @@
1
1
  import { type ContractId } from "../core/facts/types.js";
2
2
  import { type AkuId } from "../akuma/identity.js";
3
3
  import { type GitRepository } from "../git/repository.js";
4
+ import { type GitReadObservation } from "../git/read-observation.js";
4
5
  export type Dispatch = Readonly<{
5
6
  akuId: AkuId;
6
7
  contractId: ContractId;
@@ -23,7 +24,8 @@ export type DispatchPublication = Readonly<{
23
24
  failure: DispatchFailure;
24
25
  }>;
25
26
  export declare function readDispatch(repository: GitRepository, value: AkuId): Dispatch | null;
26
- export declare function readDispatches(repository: GitRepository): readonly Dispatch[];
27
+ export declare function readDispatchesAt(observation: GitReadObservation): Promise<readonly Dispatch[]>;
28
+ export declare function readDispatches(repository: GitRepository): Promise<readonly Dispatch[]>;
27
29
  export declare function publishDispatch(input: Readonly<{
28
30
  repository: GitRepository;
29
31
  akuId: AkuId;
@@ -2,7 +2,8 @@ import { createHash } from "node:crypto";
2
2
  import { AuthorityCorruptionError } from "../core/facts/errors.js";
3
3
  import { contractId } from "../core/facts/types.js";
4
4
  import { parseAkuId } from "../akuma/identity.js";
5
- import { GIT_FORMAT_BYTES, GIT_FORMAT_PATH, GIT_REF, readBlob, readGit, readGitPaths, updateGitTree, updateRefsAtomically, writeBlob, writeCommit, } from "../git/repository.js";
5
+ import { GIT_FORMAT_BYTES, GIT_FORMAT_PATH, GIT_REF, readBlob, readGitPaths, updateGitTree, updateRefsAtomically, writeBlob, writeCommit, } from "../git/repository.js";
6
+ import { withGitDecodeChannel, withGitReadObservation } from "../git/read-observation.js";
6
7
  const DISPATCH_ROOT = "dispatch";
7
8
  const DISPATCH_PREFIX = `${DISPATCH_ROOT}/`;
8
9
  const DISPATCH_SUFFIX = ".json";
@@ -77,11 +78,13 @@ export function readDispatch(repository, value) {
77
78
  const path = pathFor(akuId);
78
79
  return dispatchFromSnapshot(repository, readGitPaths(repository, [path]), akuId);
79
80
  }
80
- export function readDispatches(repository) {
81
- const snapshot = readGit(repository);
81
+ export async function readDispatchesAt(observation) {
82
+ const entries = [...observation.snapshot.paths]
83
+ .filter(([path, entry]) => path.startsWith(DISPATCH_PREFIX) && entry.type === "blob");
84
+ const blobs = await observation.readBlobs(entries.map(([, entry]) => entry.oid));
82
85
  const dispatches = [];
83
86
  const seen = new Set();
84
- for (const [path, entry] of snapshot.paths) {
87
+ for (const [path, entry] of observation.snapshot.paths) {
85
88
  if (path === DISPATCH_ROOT)
86
89
  corruption(`Dispatch authority root is not a tree: ${path}`);
87
90
  if (!path.startsWith(DISPATCH_PREFIX))
@@ -90,7 +93,10 @@ export function readDispatches(repository) {
90
93
  corruption(`unexpected Dispatch authority path: ${path}`);
91
94
  if (entry.type !== "blob")
92
95
  corruption(`Dispatch path is not a blob: ${path}`);
93
- const dispatch = decode(path, readBlob(repository, entry.oid));
96
+ const result = blobs.get(entry.oid);
97
+ if (result?.kind !== "present")
98
+ corruption(`missing Dispatch Git object: ${path}`);
99
+ const dispatch = decode(path, result.bytes);
94
100
  if (seen.has(dispatch.akuId))
95
101
  corruption(`duplicate Dispatch identity: ${dispatch.akuId}`);
96
102
  seen.add(dispatch.akuId);
@@ -98,6 +104,9 @@ export function readDispatches(repository) {
98
104
  }
99
105
  return dispatches.sort((left, right) => Buffer.compare(Buffer.from(left.akuId), Buffer.from(right.akuId)));
100
106
  }
107
+ export async function readDispatches(repository) {
108
+ return withGitDecodeChannel(repository, (channel) => withGitReadObservation(repository, channel, readDispatchesAt));
109
+ }
101
110
  function observedPublication(repository, akuId, owner) {
102
111
  const current = readDispatch(repository, akuId);
103
112
  if (current === null)
@@ -0,0 +1,10 @@
1
+ export type DurationParseResult = Readonly<{
2
+ kind: "parsed";
3
+ milliseconds: number;
4
+ }> | Readonly<{
5
+ kind: "invalid";
6
+ }> | Readonly<{
7
+ kind: "overflow";
8
+ }>;
9
+ export declare function parseDuration(value: string): DurationParseResult;
10
+ export declare function formatDuration(milliseconds: number): string;
@@ -0,0 +1,22 @@
1
+ const MULTIPLIERS = Object.freeze({
2
+ ms: 1n,
3
+ s: 1000n,
4
+ m: 60000n,
5
+ h: 3600000n,
6
+ });
7
+ export function parseDuration(value) {
8
+ const match = /^(0|[1-9][0-9]*)(ms|s|m|h)$/u.exec(value);
9
+ if (match === null)
10
+ return { kind: "invalid" };
11
+ const milliseconds = BigInt(match[1]) * MULTIPLIERS[match[2]];
12
+ if (milliseconds > BigInt(Number.MAX_SAFE_INTEGER))
13
+ return { kind: "overflow" };
14
+ return { kind: "parsed", milliseconds: Number(milliseconds) };
15
+ }
16
+ export function formatDuration(milliseconds) {
17
+ for (const [unit, multiplier] of [["h", 3_600_000], ["m", 60_000], ["s", 1_000]]) {
18
+ if (milliseconds >= multiplier && milliseconds % multiplier === 0)
19
+ return `${milliseconds / multiplier}${unit}`;
20
+ }
21
+ return `${milliseconds}ms`;
22
+ }
@@ -1,6 +1,6 @@
1
1
  import type { Offer } from "../core/facts/offer.js";
2
2
  import type { GitAdmissionSnapshot } from "./observe.js";
3
- import { type GitRepository } from "./repository.js";
3
+ import { type GitRepository, type GitRefAssertion } from "./repository.js";
4
4
  import { type ContractHead } from "../core/facts/types.js";
5
5
  type Accepted = Readonly<{
6
6
  kind: "accepted";
@@ -14,5 +14,5 @@ type Unknown = Readonly<{
14
14
  kind: "unknown";
15
15
  }>;
16
16
  export type Admission = Accepted | PublicationFailed | Unknown;
17
- export declare function admit(repository: GitRepository, offer: Offer, admission: GitAdmissionSnapshot): Admission;
17
+ export declare function admit(repository: GitRepository, offer: Offer, admission: GitAdmissionSnapshot, assertions?: readonly GitRefAssertion[]): Admission;
18
18
  export {};
@@ -1,8 +1,8 @@
1
- import { encodeEntry, } from "../core/facts/codec.js";
2
- import { GIT_FORMAT_BYTES, GIT_FORMAT_PATH, GIT_REF, updateGitTree, updateRefsAtomically, writeBlob, writeCommit, } from "./repository.js";
3
- import { contractJournalPath, gitObjectIdForSnapshot, mintContractHead, mintSnapshotId, } from "./identity.js";
1
+ import { decodeJournal, encodeEntry, } from "../core/facts/codec.js";
2
+ import { GIT_FORMAT_BYTES, GIT_FORMAT_PATH, GIT_REF, updateGitTreeFromFrozenDirectories, updateRefsAtomically, writeBlob, writeCommit, } from "./repository.js";
3
+ import { contractJournalPath, contractJournalPaths, gitObjectIdForSnapshot, mintContractHead, mintSnapshotId, } from "./identity.js";
4
4
  import { validPath } from "./tree.js";
5
- const CONTRACT_JOURNAL_PATH = /^contracts\/[0-9a-f]{2}\/[0-9a-f]{2}\/[0-9a-f]{60}\.jsonl$/u;
5
+ const CONTRACT_JOURNAL_PATH = /^contracts\/(?:active|terminal)\/[0-9a-f]{2}\/[0-9a-f]{2}\/[0-9a-f]{60}\.jsonl$/u;
6
6
  function assertAppendStructure(appends) {
7
7
  const seen = new Set();
8
8
  for (const append of appends) {
@@ -20,7 +20,7 @@ function assertAppendStructure(appends) {
20
20
  return appends;
21
21
  }
22
22
  function assertCompanionStructure(companions, appends) {
23
- const reserved = new Set([GIT_FORMAT_PATH, ...appends.map((append) => contractJournalPath(append.contractId))]);
23
+ const reserved = new Set([GIT_FORMAT_PATH, ...appends.flatMap((append) => contractJournalPaths(append.contractId))]);
24
24
  const seen = new Set();
25
25
  for (const companion of companions) {
26
26
  if (!companion || typeof companion !== "object")
@@ -39,16 +39,19 @@ function assertCompanionStructure(companions, appends) {
39
39
  }
40
40
  function readCanonicalJournal(admission, id) {
41
41
  const snapshot = admission.snapshot;
42
- const path = contractJournalPath(id);
42
+ const matches = contractJournalPaths(id).filter((path) => snapshot.paths.has(path));
43
+ if (matches.length > 1)
44
+ throw new Error(`duplicate contract journal identity: ${id}`);
45
+ const path = matches[0];
46
+ if (path === undefined)
47
+ return { bytes: Buffer.alloc(0), path: null };
43
48
  const entry = snapshot.paths.get(path);
44
- if (entry === undefined)
45
- return Buffer.alloc(0);
46
49
  if (entry.type !== "blob")
47
50
  throw new Error(`journal path is not a blob: ${path}`);
48
51
  const journal = admission.frozenJournalBytes.get(entry.oid);
49
52
  if (journal === undefined)
50
53
  throw new Error(`missing frozen journal bytes: ${path}`);
51
- return journal;
54
+ return { bytes: journal, path };
52
55
  }
53
56
  function buildOffer(repository, admission, appends, companions) {
54
57
  const snapshot = admission.snapshot;
@@ -65,17 +68,24 @@ function buildOffer(repository, admission, appends, companions) {
65
68
  });
66
69
  }
67
70
  for (const append of appends) {
68
- let journal = readCanonicalJournal(admission, append.contractId);
71
+ const current = readCanonicalJournal(admission, append.contractId);
72
+ let journal = current.bytes;
69
73
  for (const entry of append.entries)
70
74
  journal = Buffer.concat([journal, Buffer.from(encodeEntry(entry))]);
71
75
  const blob = writeBlob(repository, journal);
72
- changes.set(contractJournalPath(append.contractId), { oid: blob, mode: "100644", type: "blob" });
76
+ const entries = decodeJournal(journal.toString("utf8"));
77
+ const terminal = entries.some((entry) => entry.kind === "claimed" || entry.kind === "abandoned");
78
+ const destination = contractJournalPath(append.contractId, terminal ? "terminal" : "active");
79
+ if (current.path !== null && current.path !== destination)
80
+ changes.set(current.path, null);
81
+ changes.set(destination, { oid: blob, mode: "100644", type: "blob" });
73
82
  heads[append.contractId] = mintContractHead(blob);
74
83
  }
75
84
  return { changes, heads };
76
85
  }
77
- function publishOffer(repository, snapshot, target, changes) {
78
- const gitTree = updateGitTree(repository, snapshot.tree, changes);
86
+ function publishOffer(repository, admission, target, changes, assertions) {
87
+ const { snapshot } = admission;
88
+ const gitTree = updateGitTreeFromFrozenDirectories(repository, snapshot.tree, admission.treeDirectories, changes);
79
89
  const gitCommit = mintSnapshotId(writeCommit({
80
90
  repository,
81
91
  tree: gitTree,
@@ -90,9 +100,9 @@ function publishOffer(repository, snapshot, target, changes) {
90
100
  newOid: gitObjectIdForSnapshot(target.newOid),
91
101
  expectedOid: gitObjectIdForSnapshot(target.expectedOid),
92
102
  }]),
93
- ]);
103
+ ], assertions);
94
104
  }
95
- export function admit(repository, offer, admission) {
105
+ export function admit(repository, offer, admission, assertions = []) {
96
106
  if (!Array.isArray(offer.facts) || offer.facts.length === 0) {
97
107
  throw new Error("facts must be a nonempty array");
98
108
  }
@@ -100,7 +110,7 @@ export function admit(repository, offer, admission) {
100
110
  const companions = assertCompanionStructure(offer.companions ?? [], appends);
101
111
  const target = offer.target ?? null;
102
112
  const attempt = buildOffer(repository, admission, appends, companions);
103
- const publication = publishOffer(repository, admission.snapshot, target, attempt.changes);
113
+ const publication = publishOffer(repository, admission, target, attempt.changes, assertions);
104
114
  if (publication.kind === "published") {
105
115
  return {
106
116
  kind: "accepted",
@@ -1,3 +1,4 @@
1
+ import { type Settings } from "../settings.js";
1
2
  export type HookCommand = Readonly<{
2
3
  argv: readonly string[];
3
4
  timeoutMs: number;
@@ -28,7 +29,21 @@ export type WorktreeHookLag = Readonly<{
28
29
  failure: HookFailure;
29
30
  }>;
30
31
  type HookPhase = "create" | "destroy";
32
+ export declare function worktreeHooksFrom(input: Readonly<{
33
+ settings: Settings;
34
+ }>): WorktreeHooks;
31
35
  export declare function hookMarkerPath(administrationDirectory: string): string;
36
+ export type HookCommandRun = Readonly<{
37
+ kind: "ok";
38
+ }> | Readonly<{
39
+ kind: "cancelled";
40
+ }> | Readonly<{
41
+ kind: "failed";
42
+ command: number;
43
+ failure: HookFailure;
44
+ }>;
45
+ /** Execute an ordered command list without lifecycle state. Scratch owns this policy. */
46
+ export declare function runHookCommands(worktree: string, commands: readonly HookCommand[], signal?: AbortSignal): Promise<HookCommandRun>;
32
47
  export declare function runCreateHooks(worktree: string, administrationDirectory: string, hooks: WorktreeHooks, retryHooks: boolean): Promise<WorktreeHookLag | null>;
33
48
  export declare function runDestroyHooks(worktree: string, administrationDirectory: string, hooks: WorktreeHooks, retryHooks: boolean): Promise<WorktreeHookLag | null>;
34
49
  export {};
@@ -4,10 +4,46 @@ import { fileURLToPath } from "node:url";
4
4
  import { replaceFileDurably } from "../coordination/durable-file.js";
5
5
  import { acquireSqliteTransactionLock } from "../coordination/sqlite-transaction-lock.js";
6
6
  import { runProcess, runProcessToExit } from "../runtime/proc/run.js";
7
+ import { SettingsError } from "../settings.js";
7
8
  const MARKER_VERSION = 1;
8
9
  function object(value) {
9
10
  return value !== null && typeof value === "object" && !Array.isArray(value);
10
11
  }
12
+ export function worktreeHooksFrom(input) {
13
+ if (!object(input))
14
+ throw new TypeError("worktreeHooksFrom input must be an object");
15
+ const view = input.settings.namespace("worktree");
16
+ if (view.kind === "failed") {
17
+ throw new SettingsError(view.failures.map((failure) => `${failure.scope}: ${failure.diagnostic}`).join("; "));
18
+ }
19
+ for (const entry of view.entries) {
20
+ if (entry.name !== "create" && entry.name !== "destroy")
21
+ throw new SettingsError(`worktree has unknown entry: ${entry.name}`);
22
+ }
23
+ const commands = (phase) => {
24
+ const selected = view.entries.find((entry) => entry.name === phase);
25
+ if (selected === undefined)
26
+ return Object.freeze([]);
27
+ if (!Array.isArray(selected.value))
28
+ throw new SettingsError(`worktree.${phase} must be an array`);
29
+ return Object.freeze(selected.value.map((item, index) => {
30
+ if (!object(item))
31
+ throw new SettingsError(`worktree.${phase}[${index}] must be an object`);
32
+ const coordinate = `worktree.${phase}[${index}]`;
33
+ exactKeys(item, ["argv", "timeoutMs"], coordinate);
34
+ if (!Array.isArray(item.argv) || item.argv.length === 0 || !item.argv.every((value) => typeof value === "string")) {
35
+ throw new SettingsError(`${coordinate}.argv must be a nonempty string array`);
36
+ }
37
+ if (item.argv[0].trim().length === 0)
38
+ throw new SettingsError(`${coordinate}.argv[0] must be nonblank`);
39
+ if (!Number.isSafeInteger(item.timeoutMs) || item.timeoutMs < 1 || item.timeoutMs > 2_147_483_647) {
40
+ throw new SettingsError(`${coordinate}.timeoutMs must be an integer from 1 through 2147483647`);
41
+ }
42
+ return Object.freeze({ argv: Object.freeze([...item.argv]), timeoutMs: item.timeoutMs });
43
+ }));
44
+ };
45
+ return Object.freeze({ create: commands("create"), destroy: commands("destroy") });
46
+ }
11
47
  function exactKeys(value, keys, coordinate) {
12
48
  const actual = Object.keys(value).sort();
13
49
  const expected = [...keys].sort();
@@ -139,6 +175,23 @@ function failedOutcome(outcome) {
139
175
  }
140
176
  return outcome;
141
177
  }
178
+ /** Execute an ordered command list without lifecycle state. Scratch owns this policy. */
179
+ export async function runHookCommands(worktree, commands, signal) {
180
+ for (const [command, value] of commands.entries()) {
181
+ const outcome = await runProcess({
182
+ argv: value.argv,
183
+ timeoutMs: value.timeoutMs,
184
+ cwd: worktree,
185
+ ...(signal === undefined ? {} : { signal }),
186
+ });
187
+ if (outcome.kind === "cancelled")
188
+ return outcome;
189
+ const failure = failedOutcome(outcome);
190
+ if (failure !== null)
191
+ return { kind: "failed", command, failure };
192
+ }
193
+ return { kind: "ok" };
194
+ }
142
195
  function withProgress(marker, phase, progress) {
143
196
  return { ...marker, [phase]: progress };
144
197
  }
@@ -183,11 +236,10 @@ async function executePendingCommand(input) {
183
236
  const command = marker.commands[input.phase][input.command];
184
237
  if (command === undefined)
185
238
  throw new Error("worktree hook marker pending index is out of range");
186
- const failure = failedOutcome(await runProcess({
187
- argv: command.argv,
188
- timeoutMs: command.timeoutMs,
189
- cwd: input.worktree,
190
- }));
239
+ const result = await runHookCommands(input.worktree, [command]);
240
+ if (result.kind === "cancelled")
241
+ throw new Error("managed hook runner cancelled without a signal");
242
+ const failure = result.kind === "failed" ? result.failure : null;
191
243
  const next = input.command + 1;
192
244
  writeMarker(input.administrationDirectory, withProgress(marker, input.phase, failure === null
193
245
  ? next === marker.commands[input.phase].length ? { status: "ok" } : { status: "pending", next }
@@ -4,7 +4,11 @@ export type GitObjectId = string & {
4
4
  readonly [gitObjectIdBrand]: "GitObjectId";
5
5
  };
6
6
  export declare function contractLocator(contract: ContractId): string;
7
- export declare function contractJournalPath(contract: ContractId): string;
7
+ export declare function gitRefLocator(ref: string): string;
8
+ export declare function contractPhysicalName(contract: ContractId): string;
9
+ export type ContractJournalClass = "active" | "terminal";
10
+ export declare function contractJournalPath(contract: ContractId, journalClass?: ContractJournalClass): string;
11
+ export declare function contractJournalPaths(contract: ContractId): readonly string[];
8
12
  export declare function gitObjectId(value: string, label?: string): GitObjectId;
9
13
  export declare function mintSnapshotId(value: string): SnapshotId;
10
14
  export declare function mintChangeId(value: string): ChangeId;