@ccdd/project 3.3.1 → 4.0.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 (153) hide show
  1. package/README.md +9 -12
  2. package/dist/monitor-ui/assets/GraphView-CaxsIXtN.js +27 -0
  3. package/dist/monitor-ui/assets/ValidationView-BMqI2XQ5.css +1 -0
  4. package/dist/monitor-ui/assets/ValidationView-CGW6-Ljw.js +1 -0
  5. package/dist/monitor-ui/assets/{elk-api-D5d3gWoP.js → elk-api-DIQ2_nge.js} +1 -1
  6. package/dist/monitor-ui/assets/{elk.bundled-0eaKiuBP.js → elk.bundled-BRtnLd_M.js} +1 -1
  7. package/dist/monitor-ui/assets/{index-DDwx11D7.css → index-D0yfhUlt.css} +1 -1
  8. package/dist/monitor-ui/assets/index-LyUbXn5P.js +7 -0
  9. package/dist/monitor-ui/index.html +2 -2
  10. package/dist/scripts/prepare-demo.js +25 -44
  11. package/dist/scripts/prepare-demo.js.map +1 -1
  12. package/dist/src/artifact-scope.d.ts +6 -0
  13. package/dist/src/artifact-scope.js +27 -0
  14. package/dist/src/artifact-scope.js.map +1 -0
  15. package/dist/src/artifacts/index.d.ts +7 -155
  16. package/dist/src/artifacts/index.js +0 -354
  17. package/dist/src/artifacts/index.js.map +1 -1
  18. package/dist/src/artifacts/instruction.d.ts +6 -9
  19. package/dist/src/artifacts/instruction.js +28 -36
  20. package/dist/src/artifacts/instruction.js.map +1 -1
  21. package/dist/src/artifacts/mcp-server.js +5 -6
  22. package/dist/src/artifacts/mcp-server.js.map +1 -1
  23. package/dist/src/artifacts/scope.d.ts +12 -0
  24. package/dist/src/artifacts/scope.js +34 -0
  25. package/dist/src/artifacts/scope.js.map +1 -0
  26. package/dist/src/artifacts/tool-check.d.ts +2 -6
  27. package/dist/src/artifacts/tool-check.js +14 -18
  28. package/dist/src/artifacts/tool-check.js.map +1 -1
  29. package/dist/src/artifacts/types.d.ts +3 -37
  30. package/dist/src/artifacts/types.js +5 -89
  31. package/dist/src/artifacts/types.js.map +1 -1
  32. package/dist/src/broker/config.d.ts +5 -6
  33. package/dist/src/broker/config.js +183 -130
  34. package/dist/src/broker/config.js.map +1 -1
  35. package/dist/src/broker/graph.d.ts +14 -21
  36. package/dist/src/broker/graph.js +68 -70
  37. package/dist/src/broker/graph.js.map +1 -1
  38. package/dist/src/broker/index.d.ts +6 -28
  39. package/dist/src/broker/index.js +35 -221
  40. package/dist/src/broker/index.js.map +1 -1
  41. package/dist/src/cli.d.ts +1 -21
  42. package/dist/src/cli.js +2 -329
  43. package/dist/src/cli.js.map +1 -1
  44. package/dist/src/contracts.d.ts +12 -15
  45. package/dist/src/definitions.d.ts +33 -23
  46. package/dist/src/diagnostics-cli.d.ts +9 -0
  47. package/dist/src/diagnostics-cli.js +94 -0
  48. package/dist/src/diagnostics-cli.js.map +1 -0
  49. package/dist/src/doctor/index.d.ts +2 -4
  50. package/dist/src/doctor/index.js +17 -63
  51. package/dist/src/doctor/index.js.map +1 -1
  52. package/dist/src/executors/auth.d.ts +1 -1
  53. package/dist/src/executors/auth.js +2 -2
  54. package/dist/src/executors/auth.js.map +1 -1
  55. package/dist/src/executors/human-preparation.js +2 -10
  56. package/dist/src/executors/human-preparation.js.map +1 -1
  57. package/dist/src/executors/index.js +30 -29
  58. package/dist/src/executors/index.js.map +1 -1
  59. package/dist/src/executors/pi.js +2 -3
  60. package/dist/src/executors/pi.js.map +1 -1
  61. package/dist/src/executors/process.js +9 -0
  62. package/dist/src/executors/process.js.map +1 -1
  63. package/dist/src/monitor/actions.js +3 -3
  64. package/dist/src/monitor/actions.js.map +1 -1
  65. package/dist/src/monitor/server.js +12 -95
  66. package/dist/src/monitor/server.js.map +1 -1
  67. package/dist/src/monitor/store.js +6 -42
  68. package/dist/src/monitor/store.js.map +1 -1
  69. package/dist/src/monitor/types.d.ts +3 -13
  70. package/dist/src/monitor/ui/critic-presentation.js +1 -1
  71. package/dist/src/monitor/ui/critic-presentation.js.map +1 -1
  72. package/dist/src/monitor/ui/graph-layout.d.ts +0 -1
  73. package/dist/src/monitor/ui/graph-layout.js +1 -23
  74. package/dist/src/monitor/ui/graph-layout.js.map +1 -1
  75. package/dist/src/monitor/ui/tool-input.d.ts +1 -1
  76. package/dist/src/monitor/ui/tool-input.js +2 -3
  77. package/dist/src/monitor/ui/tool-input.js.map +1 -1
  78. package/dist/src/project/cli.js +18 -30
  79. package/dist/src/project/cli.js.map +1 -1
  80. package/dist/src/project/graph.js +2 -2
  81. package/dist/src/project/graph.js.map +1 -1
  82. package/dist/src/project/identity.js +83 -56
  83. package/dist/src/project/identity.js.map +1 -1
  84. package/dist/src/project/index.d.ts +0 -3
  85. package/dist/src/project/index.js +2 -6
  86. package/dist/src/project/index.js.map +1 -1
  87. package/dist/src/project/query.d.ts +2 -1
  88. package/dist/src/project/query.js +49 -74
  89. package/dist/src/project/query.js.map +1 -1
  90. package/dist/src/project/store.js +2 -2
  91. package/dist/src/project/store.js.map +1 -1
  92. package/dist/src/project/types.d.ts +4 -4
  93. package/dist/src/requester/index.d.ts +2 -9
  94. package/dist/src/requester/index.js +17 -96
  95. package/dist/src/requester/index.js.map +1 -1
  96. package/dist/src/sdk.d.ts +3 -8
  97. package/dist/src/sdk.js +2 -3
  98. package/dist/src/sdk.js.map +1 -1
  99. package/dist/src/tools/contracts.d.ts +34 -78
  100. package/dist/src/tools/diagnostics.js +1 -2
  101. package/dist/src/tools/diagnostics.js.map +1 -1
  102. package/dist/src/tools/environment.d.ts +1 -0
  103. package/dist/src/tools/environment.js +7 -11
  104. package/dist/src/tools/environment.js.map +1 -1
  105. package/dist/src/tools/manifest.d.ts +0 -1
  106. package/dist/src/tools/manifest.js +1 -25
  107. package/dist/src/tools/manifest.js.map +1 -1
  108. package/dist/src/tools/runner.d.ts +10 -14
  109. package/dist/src/tools/runner.js +144 -118
  110. package/dist/src/tools/runner.js.map +1 -1
  111. package/dist/src/tools/schema.js +1 -1
  112. package/dist/src/tools/schema.js.map +1 -1
  113. package/dist/src/workspaces/index.d.ts +5 -12
  114. package/dist/src/workspaces/index.js +33 -217
  115. package/dist/src/workspaces/index.js.map +1 -1
  116. package/package.json +38 -10
  117. package/dist/monitor-ui/assets/GraphView-BSZlEAs3.js +0 -27
  118. package/dist/monitor-ui/assets/ValidationView-DaEF78J-.js +0 -1
  119. package/dist/monitor-ui/assets/ValidationView-p5r8lX18.css +0 -1
  120. package/dist/monitor-ui/assets/index-BQlWbkpH.js +0 -8
  121. package/dist/src/artifacts/data.d.ts +0 -5
  122. package/dist/src/artifacts/data.js +0 -100
  123. package/dist/src/artifacts/data.js.map +0 -1
  124. package/dist/src/artifacts/groups.d.ts +0 -13
  125. package/dist/src/artifacts/groups.js +0 -93
  126. package/dist/src/artifacts/groups.js.map +0 -1
  127. package/dist/src/artifacts/human.d.ts +0 -38
  128. package/dist/src/artifacts/human.js +0 -156
  129. package/dist/src/artifacts/human.js.map +0 -1
  130. package/dist/src/artifacts/sources.d.ts +0 -11
  131. package/dist/src/artifacts/sources.js +0 -106
  132. package/dist/src/artifacts/sources.js.map +0 -1
  133. package/dist/src/review/cli.d.ts +0 -8
  134. package/dist/src/review/cli.js +0 -136
  135. package/dist/src/review/cli.js.map +0 -1
  136. package/dist/src/review/client.d.ts +0 -30
  137. package/dist/src/review/client.js +0 -193
  138. package/dist/src/review/client.js.map +0 -1
  139. package/dist/src/review/server.d.ts +0 -11
  140. package/dist/src/review/server.js +0 -249
  141. package/dist/src/review/server.js.map +0 -1
  142. package/dist/src/review/types.d.ts +0 -10
  143. package/dist/src/review/types.js +0 -17
  144. package/dist/src/review/types.js.map +0 -1
  145. package/dist/src/tools/host.d.ts +0 -7
  146. package/dist/src/tools/host.js +0 -98
  147. package/dist/src/tools/host.js.map +0 -1
  148. package/dist/src/tools/runtime-host.d.ts +0 -1
  149. package/dist/src/tools/runtime-host.js +0 -212
  150. package/dist/src/tools/runtime-host.js.map +0 -1
  151. package/dist/src/workspaces/transfer.d.ts +0 -52
  152. package/dist/src/workspaces/transfer.js +0 -372
  153. package/dist/src/workspaces/transfer.js.map +0 -1
