@astrosheep/keiyaku 4.5.20 → 4.5.21

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 (154) hide show
  1. package/README.md +4 -4
  2. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +3 -3
  3. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +22 -8
  4. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/references/automation.md +237 -0
  5. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +6 -7
  6. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-task/SKILL.md +2 -3
  7. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +3 -3
  8. package/build/src/akuma/akuma-handle.d.ts +5 -1
  9. package/build/src/akuma/akuma-handle.js +4 -1
  10. package/build/src/akuma/akuma-instance.d.ts +7 -2
  11. package/build/src/akuma/akuma-instance.js +14 -7
  12. package/build/src/akuma/akuma-observe.d.ts +11 -0
  13. package/build/src/akuma/akuma-observe.js +22 -0
  14. package/build/src/akuma/akuma-product.d.ts +1 -0
  15. package/build/src/akuma/akuma-product.js +3 -0
  16. package/build/src/akuma/akuma.d.ts +1 -0
  17. package/build/src/akuma/body.d.ts +25 -5
  18. package/build/src/akuma/body.js +65 -8
  19. package/build/src/akuma/call-request.d.ts +2 -0
  20. package/build/src/akuma/call-request.js +2 -0
  21. package/build/src/akuma/fleet-execution.d.ts +28 -0
  22. package/build/src/akuma/fleet-execution.js +36 -4
  23. package/build/src/akuma/fleet-request.d.ts +10 -0
  24. package/build/src/akuma/fleet-request.js +11 -2
  25. package/build/src/akuma/heart/facts.d.ts +2 -0
  26. package/build/src/akuma/heart/index.d.ts +1 -0
  27. package/build/src/akuma/heart/index.js +4 -1
  28. package/build/src/akuma/heart/rows.d.ts +2 -0
  29. package/build/src/akuma/heart/rows.js +7 -5
  30. package/build/src/akuma/heart/schema.d.ts +1 -1
  31. package/build/src/akuma/heart/schema.js +3 -1
  32. package/build/src/akuma/heart/tells.js +6 -5
  33. package/build/src/akuma/heart/timeline.js +1 -1
  34. package/build/src/akuma/provider.d.ts +1 -1
  35. package/build/src/akuma/provider.js +1 -1
  36. package/build/src/akuma/providers/acp/core.js +3 -6
  37. package/build/src/akuma/providers/claude/index.js +9 -12
  38. package/build/src/akuma/providers/codex-app-server/index.js +4 -11
  39. package/build/src/akuma/providers/execution-environment.d.ts +6 -0
  40. package/build/src/akuma/providers/execution-environment.js +24 -0
  41. package/build/src/akuma/providers/opencode-sdk/index.js +30 -23
  42. package/build/src/akuma/providers/opencode-sdk/session.d.ts +8 -1
  43. package/build/src/akuma/providers/opencode-sdk/session.js +6 -5
  44. package/build/src/akuma/providers/pi/events.js +7 -2
  45. package/build/src/akuma/providers/pi/index.js +3 -2
  46. package/build/src/akuma/request-rendezvous.js +8 -3
  47. package/build/src/akuma/turn-drive.d.ts +1 -0
  48. package/build/src/akuma/turn-drive.js +2 -0
  49. package/build/src/akuma-body.js +2 -0
  50. package/build/src/body/amend.js +6 -4
  51. package/build/src/body/decode.d.ts +3 -1
  52. package/build/src/body/decode.js +53 -17
  53. package/build/src/body/region.d.ts +9 -1
  54. package/build/src/body/region.js +87 -12
  55. package/build/src/body/render.js +1 -1
  56. package/build/src/body/verification.d.ts +3 -1
  57. package/build/src/body/verification.js +4 -1
  58. package/build/src/cli/accepted.js +19 -2
  59. package/build/src/cli/commands/akuma-invoke.d.ts +2 -0
  60. package/build/src/cli/commands/akuma-invoke.js +133 -13
  61. package/build/src/cli/commands/akuma.js +11 -4
  62. package/build/src/cli/commands/contract-help.js +17 -4
  63. package/build/src/cli/commands/contract.js +6 -4
  64. package/build/src/cli/commands/status-set.js +15 -24
  65. package/build/src/cli/main.js +5 -0
  66. package/build/src/cli/parse.d.ts +2 -0
  67. package/build/src/cli/parse.js +5 -0
  68. package/build/src/cli/render/akuma-activity.d.ts +99 -2
  69. package/build/src/cli/render/akuma-activity.js +484 -40
  70. package/build/src/cli/render/akuma.d.ts +10 -0
  71. package/build/src/cli/render/akuma.js +28 -21
  72. package/build/src/cli/render/audit.js +1 -1
  73. package/build/src/cli/render/board.js +1 -1
  74. package/build/src/cli/render/catalog.js +16 -13
  75. package/build/src/cli/render/contract-history.js +39 -10
  76. package/build/src/cli/render/contract-observation.js +10 -6
  77. package/build/src/cli/render/contract.js +109 -106
  78. package/build/src/cli/render/execution-progress.d.ts +23 -1
  79. package/build/src/cli/render/execution-progress.js +151 -28
  80. package/build/src/cli/render/kanshi-akuma.js +27 -39
  81. package/build/src/cli/render/kanshi.js +33 -26
  82. package/build/src/cli/render/nuke.js +1 -1
  83. package/build/src/cli/render/receipt.d.ts +1 -1
  84. package/build/src/cli/render/receipt.js +5 -5
  85. package/build/src/cli/render/refusal.js +10 -7
  86. package/build/src/cli/render/region.js +1 -1
  87. package/build/src/cli/render/settings.js +12 -2
  88. package/build/src/cli/render/status-line.d.ts +30 -0
  89. package/build/src/cli/render/status-line.js +58 -0
  90. package/build/src/cli/render/status-set.js +1 -1
  91. package/build/src/cli/render/task.d.ts +2 -0
  92. package/build/src/cli/render/task.js +11 -7
  93. package/build/src/cli/render/terminal.d.ts +0 -1
  94. package/build/src/cli/render/terminal.js +47 -15
  95. package/build/src/cli/result.d.ts +1 -0
  96. package/build/src/cli/runtime.d.ts +2 -0
  97. package/build/src/cli/runtime.js +13 -7
  98. package/build/src/cli/usage.js +2 -2
  99. package/build/src/cli/version.d.ts +1 -0
  100. package/build/src/cli/version.js +25 -0
  101. package/build/src/git/workspace.d.ts +1 -1
  102. package/build/src/git/workspace.js +1 -1
  103. package/build/src/identity/mint.d.ts +13 -0
  104. package/build/src/identity/mint.js +26 -0
  105. package/build/src/identity/normalize.d.ts +4 -0
  106. package/build/src/identity/normalize.js +30 -0
  107. package/build/src/index.d.ts +2 -0
  108. package/build/src/index.js +1 -0
  109. package/build/src/kanshi/read.js +17 -4
  110. package/build/src/kanshi/report.d.ts +1 -0
  111. package/build/src/library/akuma-creation.d.ts +2 -0
  112. package/build/src/library/akuma-creation.js +33 -17
  113. package/build/src/library/bind.js +18 -16
  114. package/build/src/library/contract-bind.d.ts +3 -1
  115. package/build/src/library/contract-bind.js +5 -3
  116. package/build/src/library/contract-operations.d.ts +2 -2
  117. package/build/src/library/contract-operations.js +1 -1
  118. package/build/src/library/contract-types.d.ts +1 -0
  119. package/build/src/library/fleet.d.ts +4 -1
  120. package/build/src/library/fleet.js +32 -8
  121. package/build/src/plugin/akuma-signals.d.ts +6 -0
  122. package/build/src/plugin/akuma-signals.js +8 -0
  123. package/build/src/plugin/public.d.ts +4 -0
  124. package/build/src/plugin/runtime.js +4 -1
  125. package/build/src/protocol/amend.js +4 -3
  126. package/build/src/protocol/attempt.d.ts +2 -0
  127. package/build/src/protocol/bind.js +41 -14
  128. package/build/src/protocol/completion.d.ts +3 -0
  129. package/build/src/protocol/completion.js +7 -1
  130. package/build/src/protocol/deliver.js +165 -92
  131. package/build/src/protocol/execution-observation.d.ts +0 -4
  132. package/build/src/protocol/intent.d.ts +15 -20
  133. package/build/src/protocol/intent.js +29 -17
  134. package/build/src/protocol/operations.d.ts +2 -0
  135. package/build/src/protocol/placement.js +5 -1
  136. package/build/src/protocol/read/status.d.ts +1 -0
  137. package/build/src/protocol/read/status.js +10 -2
  138. package/build/src/protocol/reintegrate.d.ts +1 -1
  139. package/build/src/protocol/reintegrate.js +70 -43
  140. package/build/src/protocol/result-codec.js +9 -7
  141. package/build/src/protocol/review.js +53 -30
  142. package/build/src/protocol/run.d.ts +83 -36
  143. package/build/src/protocol/run.js +90 -75
  144. package/build/src/runtime/proc/windows-launch.exe +0 -0
  145. package/build/src/settings.js +5 -14
  146. package/build/src/task/document.d.ts +5 -1
  147. package/build/src/task/document.js +6 -1
  148. package/build/src/verification/execution.d.ts +0 -1
  149. package/build/src/verification/execution.js +0 -24
  150. package/build/src/verification/observation.d.ts +0 -4
  151. package/build/src/verification/observation.js +1 -2
  152. package/package.json +3 -1
  153. package/build/src/git/verification-environment.d.ts +0 -10
  154. package/build/src/git/verification-environment.js +0 -281
