brainclaw 1.26.2 → 1.28.0

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 (88) hide show
  1. package/README.md +13 -0
  2. package/dist/brainclaw-vscode.vsix +0 -0
  3. package/dist/cli/register-coordination.js +65 -1
  4. package/dist/commands/attempt-authority.js +80 -0
  5. package/dist/commands/harvest.js +140 -61
  6. package/dist/commands/loop.js +34 -0
  7. package/dist/commands/loops-handlers.js +143 -15
  8. package/dist/commands/mcp-catalog.js +52 -18
  9. package/dist/commands/mcp-schemas.generated.js +64 -0
  10. package/dist/commands/mcp-write-claims.js +128 -1
  11. package/dist/commands/mcp-write-coordination.js +149 -76
  12. package/dist/core/agent-capability.js +1 -1
  13. package/dist/core/agentrun-reconciler.js +148 -22
  14. package/dist/core/agentruns.js +254 -29
  15. package/dist/core/assignment-request-schema.js +7 -0
  16. package/dist/core/assignment-sweeper.js +5 -3
  17. package/dist/core/assignments.js +131 -33
  18. package/dist/core/claim-request-schema.js +7 -0
  19. package/dist/core/claims.js +53 -2
  20. package/dist/core/dispatch-status.js +16 -6
  21. package/dist/core/dispatcher.js +51 -51
  22. package/dist/core/entity-operations.js +20 -0
  23. package/dist/core/events.js +4 -0
  24. package/dist/core/execution-adapters.js +189 -14
  25. package/dist/core/execution-contract.js +345 -0
  26. package/dist/core/execution.js +130 -16
  27. package/dist/core/facade-schema.js +3 -0
  28. package/dist/core/harness-adapters/base.js +150 -0
  29. package/dist/core/harness-adapters/claude.js +39 -0
  30. package/dist/core/harness-adapters/codex.js +57 -0
  31. package/dist/core/harness-adapters/harvest.js +109 -0
  32. package/dist/core/harness-adapters/index.js +8 -0
  33. package/dist/core/harness-adapters/prompt-only.js +13 -0
  34. package/dist/core/harness-adapters/registry.js +48 -0
  35. package/dist/core/harness-adapters/result.js +33 -0
  36. package/dist/core/harness-adapters/types.js +2 -0
  37. package/dist/core/ideation-loop-close.js +25 -2
  38. package/dist/core/instruction-templates.js +3 -2
  39. package/dist/core/loop-turn-dispatch.js +235 -0
  40. package/dist/core/loops/artifact-contract.js +11 -0
  41. package/dist/core/loops/attempt-authority.js +496 -0
  42. package/dist/core/loops/attempt-generations.js +509 -0
  43. package/dist/core/loops/attempt-reservation.js +197 -35
  44. package/dist/core/loops/attempt-rollout.js +404 -0
  45. package/dist/core/loops/attempt-takeover.js +155 -0
  46. package/dist/core/loops/bootstrap-acquire.js +7 -3
  47. package/dist/core/loops/brief-assembly.js +21 -4
  48. package/dist/core/loops/evidence.js +188 -0
  49. package/dist/core/loops/facade-schema.js +75 -11
  50. package/dist/core/loops/gate-policy.js +533 -0
  51. package/dist/core/loops/impl-bind.js +91 -81
  52. package/dist/core/loops/index.js +9 -0
  53. package/dist/core/loops/iteration-engine.js +31 -19
  54. package/dist/core/loops/kind-policies.js +90 -0
  55. package/dist/core/loops/lock.js +71 -13
  56. package/dist/core/loops/reconcile-turn.js +237 -18
  57. package/dist/core/loops/result-reducers.js +113 -10
  58. package/dist/core/loops/store.js +34 -3
  59. package/dist/core/loops/turn-execution.js +480 -0
  60. package/dist/core/loops/types.js +127 -3
  61. package/dist/core/loops/verbs.js +335 -99
  62. package/dist/core/loops/verify-command.js +105 -20
  63. package/dist/core/loops/workspace-digest.js +54 -0
  64. package/dist/core/review-loop-close.js +25 -3
  65. package/dist/core/review-loop-turn-dispatch.js +210 -161
  66. package/dist/core/runtime-signals.js +62 -25
  67. package/dist/core/schema.js +40 -0
  68. package/dist/core/spawn-check.js +3 -2
  69. package/dist/core/upgrades/backup.js +27 -4
  70. package/dist/facts.js +9 -8
  71. package/dist/facts.json +8 -7
  72. package/docs/cli.md +49 -1
  73. package/docs/concepts/attempt-authority.md +407 -0
  74. package/docs/concepts/evidence-attestations.md +135 -0
  75. package/docs/concepts/execution-contract.md +166 -0
  76. package/docs/concepts/harness-adapters.md +166 -0
  77. package/docs/concepts/ideation-loop.md +5 -4
  78. package/docs/concepts/loop-engine.md +302 -113
  79. package/docs/index.md +4 -1
  80. package/docs/integrations/codex.md +3 -3
  81. package/docs/integrations/mcp.md +59 -5
  82. package/docs/loops/debug.md +144 -0
  83. package/docs/loops/ideation.md +158 -0
  84. package/docs/loops/implementation.md +174 -0
  85. package/docs/loops/research.md +136 -0
  86. package/docs/loops/review.md +200 -0
  87. package/docs/mcp-schema-changelog.md +18 -5
  88. package/package.json +1 -1