@@ -3,19 +3,15 @@ import { randomUUID } from 'node:crypto';
3
3
  import { execFileSync } from 'node:child_process';
4
4
  import fs from 'node:fs';
5
5
  import path from 'node:path';
6
- import { isDeepStrictEqual } from 'node:util';
7
6
  import { setTimeout as delay } from 'node:timers/promises';
8
- import { prepareReviewRequests, readStoredArtifactScope } from '../requester/index.js';
7
+ import { prepareReviewRequests } from '../requester/index.js';
9
8
  import { readWorkspaceConfig } from './config.js';
10
- import { createGraphDefinition, prerequisiteCriticIds } from './graph.js';
9
+ import { createGraphDefinition } from './graph.js';
11
10
  import { createReviewTools } from '../tools/runner.js';
12
- import { describeReviewTools } from '../tools/runner.js';
13
- import { validateArguments } from '../tools/schema.js';
14
11
  import { createHumanClaims, HUMAN_PREPARATION_LEASE_MS } from './human-claims.js';
15
12
  import { prepareHumanReview } from '../executors/human-preparation.js';
16
13
  import { prepareWorkspace, reopenWorkspace } from '../workspaces/index.js';
17
14
  import { createProjectSnapshot } from '../project/identity.js';
18
- import { assertGeneratedReviewInputs, prepareArtifactInputs } from '../artifacts/sources.js';
19
15
  import { includedCritics, planProject } from '../project/query.js';
