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.
- package/README.md +13 -0
- package/dist/brainclaw-vscode.vsix +0 -0
- package/dist/cli/register-coordination.js +65 -1
- package/dist/commands/attempt-authority.js +80 -0
- package/dist/commands/harvest.js +140 -61
- package/dist/commands/loop.js +34 -0
- package/dist/commands/loops-handlers.js +143 -15
- package/dist/commands/mcp-catalog.js +52 -18
- package/dist/commands/mcp-schemas.generated.js +64 -0
- package/dist/commands/mcp-write-claims.js +128 -1
- package/dist/commands/mcp-write-coordination.js +149 -76
- package/dist/core/agent-capability.js +1 -1
- package/dist/core/agentrun-reconciler.js +148 -22
- package/dist/core/agentruns.js +254 -29
- package/dist/core/assignment-request-schema.js +7 -0
- package/dist/core/assignment-sweeper.js +5 -3
- package/dist/core/assignments.js +131 -33
- package/dist/core/claim-request-schema.js +7 -0
- package/dist/core/claims.js +53 -2
- package/dist/core/dispatch-status.js +16 -6
- package/dist/core/dispatcher.js +51 -51
- package/dist/core/entity-operations.js +20 -0
- package/dist/core/events.js +4 -0
- package/dist/core/execution-adapters.js +189 -14
- package/dist/core/execution-contract.js +345 -0
- package/dist/core/execution.js +130 -16
- package/dist/core/facade-schema.js +3 -0
- package/dist/core/harness-adapters/base.js +150 -0
- package/dist/core/harness-adapters/claude.js +39 -0
- package/dist/core/harness-adapters/codex.js +57 -0
- package/dist/core/harness-adapters/harvest.js +109 -0
- package/dist/core/harness-adapters/index.js +8 -0
- package/dist/core/harness-adapters/prompt-only.js +13 -0
- package/dist/core/harness-adapters/registry.js +48 -0
- package/dist/core/harness-adapters/result.js +33 -0
- package/dist/core/harness-adapters/types.js +2 -0
- package/dist/core/ideation-loop-close.js +25 -2
- package/dist/core/instruction-templates.js +3 -2
- package/dist/core/loop-turn-dispatch.js +235 -0
- package/dist/core/loops/artifact-contract.js +11 -0
- package/dist/core/loops/attempt-authority.js +496 -0
- package/dist/core/loops/attempt-generations.js +509 -0
- package/dist/core/loops/attempt-reservation.js +197 -35
- package/dist/core/loops/attempt-rollout.js +404 -0
- package/dist/core/loops/attempt-takeover.js +155 -0
- package/dist/core/loops/bootstrap-acquire.js +7 -3
- package/dist/core/loops/brief-assembly.js +21 -4
- package/dist/core/loops/evidence.js +188 -0
- package/dist/core/loops/facade-schema.js +75 -11
- package/dist/core/loops/gate-policy.js +533 -0
- package/dist/core/loops/impl-bind.js +91 -81
- package/dist/core/loops/index.js +9 -0
- package/dist/core/loops/iteration-engine.js +31 -19
- package/dist/core/loops/kind-policies.js +90 -0
- package/dist/core/loops/lock.js +71 -13
- package/dist/core/loops/reconcile-turn.js +237 -18
- package/dist/core/loops/result-reducers.js +113 -10
- package/dist/core/loops/store.js +34 -3
- package/dist/core/loops/turn-execution.js +480 -0
- package/dist/core/loops/types.js +127 -3
- package/dist/core/loops/verbs.js +335 -99
- package/dist/core/loops/verify-command.js +105 -20
- package/dist/core/loops/workspace-digest.js +54 -0
- package/dist/core/review-loop-close.js +25 -3
- package/dist/core/review-loop-turn-dispatch.js +210 -161
- package/dist/core/runtime-signals.js +62 -25
- package/dist/core/schema.js +40 -0
- package/dist/core/spawn-check.js +3 -2
- package/dist/core/upgrades/backup.js +27 -4
- package/dist/facts.js +9 -8
- package/dist/facts.json +8 -7
- package/docs/cli.md +49 -1
- package/docs/concepts/attempt-authority.md +407 -0
- package/docs/concepts/evidence-attestations.md +135 -0
- package/docs/concepts/execution-contract.md +166 -0
- package/docs/concepts/harness-adapters.md +166 -0
- package/docs/concepts/ideation-loop.md +5 -4
- package/docs/concepts/loop-engine.md +302 -113
- package/docs/index.md +4 -1
- package/docs/integrations/codex.md +3 -3
- package/docs/integrations/mcp.md +59 -5
- package/docs/loops/debug.md +144 -0
- package/docs/loops/ideation.md +158 -0
- package/docs/loops/implementation.md +174 -0
- package/docs/loops/research.md +136 -0
- package/docs/loops/review.md +200 -0
- package/docs/mcp-schema-changelog.md +18 -5
- 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
|