brainclaw 1.26.2 → 1.27.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 (86) 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 +87 -14
  8. package/dist/commands/mcp-catalog.js +42 -18
  9. package/dist/commands/mcp-schemas.generated.js +44 -0
  10. package/dist/commands/mcp-write-claims.js +128 -1
  11. package/dist/commands/mcp-write-coordination.js +146 -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 +160 -14
  25. package/dist/core/execution-contract.js +345 -0
  26. package/dist/core/execution.js +130 -16
  27. package/dist/core/harness-adapters/base.js +150 -0
  28. package/dist/core/harness-adapters/claude.js +39 -0
  29. package/dist/core/harness-adapters/codex.js +57 -0
  30. package/dist/core/harness-adapters/harvest.js +109 -0
  31. package/dist/core/harness-adapters/index.js +8 -0
  32. package/dist/core/harness-adapters/prompt-only.js +13 -0
  33. package/dist/core/harness-adapters/registry.js +48 -0
  34. package/dist/core/harness-adapters/result.js +33 -0
  35. package/dist/core/harness-adapters/types.js +2 -0
  36. package/dist/core/ideation-loop-close.js +25 -2
  37. package/dist/core/instruction-templates.js +3 -2
  38. package/dist/core/loop-turn-dispatch.js +207 -0
  39. package/dist/core/loops/artifact-contract.js +11 -0
  40. package/dist/core/loops/attempt-authority.js +476 -0
  41. package/dist/core/loops/attempt-generations.js +509 -0
  42. package/dist/core/loops/attempt-reservation.js +197 -35
  43. package/dist/core/loops/attempt-rollout.js +404 -0
  44. package/dist/core/loops/attempt-takeover.js +155 -0
  45. package/dist/core/loops/bootstrap-acquire.js +7 -3
  46. package/dist/core/loops/evidence.js +187 -0
  47. package/dist/core/loops/facade-schema.js +41 -10
  48. package/dist/core/loops/gate-policy.js +485 -0
  49. package/dist/core/loops/impl-bind.js +37 -79
  50. package/dist/core/loops/index.js +9 -0
  51. package/dist/core/loops/iteration-engine.js +31 -19
  52. package/dist/core/loops/kind-policies.js +90 -0
  53. package/dist/core/loops/lock.js +71 -13
  54. package/dist/core/loops/reconcile-turn.js +235 -18
  55. package/dist/core/loops/result-reducers.js +99 -10
  56. package/dist/core/loops/store.js +30 -3
  57. package/dist/core/loops/turn-execution.js +480 -0
  58. package/dist/core/loops/types.js +113 -2
  59. package/dist/core/loops/verbs.js +332 -99
  60. package/dist/core/loops/verify-command.js +31 -8
  61. package/dist/core/loops/workspace-digest.js +54 -0
  62. package/dist/core/review-loop-close.js +25 -3
  63. package/dist/core/review-loop-turn-dispatch.js +210 -161
  64. package/dist/core/runtime-signals.js +62 -25
  65. package/dist/core/schema.js +35 -0
  66. package/dist/core/spawn-check.js +3 -2
  67. package/dist/core/upgrades/backup.js +27 -4
  68. package/dist/facts.js +7 -6
  69. package/dist/facts.json +6 -5
  70. package/docs/cli.md +49 -1
  71. package/docs/concepts/attempt-authority.md +407 -0
  72. package/docs/concepts/evidence-attestations.md +135 -0
  73. package/docs/concepts/execution-contract.md +166 -0
  74. package/docs/concepts/harness-adapters.md +166 -0
  75. package/docs/concepts/ideation-loop.md +5 -4
  76. package/docs/concepts/loop-engine.md +302 -113
  77. package/docs/index.md +4 -1
  78. package/docs/integrations/codex.md +3 -3
  79. package/docs/integrations/mcp.md +59 -5
  80. package/docs/loops/debug.md +144 -0
  81. package/docs/loops/ideation.md +158 -0
  82. package/docs/loops/implementation.md +154 -0
  83. package/docs/loops/research.md +136 -0
  84. package/docs/loops/review.md +200 -0
  85. package/docs/mcp-schema-changelog.md +14 -5
  86. package/package.json +1 -1
@@ -5,6 +5,56 @@ import { buildClaimEnvPrefix, buildWorkerIdentityEnv } from './execution-profile
5
5
  import { getCapabilityProfile } from './agent-capability.js';
6
6
  import { nowISO } from './ids.js';
7
7
  import { ensureRuntimeDirs, getRuntimeLogPath, getRuntimeSignalPath, } from './runtime-signals.js';