20
16
  import { readEvidence } from '../project/store.js';
21
17
  const object = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
@@ -142,7 +138,7 @@ function prepareStateDirectory(repoPath, stateDir) {
142
138
  const canonical = canonicalFuturePath(stateDir);
143
139
  const relative = path.relative(repoPath, canonical);
144
140
  if (relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative))) {
145
- throw codedError('CCDD state, logs and copied workspaces must be outside the reviewed repository.', 'WORKSPACE_UNSAFE');
141
+ throw codedError('CCDD state, logs and review outputs must be outside the reviewed repository.', 'WORKSPACE_UNSAFE');
146
142
  }
147
143
  fs.mkdirSync(canonical, { recursive: true, mode: 0o700 });
148
144
  return fs.realpathSync(canonical);
@@ -239,59 +235,17 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
239
235
  event: (request, type, message) => appendEvent(request.runId, request.id, type, message),
240
236
  assertWaiting: request => {
241
237
  ensureOpen();
238
+ if (request.configManifest?.version !== 2)
239
+ throw new Error('Historical reviews are available for result lookup only.');
242
240
  if (!request.notifiedAt)
243
241
  throw new Error('Human alarm delivery is still pending.');
244
- if (request.workspace.mode === 'lock' && !ownerAlive(ownerData(request.runId)))
245
- throw new Error('A lock review requires its monitoring worker to remain alive.');
242
+ if (!ownerAlive(ownerData(request.runId)))
243
+ throw new Error('An in-place review requires its monitoring worker to remain alive.');
246
244
  },
247
245
  });