@@ -1,6 +1,7 @@
1
1
  /** @architectureCompositionRoot */
2
2
  import { type ActivityHistory, type OutcomeRow, type AkumaStatus, type InterruptReceipt } from "../akuma/akuma.js";
3
3
  import { type ExecutionContext } from "../akuma/requests.js";
4
+ import type { WaitObserver } from "../akuma/fleet-execution.js";
4
5
  import { type DispatchAssociation } from "../dispatch/index.js";
5
6
  import { type AkumaAddressInput, type AkumaSetAddressInput } from "./address.js";
6
7
  import { type AkumaKillResult, type AkumaObservation, type AkumaObservationStage, type AkumaTellResult, type AkumaWaitResult } from "../akuma/fleet-observation.js";
@@ -10,6 +11,7 @@ export type AkumaWaitInput = AkumaSetAddressInput & Readonly<{
10
11
  }>;
11
12
  export type AkumaTellInput = AkumaAddressInput & Readonly<{
12
13
  body: string;
14
+ initiator?: string;
13
15
  }>;
14
16
  export type { TellResult, TellWake } from "../akuma/akuma.js";
15
17
  export type { CreatedTaskObservation } from "../task/created-observation.js";
@@ -17,6 +19,7 @@ export type { DispatchAssociation } from "../dispatch/association.js";
17
19
  export type { AkumaKillResult, AkumaObservation, AkumaObservationStage, AkumaTellResult, AkumaUnobserved, AkumaWaitResult, } from "../akuma/fleet-observation.js";