8
+ const CONTRACT_BOOTSTRAP_SOURCE = `'use strict';
9
+ const fs = require('node:fs');
10
+ const [ackPath, turnId, runId, nonce, expectedContractHash, expectedSnapshotHash, attemptEpoch, workspaceDigest, expectedWorkspaceB64] = process.argv.slice(2);
11
+ const contractHash = process.env.BRAINCLAW_EXECUTION_CONTRACT_HASH || '';
12
+ const snapshotHash = process.env.BRAINCLAW_CAPABILITY_SNAPSHOT_HASH || '';
13
+ const normalize = (value) => {
14
+ let resolved = fs.realpathSync.native(value);
15
+ if (process.platform === 'win32') resolved = resolved.toLowerCase();
16
+ return resolved;
17
+ };
18
+ const expectedWorkspace = Buffer.from(expectedWorkspaceB64 || '', 'base64url').toString('utf8');
19
+ const actualCwd = normalize(process.cwd());
20
+ const accepted = contractHash === expectedContractHash
21
+ && snapshotHash === expectedSnapshotHash
22
+ && expectedWorkspace !== ''
23
+ && actualCwd === normalize(expectedWorkspace);
24
+ let fd;
25
+ try {
26
+ fd = fs.openSync(ackPath, 'wx');
27
+ } catch (error) {
28
+ if (error && error.code === 'EEXIST') process.exit(79);
29
+ throw error;
30
+ }
31
+ try {
32
+ fs.writeFileSync(fd, JSON.stringify({
33
+ status: accepted ? 'accepted' : 'rejected',
34
+ turn_id: turnId,
35
+ run_id: runId,
36
+ nonce,
37
+ ...(attemptEpoch ? { attempt_epoch: Number(attemptEpoch) } : {}),
38
+ ...(workspaceDigest ? { workspace_digest: workspaceDigest } : {}),
39
+ contract_hash: contractHash,
40
+ capability_snapshot_hash: snapshotHash,
41
+ cwd: actualCwd,
42
+ }));
43
+ } finally {
44
+ fs.closeSync(fd);
45
+ }
46
+ if (!accepted) process.exitCode = 78;
47
+ `;
48
+ /** Materialize the tiny child bootstrap used by both cmd.exe and POSIX shells. */
49
+ export function writeContractBootstrapScript(scriptPath) {
50
+ fs.mkdirSync(path.dirname(scriptPath), { recursive: true });
51
+ try {
52
+ if (fs.readFileSync(scriptPath, 'utf8') === CONTRACT_BOOTSTRAP_SOURCE)
53
+ return;
54
+ }
55
+ catch { /* create or replace below */ }
56
+ fs.writeFileSync(scriptPath, CONTRACT_BOOTSTRAP_SOURCE, 'utf8');
57
+ }
8
58
  // The turn-echo values are raw-embedded into a shell one-liner (see marker()),
9
59
  // so the `[A-Za-z0-9_-]` safety invariant documented on TurnEcho is LOAD-BEARING,
10
60
  // not cosmetic. A stray `"` desyncs cmd.exe quote-parity (no sentinel file is