248
- const assertRemoteClaim = (requestId, reviewerId, attemptId) => {
249
- ensureOpen();
250
- const request = required(requestData(requestId), 'request');
251
- if (request.profile.kind !== 'human' || request.status !== 'WAITING_HUMAN' || request.claimedBy !== reviewerId || request.claimAttemptId !== attemptId)
252
- throw new Error('This confirmed Claim is no longer owned by this reviewer.');
253
- if (request.workspace.mode !== 'copy')
254
- throw new Error('Remote Human review requires a copied workspace.');
255
- return request;
256
- };
257
- function prerequisites(request, run, requests) {
258
- if (run.project)
259
- return []; // Project requests are issued only after a pull readiness check.
260
- if (run.scope?.kind === 'critic')
261
- return [];
262
- if (run.graph) {
263
- const byCritic = new Map(requests.map(item => [item.criticId, item]));
264
- return prerequisiteCriticIds(request, run.graph).map(id => {
265
- const dependency = byCritic.get(id);
266
- if (!dependency || dependency.runId !== run.id)
267
- throw codedError(`Required Critic ${id} is missing from this Run.`, 'REVIEW_GRAPH_INVALID');
268
- return dependency;
269
- });
270
- }
271
- // Historical chains retain their explicit links; no target Artifact is guessed.
272
- if (request.predecessorId === null || (request.predecessorId === undefined && requests.length === 1))
273
- return [];
274
- const previous = requests.find(item => item.id === request.predecessorId);
275
- if (!previous || previous.runId !== run.id)
276
- throw codedError('This historical Run has no usable predecessor link. Submit a new review.', 'REVIEW_GRAPH_INVALID');
277
- return [previous];
278
- }
279
- function dependencyFailure(request, run, requests, seen = new Set()) {
280
- if (seen.has(request.id))
281
- return null;
282
- seen.add(request.id);
283
- for (const dependency of prerequisites(request, run, requests)) {
284
- if (dependency.status === 'RED' || dependency.status === 'ERROR')
285
- return dependency;
286
- const failure = dependencyFailure(dependency, run, requests, seen);
287
- if (failure)
288
- return failure;
289
- }
290
- return null;
291
- }
292
246
  function refreshReadinessWithin(runId) {
293
247
  const run = required(runData(runId), 'Run'), requests = runRequests(runId);
294
- if (run.project) {
248
+ if (run.project?.version === 2) {
295
249
  if (terminal.has(run.status))
296
250
  return;
297
251
  const plan = planProject(run.project.snapshot, readEvidence(db), { selection: run.project.selection, recursive: run.project.recursive, force: run.project.force, runId, attempts: requests });
@@ -308,30 +262,12 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
308
262
  }
309
263
  return;
310
264
  }
311
- for (const request of requests) {
312
- if (request.status !== 'BLOCKED')
313
- continue;
314
- const dependencies = prerequisites(request, run, requests);
315
- if (dependencies.every(item => item.status === 'GREEN')) {
316
- request.status = 'QUEUED';
317
- request.blockedReason = null;
318
- saveRequest(request);
319
- appendEvent(runId, request.id, 'request.queued', 'All required Artifact reviews are GREEN.');
320
- }
321
- else {
322
- const failed = dependencyFailure(request, run, requests);
323
- const reason = failed ? `${failed.criticId} returned ${failed.status}; dependent reviews cannot start.` : `Waiting for ${dependencies.filter(item => item.status !== 'GREEN').map(item => item.criticId).join(', ')} to become GREEN.`;
324
- if (request.blockedReason !== reason) {
325
- request.blockedReason = reason;
326
- saveRequest(request);
327
- }
328
- }
329
- }
265
+ throw new Error('Historical Runs cannot be resumed; submit a new validation request.');
330
266
  }
331
267
  const updateRunStatus = (runId) => {
332
268
  const states = runRequests(runId).map(request => request.status);
333
269
  const run = required(runData(runId), 'Run');
334
- if (run.project) {
270
+ if (run.project?.version === 2) {
335
271
  if (terminal.has(run.status))
336
272
  return;
337
273
  const plan = planProject(run.project.snapshot, readEvidence(db), { selection: run.project.selection, recursive: run.project.recursive, force: run.project.force, runId, attempts: runRequests(runId) });
@@ -348,19 +284,7 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
348
284
  }
349
285
  return;
350
286
  }
351
- // A branch failure is a verdict for that branch, not a stop signal for its siblings.
352
- const status = states.includes('RUNNING') ? 'RUNNING' : states.includes('QUEUED') ? 'QUEUED' :
353
- states.includes('WAITING_HUMAN') ? 'WAITING_HUMAN' : states.includes('ERROR') ? 'ERROR' : states.includes('RED') ? 'RED' :
354
- states.length > 0 && states.every(state => state === 'GREEN') ? 'GREEN' : 'ERROR';
355
- if (run.status !== status) {
356
- run.status = status;
357
- if (terminal.has(status))
358
- run.completedAt = now();
359
- else
360
- delete run.completedAt;
361
- saveRun(run);
362
- appendEvent(runId, null, 'run.status', `Run ${status}`, { status });
363
- }
287
+ throw new Error('Historical Runs are available for result lookup only.');
364
288
  };
365
289
  function finishWithin(requestId, outcome, expectedStates = ['RUNNING', 'WAITING_HUMAN']) {
366
290
  const { result, error } = outcome, hasError = Object.hasOwn(outcome, 'error');
@@ -394,7 +318,7 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
394
318
  saveRequest(request);
395
319
  appendEvent(runId, request.id, 'request.error', request.error, { status: request.status, ...(request.errorCode ? { code: request.errorCode } : {}) });
396
320
  }