@@ -0,0 +1,480 @@
1
+ /** Common projections-before-crossing contract for every worker-backed LoopKind phase. */
2
+ import { ensureAgentRunProjection } from '../agentruns.js';
3
+ import { ensureAssignmentProjection } from '../assignments.js';
4
+ import { ensureClaimAssignmentBinding, loadClaim } from '../claims.js';
5
+ import { CapabilityRequirementSchema, ExecutionContractSchema, attestHarnessContractAcceptance, executionContractRef, resolveCapabilitySnapshot, validateWorkerContractAcceptance, HarnessCapabilityBindingSchema, } from '../execution-contract.js';
6
+ import { resolveHarnessBinding } from '../harness-adapters/index.js';
7
+ import { bindTurnProjection } from './verbs.js';
8
+ import { deriveChildIds, getReservation, launchGrant, resolveTurnId } from './attempt-reservation.js';
9
+ import { bootstrapAttemptAuthorityV2, crossActiveAttemptGenerationV2, executionContractForGeneration, prepareAttempt, projectAndCross, } from './attempt-authority.js';
10
+ import { resolveTurnGenerationChain } from './attempt-generations.js';
11
+ import { readLocalAuthorityHome, resolveActiveAttemptRollout } from './attempt-rollout.js';
12
+ import { getLoop } from './store.js';
13
+ import { phasePolicy } from './kind-policies.js';
14
+ /** Idempotently materialize every child projection required before launch. */
15
+ export function ensureTurnExecutionProjections(reservation, input, cwd) {
16
+ if (reservation.claim_id === '')
17
+ throw new Error('attempt reservation has no claim');
18
+ ensureAssignmentProjection({
19
+ id: input.assignment_id,
20
+ short_label: input.assignment_id,
21
+ claim_id: reservation.claim_id,
22
+ agent: input.agent,
23
+ agent_id: input.agent_id,
24
+ dispatcher_agent: input.dispatcher_agent,
25
+ dispatcher_session_id: input.dispatcher_session_id,
26
+ scope: input.scope,
27
+ description: input.description,
28
+ // Assignment is the stable logical attempt. Its contract projection stays
29
+ // generation-zero; each physical AgentRun below carries its own contract.
30
+ execution_contract_ref: reservation.execution_contract_ref,
31
+ capability_snapshot: reservation.capability_snapshot,
32
+ tags: input.assignment_tags ?? ['coordinate', 'loop', 'turn-owned'],
33
+ }, cwd);
34
+ input.on_projection?.('assignment');
35
+ ensureAgentRunProjection({
36
+ id: input.run_id,
37
+ short_label: input.run_id,
38
+ assignment_id: input.assignment_id,
39
+ claim_id: reservation.claim_id,
40
+ attempt_index: input.attempt_index ?? 1,
41
+ agent: input.agent,
42
+ agent_id: input.agent_id,
43
+ transport: 'cli_spawn',
44
+ status: 'created',
45
+ scope: input.scope,
46
+ description: input.description,
47
+ worktree_path: input.worktree_path,
48
+ execution_contract_ref: input.execution_contract_ref ?? reservation.execution_contract_ref,
49
+ capability_snapshot: input.capability_snapshot ?? reservation.capability_snapshot,
50
+ tags: input.run_tags ?? ['turn-owned', 'loop'],
51
+ }, cwd);
52
+ input.on_projection?.('run');
53
+ ensureClaimAssignmentBinding(reservation.claim_id, input.assignment_id, cwd, {
54
+ worktreePath: input.worktree_path,
55
+ });
56
+ input.on_projection?.('claim_binding');
57
+ bindTurnProjection({
58
+ id: input.loop_id,
59
+ slot_id: input.slot_id,
60
+ actor: input.dispatcher_agent_id ?? input.dispatcher_agent,
61
+ input: input.task,
62
+ turn_id: input.turn_id,
63
+ assignment_id: input.assignment_id,
64
+ claim_id: reservation.claim_id,
65
+ }, cwd);
66
+ input.on_projection?.('slot_binding');
67
+ input.on_projection?.('before_crossing');
68
+ }
69
+ function preconditionDenied(reason) {
70
+ return { kind: 'denied', reason, code: 'precondition', authority_claimed: false, claim_disposition: 'release' };
71
+ }
72
+ function authorityDenied(input, turnId, reason) {
73
+ const reservation = getReservation(turnId, input.cwd);
74
+ const ownsAuthority = reservation?.claim_id === input.claim_id;
75
+ const crossed = reservation?.launch?.status === 'crossed';
76
+ return {
77
+ kind: 'denied',
78
+ reason,
79
+ code: ownsAuthority ? (crossed ? 'already_crossed' : 'repairable') : 'authority_conflict',
80
+ authority_claimed: ownsAuthority,
81
+ claim_disposition: ownsAuthority ? 'retain' : 'release',
82
+ turn_id: turnId,
83
+ };
84
+ }
85
+ function defaultRequiredRole(kind, phase) {
86
+ if (kind === 'review')
87
+ return phase === 'author_response' ? 'execute' : 'review';
88
+ if (kind === 'ideation')
89
+ return 'review';
90
+ if (kind === 'research')
91
+ return 'consult';
92
+ return 'execute';
93
+ }
94
+ /**
95
+ * Common worker-attempt path. It refuses engine/manual phases and never advances
96
+ * a phase or evaluates a gate; those decisions stay in the Loop Engine.
97
+ * The first phase in a `(loop, slot, iteration)` retains the historical identity.
98
+ * If the same slot executes another worker phase without an iteration bump, the
99
+ * resolver uses a phase-qualified identity while preserving compatible legacy
100
+ * reservations for crash and upgrade replay.
101
+ */
102
+ export function prepareTurnExecution(input) {
103
+ const loop = getLoop(input.loop_id, input.cwd);
104
+ if (!loop)
105
+ return preconditionDenied(`loop ${input.loop_id} not found`);
106
+ if (loop.kind !== input.kind)
107
+ return preconditionDenied(`loop kind mismatch: ${loop.kind} != ${input.kind}`);
108
+ if (loop.status !== 'open')
109
+ return preconditionDenied(`loop ${loop.id} is ${loop.status}, not open`);
110
+ if (loop.current_phase !== input.phase) {
111
+ return preconditionDenied(`phase mismatch: loop is '${loop.current_phase}', attempt requested '${input.phase}'`);
112
+ }
113
+ const execution = phasePolicy(input.kind, input.phase);
114
+ if (!execution)
115
+ return preconditionDenied(`unknown ${input.kind} phase '${input.phase}'`);
116
+ if (execution.execution !== 'worker') {
117
+ return preconditionDenied(`${input.kind}.${input.phase} is ${execution.execution}, not a worker phase`);
118
+ }
119
+ const iteration = loop.iteration_count;
120
+ const slot = loop.slots.find((candidate) => candidate.slot_id === input.slot_id);
121
+ if (!slot)
122
+ return preconditionDenied(`slot ${input.slot_id} not found in loop ${loop.id}`);
123
+ const turnId = resolveTurnId({
124
+ loop_id: loop.id,
125
+ slot_id: input.slot_id,
126
+ phase: input.phase,
127
+ iteration,
128
+ current_turn_id: slot.current_turn_id,
129
+ }, input.cwd);
130
+ const childIds = deriveChildIds(turnId);
131
+ const existingReservation = getReservation(turnId, input.cwd);
132
+ if (slot.agent !== undefined && slot.agent !== input.agent) {
133
+ return preconditionDenied(`slot ${input.slot_id} belongs to agent '${slot.agent}', not '${input.agent}'`);
134
+ }
135
+ if (slot.agent_id !== undefined && slot.agent_id !== input.agent_id) {
136
+ return preconditionDenied(`slot ${input.slot_id} belongs to agent_id '${slot.agent_id}', not '${input.agent_id ?? 'none'}'`);
137
+ }
138
+ if (slot.claim_id !== undefined && slot.claim_id !== input.claim_id) {
139
+ return preconditionDenied(`slot ${input.slot_id} is bound to claim ${slot.claim_id}, not ${input.claim_id}`);
140
+ }
141
+ if (slot.current_turn_id !== undefined
142
+ && slot.current_turn_id !== turnId
143
+ && ['assigned', 'working', 'waiting_input'].includes(slot.status)) {
144
+ return preconditionDenied(`slot ${input.slot_id} has active turn ${slot.current_turn_id}, not ${turnId}`);
145
+ }
146
+ const claim = (() => {
147
+ try {
148
+ return loadClaim(input.claim_id, input.cwd);
149
+ }
150
+ catch {
151
+ return undefined;
152
+ }
153
+ })();
154
+ if (!claim)
155
+ return preconditionDenied(`claim ${input.claim_id} not found`);
156
+ if (claim.status !== 'active')
157
+ return preconditionDenied(`claim ${input.claim_id} is ${claim.status}, not active`);
158
+ if (claim.agent !== input.agent) {
159
+ return preconditionDenied(`claim ${input.claim_id} belongs to agent '${claim.agent}', not '${input.agent}'`);
160
+ }
161
+ if (claim.scope !== input.scope) {
162
+ return preconditionDenied(`claim ${input.claim_id} covers '${claim.scope}', not '${input.scope}'`);
163
+ }
164
+ if (claim.worktree_path && input.worktree_path && claim.worktree_path !== input.worktree_path) {
165
+ return preconditionDenied(`claim ${input.claim_id} worktree does not match the dispatch worktree`);
166
+ }
167
+ const dispatchLeaseMs = input.dispatch_lease_ms ?? 30 * 60_000;
168
+ const grantLeaseMs = input.grant_lease_ms ?? 10 * 60_000;
169
+ const hasCompleteExistingContract = Boolean(existingReservation?.execution_contract
170
+ && existingReservation.execution_contract_ref
171
+ && existingReservation.capability_snapshot);
172
+ const hasPartialExistingContract = Boolean(existingReservation) && !hasCompleteExistingContract && Boolean(existingReservation?.execution_contract
173
+ || existingReservation?.execution_contract_ref
174
+ || existingReservation?.capability_snapshot);
175
+ if (hasPartialExistingContract) {
176
+ return preconditionDenied('existing reservation has an incomplete execution-contract triplet');
177
+ }
178
+ const legacyUncontracted = Boolean(existingReservation) && !hasCompleteExistingContract;
179
+ const capabilityRequirement = CapabilityRequirementSchema.parse(input.capability_requirement
180
+ ?? existingReservation?.execution_contract?.capability_requirement
181
+ ?? {
182
+ roles: [defaultRequiredRole(input.kind, input.phase)],
183
+ required_surfaces: ['cli_spawn'],
184
+ // The transport requirement is CLI spawnability. Some integrations (for
185
+ // example Cline) expose a spawnable CLI while their native interaction
186
+ // surface remains an extension; callers can still constrain that native
187
+ // surface explicitly through capability_requirement.execution_surfaces.
188
+ execution_surfaces: [],
189
+ model: input.model,
190
+ required_tools: [],
191
+ });
192
+ if (input.capability_requirement
193
+ && existingReservation?.execution_contract
194
+ && JSON.stringify(capabilityRequirement) !== JSON.stringify(existingReservation.execution_contract.capability_requirement)) {
195
+ return preconditionDenied('capability requirement differs from the immutable existing execution contract');
196
+ }
197
+ let requestedHarnessBinding;
198
+ try {
199
+ const resolved = input.harness_binding ?? resolveHarnessBinding(input.agent, input.model);
200
+ requestedHarnessBinding = HarnessCapabilityBindingSchema.parse(resolved);
201
+ }
202
+ catch (error) {
203
+ return preconditionDenied(error instanceof Error ? error.message : String(error));
204
+ }
205
+ const frozenHarnessBinding = existingReservation?.capability_snapshot?.resolved.harness;
206
+ if (frozenHarnessBinding && JSON.stringify(frozenHarnessBinding) !== JSON.stringify(requestedHarnessBinding)) {
207
+ return preconditionDenied(`harness binding differs from immutable capability snapshot: frozen `
208
+ + `${frozenHarnessBinding.adapter_id}@${frozenHarnessBinding.adapter_version}, requested `
209
+ + `${requestedHarnessBinding.adapter_id}@${requestedHarnessBinding.adapter_version}`);
210
+ }
211
+ const capabilitySnapshot = existingReservation?.capability_snapshot
212
+ ?? resolveCapabilitySnapshot(input.agent, capabilityRequirement, input.agent_id, requestedHarnessBinding);
213
+ if (!capabilitySnapshot.accepted) {
214
+ const reasons = capabilitySnapshot.reasons.map((reason) => reason.code).join(', ');
215
+ return preconditionDenied(`capability requirements rejected for ${input.agent}: ${reasons}`);
216
+ }
217
+ const effectiveCompletionMode = existingReservation?.completion_mode ?? execution.completion_mode;
218
+ const effectiveExpectedArtifacts = existingReservation?.expected_artifacts ?? execution.expected_artifacts;
219
+ const contract = legacyUncontracted ? undefined : (existingReservation?.execution_contract ?? ExecutionContractSchema.parse({
220
+ schema_version: 1,
221
+ minimum_reader_version: 1,
222
+ identity: {
223
+ loop_id: loop.id,
224
+ turn_id: turnId,
225
+ logical_attempt_epoch: existingReservation?.epoch ?? 0,
226
+ assignment_id: childIds.assignment_id,
227
+ run_id: childIds.run_id,
228
+ kind: input.kind,
229
+ phase: input.phase,
230
+ iteration,
231
+ },
232
+ artifact_contract: {
233
+ completion_mode: effectiveCompletionMode,
234
+ expected_artifacts: effectiveExpectedArtifacts,
235
+ },
236
+ capability_requirement: capabilityRequirement,
237
+ workspace_policy: {
238
+ scope: input.scope,
239
+ cwd: input.cwd,
240
+ worktree_path: input.worktree_path,
241
+ isolation: input.workspace_policy?.isolation ?? (input.worktree_path ? 'worktree' : 'shared_checkout'),
242
+ write_access: input.workspace_policy?.write_access ?? 'workspace',
243
+ },
244
+ timeout_policy: {
245
+ dispatch_lease_ms: dispatchLeaseMs,
246
+ grant_lease_ms: grantLeaseMs,
247
+ },
248
+ evidence_policy: {
249
+ require_turn_id: true,
250
+ require_run_id: true,
251
+ require_nonce: true,
252
+ artifact_hash: 'optional',
253
+ },
254
+ protocol: { name: 'attempt-authority', minimum_version: 1 },
255
+ }));
256
+ const contractRef = contract
257
+ ? (existingReservation?.execution_contract_ref ?? executionContractRef(contract, capabilitySnapshot))
258
+ : undefined;
259
+ const v2 = resolveTurnGenerationChain(input.cwd, turnId);
260
+ const activeRollout = resolveActiveAttemptRollout(input.cwd);
261
+ const localHome = readLocalAuthorityHome(input.cwd);
262
+ if (activeRollout && (!localHome || !contractRef)) {
263
+ return preconditionDenied(!localHome
264
+ ? 'AttemptAuthority v2 rollout is active but this store/device has no local authority_home'
265
+ : 'AttemptAuthority v2 rollout is active but the turn has no immutable execution contract');
266
+ }
267
+ // Crash-safe v1→v2 cutover repair. The legacy launch fence is crossed before
268
+ // the immutable generation-zero cells are published. If the coordinator dies
269
+ // in that narrow window, a replay must materialize/adopt the v2 anchor so the
270
+ // turn can be fenced/taken over. It MUST NOT return spawn authority: the
271
+ // pre-crash caller may have received the crossed grant, so only a successor
272
+ // generation can safely recover liveness without a duplicate launch.
273
+ if (activeRollout
274
+ && localHome
275
+ && contractRef
276
+ && !v2
277
+ && existingReservation?.decision === 'committed'
278
+ && launchGrant(turnId, input.cwd)?.status === 'crossed') {
279
+ try {
280
+ bootstrapAttemptAuthorityV2({
281
+ turn_id: turnId,
282
+ authority_home: localHome,
283
+ actor: input.dispatcher_agent_id ?? input.dispatcher_agent,
284
+ writer_id: input.dispatcher_agent_id ?? input.dispatcher_agent,
285
+ cwd: input.cwd,
286
+ workspace_path: input.worktree_path ?? input.cwd,
287
+ on_stage: (stage) => input.on_cutover_stage?.(stage === 'initial_anchored' ? 'initial_anchored' : 'v2_anchored'),
288
+ });
289
+ return authorityDenied(input, turnId, 'repaired v1→v2 cutover after a crossed legacy launch; spawn authority remains fenced — use takeover');
290
+ }
291
+ catch (error) {
292
+ return authorityDenied(input, turnId, error instanceof Error ? error.message : String(error));
293
+ }
294
+ }
295
+ if (v2?.status === 'active' && v2.latest_generation.attempt_epoch === 0) {
296
+ if (activeRollout && localHome && contractRef) {
297
+ try {
298
+ bootstrapAttemptAuthorityV2({
299
+ turn_id: turnId,
300
+ authority_home: localHome,
301
+ actor: input.dispatcher_agent_id ?? input.dispatcher_agent,
302
+ writer_id: input.dispatcher_agent_id ?? input.dispatcher_agent,
303
+ cwd: input.cwd,
304
+ workspace_path: v2.latest_generation.workspace_path,
305
+ });
306
+ }
307
+ catch (error) {
308
+ return authorityDenied(input, turnId, error instanceof Error ? error.message : String(error));
309
+ }
310
+ }
311
+ return authorityDenied(input, turnId, 'generation zero launch is already crossed; use takeover for recovery');
312
+ }
313
+ if (input.accepted_execution_contract && !contractRef) {
314
+ return preconditionDenied('legacy uncontracted reservation cannot claim worker contract acceptance');
315
+ }
316
+ if (contractRef && !(v2?.status === 'active' && v2.latest_generation.attempt_epoch > 0)) {
317
+ let accepted;
318
+ try {
319
+ accepted = input.accepted_execution_contract
320
+ ?? attestHarnessContractAcceptance(contractRef, capabilitySnapshot, requestedHarnessBinding);
321
+ }
322
+ catch (error) {
323
+ return preconditionDenied(`worker contract acceptance unavailable before crossing: ${error instanceof Error ? error.message : String(error)}`);
324
+ }
325
+ const acceptance = validateWorkerContractAcceptance(contractRef, accepted, undefined);
326
+ if (acceptance.kind !== 'accepted') {
327
+ return preconditionDenied(`worker contract mismatch before crossing: expected ${contractRef.hash}/${contractRef.snapshot_hash}, accepted ${accepted.contract_hash}/${accepted.capability_snapshot_hash}; abort and reselect`);
328
+ }
329
+ }
330
+ // A takeover closes the old epoch and embeds a fresh active generation in
331
+ // close(epoch). Re-entering the common worker path projects that generation
332
+ // and contends on its immutable launch cell; the first caller wins spawn
333
+ // authority, every replay is adopted and MUST NOT spawn.
334
+ if (v2?.status === 'active' && v2.latest_generation.attempt_epoch > 0) {
335
+ const reservation = existingReservation;
336
+ if (!reservation || !localHome) {
337
+ return authorityDenied(input, turnId, 'AttemptAuthority v2 authority_home is unavailable');
338
+ }
339
+ try {
340
+ const generation = v2.latest_generation;
341
+ const generationContract = executionContractForGeneration(reservation, generation);
342
+ let accepted;
343
+ try {
344
+ accepted = input.accepted_execution_contract
345
+ ?? attestHarnessContractAcceptance(generationContract.ref, reservation.capability_snapshot, requestedHarnessBinding);
346
+ }
347
+ catch (error) {
348
+ return preconditionDenied(`worker contract acceptance unavailable before crossing: ${error instanceof Error ? error.message : String(error)}`);
349
+ }
350
+ const acceptance = validateWorkerContractAcceptance(generationContract.ref, accepted, undefined);
351
+ if (acceptance.kind !== 'accepted') {
352
+ return preconditionDenied('worker rejected the active generation execution contract before crossing; abort and reselect');
353
+ }
354
+ ensureTurnExecutionProjections(reservation, {
355
+ loop_id: loop.id,
356
+ slot_id: input.slot_id,
357
+ turn_id: turnId,
358
+ assignment_id: generation.assignment_id,
359
+ run_id: generation.run_id,
360
+ agent: input.agent,
361
+ agent_id: input.agent_id,
362
+ dispatcher_agent: input.dispatcher_agent,
363
+ dispatcher_agent_id: input.dispatcher_agent_id,
364
+ dispatcher_session_id: input.dispatcher_session_id,
365
+ scope: input.scope,
366
+ description: input.description,
367
+ task: input.task,
368
+ worktree_path: generation.workspace_path,
369
+ assignment_tags: input.assignment_tags,
370
+ run_tags: [...(input.run_tags ?? ['turn-owned', 'loop']), `attempt-generation:${generation.attempt_epoch}`],
371
+ attempt_index: generation.attempt_epoch + 1,
372
+ execution_contract_ref: generationContract.ref,
373
+ capability_snapshot: reservation.capability_snapshot,
374
+ on_projection: input.on_projection,
375
+ }, input.cwd);
376
+ const crossing = crossActiveAttemptGenerationV2(turnId, generation.attempt_epoch, localHome, input.dispatcher_agent_id ?? input.dispatcher_agent, input.dispatcher_agent_id ?? input.dispatcher_agent, input.cwd);
377
+ if (!crossing.won)
378
+ return authorityDenied(input, turnId, 'attempt generation launch already crossed');
379
+ return {
380
+ kind: 'won',
381
+ turn_id: turnId,
382
+ assignment_id: generation.assignment_id,
383
+ run_id: generation.run_id,
384
+ nonce: generation.launch_nonce,
385
+ attempt_epoch: generation.attempt_epoch,
386
+ workspace_digest: generation.workspace_digest,
387
+ workspace_path: generation.workspace_path,
388
+ contract_status: 'contracted',
389
+ execution_contract_ref: generationContract.ref,
390
+ capability_snapshot: reservation.capability_snapshot,
391
+ };
392
+ }
393
+ catch (error) {
394
+ return authorityDenied(input, turnId, error instanceof Error ? error.message : String(error));
395
+ }
396
+ }
397
+ try {
398
+ const now = Date.now();
399
+ const prepared = prepareAttempt({
400
+ turn_id: turnId,
401
+ loop_id: loop.id,
402
+ slot_id: input.slot_id,
403
+ target_slot_generation: iteration,
404
+ loop_version_at_reserve: loop.version,
405
+ agent: input.agent,
406
+ agent_id: input.agent_id,
407
+ claim_id: input.claim_id,
408
+ phase: input.phase,
409
+ iteration,
410
+ completion_mode: effectiveCompletionMode,
411
+ expected_artifacts: effectiveExpectedArtifacts,
412
+ execution_contract: contract,
413
+ execution_contract_ref: contractRef,
414
+ capability_snapshot: contractRef ? capabilitySnapshot : undefined,
415
+ store_root: input.cwd,
416
+ cwd: input.cwd,
417
+ lease_deadline: new Date(now + dispatchLeaseMs).toISOString(),
418
+ grant_lease_deadline: new Date(now + grantLeaseMs).toISOString(),
419
+ authority_actor: input.dispatcher_agent_id ?? input.dispatcher_agent,
420
+ on_stage: input.on_authority_stage,
421
+ }, input.cwd);
422
+ if (prepared.launch_status !== 'armed') {
423
+ return authorityDenied(input, turnId, `launch grant is ${prepared.launch_status}`);
424
+ }
425
+ const crossing = projectAndCross(prepared, (reservation) => ensureTurnExecutionProjections(reservation, {
426
+ loop_id: loop.id,
427
+ slot_id: input.slot_id,
428
+ turn_id: turnId,
429
+ assignment_id: childIds.assignment_id,
430
+ run_id: childIds.run_id,
431
+ agent: input.agent,
432
+ agent_id: input.agent_id,
433
+ dispatcher_agent: input.dispatcher_agent,
434
+ dispatcher_agent_id: input.dispatcher_agent_id,
435
+ dispatcher_session_id: input.dispatcher_session_id,
436
+ scope: input.scope,
437
+ description: input.description,
438
+ task: input.task,
439
+ worktree_path: input.worktree_path,
440
+ assignment_tags: input.assignment_tags,
441
+ run_tags: input.run_tags,
442
+ on_projection: input.on_projection,
443
+ }, input.cwd), input.cwd, input.dispatcher_agent_id ?? input.dispatcher_agent);
444
+ if (crossing.kind !== 'won')
445
+ return authorityDenied(input, turnId, 'launch grant already crossed');
446
+ input.on_cutover_stage?.('legacy_crossed');
447
+ let generationFence = {};
448
+ if (activeRollout && localHome && contractRef) {
449
+ const generation = bootstrapAttemptAuthorityV2({
450
+ turn_id: turnId,
451
+ authority_home: localHome,
452
+ actor: input.dispatcher_agent_id ?? input.dispatcher_agent,
453
+ writer_id: input.dispatcher_agent_id ?? input.dispatcher_agent,
454
+ cwd: input.cwd,
455
+ workspace_path: input.worktree_path ?? input.cwd,
456
+ on_stage: (stage) => input.on_cutover_stage?.(stage === 'initial_anchored' ? 'initial_anchored' : 'v2_anchored'),
457
+ });
458
+ generationFence = {
459
+ attempt_epoch: generation.attempt_epoch,
460
+ workspace_digest: generation.workspace_digest,
461
+ };
462
+ }
463
+ return {
464
+ kind: 'won',
465
+ turn_id: turnId,
466
+ assignment_id: childIds.assignment_id,
467
+ run_id: childIds.run_id,
468
+ nonce: prepared.token,
469
+ contract_status: contractRef ? 'contracted' : 'legacy_uncontracted',
470
+ execution_contract_ref: contractRef,
471
+ capability_snapshot: contractRef ? capabilitySnapshot : undefined,
472
+ workspace_path: input.worktree_path ?? input.cwd,
473
+ ...generationFence,
474
+ };
475
+ }
476
+ catch (error) {
477
+ return authorityDenied(input, turnId, error instanceof Error ? error.message : String(error));
478
+ }
479
+ }
480
+ //# sourceMappingURL=turn-execution.js.map