18
20
  export type AkumaInterruptInput = AkumaAddressInput & Readonly<{
19
21
  body: string;
22
+ initiator?: string;
20
23
  }>;
21
24
  export type AkumaInterruptResult = Readonly<{
22
25
  id: AkumaStatus["id"];
@@ -56,7 +59,7 @@ export type AkumaHistoryResult = Readonly<{
56
59
  contract: DispatchAssociation;
57
60
  }>;
58
61
  export declare function statusAkuma(input: AkumaAddressInput): Promise<AkumaObservation>;
59
- export declare function waitAkuma(input: AkumaWaitInput, execution?: ExecutionContext): Promise<AkumaWaitResult>;
62
+ export declare function waitAkuma(input: AkumaWaitInput, execution?: ExecutionContext, observer?: WaitObserver): Promise<AkumaWaitResult>;
60
63
  export declare function killAkuma(input: AkumaSetAddressInput, execution?: ExecutionContext): Promise<AkumaKillResult>;
61
64
  export declare function tellAkuma(input: AkumaTellInput, execution?: ExecutionContext): Promise<AkumaTellResult>;
62
65
  export declare function interruptAkuma(input: AkumaInterruptInput): Promise<AkumaInterruptResult>;
@@ -4,6 +4,7 @@ import { createAkumaProduct } from "../akuma/akuma-product.js";
4
4
  import { executionChannel, localExecutionContext } from "../akuma/requests.js";
5
5
  import { requestForwardedFleetKill, requestForwardedFleetTell, requestForwardedFleetWait, } from "../akuma/fleet-request.js";
6
6
  import { executeKillAkuma, executeTellAkuma, executeWaitAkuma } from "../akuma/fleet-execution.js";
7
+ import { readAliases } from "../alias/index.js";
7
8
  import { observeDispatchAssociation } from "../dispatch/index.js";
8
9
  import { observeCreatedTaskObservations } from "../task/created-observation.js";
9
10
  import { addressAkuma, addressAkumaSet } from "./address.js";
@@ -23,6 +24,26 @@ async function dispatchAssociation(repo, id) {
23
24
  async function createdTasksFor(path, statuses) {
24
25
  return await observeCreatedTaskObservations(path, statuses.map((status) => status.id));
25
26
  }
27
+ /**
28
+ * One wait's identity-fact resolver: the alias bound to an Akuma in its world and
29
+ * its Dispatch association. The alias index is read once, on the first observed
30
+ * Akuma, and each Akuma's alias is the first binding the canonical order offers.
31
+ */
32
+ function waitIdentityFacts(path, repo) {
33
+ let aliases;
34
+ return async (id) => {
35
+ if (aliases === undefined) {
36
+ const bound = new Map();
37
+ for (const binding of await readAliases(path)) {
38
+ if (!bound.has(binding.akuId))
39
+ bound.set(binding.akuId, binding.alias);
40
+ }
41
+ aliases = bound;
42
+ }
43
+ const alias = aliases.get(id);
44
+ return { ...(alias === undefined ? {} : { alias }), contract: await dispatchAssociation(repo, id) };
45
+ };
46
+ }
26
47
  async function observeAkuma(status, path, repo) {
27
48
  return (await observeAkumaSet([status], path, repo))[0];
28
49
  }
@@ -85,7 +106,7 @@ export async function statusAkuma(input) {
85
106
  const addressed = await addressAkuma(input);
86
107
  return await observeAkuma(await source(addressed.path).selectHandle({ id: addressed.id }).status(), addressed.path, input.repo);
87
108
  }
88
- export async function waitAkuma(input, execution = localExecutionContext()) {
109
+ export async function waitAkuma(input, execution = localExecutionContext(), observer) {
89
110
  const values = requireInput(input, "Keiyaku.wait input");
90
111
  for (const key of Object.keys(values)) {
91
112
  if (!["path", "akuma", "repo", "completion", "timeoutMs"].includes(key)) {
@@ -94,13 +115,11 @@ export async function waitAkuma(input, execution = localExecutionContext()) {
94
115
  }
95
116
  const addressed = await addressAkumaSet(setAddress(values));
96
117
  const completion = values.completion;
97
- if (addressed.ids.length > 1 && completion !== "any" && completion !== "all") {
98
- throw new TypeError("completion must be any or all when waiting for multiple Akuma");
99
- }
100
118
  if (completion !== undefined && completion !== "any" && completion !== "all") {
101
119
  throw new TypeError("completion must be any or all");
102
120
  }
103
- const selected = completion ?? "all";
121
+ // An omitted mode is any: a plural wait returns when any selected Akuma is complete.
122
+ const selected = completion ?? "any";
104
123
  const timeoutMs = timeout(values.timeoutMs);
105
124
  const channel = executionChannel(execution);
106
125
  const repo = values.repo;
@@ -117,6 +136,9 @@ export async function waitAkuma(input, execution = localExecutionContext()) {
117
136
  ids: addressed.ids,
118
137
  completion: selected,
119
138
  ...(timeoutMs === undefined ? {} : { timeoutMs }),
139
+ identity: waitIdentityFacts(addressed.path, repo),
140
+ ...(observer?.selected === undefined ? {} : { onSelected: observer.selected }),
141
+ ...(observer?.observe === undefined ? {} : { observe: observer.observe }),
120
142
  }));
121
143
  }
122
144
  export async function killAkuma(input, execution = localExecutionContext()) {
@@ -136,7 +158,7 @@ export async function killAkuma(input, execution = localExecutionContext()) {
136
158
  export async function tellAkuma(input, execution = localExecutionContext()) {
137
159
  const values = requireInput(input, "Keiyaku.tell input");
138
160
  for (const key of Object.keys(values)) {
139
- if (!["path", "akuma", "body", "repo"].includes(key)) {
161
+ if (!["path", "akuma", "body", "repo", "initiator"].includes(key)) {
140
162
  throw new TypeError(`Keiyaku.tell input has unknown field: ${key}`);
141
163
  }
142
164
  }
@@ -149,18 +171,20 @@ export async function tellAkuma(input, execution = localExecutionContext()) {
149
171
  directory: channel.directory,
150
172
  target: addressed.id,
151
173
  body: values.body,
174
+ ...(input.initiator === undefined ? {} : { initiator: input.initiator }),
152
175
  });
153
176
  }
154
177
  return await executeTellAkuma({
155
178
  path: addressed.path,
156
179
  id: addressed.id,
157
180
  body: values.body,
181
+ ...(input.initiator === undefined ? {} : { initiator: input.initiator }),
158
182
  });
159
183
  }
160
184
  export async function interruptAkuma(input) {
161
185
  const values = requireInput(input, "Keiyaku.interrupt input");
162
186
  for (const key of Object.keys(values)) {
163
- if (!["path", "akuma", "body", "repo"].includes(key)) {
187
+ if (!["path", "akuma", "body", "repo", "initiator"].includes(key)) {
164
188
  throw new TypeError(`Keiyaku.interrupt input has unknown field: ${key}`);
165
189
  }
166
190
  }
@@ -168,7 +192,7 @@ export async function interruptAkuma(input) {
168
192
  throw new TypeError("body must be a string");
169
193
  const addressed = await addressAkuma(directAddress(values));
170
194
  const handle = source(addressed.path).selectHandle({ id: addressed.id });
171
- const receipt = await handle.interrupt(values.body);
195
+ const receipt = await handle.interrupt(values.body, input.initiator === undefined ? {} : { initiator: input.initiator });
172
196
  const observation = await observeAkumaStage(addressed.path, addressed.id, values.repo);
173
197
  return { id: addressed.id, receipt, observation };
174
198
  }
@@ -8,5 +8,11 @@ type CalledPluginSignalInput = Readonly<{
8
8
  callerAkumaId?: string;
9
9
  contractId?: string;
10
10
  }>;
11
+ export declare function emitInitiatingPluginSignal(input: Readonly<{
12
+ world: WorldRoot;
13
+ settings?: Settings;
14
+ initiator: string;
15
+ reportDiagnostic: (message: string) => void;
16
+ }>): Promise<void>;
11
17
  export declare function emitCalledPluginSignal(input: CalledPluginSignalInput): Promise<void>;
12
18
  export {};
@@ -1,4 +1,12 @@
1
1
  import { pluginRuntime } from "./runtime.js";
2
+ export async function emitInitiatingPluginSignal(input) {
3
+ const runtime = await pluginRuntime({
4
+ world: input.world,
5
+ ...(input.settings === undefined ? {} : { settings: input.settings }),
6
+ reportDiagnostic: input.reportDiagnostic,
7
+ });
8
+ await runtime.emit({ kind: "akuma.initiating", initiator: input.initiator }, input.reportDiagnostic);
9
+ }
2
10
  export async function emitCalledPluginSignal(input) {
3
11
  const runtime = await pluginRuntime({
4
12
  world: input.world,
@@ -16,6 +16,9 @@ export type PluginOutcome = Readonly<{
16
16
  }>;
17
17
  export type PluginBodyEnd = "exited" | "broke-off" | "put-down" | "hung";
18
18
  export type PluginSignalMap = Readonly<{
19
+ "akuma.initiating": Readonly<{
20
+ initiator: string;
21
+ }>;
19
22
  "akuma.called": Readonly<{
20
23
  akumaId: string;
21
24
  callerAkumaId?: string;
@@ -24,6 +27,7 @@ export type PluginSignalMap = Readonly<{
24
27
  "akuma.turn-outcome": Readonly<{
25
28
  akumaId: string;
26
29
  turnSequence: number;
30
+ initiator?: string;
27
31
  outcome: PluginOutcome;
28
32
  contractId?: string;
29
33
  }>;
@@ -208,7 +208,10 @@ function handlers(pluginId, instance) {
208
208
  throw new TypeError("plugin signals must be an object");
209
209
  const registered = [];
210
210
  for (const [kind, handler] of Object.entries(instance.signals)) {
211
- if (kind !== "akuma.called" && kind !== "akuma.turn-outcome" && kind !== "akuma.body-ended") {
211
+ if (kind !== "akuma.initiating" &&
212
+ kind !== "akuma.called" &&
213
+ kind !== "akuma.turn-outcome" &&
214
+ kind !== "akuma.body-ended") {
212
215
  throw new TypeError(`plugin signal is unknown: ${kind}`);
213
216
  }
214
217
  if (typeof handler !== "function")
@@ -30,14 +30,15 @@ export async function amendOperation(input) {
30
30
  const initial = await observeContractsForAdmissionAt(input.scope, input.channel, [input.contractId]);
31
31
  source = contractState(initial.decision, input.contractId)?.terms;
32
32
  }
33
+ // Deriving the caller-stamped document is pure: it never depends on the latest Contract.
34
+ const amendment = source === undefined || input.deriveAmendment === undefined
35
+ ? undefined
36
+ : { source, ...input.deriveAmendment(source) };
33
37
  return intentOutcomeWithSeatClose(await withPrivateStatePublicationSeat(input.scope, async (seat) => {
34
38
  const attempts = mintAttempts({ entryCount: 1 });
35
39
  for (let index = 0; index < attempts.length; index += 1) {
36
40
  let observation = await observeContractsForAdmissionAt(input.scope, input.channel, [input.contractId]);
37
41
  const state = contractState(observation.decision, input.contractId);
38
- const amendment = source === undefined || input.deriveAmendment === undefined
39
- ? undefined
40
- : { source, ...input.deriveAmendment(source) };
41
42
  if (amendment !== undefined) {
42
43
  observation = await extendPrerequisiteClosureAt(input.channel, observation, [
43
44
  ...new Set([...(state?.terms.after ?? []), ...amendment.terms.after]),
@@ -35,6 +35,8 @@ export type AttemptTerminal = Readonly<{
35
35
  kind: "collision";
36
36
  }> | PublicationFailed;
37
37
  export type DecidedOfferResult<Refusal = never> = AcceptedAdmission | AttemptTerminal | Readonly<{
38
+ kind: "stale";
39
+ }> | Readonly<{
38
40
  kind: "redecide";
39
41
  }> | Readonly<{
40
42
  kind: "refused";
@@ -1,8 +1,8 @@
1
- import { normalizeTargetBranch, observeBindCoordinates } from "../git/observe.js";
1
+ import { normalizeTargetBranch, observeBindCoordinates, } from "../git/observe.js";
2
2
  import { gitObjectIdForSnapshot } from "../git/identity.js";
3
3
  import { contractId } from "../core/facts/types.js";
4
4
  import { decideBind } from "../core/verbs/bind.js";
5
- import { admitIntent } from "./intent.js";
5
+ import { admitPreparedIntent } from "./intent.js";
6
6
  import { complete } from "./outcome.js";
7
7
  export function decodeTargetInputRefusal(value) {
8
8
  if (value === null || typeof value !== "object" || Array.isArray(value))
@@ -29,19 +29,44 @@ export function decodeForkSourceMovedRefusal(value) {
29
29
  throw new Error("malformed fork-source refusal");
30
30
  }
31
31
  }
32
- async function bindPreparation(input, selection, seed) {
32
+ /**
33
+ * Acquire the repeatable Git coordinates outside the publication seat. The artifact carries the
34
+ * observed start/target plus the expected ref assertions that publication must still confirm.
35
+ */
36
+ async function prepareBindCoordinates(input, selection) {
33
37
  if (input.verification.kind === "refused") {
34
- return { kind: "prepared", input: { ...seed, preparation: input.verification } };
38
+ return { kind: "prepared", prepared: { kind: "declaration-refused", preparation: input.verification } };
35
39
  }
36
40
  const observed = await observeBindCoordinates(input.scope, selection);
37
41
  if (observed === null)
38
- return { kind: "refused", refusal: { kind: "target-missing" } };
39
- if (!("start" in observed)) {
40
- return { kind: "refused", refusal: { kind: "unborn-head" } };
42
+ return { kind: "prepared", prepared: { kind: "unresolved", reason: "target-missing" } };
43
+ if ("kind" in observed)
44
+ return { kind: "prepared", prepared: { kind: "unresolved", reason: "unborn-head" } };
45
+ return {
46
+ kind: "prepared",
47
+ prepared: { kind: "resolved", observed },
48
+ ...(input.coordinates === undefined
49
+ ? { assertions: [{ ref: observed.target ?? "HEAD", oid: gitObjectIdForSnapshot(observed.start) }] }
50
+ : {}),
51
+ };
52
+ }
53
+ /**
54
+ * Assemble the decision input from the fresh in-custody observation. Coordinates fixed by the
55
+ * invocation are never replaced; mutable coordinate disappearance is read again as current evidence.
56
+ */
57
+ async function assembleBindInput(input, selection, seed, prepared) {
58
+ if (prepared.kind === "declaration-refused") {
59
+ return { kind: "prepared", input: { ...seed, preparation: prepared.preparation } };
60
+ }
61
+ if (prepared.kind === "unresolved") {
62
+ const current = await observeBindCoordinates(input.scope, selection);
63
+ if (current === null)
64
+ return { kind: "refused", refusal: { kind: "target-missing" } };
65
+ if ("kind" in current)
66
+ return { kind: "refused", refusal: { kind: "unborn-head" } };
67
+ return { kind: "stale" };
41
68
  }
42
- const start = input.coordinates?.start ?? observed.start;
43
- const oid = gitObjectIdForSnapshot(input.coordinates === undefined ? start : observed.start);
44
- const assertions = input.coordinates === undefined ? [{ ref: observed.target ?? "HEAD", oid }] : [];
69
+ const observed = prepared.observed;
45
70
  return {
46
71
  kind: "prepared",
47
72
  input: {
@@ -50,7 +75,7 @@ async function bindPreparation(input, selection, seed) {
50
75
  kind: "prepared",
51
76
  data: {
52
77
  coordinates: {
53
- start,
78
+ start: input.coordinates?.start ?? observed.start,
54
79
  ...(observed.target === undefined ? {} : { target: observed.target }),
55
80
  workspace: input.workspace,
56
81
  },
@@ -58,7 +83,6 @@ async function bindPreparation(input, selection, seed) {
58
83
  },
59
84
  },
60
85
  },
61
- assertions,
62
86
  };
63
87
  }
64
88
  export async function bindOperation(input) {
@@ -71,13 +95,16 @@ export async function bindOperation(input) {
71
95
  }
72
96
  const at = new Date().toISOString();
73
97
  const id = input.contractId;
74
- return complete(await admitIntent(input.channel, input.scope, {
98
+ return complete(await admitPreparedIntent(input.channel, input.scope, {
75
99
  contractId: id,
76
100
  ...(input.actor === undefined ? {} : { actor: input.actor }),
77
101
  at,
78
102
  }, decideBind, {
79
103
  observedContracts: [id, ...input.terms.after, ...(input.source === undefined ? [] : [input.source.contractId])],
80
- prepareInput: async (_observation, original) => bindPreparation(input, selection, original),
104
+ preparation: {
105
+ external: async () => await prepareBindCoordinates(input, selection),
106
+ assemble: async (_observation, original, prepared) => await assembleBindInput(input, selection, original, prepared),
107
+ },
81
108
  ...(input.source === undefined
82
109
  ? {}
83
110
  : {
@@ -6,6 +6,9 @@ import { type ContractCheckpoint, type ExecutionProgress, type ExecutionStop } f
6
6
  import type { DocumentDerivation, PlacementStop, VerificationStop } from "./operations.js";
7
7
  export type CandidateCompletion = Readonly<{
8
8
  integration: SnapshotId;
9
+ /** The reference this placement advanced and the head it advanced from, both observed at completion time. */
10
+ predecessor?: SnapshotId;
11
+ target?: string;
9
12
  verification?: Readonly<{
10
13
  mode: "ran" | "reused";
11
14
  verdict: "satisfied" | "unsatisfied";
@@ -82,6 +82,8 @@ function completedResult(cursor) {
82
82
  const integration = state.currentIntegration?.snapshot;
83
83
  if (integration === undefined)
84
84
  throw new Error("accepted placement requires its integration snapshot");
85
+ const target = state.coordinates.target;
86
+ const predecessor = state.currentIntegration?.predecessor;
85
87
  // Never attach a superseded run's verdict to the final integration.
86
88
  const current = currentVerifiedAttestation(state);
87
89
  const verification = current === undefined
@@ -96,7 +98,11 @@ function completedResult(cursor) {
96
98
  checkpoint: cursor.checkpoint,
97
99
  evidence: {
98
100
  ...evidence,
99
- completion: { integration, ...(verification === undefined ? {} : { verification }) },
101
+ completion: {
102
+ integration,
103
+ ...(predecessor === undefined || target === undefined ? {} : { predecessor, target }),
104
+ ...(verification === undefined ? {} : { verification }),
105
+ },
100
106
  ...(current === undefined || verification?.mode !== "reused" ? {} : { verificationReuse: current }),
101
107
  ...(current?.verdict !== "unsatisfied" || current.summary === undefined
102
108
  ? {}