397
- if (run.project) {
321
+ if (run.project?.version === 2) {
398
322
  const plan = planProject(run.project.snapshot, readEvidence(db), { selection: run.project.selection, recursive: run.project.recursive, force: run.project.force, runId, attempts: runRequests(runId) });
399
323
  run.project.evidenceRequestIds = [...new Set(plan.critics.flatMap(c => c.result ? [c.result.requestId] : []))];
400
324
  }
@@ -408,13 +332,7 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
408
332
  const owner = ownerData(runId);
409
333
  if (!owner || ownerAlive(owner))
410
334
  return;
411
- const run = runData(runId);
412
- const unfinished = runRequests(runId).filter(request => !terminal.has(request.status));
413
- const safeHumanWait = run?.workspace?.mode === 'copy' && unfinished.some(request => request.status === 'WAITING_HUMAN') &&
414
- unfinished.every(request => request.status === 'BLOCKED' || (request.status === 'WAITING_HUMAN' && request.notifiedAt));
415
- if (!safeHumanWait) {
416
- failWithin(runId, codedError('The review worker exited before completing its work. Submit a new run to retry.', 'WORKER_EXITED'));
417
- }
335
+ failWithin(runId, codedError('The review worker exited before completing its work. Submit a new run to retry.', 'WORKER_EXITED'));
418
336
  db.prepare('DELETE FROM run_owners WHERE run_id = ? AND token = ?').run(runId, owner.token);
419
337
  appendEvent(runId, null, 'worker.exited', 'The previous review worker is no longer running.');
420
338
  }
@@ -455,7 +373,7 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
455
373
  request.startedAt = now();
456
374
  request.blockedReason = null;
457
375
  saveRequest(request);
458
- appendEvent(runId, request.id, 'request.started', `${request.title}: reviewing ${workspace.descriptor.mode} workspace.`, { snapshotHash: workspace.descriptor.hash });
376
+ appendEvent(runId, request.id, 'request.started', `${request.title}: reviewing the supplied workspace.`, { snapshotHash: workspace.descriptor.hash });
459
377
  updateRunStatus(runId);
460
378
  });
461
379
  changed();
@@ -463,7 +381,6 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
463
381
  signal.throwIfAborted();
464
382
  await workspace.assertUnchanged();
465
383
  const request = required(requestData(requestId), 'Request');
466
- assertGeneratedReviewInputs(request);
467
384
  const runDir = path.join(stateDir, 'runs', runId, request.id);
468
385
  fs.mkdirSync(runDir, { recursive: true, mode: 0o700 });
469
386
  if (required(runData(runId), 'Run').project) {
@@ -543,6 +460,8 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
543
460
  stored = runData(runId);
544
461
  if (!stored)
545
462
  throw new Error('Unknown Run.');
463
+ if (stored.project?.version !== 2)
464
+ throw new Error('Historical Runs cannot be resumed; submit a new validation request.');
546
465
  if (terminal.has(stored.status))
547
466
  return false;
548
467
  if (!stored.workspace)
@@ -598,7 +517,7 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
598
517
  inFlight.set(queued.id, { kind: queued.profile.kind, promise });
599
518
  }
600
519
  if (inFlight.size) {
601
- // Human alarms and independent branches progress together; completed dependencies can unblock work immediately.
520
+ // Human alarms and independent evaluations progress together.
602
521
  await Promise.race([...inFlight.values()].map(item => item.promise).concat(delay(50, undefined, { signal: reviewSignal })));
603
522
  continue;
604
523
  }
@@ -606,24 +525,7 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
606
525
  if (waiting.length) {
607
526
  if (waiting.some(request => !request.notifiedAt))
608
527
  throw new Error('Human notification did not complete; submit a new Run to retry.');
609
- if (ownedRun.workspace.mode === 'copy') {
610
- await workspace.assertUnchanged();
611
- // A result can arrive during input validation. Release only after rechecking all branches atomically.
612
- const paused = transaction(() => {
613
- const pending = runRequests(runId).filter(request => !terminal.has(request.status));
614
- if (!pending.some(request => request.status === 'WAITING_HUMAN') || pending.some(request => request.status !== 'BLOCKED' && (request.status !== 'WAITING_HUMAN' || !request.notifiedAt)))
615
- return false;
616
- if (ownerData(runId)?.token !== token)
617
- throw codedError('Review ownership was lost.', 'RUN_OWNERSHIP_LOST');
618
- db.prepare('DELETE FROM run_owners WHERE run_id = ? AND token = ?').run(runId, token);
619
- appendEvent(runId, null, 'worker.paused', 'Copied Human reviews are persisted; no worker is needed while awaiting results.');
620
- return true;
621
- });
622
- if (paused)
623
- break;
624
- continue;
625
- }
626
- // The lock observer stays alive with filesystem events and metadata polls.
528
+ // The workspace observer stays alive with filesystem events and metadata polls.
627
529
  // Notification already crossed its final content boundary; idle waiting
628
530
  // must not rehash the entire workspace on every scheduling iteration.
629
531
  await delay(100, undefined, { signal: reviewSignal });
@@ -661,18 +563,17 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
661
563
  return entry.promise;
662
564
  }