@@ -18,7 +68,9 @@ const TURN_ECHO_SAFE = /^[A-Za-z0-9_-]+$/;
18
68
  export function buildAckWrapCommand(bashCommand, paths, isWin32, turnEcho) {
19
69
  if (turnEcho) {
20
70
  for (const [field, value] of Object.entries(turnEcho)) {
21
- if (!TURN_ECHO_SAFE.test(value)) {
71
+ if (value === undefined)
72
+ continue;
73
+ if (!TURN_ECHO_SAFE.test(String(value))) {
22
74
  throw new Error(`buildAckWrapCommand: turnEcho.${field} must match ${TURN_ECHO_SAFE} to be shell-safe for the completion sentinel (got ${JSON.stringify(value)})`);
23
75
  }
24
76
  }
@@ -32,12 +84,42 @@ export function buildAckWrapCommand(bashCommand, paths, isWin32, turnEcho) {
32
84
  const marker = (p, status) => {
33
85
  if (!turnEcho)
34
86
  return touch(p);
35
- const body = JSON.stringify({ turn_id: turnEcho.turn_id, run_id: turnEcho.run_id, nonce: turnEcho.nonce, status });
87
+ const body = JSON.stringify({
88
+ turn_id: turnEcho.turn_id,
89
+ run_id: turnEcho.run_id,
90
+ nonce: turnEcho.nonce,
91
+ ...(turnEcho.contract_hash ? { contract_hash: turnEcho.contract_hash } : {}),
92
+ ...(turnEcho.capability_snapshot_hash ? { capability_snapshot_hash: turnEcho.capability_snapshot_hash } : {}),
93
+ ...(turnEcho.attempt_epoch !== undefined ? { attempt_epoch: turnEcho.attempt_epoch } : {}),
94
+ ...(turnEcho.workspace_digest ? { workspace_digest: turnEcho.workspace_digest } : {}),
95
+ status,
96
+ });
36
97
  return isWin32 ? `echo ${body}>"${p}"` : `printf '%s' '${body}' > "${p}"`;
37
98
  };
38
- const redirected = `${bashCommand} > "${paths.stdoutLog}" 2> "${paths.stderrLog}"`;
39
- return (`${touch(paths.ackPath)} && ` +
40
- `( ${redirected} && ${marker(paths.completedPath, 'completed')} || ${marker(paths.failedPath, 'failed')} )`);
99
+ const ack = (() => {
100
+ if (!turnEcho?.contract_hash || !turnEcho.capability_snapshot_hash)
101
+ return touch(paths.ackPath);
102
+ if (!paths.contractBootstrapPath) {
103
+ throw new Error('buildAckWrapCommand: contracted turn requires contractBootstrapPath');
104
+ }
105
+ // This child reads the EFFECTIVE environment it received. It writes the
106
+ // actual values and exits non-zero on mismatch, so `&&` prevents worker exec.
107
+ if (!paths.expectedWorkspacePath) {
108
+ throw new Error('buildAckWrapCommand: contracted turn requires expectedWorkspacePath');
109
+ }
110
+ const expectedWorkspaceB64 = Buffer.from(paths.expectedWorkspacePath, 'utf8').toString('base64url');
111
+ return `"${process.execPath}" "${paths.contractBootstrapPath}" "${paths.ackPath}" "${turnEcho.turn_id}" "${turnEcho.run_id}" "${turnEcho.nonce}" "${turnEcho.contract_hash}" "${turnEcho.capability_snapshot_hash}" "${turnEcho.attempt_epoch ?? ''}" "${turnEcho.workspace_digest ?? ''}" "${expectedWorkspaceB64}"`;
112
+ })();
113
+ const stdinRedirect = paths.stdinFilePath ? ` < "${paths.stdinFilePath}"` : '';
114
+ const redirected = `${bashCommand}${stdinRedirect} > "${paths.stdoutLog}" 2> "${paths.stderrLog}"`;
115
+ const cleanup = paths.stdinFilePath
116
+ ? isWin32
117
+ ? ` & del /q "${paths.stdinFilePath}" > nul 2>&1`
118
+ : `; rm -f -- "${paths.stdinFilePath}"`
119
+ : '';
120
+ return (`${ack} && ` +
121
+ `( ${redirected} && ${marker(paths.completedPath, 'completed')} || ${marker(paths.failedPath, 'failed')} )` +
122
+ cleanup);
41
123
  }
42
124
  /**
43
125
  * Check if a binary is resolvable on the system PATH.
@@ -115,10 +197,51 @@ export class CliExecutionAdapter {
115
197
  return { canSpawn: true, reason: 'agent has spawnable profile' };
116
198
  }
117
199
  prepareManualCommand(invoke, options) {
200
+ const isWin32 = process.platform === 'win32';
201
+ const shell = isWin32 ? 'cmd' : (invoke.shell ? 'bash' : 'sh');
202
+ if (options.turnEcho?.contract_hash
203
+ && options.turnEcho.capability_snapshot_hash
204
+ && options.assignmentId
205
+ && (options.ackRoot ?? options.worktreePath)) {
206
+ const signalRoot = options.ackRoot ?? options.worktreePath;
207
+ ensureRuntimeDirs(signalRoot);
208
+ const runtimeRunId = options.turnEcho?.run_id;
209
+ const ackPath = getRuntimeSignalPath(signalRoot, options.assignmentId, 'ack', runtimeRunId);
210
+ const contractBootstrapPath = `${ackPath}.bootstrap.cjs`;
211
+ writeContractBootstrapScript(contractBootstrapPath);
212
+ const stdinFilePath = isWin32 && invoke.promptDelivery === 'stdin_pipe' && invoke.promptText
213
+ ? `${ackPath}.stdin`
214
+ : undefined;
215
+ if (stdinFilePath && invoke.promptText) {
216
+ fs.writeFileSync(stdinFilePath, invoke.promptText, { encoding: 'utf8', mode: 0o600 });
217
+ }
218
+ const wrapped = buildAckWrapCommand(invoke.bashCommand, {
219
+ ackPath,
220
+ completedPath: getRuntimeSignalPath(signalRoot, options.assignmentId, 'completed', runtimeRunId),
221
+ failedPath: getRuntimeSignalPath(signalRoot, options.assignmentId, 'failed', runtimeRunId),
222
+ stdoutLog: getRuntimeLogPath(signalRoot, options.assignmentId, 'stdout', runtimeRunId),
223
+ stderrLog: getRuntimeLogPath(signalRoot, options.assignmentId, 'stderr', runtimeRunId),
224
+ stdinFilePath,
225
+ contractBootstrapPath,
226
+ expectedWorkspacePath: options.worktreePath,
227
+ }, isWin32, options.turnEcho);
228
+ const contractEnv = isWin32
229
+ ? [
230
+ options.claimId && options.claimId !== '(dry-run)' ? `set BRAINCLAW_CLAIM_ID=${options.claimId}` : undefined,
231
+ `set BRAINCLAW_EXECUTION_CONTRACT_HASH=${options.turnEcho.contract_hash}`,
232
+ `set BRAINCLAW_CAPABILITY_SNAPSHOT_HASH=${options.turnEcho.capability_snapshot_hash}`,
233
+ ].filter((item) => Boolean(item)).join(' && ') + ' && '
234
+ : `export ${[
235
+ options.claimId && options.claimId !== '(dry-run)' ? `BRAINCLAW_CLAIM_ID="${options.claimId}"` : undefined,
236
+ `BRAINCLAW_EXECUTION_CONTRACT_HASH="${options.turnEcho.contract_hash}"`,
237
+ `BRAINCLAW_CAPABILITY_SNAPSHOT_HASH="${options.turnEcho.capability_snapshot_hash}"`,
238
+ ].filter((item) => Boolean(item)).join(' ')}; `;
239
+ return { command: `${contractEnv}${wrapped}`, shell, contractWrapped: true };
240
+ }
118
241
  const envPrefix = buildManualEnvPrefix(options.claimId);
119
242
  return {
120
243
  command: `${envPrefix}${invoke.bashCommand}`,
121
- shell: process.platform === 'win32' ? 'cmd' : (invoke.shell ? 'bash' : 'sh'),
244
+ shell,
122
245
  };
123
246
  }
124
247
  start(invoke, options) {
@@ -131,7 +254,16 @@ export class CliExecutionAdapter {
131
254
  const env = buildWorkerIdentityEnv(process.env, {
132
255
  agent: options.agent,
133
256
  claimId: options.claimId,
134
- extraEnv: { ...buildGitAttributionEnv(options.agent), ...(invoke.env ?? {}) },
257
+ extraEnv: {
258
+ ...buildGitAttributionEnv(options.agent),
259
+ ...(invoke.env ?? {}),
260
+ // Contract identity wins over any agent/invoke-provided environment.
261
+ // The child bootstrap independently verifies these effective values.
262
+ ...(options.turnEcho?.contract_hash ? { BRAINCLAW_EXECUTION_CONTRACT_HASH: options.turnEcho.contract_hash } : {}),
263
+ ...(options.turnEcho?.capability_snapshot_hash
264
+ ? { BRAINCLAW_CAPABILITY_SNAPSHOT_HASH: options.turnEcho.capability_snapshot_hash }
265
+ : {}),
266
+ },
135
267
  });
136
268
  if (invoke.promptDelivery === 'temp_file' && invoke.tempFilePath && invoke.promptText) {
137
269
  const dir = path.dirname(invoke.tempFilePath);
@@ -156,7 +288,8 @@ export class CliExecutionAdapter {
156
288
  // process just ignores stdout/stderr here. stdin stays a pipe when the
157
289
  // prompt is delivered that way (the grouped agent command inherits it).
158
290
  const useAckWrap = !!(options.assignmentId && (options.ackRoot ?? options.worktreePath));
159
- const stdinTarget = needsStdin ? 'pipe' : 'ignore';
291
+ const useWindowsStdinFile = isWin32 && useAckWrap && Boolean(needsStdin);
292
+ const stdinTarget = needsStdin && !useWindowsStdinFile ? 'pipe' : 'ignore';
160
293
  const stdio = [stdinTarget, 'ignore', 'ignore'];
161
294
  // pln#476 + pln#520 step 4: wrap the spawn so the worker shell touches the
162
295
  // pre-exec `ack` sentinel, redirects logs at the shell level, and emits a
@@ -168,12 +301,25 @@ export class CliExecutionAdapter {
168
301
  if (useAckWrap) {
169
302
  const signalRoot = options.ackRoot ?? options.worktreePath;
170
303
  ensureRuntimeDirs(signalRoot);
304
+ const runtimeRunId = options.turnEcho?.run_id;
305
+ const ackPath = getRuntimeSignalPath(signalRoot, options.assignmentId, 'ack', runtimeRunId);
306
+ const contractBootstrapPath = `${ackPath}.bootstrap.cjs`;
307
+ if (options.turnEcho?.contract_hash && options.turnEcho.capability_snapshot_hash) {
308
+ writeContractBootstrapScript(contractBootstrapPath);
309
+ }
310
+ const stdinFilePath = useWindowsStdinFile ? `${ackPath}.stdin` : undefined;
311
+ if (stdinFilePath) {
312
+ fs.writeFileSync(stdinFilePath, invoke.promptText, { encoding: 'utf8', mode: 0o600 });
313
+ }
171
314
  const wrappedCmd = buildAckWrapCommand(invoke.bashCommand, {
172
- ackPath: getRuntimeSignalPath(signalRoot, options.assignmentId, 'ack'),
173
- completedPath: getRuntimeSignalPath(signalRoot, options.assignmentId, 'completed'),
174
- failedPath: getRuntimeSignalPath(signalRoot, options.assignmentId, 'failed'),
175
- stdoutLog: getRuntimeLogPath(signalRoot, options.assignmentId, 'stdout'),
176
- stderrLog: getRuntimeLogPath(signalRoot, options.assignmentId, 'stderr'),
315
+ ackPath,
316
+ completedPath: getRuntimeSignalPath(signalRoot, options.assignmentId, 'completed', runtimeRunId),
317
+ failedPath: getRuntimeSignalPath(signalRoot, options.assignmentId, 'failed', runtimeRunId),
318
+ stdoutLog: getRuntimeLogPath(signalRoot, options.assignmentId, 'stdout', runtimeRunId),
319
+ stderrLog: getRuntimeLogPath(signalRoot, options.assignmentId, 'stderr', runtimeRunId),
320
+ stdinFilePath,
321
+ contractBootstrapPath,
322
+ expectedWorkspacePath: options.worktreePath,
177
323
  }, isWin32, options.turnEcho);
178
324
  child = spawn(wrappedCmd, [], {
179
325
  detached: !isWin32,
@@ -201,7 +347,7 @@ export class CliExecutionAdapter {
201
347
  // On Windows shell:true: this never fires for ENOENT (cmd.exe succeeds);
202
348
  // the isBinaryOnPath pre-check above catches that case instead.
203
349
  child.on('error', () => { });
204
- if (needsStdin && child.stdin) {
350
+ if (needsStdin && !useWindowsStdinFile && child.stdin) {
205
351
  child.stdin.write(invoke.promptText);
206
352
  child.stdin.end();
207
353
  }
@@ -0,0 +1,345 @@
1
+ import crypto from 'node:crypto';
2
+ import { z } from 'zod';
3
+ import { getCapabilityProfile, dispatchCanCommit, } from './agent-capability.js';
4
+ import { ExpectedArtifactSchema } from './loops/artifact-contract.js';
5
+ export const EXECUTION_CONTRACT_VERSION = 1;
6
+ export const EXECUTION_CONTRACT_PROTOCOL_VERSION = 1;
7
+ const RoleCapabilitySchema = z.enum(['execute', 'coordinate', 'review', 'consult']);
8
+ const ExecutionSurfaceSchema = z.enum(['cli', 'ide', 'extension', 'remote']);
9
+ export const CapabilityRequirementSchema = z.object({
10
+ roles: z.array(RoleCapabilitySchema).default(['execute']),
11
+ required_surfaces: z.array(z.enum([
12
+ 'mcp',
13
+ 'hooks',
14
+ 'skills',
15
+ 'rules',
16
+ 'auto_approve',
17
+ 'cli_spawn',
18
+ 'commit',
19
+ 'inbox',
20
+ ])).default([]),
21
+ execution_surfaces: z.array(ExecutionSurfaceSchema).default([]),
22
+ model: z.string().min(1).optional(),
23
+ required_tools: z.array(z.string().min(1)).default([]),
24
+ });
25
+ export const CapabilityResolutionReasonSchema = z.object({
26
+ code: z.enum([
27
+ 'agent_profile_missing',
28
+ 'role_unsupported',
29
+ 'surface_unsupported',
30
+ 'execution_surface_mismatch',
31
+ 'model_unsupported',
32
+ 'tool_catalog_unattested',
33
+ ]),
34
+ requirement: z.string().min(1),
35
+ expected: z.string().optional(),
36
+ actual: z.string().optional(),
37
+ });
38
+ export const HarnessCapabilityBindingSchema = z.object({
39
+ adapter_id: z.string().min(1),
40
+ adapter_version: z.string().min(1),
41
+ requested_model: z.string().min(1).optional(),
42
+ resolved_model: z.string().min(1).optional(),
43
+ model_resolution: z.enum(['exact', 'defaulted', 'unattested']),
44
+ });
45
+ export const CapabilitySnapshotSchema = z.object({
46
+ schema_version: z.literal(1),
47
+ agent: z.string().min(1),
48
+ agent_id: z.string().min(1).optional(),
49
+ profile_name: z.string().min(1).optional(),
50
+ accepted: z.boolean(),
51
+ requested: CapabilityRequirementSchema,
52
+ resolved: z.object({
53
+ roles: z.array(RoleCapabilitySchema),
54
+ surfaces: z.array(z.string().min(1)),
55
+ execution_surface: ExecutionSurfaceSchema.optional(),
56
+ model: z.string().min(1).optional(),
57
+ invoke_binary: z.string().min(1).optional(),
58
+ tool_catalog_attested: z.boolean(),
59
+ harness: HarnessCapabilityBindingSchema.optional(),
60
+ }),
61
+ reasons: z.array(CapabilityResolutionReasonSchema),
62
+ });
63
+ export const ExecutionContractSchema = z.object({
64
+ schema_version: z.literal(EXECUTION_CONTRACT_VERSION),
65
+ minimum_reader_version: z.number().int().positive().max(EXECUTION_CONTRACT_VERSION).default(1),
66
+ identity: z.object({
67
+ loop_id: z.string().min(1),
68
+ turn_id: z.string().min(1),
69
+ logical_attempt_epoch: z.number().int().nonnegative(),
70
+ assignment_id: z.string().min(1),
71
+ run_id: z.string().min(1),
72
+ kind: z.enum(['review', 'ideation', 'implementation', 'research', 'debug']),
73
+ phase: z.string().min(1),
74
+ iteration: z.number().int().nonnegative(),
75
+ }),
76
+ artifact_contract: z.object({
77
+ completion_mode: z.enum(['file', 'mcp', 'either']),
78
+ expected_artifacts: z.array(ExpectedArtifactSchema),
79
+ }),
80
+ capability_requirement: CapabilityRequirementSchema,
81
+ workspace_policy: z.object({
82
+ scope: z.string().min(1),
83
+ cwd: z.string().min(1),
84
+ worktree_path: z.string().min(1).optional(),
85
+ isolation: z.enum(['worktree', 'shared_checkout', 'none']),
86
+ write_access: z.enum(['read_only', 'workspace', 'unrestricted']),
87
+ }),
88
+ timeout_policy: z.object({
89
+ dispatch_lease_ms: z.number().int().positive(),
90
+ grant_lease_ms: z.number().int().positive(),
91
+ }),
92
+ evidence_policy: z.object({
93
+ require_turn_id: z.literal(true),
94
+ require_run_id: z.literal(true),
95
+ require_nonce: z.literal(true),
96
+ artifact_hash: z.enum(['required', 'optional']),
97
+ }),
98
+ protocol: z.object({
99
+ name: z.literal('attempt-authority'),
100
+ minimum_version: z.number().int().positive().max(EXECUTION_CONTRACT_PROTOCOL_VERSION),
101
+ }),
102
+ });
103
+ export const ExecutionContractRefSchema = z.object({
104
+ version: z.literal(EXECUTION_CONTRACT_VERSION),
105
+ hash: z.string().regex(/^[a-f0-9]{64}$/),
106
+ snapshot_hash: z.string().regex(/^[a-f0-9]{64}$/),
107
+ turn_id: z.string().min(1),
108
+ });
109
+ export const RuntimeCapabilityObservationSchema = z.object({
110
+ contract_hash: z.string().regex(/^[a-f0-9]{64}$/),
111
+ capability_snapshot_hash: z.string().regex(/^[a-f0-9]{64}$/),
112
+ adapter_id: z.string().min(1).optional(),
113
+ adapter_version: z.string().min(1).optional(),
114
+ observed_surfaces: z.array(z.string().min(1)).default([]),
115
+ observed_model: z.string().min(1).optional(),
116
+ accepted_contract_hash: z.string().regex(/^[a-f0-9]{64}$/).optional(),
117
+ accepted_capability_snapshot_hash: z.string().regex(/^[a-f0-9]{64}$/).optional(),
118
+ });
119
+ function canonicalValue(value) {
120
+ if (typeof value === 'string')
121
+ return value.normalize('NFC');
122
+ if (Array.isArray(value))
123
+ return value.map(canonicalValue);
124
+ if (value && typeof value === 'object') {
125
+ const normalized = Object.entries(value)
126
+ .filter(([, item]) => item !== undefined)
127
+ .map(([key, item]) => [key.normalize('NFC'), canonicalValue(item)])
128
+ .sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
129
+ if (new Set(normalized.map(([key]) => key)).size !== normalized.length) {
130
+ throw new Error('canonical execution contract contains duplicate NFC-normalized keys');
131
+ }
132
+ return Object.fromEntries(normalized);
133
+ }
134
+ return value;
135
+ }
136
+ export function canonicalExecutionContract(contract) {
137
+ return JSON.stringify(canonicalValue(ExecutionContractSchema.parse(contract)));
138
+ }
139
+ export function executionContractHash(contract) {
140
+ return crypto.createHash('sha256').update(canonicalExecutionContract(contract), 'utf8').digest('hex');
141
+ }
142
+ export function capabilitySnapshotHash(snapshot) {
143
+ const canonical = JSON.stringify(canonicalValue(CapabilitySnapshotSchema.parse(snapshot)));
144
+ return crypto.createHash('sha256').update(canonical, 'utf8').digest('hex');
145
+ }
146
+ function profileSurfaces(profile) {
147
+ const surfaces = [
148
+ profile.hasMcp && 'mcp',
149
+ profile.hasHooks && 'hooks',
150
+ profile.hasSkills && 'skills',
151
+ profile.hasRules && 'rules',
152
+ profile.hasAutoApprove && 'auto_approve',
153
+ profile.runtime.canBeSpawnedCli && 'cli_spawn',
154
+ profile.runtime.inbox && 'inbox',
155
+ dispatchCanCommit(profile) && 'commit',
156
+ ].filter((value) => Boolean(value));
157
+ return [...new Set(surfaces)].sort();
158
+ }
159
+ function resolvedModel(profile, requested) {
160
+ if (!requested)
161
+ return profile.default_model;
162
+ if (profile.model_flag || profile.default_model === requested)
163
+ return requested;
164
+ return undefined;
165
+ }
166
+ export function resolveCapabilitySnapshot(agent, requirementInput, agentId, harness) {
167
+ const requested = CapabilityRequirementSchema.parse(requirementInput);
168
+ const harnessBinding = harness ? HarnessCapabilityBindingSchema.parse(harness) : undefined;
169
+ const profile = getCapabilityProfile(agent);
170
+ const reasons = [];
171
+ if (!profile) {
172
+ reasons.push({ code: 'agent_profile_missing', requirement: 'agent_profile', expected: agent });
173
+ return CapabilitySnapshotSchema.parse({
174
+ schema_version: 1,
175
+ agent,
176
+ agent_id: agentId,
177
+ accepted: false,
178
+ requested,
179
+ resolved: { roles: [], surfaces: [], tool_catalog_attested: false },
180
+ reasons,
181
+ });
182
+ }
183
+ const surfaces = profileSurfaces(profile);
184
+ for (const role of requested.roles) {
185
+ if (!profile.role_capabilities.includes(role)) {
186
+ reasons.push({
187
+ code: 'role_unsupported',
188
+ requirement: `role:${role}`,
189
+ expected: role,
190
+ actual: profile.role_capabilities.join(','),
191
+ });
192
+ }
193
+ }
194
+ for (const surface of requested.required_surfaces) {
195
+ if (!surfaces.includes(surface)) {
196
+ reasons.push({
197
+ code: 'surface_unsupported',
198
+ requirement: `surface:${surface}`,
199
+ expected: surface,
200
+ actual: surfaces.join(','),
201
+ });
202
+ }
203
+ }
204
+ if (requested.execution_surfaces.length > 0
205
+ && !requested.execution_surfaces.includes(profile.execution_env.surface)) {
206
+ reasons.push({
207
+ code: 'execution_surface_mismatch',
208
+ requirement: 'execution_surface',
209
+ expected: requested.execution_surfaces.join(','),
210
+ actual: profile.execution_env.surface,
211
+ });
212
+ }
213
+ const model = resolvedModel(profile, requested.model);
214
+ if (requested.model && !model) {
215
+ reasons.push({
216
+ code: 'model_unsupported',
217
+ requirement: 'model',
218
+ expected: requested.model,
219
+ actual: profile.default_model,
220
+ });
221
+ }
222
+ // Current profiles attest transport surfaces, not per-tool catalogs. Refuse
223
+ // named tools rather than guessing from hasMcp/hasSkills.
224
+ if (requested.required_tools.length > 0) {
225
+ reasons.push({
226
+ code: 'tool_catalog_unattested',
227
+ requirement: 'required_tools',
228
+ expected: requested.required_tools.join(','),
229
+ actual: 'unattested',
230
+ });
231
+ }
232
+ return CapabilitySnapshotSchema.parse({
233
+ schema_version: 1,
234
+ agent,
235
+ agent_id: agentId,
236
+ profile_name: profile.name,
237
+ accepted: reasons.length === 0,
238
+ requested,
239
+ resolved: {
240
+ roles: profile.role_capabilities,
241
+ surfaces,
242
+ execution_surface: profile.execution_env.surface,
243
+ model,
244
+ invoke_binary: profile.invoke_binary,
245
+ tool_catalog_attested: false,
246
+ harness: harnessBinding,
247
+ },
248
+ reasons,
249
+ });
250
+ }
251
+ export function executionContractRef(contract, snapshot) {
252
+ return {
253
+ version: contract.schema_version,
254
+ hash: executionContractHash(contract),
255
+ snapshot_hash: capabilitySnapshotHash(snapshot),
256
+ turn_id: contract.identity.turn_id,
257
+ };
258
+ }
259
+ export function assertExecutionContractIntegrity(contract, ref, snapshot, selectedAgent) {
260
+ const parsedContract = ExecutionContractSchema.parse(contract);
261
+ const parsedRef = ExecutionContractRefSchema.parse(ref);
262
+ const parsedSnapshot = CapabilitySnapshotSchema.parse(snapshot);
263
+ if (parsedRef.turn_id !== parsedContract.identity.turn_id) {
264
+ throw new Error(`execution contract turn mismatch: ${parsedRef.turn_id} != ${parsedContract.identity.turn_id}`);
265
+ }
266
+ const actualHash = executionContractHash(parsedContract);
267
+ if (parsedRef.hash !== actualHash) {
268
+ throw new Error(`execution contract hash mismatch: ${parsedRef.hash} != ${actualHash}`);
269
+ }
270
+ const actualSnapshotHash = capabilitySnapshotHash(parsedSnapshot);
271
+ if (parsedRef.snapshot_hash !== actualSnapshotHash) {
272
+ throw new Error(`capability snapshot hash mismatch: ${parsedRef.snapshot_hash} != ${actualSnapshotHash}`);
273
+ }
274
+ if (!parsedSnapshot.accepted) {
275
+ throw new Error('execution contract capability snapshot was not accepted');
276
+ }
277
+ if (JSON.stringify(parsedSnapshot.requested) !== JSON.stringify(parsedContract.capability_requirement)) {
278
+ throw new Error('execution contract capability requirement differs from its resolved snapshot');
279
+ }
280
+ if (selectedAgent && parsedSnapshot.agent !== selectedAgent.agent) {
281
+ throw new Error(`capability snapshot agent mismatch: ${parsedSnapshot.agent} != ${selectedAgent.agent}`);
282
+ }
283
+ if (selectedAgent && parsedSnapshot.agent_id !== selectedAgent.agent_id) {
284
+ throw new Error(`capability snapshot agent_id mismatch: ${parsedSnapshot.agent_id ?? 'none'} != ${selectedAgent.agent_id ?? 'none'}`);
285
+ }
286
+ }
287
+ /**
288
+ * Pre-crossing acceptance emitted by the frozen harness adapter. The child
289
+ * process still confirms the effective environment in its bootstrap ACK after
290
+ * spawn; this attestation proves, before authority crosses, that the selected
291
+ * adapter accepted the exact immutable contract it is about to deliver.
292
+ */
293
+ export function attestHarnessContractAcceptance(expectedRef, snapshot, binding) {
294
+ const parsedSnapshot = CapabilitySnapshotSchema.parse(snapshot);
295
+ const parsedBinding = HarnessCapabilityBindingSchema.parse(binding);
296
+ if (!parsedSnapshot.accepted) {
297
+ throw new Error('harness cannot accept a rejected capability snapshot');
298
+ }
299
+ const frozen = parsedSnapshot.resolved.harness;
300
+ if (!frozen)
301
+ throw new Error('capability snapshot has no frozen harness binding');
302
+ if (frozen.adapter_id !== parsedBinding.adapter_id
303
+ || frozen.adapter_version !== parsedBinding.adapter_version
304
+ || frozen.requested_model !== parsedBinding.requested_model
305
+ || frozen.resolved_model !== parsedBinding.resolved_model) {
306
+ throw new Error(`harness acceptance mismatch: frozen ${frozen.adapter_id}@${frozen.adapter_version}, `
307
+ + `selected ${parsedBinding.adapter_id}@${parsedBinding.adapter_version}`);
308
+ }
309
+ return {
310
+ contract_hash: expectedRef.hash,
311
+ capability_snapshot_hash: expectedRef.snapshot_hash,
312
+ };
313
+ }
314
+ /**
315
+ * Deterministic capability selection/reselection. Input order is deliberately
316
+ * irrelevant so replaying the same candidate set produces the same worker.
317
+ * Callers can exclude a failed pre-cross candidate and run the resolver again.
318
+ */
319
+ export function resolveExecutionCandidate(candidates, requirementInput, options = {}) {
320
+ const requirement = CapabilityRequirementSchema.parse(requirementInput);
321
+ const excluded = new Set((options.exclude ?? []).map(({ agent, agent_id }) => `${agent.normalize('NFC')}\0${(agent_id ?? '').normalize('NFC')}`));
322
+ const ordered = candidates
323
+ .filter(({ agent, agent_id }) => !excluded.has(`${agent.normalize('NFC')}\0${(agent_id ?? '').normalize('NFC')}`))
324
+ .map((candidate) => ({ ...candidate, agent: candidate.agent.normalize('NFC'), agent_id: candidate.agent_id?.normalize('NFC') }))
325
+ .sort((left, right) => (right.preference ?? 0) - (left.preference ?? 0)
326
+ || left.agent.localeCompare(right.agent, 'en')
327
+ || (left.agent_id ?? '').localeCompare(right.agent_id ?? '', 'en'));
328
+ const evaluated = ordered.map((candidate) => ({
329
+ ...candidate,
330
+ snapshot: resolveCapabilitySnapshot(candidate.agent, requirement, candidate.agent_id),
331
+ }));
332
+ const selected = evaluated.find((candidate) => candidate.snapshot.accepted);
333
+ return selected ? { kind: 'selected', selected, evaluated } : { kind: 'rejected', evaluated };
334
+ }
335
+ export function validateWorkerContractAcceptance(expectedRef, acceptedRef, launchStatus) {
336
+ const expected = { contract_hash: expectedRef.hash, capability_snapshot_hash: expectedRef.snapshot_hash };
337
+ if (expected.contract_hash === acceptedRef.contract_hash
338
+ && expected.capability_snapshot_hash === acceptedRef.capability_snapshot_hash)
339
+ return { kind: 'accepted' };
340
+ if (launchStatus === 'crossed') {
341
+ return { kind: 'post_crossing_anomaly', expected, accepted: acceptedRef, respawn: false };
342
+ }
343
+ return { kind: 'abort_and_reselect', expected, accepted: acceptedRef };
344
+ }
345
+ //# sourceMappingURL=execution-contract.js.map