663
565
  return {
664
- async submitProject({ mode = 'copy', requesterId = 'cli', selection, recursive = false, force = false }) {
566
+ async submitProject({ requesterId = 'cli', selection, recursive = false, force = false, ...removed }) {
665
567
  ensureOpen();
666
568
  requireExecutors();
569
+ if ('mode' in removed)
570
+ throw new Error('Workspace modes are no longer supported; supply an unchanged workspace.');
667
571
  if (!requesterId.trim() || requesterId.length > 200)
668
572
  throw new Error('requesterId is required (maximum 200 characters).');
669
- if (!['lock', 'copy'].includes(mode))
670
- throw new Error('mode must be lock or copy.');
671
573
  await requireExecutors().validateWorkspace?.(repoPath);
672
- const workspace = await workspaceAdapter.prepareWorkspace({ repoPath, stateDir, mode, integrity: workspaceIntegrity });
574
+ const workspace = await workspaceAdapter.prepareWorkspace({ repoPath, stateDir, integrity: workspaceIntegrity });
673
575
  try {
674
- const loaded = await readWorkspaceConfig(workspace.descriptor.path);
675
- const config = await prepareArtifactInputs(loaded.config, workspace.descriptor.path, 'review', workspace.signal);
576
+ const { config } = await readWorkspaceConfig(workspace.descriptor.path, workspace.signal);
676
577
  const snapshot = await createProjectSnapshot(config, workspace.descriptor.path, workspace.descriptor.hash, workspace.signal, workspace.descriptor.integrity);
677
578
  const ids = includedCritics(snapshot, selection, recursive);
678
579
  const templates = [];
@@ -680,7 +581,7 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
680
581
  templates.push(...await prepareReviewRequests({ repoPath: workspace.descriptor.path, repoId, snapshotHash: workspace.descriptor.hash, criticId: id, preparedConfig: config }));
681
582
  const id = randomUUID(), createdAt = now();
682
583
  const record = { id, repoId, snapshotHash: workspace.descriptor.hash, workspace: workspace.descriptor, requesterId,
683
- scope: { kind: 'project' }, graph: createGraphDefinition(config), project: { version: 1, snapshot, selection, recursive, force, templates }, status: 'QUEUED', createdAt };
584
+ scope: { kind: 'project' }, graph: createGraphDefinition(config), project: { version: 2, snapshot, selection, recursive, force, templates }, status: 'QUEUED', createdAt };
684
585
  await workspace.assertUnchanged();
685
586
  workspace.signal.throwIfAborted();
686
587
  transaction(() => {
@@ -696,63 +597,6 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
696
597
  await workspace.close();
697
598
  }
698
599
  },
699
- async submit({ mode = 'copy', requesterId, reviewRequests, criticId, snapshotCommit } = {}) {
700
- ensureOpen();
701
- requireExecutors();
702
- if (snapshotCommit !== undefined)
703
- throw new Error('snapshotCommit is no longer accepted; use mode copy or lock with the current workspace.');
704
- if (typeof requesterId !== 'string' || !requesterId.trim() || requesterId.length > 200)
705
- throw new Error('requesterId is required (maximum 200 characters).');
706
- if (!['lock', 'copy'].includes(mode))
707
- throw new Error('mode must be lock or copy.');
708
- await requireExecutors().validateWorkspace?.(repoPath);
709
- const workspace = await workspaceAdapter.prepareWorkspace({ repoPath, stateDir, mode, integrity: workspaceIntegrity });
710
- try {
711
- const descriptor = copy(workspace.descriptor);
712
- const { config } = await readWorkspaceConfig(descriptor.path);
713
- const graph = createGraphDefinition(config);
714
- const expected = await prepareReviewRequests({ repoPath: descriptor.path, repoId, snapshotHash: descriptor.hash, criticId, preparedConfig: config });
715
- const supplied = reviewRequests === undefined ? expected : reviewRequests;
716
- if (!isDeepStrictEqual(supplied, expected))
717
- throw new Error('Submitted review request envelopes must exactly match the Artifact definitions, payload, profiles and dependencies in the requested workspace.');
718
- const id = randomUUID();
719
- const createdAt = now();
720
- const requests = copy(supplied).map(envelope => ({
721
- ...envelope, id: randomUUID(), runId: id, workspace: descriptor, worktreePath: descriptor.path,
722
- status: 'BLOCKED', createdAt, startedAt: null, completedAt: null,
723
- result: null, error: null, claimedBy: null, claimedAt: null, notifiedAt: null,
724
- }));
725
- for (const request of requests) {
726
- const dependencies = criticId === undefined ? prerequisiteCriticIds(request, graph) : [];
727
- request.status = dependencies.length ? 'BLOCKED' : 'QUEUED';
728
- request.blockedReason = dependencies.length ? `Waiting for ${dependencies.join(', ')} to become GREEN.` : null;
729
- const capability = await requireExecutors().canExecute(copy(request));
730
- if (!capability?.ok)
731
- throw new Error(`Cannot execute ${request.criticId}: ${capability?.reason ?? 'No compatible executor.'}`);
732
- if (request.profile.kind === 'human' && typeof requireExecutors().notifyHuman !== 'function')
733
- throw new Error('Human execution requires an alarm method.');
734
- }
735
- await workspace.assertUnchanged();
736
- workspace.signal.throwIfAborted();
737
- ensureOpen();
738
- const scope = criticId === undefined ? { kind: 'graph' } : { kind: 'critic', criticId: criticId };
739
- const record = { id, repoId, snapshotHash: descriptor.hash, workspace: descriptor, requesterId, scope, graph, status: 'QUEUED', createdAt };
740
- transaction(() => {
741
- db.prepare('INSERT INTO runs(id,created_at,status,data) VALUES (?,?,?,?)').run(id, createdAt, record.status, JSON.stringify(record));
742
- requests.forEach((request, index) => db.prepare('INSERT INTO requests(id,run_id,ordinal,status,data) VALUES (?,?,?,?,?)').run(request.id, id, index, request.status, JSON.stringify(request)));
743
- appendEvent(id, null, 'run.submitted', 'Review persisted; a request-scoped worker can execute it.', { snapshotHash: descriptor.hash, mode, requesterId, scope });
744
- for (const request of requests)
745
- if (request.status === 'QUEUED')
746
- appendEvent(id, request.id, 'request.queued', 'Required Artifact reviews are satisfied; the review is ready.');
747
- appendEvent(id, null, 'workspace.ready', mode === 'copy' ? 'Content-addressed copied workspace is ready.' : 'Current workspace is fixed for this review; changes invalidate it.', { path: descriptor.path, snapshotHash: descriptor.hash });
748
- });
749
- changed();
750
- return required(getRun(id), 'Run');
751
- }
752
- finally {
753
- await workspace.close();
754
- }
755
- },
756
600
  run,
757
601
  reconcile,
758
602
  listRuns() {
@@ -779,7 +623,6 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
779
623
  },
780
624
  renewHumanTryClaim: humanClaims.renew,
781
625
  releaseHumanTryClaim: humanClaims.release,
782
- confirmHumanClaim: humanClaims.confirm,
783
626
  async claimHuman(requestId, reviewerId, { signal } = {}) {
784
627
  ensureOpen();
785
628
  if (typeof reviewerId !== 'string' || !reviewerId.trim())
@@ -788,6 +631,8 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
788
631
  if (existing)
789
632
  reconcile(existing.runId);
790
633
  const request = required(requestData(requestId), 'request');
634
+ if (request.configManifest?.version !== 2)
635
+ throw new Error('Historical reviews are available for result lookup only.');
791
636
  if (request.status === 'WAITING_HUMAN' && request.claimedBy === reviewerId)
792
637
  return request;
793
638
  if (request.claimedBy)
@@ -819,32 +664,6 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
819
664
  clearInterval(heartbeat);
820
665
  }
821
666
  },
822
- authorizeRemoteHumanTool(requestId, { reviewerId, attemptId, toolName, arguments: args = {} }) {
823
- const request = assertRemoteClaim(requestId, reviewerId, attemptId);
824
- if (!request.configManifest)
825
- throw new Error('Remote tool execution requires a TypeScript tool manifest.');
826
- const tool = describeReviewTools({ artifacts: request.artifacts, configManifest: request.configManifest, audience: 'human' }).find(value => value.name === toolName);
827
- if (!tool)
828
- throw new Error('Unknown registered Human tool.');
829
- validateArguments(tool.inputSchema, args);
830
- return tool;
831
- },
832
- recordRemoteHumanTool(requestId, { reviewerId, attemptId, toolName, arguments: args = {} }) {
833
- // Client reports only after real local execution and input validation. A receipt
834
- // never constitutes content observation or a semantic verdict.
835
- const request = assertRemoteClaim(requestId, reviewerId, attemptId);
836
- if (!request.configManifest)
837
- throw new Error('Remote tool execution requires a TypeScript tool manifest.');
838
- const tool = describeReviewTools({ artifacts: request.artifacts, configManifest: request.configManifest, audience: 'human' }).find(value => value.name === toolName);
839
- if (!tool)
840
- throw new Error('Unknown registered Human tool.');
841
- validateArguments(tool.inputSchema, args);
842
- transaction(() => {
843
- assertRemoteClaim(requestId, reviewerId, attemptId);
844
- appendEvent(request.runId, requestId, 'human.tool.executed', 'The claimed reviewer reported successful local tool execution.', { name: tool.name, artifactId: tool.artifactId, operation: tool.operation });
845
- });
846
- changed();
847
- },
848
667
  async executeHumanTool(requestId, { reviewerId, toolName, arguments: args = {}, signal }) {
849
668
  ensureOpen();
850
669
  const first = requestData(requestId);
@@ -855,10 +674,12 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
855
674
  const current = requestData(requestId);
856
675
  if (!current || current.profile.kind !== 'human' || current.status !== 'WAITING_HUMAN')
857
676
  throw new Error('Request is not waiting for a human review.');
677
+ if (current.configManifest?.version !== 2)
678
+ throw new Error('Historical reviews are available for result lookup only.');
858
679
  if (!reviewerId || current.claimedBy !== reviewerId)
859
680
  throw new Error('Only the reviewer who claimed this request can use its tools.');
860
- if (current.workspace.mode === 'lock' && !ownerAlive(ownerData(current.runId)))
861
- throw new Error('A lock review requires its monitoring worker to remain alive.');
681
+ if (!ownerAlive(ownerData(current.runId)))
682
+ throw new Error('An in-place review requires its monitoring worker to remain alive.');
862
683
  return current;
863
684
  };
864
685
  const request = assertClaim();
@@ -870,16 +691,8 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
870
691
  // remains mandatory because configuration loading and tool execution can mutate it.
871
692
  workspace.signal.throwIfAborted();
872
693
  inputsValidated = true;
873
- if (!request.configManifest) {
874
- if (Object.values(request.artifactTypes).some(type => type.custom))
875
- throw codedError('Stored TS Artifact configuration has no tool manifest.', 'WORKSPACE_ARTIFACT_MISMATCH');
876
- const expected = await readStoredArtifactScope({ repoPath: workspace.descriptor.path, criticId: request.criticId });
877
- if (!expected || expected.profile.kind !== 'human' || !isDeepStrictEqual(expected.artifactTypes, request.artifactTypes) || !isDeepStrictEqual(expected.artifacts, request.artifacts) || !isDeepStrictEqual(expected.artifactGroups ?? [], request.artifactGroups ?? [])) {
878
- throw codedError('Stored Artifact tools do not match the reviewed workspace configuration.', 'WORKSPACE_ARTIFACT_MISMATCH');
879
- }
880
- }
881
694
  const registry = await createReviewTools({
882
- worktreePath: workspace.descriptor.path, artifacts: request.artifacts, artifactGroups: request.artifactGroups, artifactTypes: request.artifactTypes,
695
+ worktreePath: workspace.descriptor.path, artifacts: request.artifacts,
883
696
  configManifest: request.configManifest, criticId: request.criticId, audience: 'human',
884
697
  runDir: path.resolve(stateDir, 'runs', request.runId, request.id, 'human-tools'), signal: workspace.signal,
885
698
  });
@@ -928,6 +741,8 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
928
741
  const request = requestData(requestId);
929
742
  if (!request || request.profile.kind !== 'human' || request.status !== 'WAITING_HUMAN')
930
743
  throw new Error('Request is not waiting for a human review.');
744
+ if (request.configManifest?.version !== 2)
745
+ throw new Error('Historical reviews are available for result lookup only.');
931
746
  if (!reviewerId || request.claimedBy !== reviewerId)
932
747
  throw new Error('Only the reviewer who claimed this request can submit its result.');
933
748
  if (!request.notifiedAt)
@@ -938,7 +753,6 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
938
753
  workspace = await workspaceAdapter.reopenWorkspace(request.workspace);
939
754
  // Reopening validates input under its integrity policy. No asynchronous work or
940
755
  // user code runs between this boundary and committing the submitted result.
941
- assertGeneratedReviewInputs(request);
942
756
  workspace.signal.throwIfAborted();
943
757
  ensureOpen();
944
758
  inputsValidated = true;
@@ -948,8 +762,8 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
948
762
  throw new Error('This human review has already completed or failed.');
949
763
  if (current.claimedBy !== reviewerId)
950
764
  throw new Error('Only the reviewer who claimed this request can submit its result.');
951
- if (current.workspace.mode === 'lock' && !ownerAlive(ownerData(current.runId)))
952
- throw new Error('A lock review requires its monitoring worker to remain alive.');
765
+ if (!ownerAlive(ownerData(current.runId)))
766
+ throw new Error('An in-place review requires its monitoring worker to remain alive.');
953
767
  required(workspace, 'Workspace').signal.throwIfAborted();
954
768
  finishWithin(requestId, { result: validated });
955
769
  });