@zq-silk/yui 0.8.9 → 0.9.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 (35) hide show
  1. package/ARCHITECTURE.md +48 -46
  2. package/README.md +33 -31
  3. package/dist/cli/commandCatalog.js +7 -7
  4. package/dist/cli.js +1 -31
  5. package/dist/commands/executionAuditCommands.js +2 -2
  6. package/dist/commands/taskCommands.js +110 -307
  7. package/dist/commands/taskCompletionGate.js +15 -12
  8. package/dist/commands/taskContextCommand.js +18 -11
  9. package/dist/commands/taskNextActionCommand.js +4 -5
  10. package/dist/commands/taskWorkspaceCommands.js +2 -2
  11. package/dist/execution/executionGroup.js +0 -3
  12. package/dist/executor/agentExecutor.js +4 -2
  13. package/dist/executor/effectiveLaunch.js +33 -3
  14. package/dist/integration/gitIntegrationService.js +1 -1
  15. package/dist/lifecycle/exactRunTerminalization.js +12 -8
  16. package/dist/observability/orchestrationMetrics.js +5 -19
  17. package/dist/profile/agentProfile.js +1 -1
  18. package/dist/repository/taskWorkspaceCoordinator.js +2 -0
  19. package/dist/repository/taskWorkspacePreparer.js +173 -26
  20. package/dist/review/reviewRound.js +41 -24
  21. package/dist/storage/migration/productionRegistry.js +138 -0
  22. package/dist/storage/sqliteStore.js +1 -1
  23. package/dist/storage/taskStore.js +26 -27
  24. package/dist/task/completionReadiness.js +24 -22
  25. package/dist/task/nextAction.js +47 -61
  26. package/dist/task/task.js +12 -19
  27. package/dist/web/assets/client/components.js +3 -1
  28. package/dist/web/assets/client/i18n.js +6 -0
  29. package/dist/web/webSnapshot.js +0 -3
  30. package/i18n/README.zh-CN.md +18 -19
  31. package/package.json +1 -1
  32. package/skills/yui-leader/SKILL.md +47 -43
  33. package/skills/yui-operator/SKILL.md +24 -33
  34. package/skills/yui-reviewer/SKILL.md +18 -12
  35. package/skills/yui-worker/SKILL.md +5 -3
@@ -36,13 +36,13 @@ import { mailboxHasWork as workMailboxHasWork, nextPendingBatch } from "../coord
36
36
  import { RUNTIME_CLEANUP_REQUIRED_REASON, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
37
37
  import { blockingProviderContinuations, projectProviderContinuations } from "../runtime/runtimeContinuationProjection.js";
38
38
  import { runtimeObservationFromTaskEvent } from "../runtime/runtimeObservation.js";
39
- import { activateTask, addTaskProjectBinding, archiveTask, completeTask, createTask, retireTask, reopenTask, taskDeliveryPath, updateTaskMetadata } from "../task/task.js";
39
+ import { activateTask, addTaskProjectBinding, archiveTask, completeTask, createTask, retireTask, reopenTask, updateTaskMetadata } from "../task/task.js";
40
40
  import { resolveTaskRecordReference } from "../task/taskRecordReference.js";
41
41
  import { TASK_COMPLETION_PUBLISHED_TREE_AUTHORIZED_EVENT } from "../task/publicationReference.js";
42
42
  import { projectCompletionReadiness } from "../task/completionReadiness.js";
43
43
  import { resolveProject } from "../repository/project.js";
44
44
  import { acquireProjectMaintenanceLocks } from "../repository/projectMaintenanceLock.js";
45
- import { currentWorkItemCandidate, governingWorkItemCandidate, currentWorkItemExecutionGroup, workItemExecutionGroupById, createWorkItem, attachWorkItemExecutionGroup, updateWorkItemExecutionGroup, retireWorkItem, retryFailedWorkItem, submitWorkItemCandidate, updateWorkItemWriteProjects, updateWorkItemStatus } from "../workItem/workItem.js";
45
+ import { currentWorkItemCandidate, currentWorkItemExecutionGroup, workItemExecutionGroupById, createWorkItem, attachWorkItemExecutionGroup, updateWorkItemExecutionGroup, retireWorkItem, retryFailedWorkItem, submitWorkItemCandidate, updateWorkItemWriteProjects, updateWorkItemStatus } from "../workItem/workItem.js";
46
46
  import { addExecutionLane, createExecutionGroup, resolveExecutionGroup, restartExecutionLane, updateExecutionLane } from "../execution/executionGroup.js";
47
47
  import { sameTaskFinalReviewContract, taskFinalReviewConfig, validateTaskFinalReviewContract } from "../review/taskFinalReviewContract.js";
48
48
  import { classifyReviewRoundOutcome, isSemanticReviewRound } from "../review/reviewOutcomeClassifier.js";
@@ -367,13 +367,12 @@ function taskProjectCommand(args, store, options) {
367
367
  }
368
368
  function updateTaskCommand(args, store, options) {
369
369
  const optionNames = new Set([
370
- "--title", "--description", "--priority", "--tags", "--due-at", "--delivery"
370
+ "--title", "--type", "--description", "--priority", "--tags", "--due-at"
371
371
  ]);
372
372
  const flagOptions = new Set([
373
- "--clear-description", "--clear-priority", "--clear-tags", "--clear-due-at",
374
- "--require-integration"
373
+ "--clear-type", "--clear-description", "--clear-priority", "--clear-tags", "--clear-due-at"
375
374
  ]);
376
- const usage = "Task update usage: yui task update <id> [--title <text>] [--description <text>|--clear-description] [--priority <low|medium|high|urgent>|--clear-priority] [--tags <comma-separated>|--clear-tags] [--due-at <RFC3339>|--clear-due-at] [--delivery <direct|integrated>] [--require-integration].";
375
+ const usage = "Task update usage: yui task update <id> [--title <text>] [--type <project-defined-type>|--clear-type] [--description <text>|--clear-description] [--priority <low|medium|high|urgent>|--clear-priority] [--tags <comma-separated>|--clear-tags] [--due-at <RFC3339>|--clear-due-at].";
377
376
  const parsed = parseTail(args, optionNames, usage, flagOptions);
378
377
  exactPositionals(parsed.positionals, 1, usage);
379
378
  if (parsed.options.size === 0)
@@ -397,44 +396,16 @@ function updateTaskCommand(args, store, options) {
397
396
  const tags = parsed.options.has("--tags")
398
397
  ? parseTaskTags(requiredOption(parsed.options, "--tags"))
399
398
  : undefined;
400
- const requestedDelivery = parsed.options.has("--delivery")
401
- ? parseTaskDelivery(requiredOption(parsed.options, "--delivery"))
402
- : undefined;
403
- if (requestedDelivery === "direct" && parsed.options.has("--require-integration")) {
404
- throw usageError("--delivery direct conflicts with --require-integration.", usage);
405
- }
406
- const enableIntegration = requestedDelivery === "integrated"
407
- || parsed.options.has("--require-integration");
408
399
  const now = clock(options);
409
400
  const result = store.transaction((tx) => {
410
401
  const current = requireTask(tx, parsed.positionals[0]);
411
402
  if (current.status === "archived")
412
403
  throw usageError(`Task is archived: ${current.id}.`);
413
- if ((requestedDelivery !== undefined || parsed.options.has("--require-integration"))
414
- && current.projectBindings.length === 0) {
415
- throw usageError(`Task ${current.id} has no Project; delivery selection is not applicable.`);
416
- }
417
- if (requestedDelivery === "direct" && current.requireIntegration === true) {
418
- throw usageError(`Task ${current.id} already uses integrated delivery and cannot be downgraded to direct.`);
419
- }
420
- if (enableIntegration && current.status === "completed") {
421
- throw usageError(`Task ${current.id} is completed; use task reopen before enabling integration evidence.`);
422
- }
423
- if (enableIntegration && current.requireIntegration !== true) {
424
- assertTaskDeliveryPromotionEligible(tx, current, options.directTaskMainSnapshot);
425
- }
426
- if (parsed.options.size === 1
427
- && enableIntegration
428
- && current.requireIntegration === true) {
429
- return { task: current, integrationState: "already-enabled" };
430
- }
431
- if (parsed.options.size === 1
432
- && requestedDelivery === "direct"
433
- && taskDeliveryPath(current) === "direct") {
434
- return { task: current, integrationState: "already-direct" };
435
- }
436
404
  const updated = updateTaskMetadata(current, {
437
405
  ...(parsed.options.has("--title") ? { title: requiredOption(parsed.options, "--title") } : {}),
406
+ ...(parsed.options.has("--type")
407
+ ? { type: requiredOption(parsed.options, "--type") }
408
+ : parsed.options.has("--clear-type") ? { type: null } : {}),
438
409
  ...(parsed.options.has("--description")
439
410
  ? { description: requiredOption(parsed.options, "--description") }
440
411
  : parsed.options.has("--clear-description") ? { description: null } : {}),
@@ -446,94 +417,18 @@ function updateTaskCommand(args, store, options) {
446
417
  : { tags }),
447
418
  ...(dueAt === undefined
448
419
  ? parsed.options.has("--clear-due-at") ? { dueAt: null } : {}
449
- : { dueAt }),
450
- ...(enableIntegration ? { requireIntegration: true } : {})
420
+ : { dueAt })
451
421
  }, now);
452
422
  tx.saveTask(updated);
453
423
  recordTaskEvent(tx, updated.id, "task.updated", {
454
424
  status: updated.status,
455
- ...(requestedDelivery === undefined && !parsed.options.has("--require-integration")
456
- ? {}
457
- : {
458
- completionEvidence: enableIntegration
459
- ? "integration-required"
460
- : "direct",
461
- deliveryPath: taskDeliveryPath(updated)
462
- })
425
+ ...(updated.type === undefined ? {} : { taskType: updated.type })
463
426
  }, now);
464
427
  enqueueWork(tx, taskMailbox(updated.id), "task-updated", now, [taskRef(updated.id)]);
465
- return {
466
- task: updated,
467
- integrationState: enableIntegration
468
- ? "enabled"
469
- : requestedDelivery === "direct"
470
- ? "direct"
471
- : "unchanged"
472
- };
428
+ return updated;
473
429
  });
474
- if (result.integrationState !== "already-enabled"
475
- && result.integrationState !== "already-direct") {
476
- notifyMailbox(options.runtime, taskMailbox(result.task.id), result.task.id);
477
- }
478
- return result.integrationState === "enabled"
479
- ? `Updated task ${result.task.id}\nDelivery: integrated (WorkItem, ChangeSet, and committed Integration required)\n`
480
- : result.integrationState === "already-enabled"
481
- ? `Task ${result.task.id} already uses integrated delivery\n`
482
- : result.integrationState === "already-direct"
483
- ? `Task ${result.task.id} already uses direct delivery\n`
484
- : result.integrationState === "direct"
485
- ? `Updated task ${result.task.id}\nDelivery: direct\n`
486
- : `Updated task ${result.task.id}\n`;
487
- }
488
- function assertTaskDeliveryPromotionEligible(store, task, snapshot) {
489
- const evidence = [
490
- ...store.listWorkItems(task.id).map(({ id }) => `WorkItem ${id}`),
491
- ...store.listChangeSets(task.id).map(({ id }) => `ChangeSet ${id}`),
492
- ...store.listIntegrationAttempts(task.id).map(({ id }) => `IntegrationAttempt ${id}`),
493
- ...store.listReviewRounds(task.id).map(({ id }) => `ReviewRound ${id}`)
494
- ];
495
- if (evidence.length > 0) {
496
- throw usageError(`Task ${task.id} cannot promote to integrated delivery after delivery evidence exists: `
497
- + `${evidence.join(", ")}. Create an integrated replacement Task or keep the current direct contract.`);
498
- }
499
- if (task.status !== "draft" && task.status !== "active") {
500
- throw usageError(`Task ${task.id} must be Draft or Active to promote delivery; current status is ${task.status}.`);
501
- }
502
- const workspace = store.getTaskWorkspace(task.id);
503
- if (task.status === "draft" && workspace === null)
504
- return;
505
- if (snapshot === undefined) {
506
- throw usageError(`Task ${task.id} delivery promotion requires a CLI-verified clean Task-main snapshot.`);
507
- }
508
- if (workspace === null
509
- || workspace.owner.type !== "task"
510
- || workspace.owner.taskId !== task.id) {
511
- throw usageError(`Task has no authoritative main workspace: ${task.id}.`);
512
- }
513
- const snapshotIds = snapshot.schemaVersion === 1 && Array.isArray(snapshot.projects)
514
- ? snapshot.projects.map(({ projectId }) => projectId)
515
- : [];
516
- if (snapshotIds.length !== task.projectBindings.length
517
- || new Set(snapshotIds).size !== snapshotIds.length) {
518
- throw usageError(`Task-main promotion snapshot does not match bound Projects: ${task.id}.`);
519
- }
520
- for (const binding of task.projectBindings) {
521
- const project = snapshot.projects.find(({ projectId }) => projectId === binding.projectId);
522
- const entry = workspace.entries.find(({ projectId }) => projectId === binding.projectId);
523
- if (project === undefined
524
- || entry === undefined
525
- || entry.access !== "write"
526
- || project.directory !== entry.directory
527
- || project.branch !== entry.branch
528
- || project.baseCommit !== entry.baseCommit) {
529
- throw usageError(`Task-main promotion snapshot changed before mutation: ${task.id}/${binding.projectId}.`);
530
- }
531
- if (project.headCommit !== project.baseCommit) {
532
- throw usageError(`Task ${task.id} main already advanced for Project ${binding.projectId}; `
533
- + "cannot promote without losing ChangeSet provenance. Create an integrated replacement "
534
- + "Task or keep the current direct contract.");
535
- }
536
- }
430
+ notifyMailbox(options.runtime, taskMailbox(result.id), result.id);
431
+ return `Updated task ${result.id}\n`;
537
432
  }
538
433
  /** Compatibility helper for call sites that cannot yet handle foreground enter. */
539
434
  export function runTaskOutputCommand(args, store, options = {}) {
@@ -569,25 +464,22 @@ function createTaskCommand(args, store, options) {
569
464
  const now = clock(options);
570
465
  const created = store.transaction((tx) => createTaskAggregate(tx, parsed.title, {
571
466
  projectBindings: parsed.projectBindings,
572
- ...(parsed.requireIntegration ? { requireIntegration: true } : {})
467
+ ...(parsed.type === undefined ? {} : { type: parsed.type })
573
468
  }, now, parsed.defaultProjectIds));
574
469
  notifyMailbox(options.runtime, taskMailbox(created.task.id), created.task.id);
575
470
  return output(`Created Draft task ${created.task.id}: ${created.task.title}\n`
576
471
  + `Assigned role: ${created.leader.name}\n`
577
- + `Delivery: ${taskDeliveryPath(created.task)}\n`
578
- + (created.task.requireIntegration
579
- ? "Completion: WorkItem, ChangeSet, and committed Integration required\n"
580
- : created.task.projectBindings.length > 0
581
- ? "Completion: clean committed Task main required; no WorkItem, ChangeSet, IntegrationAttempt, or managed ReviewRound required\n"
582
- : "Completion: no Project delivery evidence required\n"), {
472
+ + `Type: ${created.task.type ?? "unspecified"}\n`
473
+ + (created.task.projectBindings.length > 0
474
+ ? "Execution: Leader decides whether independent WorkItems are warranted\n"
475
+ : "Execution: no Project delivery evidence required\n"), {
583
476
  task: created.task,
584
- leader: created.leader,
585
- deliveryPath: taskDeliveryPath(created.task)
477
+ leader: created.leader
586
478
  });
587
479
  }
588
480
  function parseTaskCreation(args, store) {
589
- const usage = "Task create usage: yui task create <title> [--project <project> ...] [--base <project>=<ref> ...] [--delivery <direct|integrated>] [--require-integration].";
590
- const parsed = parseMultiValueTail(args, new Set(["--delivery"]), new Set(["--project", "--base"]), usage, new Set(["--require-integration"]));
481
+ const usage = "Task create usage: yui task create <title> [--type <project-defined-type>] [--project <project> ...] [--base <project>=<ref> ...].";
482
+ const parsed = parseMultiValueTail(args, new Set(["--type"]), new Set(["--project", "--base"]), usage);
591
483
  exactPositionals(parsed.positionals, 1, usage);
592
484
  const projectReferences = parsed.multiOptions.get("--project") ?? [];
593
485
  const baseOptions = parsed.multiOptions.get("--base") ?? [];
@@ -603,16 +495,6 @@ function parseTaskCreation(args, store) {
603
495
  if (new Set(projects.map(({ id }) => id)).size !== projects.length) {
604
496
  throw usageError("A Task cannot bind the same Project more than once.");
605
497
  }
606
- const requestedDelivery = parsed.options.has("--delivery")
607
- ? parseTaskDelivery(requiredOption(parsed.options, "--delivery"))
608
- : undefined;
609
- if ((requestedDelivery !== undefined || parsed.options.has("--require-integration"))
610
- && projects.length === 0) {
611
- throw usageError("Delivery selection requires at least one --project.", usage);
612
- }
613
- if (requestedDelivery === "direct" && parsed.options.has("--require-integration")) {
614
- throw usageError("--delivery direct conflicts with --require-integration.", usage);
615
- }
616
498
  const bases = new Map();
617
499
  for (const option of baseOptions) {
618
500
  const separator = option.indexOf("=");
@@ -637,14 +519,15 @@ function parseTaskCreation(args, store) {
637
519
  .map(({ id }) => id);
638
520
  return {
639
521
  title: parsed.positionals[0],
522
+ ...(parsed.options.has("--type")
523
+ ? { type: requiredOption(parsed.options, "--type") }
524
+ : {}),
640
525
  projectBindings: projects.map((project) => ({
641
526
  projectId: project.id,
642
527
  directory: project.name,
643
528
  baseRef: bases.get(project.id) ?? project.developmentBranch
644
529
  })),
645
- defaultProjectIds,
646
- requireIntegration: requestedDelivery === "integrated"
647
- || parsed.options.has("--require-integration")
530
+ defaultProjectIds
648
531
  };
649
532
  }
650
533
  function createTaskAggregate(store, title, metadata, now, defaultProjectIds = []) {
@@ -654,7 +537,7 @@ function createTaskAggregate(store, title, metadata, now, defaultProjectIds = []
654
537
  store.saveRole(task.id, leader);
655
538
  recordTaskEvent(store, task.id, "task.created", {
656
539
  status: task.status,
657
- deliveryPath: taskDeliveryPath(task),
540
+ ...(task.type === undefined ? {} : { taskType: task.type }),
658
541
  ...(defaultProjectIds.length === 0
659
542
  ? {}
660
543
  : { defaultProjectIds: defaultProjectIds.join(",") })
@@ -703,16 +586,14 @@ function showTaskCommand(args, store) {
703
586
  `Task: ${task.id}`,
704
587
  `Title: ${task.title}`,
705
588
  `Status: ${task.status}`,
706
- `Delivery: ${taskDeliveryPath(task)}`,
589
+ `Type: ${task.type ?? "unspecified"}`,
707
590
  ...(task.description === undefined ? [] : [`Description: ${task.description}`]),
708
591
  ...(task.priority === undefined ? [] : [`Priority: ${task.priority}`]),
709
592
  ...(task.tags === undefined ? [] : [`Tags: ${task.tags.join(", ")}`]),
710
593
  ...(task.dueAt === undefined ? [] : [`Due: ${presentTime(task.dueAt, timeZone)}`]),
711
- `Completion evidence: ${task.requireIntegration === true
712
- ? "WorkItem, ChangeSet, and committed Integration required"
713
- : task.projectBindings.length > 0
714
- ? "clean committed Task main required"
715
- : "no Project evidence required"}`,
594
+ `Execution topology: ${task.projectBindings.length > 0
595
+ ? "Leader-owned; WorkItems are optional independent delivery units"
596
+ : "no Project delivery evidence required"}`,
716
597
  ...(task.completedAt === undefined ? [] : [`Completed: ${presentTime(task.completedAt, timeZone)}`]),
717
598
  ...(task.completedBy === undefined ? [] : [`Completed by: ${task.completedBy}`]),
718
599
  ...(task.completionSummary === undefined ? [] : [`Completion summary: ${task.completionSummary}`]),
@@ -741,12 +622,7 @@ function showTaskCommand(args, store) {
741
622
  `Created: ${presentTime(task.createdAt, timeZone)}`,
742
623
  `Updated: ${presentTime(task.updatedAt, timeZone)}`
743
624
  ].join("\n").concat("\n");
744
- return output(rendered, {
745
- task,
746
- deliveryPath: taskDeliveryPath(task),
747
- counts,
748
- hasBrief: brief !== null
749
- });
625
+ return output(rendered, { task, counts, hasBrief: brief !== null });
750
626
  }
751
627
  function activateTaskCommand(args, store, options) {
752
628
  exactPositionals(args, 1, "Task activate usage: yui task activate <task>.");
@@ -860,7 +736,7 @@ function completeTaskCommand(args, store, options) {
860
736
  terminalizedLeaderRun = true;
861
737
  }
862
738
  // A final (Task-scoped) review is the Task delivery policy: it reviews the
863
- // complete integrated Task heads, not a single WorkItem Candidate. If the
739
+ // complete frozen Task heads, not a single WorkItem Candidate. If the
864
740
  // review config requests a final review, create the Task ReviewRound here
865
741
  // and return it for dispatch instead of completing the Task.
866
742
  const pendingFinalReviewIds = new Set(tx.listReviewRounds(task.id)
@@ -915,7 +791,6 @@ function completeTaskCommand(args, store, options) {
915
791
  recordTaskEvent(tx, task.id, "task.completed", {
916
792
  by: actor,
917
793
  summary,
918
- deliveryPath: taskDeliveryPath(task),
919
794
  ...(actualTaskCandidate === undefined
920
795
  ? {}
921
796
  : {
@@ -2518,14 +2393,16 @@ function executionTargetForWorkItem(taskId, workItemId, revision, item, workspac
2518
2393
  }
2519
2394
  function executionTargetForReviewRound(task, round, item, candidate) {
2520
2395
  const taskScope = (round.scope ?? "work-item") === "task";
2396
+ if (!taskScope && (item === undefined || candidate === undefined)) {
2397
+ throw dataError(`WorkItem ReviewRound target is missing its Candidate: ${round.id}.`);
2398
+ }
2521
2399
  const projects = taskScope
2522
2400
  ? round.taskCandidate?.projects ?? []
2523
2401
  : candidate.gitSnapshot?.projects ?? [];
2524
2402
  const fingerprint = JSON.stringify({
2525
2403
  taskId: task.id,
2526
2404
  reviewRoundId: round.id,
2527
- workItemId: item.id,
2528
- candidateId: candidate.id,
2405
+ ...(taskScope ? {} : { workItemId: item.id, candidateId: candidate.id }),
2529
2406
  scope: taskScope ? "task" : "work-item",
2530
2407
  projects,
2531
2408
  contractDigest: round.taskFinalReviewContract?.digest
@@ -2534,9 +2411,10 @@ function executionTargetForReviewRound(task, round, item, candidate) {
2534
2411
  schemaVersion: 1,
2535
2412
  kind: taskScope ? "task-final-review" : "work-item",
2536
2413
  taskId: task.id,
2537
- workItemId: item.id,
2538
- candidateId: candidate.id,
2539
- revision: candidate.workItemRevision,
2414
+ ...(taskScope ? {} : { workItemId: item.id, candidateId: candidate.id }),
2415
+ // A Task-final ReviewRound is itself the immutable semantic target. Runs
2416
+ // retry that same revision; a changed frozen Task creates a new Round.
2417
+ revision: taskScope ? 1 : candidate.workItemRevision,
2540
2418
  projects,
2541
2419
  ...(round.taskFinalReviewContract === undefined
2542
2420
  ? {}
@@ -3096,7 +2974,7 @@ function resolveReviewExecutionGroup(args, store, options) {
3096
2974
  reconcileReviewFindingsAfterReview(tx, task.id, terminal.id, now);
3097
2975
  }
3098
2976
  enqueueWork(tx, leaderMailbox(task.id), "review-group-resolved", now, [
3099
- workItemRef(task.id, round.workItemId)
2977
+ ...(round.workItemId === undefined ? [] : [workItemRef(task.id, round.workItemId)])
3100
2978
  ]);
3101
2979
  return terminal;
3102
2980
  });
@@ -3323,7 +3201,6 @@ function requestTaskReviewRound(args, store, options) {
3323
3201
  if (producerCollision !== null) {
3324
3202
  throw usageError(producerCollision);
3325
3203
  }
3326
- const anchor = latestTaskReviewAnchor(tx, task);
3327
3204
  const taskRounds = reviewRoundsByIdentity(tx.listReviewRounds(task.id))
3328
3205
  .filter((entry) => (entry.scope ?? "work-item") === "task");
3329
3206
  const exact = taskRounds.filter((entry) => (entry.reviewerRoleName === reviewerRoleName
@@ -3441,11 +3318,11 @@ function requestTaskReviewRound(args, store, options) {
3441
3318
  deltaRecord = validateDeltaRecheckRequest(tx, task.id, reviewerRoleName, provenance.candidate, options.deltaRecheckPreflight);
3442
3319
  }
3443
3320
  let created = deltaRecord === undefined
3444
- ? createTaskReviewRound(tx.nextReviewRoundId(task.id), task.id, anchor.item.id, anchor.candidate.id, reviewerRoleName, "leader", provenance.candidate, now, taskFinalContract)
3445
- : createTaskDeltaReviewRound(tx.nextReviewRoundId(task.id), task.id, anchor.item.id, anchor.candidate.id, reviewerRoleName, "leader", provenance.candidate, deltaRecord, now, taskFinalContract);
3321
+ ? createTaskReviewRound(tx.nextReviewRoundId(task.id), task.id, reviewerRoleName, "leader", provenance.candidate, now, taskFinalContract)
3322
+ : createTaskDeltaReviewRound(tx.nextReviewRoundId(task.id), task.id, reviewerRoleName, "leader", provenance.candidate, deltaRecord, now, taskFinalContract);
3446
3323
  let group = createExecutionGroup(`execution-group-${created.id}`, task.id, {
3447
3324
  purpose: "review",
3448
- target: executionTargetForReviewRound(task, created, anchor.item, anchor.candidate),
3325
+ target: executionTargetForReviewRound(task, created),
3449
3326
  strategy,
3450
3327
  lanes: laneRoles.map((roleName) => ({ roleName, reviewRoundId: created.id }))
3451
3328
  }, now);
@@ -3471,8 +3348,6 @@ function requestTaskReviewRound(args, store, options) {
3471
3348
  }
3472
3349
  recordTaskEvent(tx, task.id, "review.task-final-requested", {
3473
3350
  reviewRoundId: created.id,
3474
- workItemId: created.workItemId,
3475
- candidateId: created.candidateId,
3476
3351
  reviewerRoleName: created.reviewerRoleName,
3477
3352
  requestedBy: created.requestedBy,
3478
3353
  taskCandidate: JSON.stringify(created.taskCandidate),
@@ -3532,14 +3407,10 @@ function forceFreshTaskReviewRound(args, store, options) {
3532
3407
  if (replacement === null
3533
3408
  || replacement.id === source.id
3534
3409
  || (replacement.scope ?? "work-item") !== "task"
3535
- || replacement.workItemId !== source.workItemId
3536
- || replacement.candidateId !== source.candidateId
3537
3410
  || replacement.reviewerRoleName !== source.reviewerRoleName
3538
3411
  || replacement.deltaRecheck !== undefined
3539
3412
  || !sameTaskFinalReviewContract(replacement.taskFinalReviewContract, source.taskFinalReviewContract)
3540
3413
  || !isSameTaskReviewCandidate(replacement.taskCandidate, source.taskCandidate)
3541
- || replacementEvent.payload.workItemId !== replacement.workItemId
3542
- || replacementEvent.payload.candidateId !== replacement.candidateId
3543
3414
  || replacementEvent.payload.reviewerRoleName !== replacement.reviewerRoleName
3544
3415
  || replacementEvent.payload.taskCandidate !== JSON.stringify(replacement.taskCandidate)) {
3545
3416
  throw dataError(`Force-fresh audit for ${source.id} does not match its replacement Round.`);
@@ -3564,12 +3435,6 @@ function forceFreshTaskReviewRound(args, store, options) {
3564
3435
  || source.executionGroup.lanes[0].roleName !== source.reviewerRoleName)) {
3565
3436
  throw usageError(`ReviewRound ${source.id} is not a single-Reviewer full Review; force-fresh is refused.`);
3566
3437
  }
3567
- const item = tx.getWorkItem(task.id, source.workItemId);
3568
- const candidate = item?.candidates.find(({ id }) => id === source.candidateId);
3569
- if (item === null || item === undefined || candidate === undefined) {
3570
- throw dataError(`Final Review anchor Candidate is no longer available: `
3571
- + `${source.workItemId}/${source.candidateId}.`);
3572
- }
3573
3438
  const provenance = taskReviewProvenance(tx, task, options);
3574
3439
  if (!isSameTaskReviewCandidate(source.taskCandidate, provenance.candidate)) {
3575
3440
  throw usageError(`Final ReviewRound ${source.id} freezes a candidate that is no longer the current Task candidate.`);
@@ -3598,10 +3463,10 @@ function forceFreshTaskReviewRound(args, store, options) {
3598
3463
  reviewer = createTaskRole(tx, task, source.reviewerRoleName, undefined, now, source.reviewerRoleName);
3599
3464
  tx.saveRole(task.id, reviewer);
3600
3465
  }
3601
- let created = createTaskReviewRound(tx.nextReviewRoundId(task.id), task.id, source.workItemId, source.candidateId, source.reviewerRoleName, "leader", source.taskCandidate, now, taskFinalContract);
3466
+ let created = createTaskReviewRound(tx.nextReviewRoundId(task.id), task.id, source.reviewerRoleName, "leader", source.taskCandidate, now, taskFinalContract);
3602
3467
  const group = createExecutionGroup(`execution-group-${created.id}`, task.id, {
3603
3468
  purpose: "review",
3604
- target: executionTargetForReviewRound(task, created, item, candidate),
3469
+ target: executionTargetForReviewRound(task, created),
3605
3470
  strategy: { mode: "fixed", count: 1 },
3606
3471
  lanes: [{ roleName: reviewer.name, reviewRoundId: created.id }]
3607
3472
  }, now);
@@ -3611,8 +3476,6 @@ function forceFreshTaskReviewRound(args, store, options) {
3611
3476
  sourceReviewRoundId: source.id,
3612
3477
  ...(source.reviewerRunId === undefined ? {} : { sourceReviewerRunId: source.reviewerRunId }),
3613
3478
  reviewRoundId: created.id,
3614
- workItemId: created.workItemId,
3615
- candidateId: created.candidateId,
3616
3479
  reviewerRoleName: created.reviewerRoleName,
3617
3480
  taskCandidate: JSON.stringify(created.taskCandidate),
3618
3481
  reason: "source-round-terminal-without-semantic-review",
@@ -3763,15 +3626,9 @@ function retryFailedTaskReviewRound(args, store, options) {
3763
3626
  if (!sameTaskFinalReviewContract(round.taskFinalReviewContract, taskFinalContract)) {
3764
3627
  throw usageError(`Task final-review contract does not match ReviewRound ${round.id}.`);
3765
3628
  }
3766
- // Re-read the exact current integrated heads and all ChangeSet-linked
3767
- // WorkItem producer roles before touching any record. A moved head,
3768
- // missing anchor, malformed provenance, or reviewer collision fails closed
3769
- // with the old failed Round byte-for-byte unchanged.
3770
- const item = tx.getWorkItem(task.id, round.workItemId);
3771
- if (item === null || item.candidates.every(({ id }) => id !== round.candidateId)) {
3772
- throw dataError(`Final Review anchor Candidate is no longer available: `
3773
- + `${round.workItemId}/${round.candidateId}.`);
3774
- }
3629
+ // Re-read the exact current Task heads and producer roles before touching
3630
+ // any record. A moved head or reviewer collision fails closed with the old
3631
+ // failed Round byte-for-byte unchanged.
3775
3632
  const provenance = taskReviewProvenance(tx, task, options);
3776
3633
  if (!isSameTaskReviewCandidate(round.taskCandidate, provenance.candidate)) {
3777
3634
  throw usageError(`Final ReviewRound ${round.id} freezes a candidate that is no longer the current Task candidate.`);
@@ -3834,9 +3691,7 @@ function retryFailedTaskReviewRound(args, store, options) {
3834
3691
  const resetRound = retryTaskReviewRound(round);
3835
3692
  tx.saveReviewRound(task.id, resetRound);
3836
3693
  recordTaskEvent(tx, task.id, "review.task-final-retried", {
3837
- reviewRoundId: round.id,
3838
- workItemId: round.workItemId,
3839
- candidateId: round.candidateId
3694
+ reviewRoundId: round.id
3840
3695
  }, now);
3841
3696
  return { round: resetRound, created: true };
3842
3697
  });
@@ -4026,7 +3881,7 @@ function listRuns(args, store, options) {
4026
3881
  * on. This is deliberately narrower than retry: it cannot manufacture a
4027
3882
  * review or fail an arbitrary Round, and every identity/mailbox fence is
4028
3883
  * checked before the old Round changes. The next normal Task completion then
4029
- * creates one fresh Round over the newer integrated heads.
3884
+ * creates one fresh Round over the newer frozen Task heads.
4030
3885
  */
4031
3886
  function settleStaleFinalReviewRun(args, store, options) {
4032
3887
  exactPositionals(args, 1, "Task run settle usage: yui task run settle <task>/<run>.");
@@ -4065,14 +3920,6 @@ function settleStaleFinalReviewRun(args, store, options) {
4065
3920
  if (validation.disposition !== "applied" || validation.round === null) {
4066
3921
  throw usageError(`Review Run ${run.id} identity does not match its ReviewRound or frozen Task state changed: ${validation.reason ?? "mismatch"}.`);
4067
3922
  }
4068
- const item = tx.getWorkItem(task.id, round.workItemId);
4069
- if (item === null) {
4070
- throw dataError(`Work item not found for run ${run.id}: ${round.workItemId}.`);
4071
- }
4072
- const candidate = item.candidates.find(({ id }) => id === round.candidateId);
4073
- if (candidate === undefined) {
4074
- throw dataError(`ReviewRound Candidate not found: ${round.candidateId}.`);
4075
- }
4076
3923
  const activeReviewerRun = tx.listAgentRuns(task.id).find((entry) => (entry.purpose === "review" && entry.status === "active"));
4077
3924
  const activeRoleRun = tx.getActiveAgentRun(task.id, round.reviewerRoleName);
4078
3925
  if (activeReviewerRun !== undefined || activeRoleRun !== null) {
@@ -4159,7 +4006,6 @@ function settleStaleFinalReviewRun(args, store, options) {
4159
4006
  recordTaskEvent(tx, task.id, "run.review-stale-settled", {
4160
4007
  runId: run.id,
4161
4008
  reviewRoundId: round.id,
4162
- candidateId: candidate.id,
4163
4009
  previousTaskCandidate: JSON.stringify(round.taskCandidate),
4164
4010
  currentTaskCandidate: JSON.stringify(currentTaskCandidate)
4165
4011
  }, now);
@@ -4364,7 +4210,7 @@ function taskReviewProducerCollision(provenance, reviewerRoleName) {
4364
4210
  .sort((left, right) => left.localeCompare(right, undefined, { numeric: true }));
4365
4211
  return workItemIds.length === 0
4366
4212
  ? null
4367
- : `Reviewer Role must be separate from every integrated Candidate producer: `
4213
+ : `Reviewer Role must be separate from every WorkItem Candidate producer: `
4368
4214
  + `${reviewerRoleName} (${workItemIds.join(", ")}).`;
4369
4215
  }
4370
4216
  /**
@@ -4381,7 +4227,7 @@ function taskReviewProducerCollision(provenance, reviewerRoleName) {
4381
4227
  function taskReviewProvenance(store, task, options, expected) {
4382
4228
  const candidate = actualTaskReviewCandidateForMutation(store, task, options);
4383
4229
  if (expected !== undefined && !isSameTaskReviewCandidate(candidate, expected)) {
4384
- throw usageError(`Task-final ReviewRound frozen integrated heads changed for its Project set.`);
4230
+ throw usageError(`Task-final ReviewRound frozen Task heads changed for its Project set.`);
4385
4231
  }
4386
4232
  const producerRoles = new Set();
4387
4233
  const producerWorkItemIds = new Map();
@@ -4484,32 +4330,18 @@ function assertNoConflictingTaskReviewRound(rounds, reusableRoundIds = []) {
4484
4330
  throw usageError(`Another active Task-final ReviewRound already exists: ${conflicting.id}/${conflicting.reviewerRoleName}.`);
4485
4331
  }
4486
4332
  }
4487
- function latestTaskReviewAnchor(store, task) {
4488
- const item = [...store.listWorkItems(task.id)]
4489
- .filter(({ candidates }) => candidates.length > 0)
4490
- .sort((left, right) => (left.updatedAt.localeCompare(right.updatedAt) || left.id.localeCompare(right.id)))
4491
- .at(-1);
4492
- if (item === undefined) {
4493
- throw usageError(`Task ${task.id} has no WorkItem Candidate to anchor its final Review.`);
4494
- }
4495
- const candidate = item.candidates.at(-1);
4496
- if (candidate === undefined) {
4497
- throw usageError(`Task ${task.id} has no WorkItem Candidate to anchor its final Review.`);
4498
- }
4499
- return { item, candidate };
4500
- }
4501
4333
  /**
4502
4334
  * Queues a Task-scoped final ReviewRound. The reviewer Role must be separate
4503
4335
  * from the Candidate producer. Returns the round (pending or failed) so the
4504
4336
  * caller can surface it to the CLI for workspace preparation and dispatch.
4505
4337
  */
4506
- function queueTaskReviewRound(store, task, item, candidateId, config, taskCandidate, options, now, requestedBy = "policy", taskFinalContract) {
4338
+ function queueTaskReviewRound(store, task, config, taskCandidate, options, now, requestedBy = "policy", taskFinalContract) {
4507
4339
  assertNoConflictingTaskReviewRound(store.listReviewRounds(task.id));
4508
4340
  const provenance = taskReviewProvenance(store, task, options, taskCandidate);
4509
4341
  if (!isSameTaskReviewCandidate(provenance.candidate, taskCandidate)) {
4510
- throw usageError(`Task-final ReviewRound integrated heads changed before queueing.`);
4342
+ throw usageError(`Task-final ReviewRound frozen Task heads changed before queueing.`);
4511
4343
  }
4512
- const pending = createTaskReviewRound(store.nextReviewRoundId(task.id), task.id, item.id, candidateId, config.roleName, requestedBy, taskCandidate, now, taskFinalContract);
4344
+ const pending = createTaskReviewRound(store.nextReviewRoundId(task.id), task.id, config.roleName, requestedBy, taskCandidate, now, taskFinalContract);
4513
4345
  store.saveReviewRound(task.id, pending);
4514
4346
  const producerCollision = taskReviewProducerCollision(provenance, config.roleName);
4515
4347
  if (producerCollision !== null) {
@@ -4546,8 +4378,8 @@ function prepareFinalTaskReview(store, task, now, taskFinalContract, options) {
4546
4378
  // Any Task-final ReviewRound is durable completion evidence/obligation.
4547
4379
  // Once one exists, later changes to the mutable global review config cannot
4548
4380
  // weaken the requirement or change its reviewer. Before the first such
4549
- // Round, the current global `final` config establishes the initial Round
4550
- // only for integrated delivery.
4381
+ // Round, the current global `final` config remains an available Reviewer
4382
+ // default but does not establish an obligation by itself.
4551
4383
  const taskRounds = reviewRoundsByIdentity(store.listReviewRounds(task.id))
4552
4384
  .filter((round) => ((round.scope ?? "work-item") === "task"
4553
4385
  && (taskFinalContract === undefined || sameTaskFinalReviewContract(round.taskFinalReviewContract, taskFinalContract))));
@@ -4557,16 +4389,10 @@ function prepareFinalTaskReview(store, task, now, taskFinalContract, options) {
4557
4389
  config = taskFinalReviewConfig(taskFinalContract);
4558
4390
  }
4559
4391
  else if (establishedRound === undefined) {
4560
- const globalConfig = store.getReviewConfig();
4561
- // Direct delivery is an explicit low-overhead contract. Mutable global
4562
- // policy must not create a managed Round during direct completion; risk
4563
- // that warrants one promotes the Task to integrated delivery. Any already-
4564
- // established Task Round or immutable contract remains authoritative
4565
- // through the branches above.
4566
- config = taskDeliveryPath(task) === "integrated"
4567
- && globalConfig?.trigger === "final"
4568
- ? globalConfig
4569
- : null;
4392
+ // The configured Reviewer is available to the Leader, but does not choose
4393
+ // the Task topology. A managed final Review becomes an obligation only
4394
+ // after the Leader requests one (or an immutable Task contract requires it).
4395
+ config = null;
4570
4396
  }
4571
4397
  else {
4572
4398
  config = { roleName: establishedRound.reviewerRoleName, trigger: "final" };
@@ -4593,10 +4419,7 @@ function prepareFinalTaskReview(store, task, now, taskFinalContract, options) {
4593
4419
  && latest.deltaRecheck !== undefined
4594
4420
  && latest.deltaRecheck.disposition === "requires-full-review") {
4595
4421
  // Fall through to queue a full Review for the same candidate.
4596
- const anchor = taskFinalContract === undefined
4597
- ? latestTaskReviewAnchor(store, task)
4598
- : latestTaskReviewContractAnchor(store, task, taskFinalContract);
4599
- const escalated = queueTaskReviewRound(store, task, anchor.item, anchor.candidate.id, config, taskCandidate, options, now, establishedRound?.requestedBy ?? "policy", taskFinalContract);
4422
+ const escalated = queueTaskReviewRound(store, task, config, taskCandidate, options, now, establishedRound?.requestedBy ?? "policy", taskFinalContract);
4600
4423
  // Record the escalation lineage on the delta Round so the full Review
4601
4424
  // is traceable from the non-accepting delta disposition.
4602
4425
  if (latest.deltaRecheck.escalatedToReviewRoundId === undefined) {
@@ -4617,10 +4440,7 @@ function prepareFinalTaskReview(store, task, now, taskFinalContract, options) {
4617
4440
  : latest;
4618
4441
  }
4619
4442
  }
4620
- const anchor = taskFinalContract === undefined
4621
- ? latestTaskReviewAnchor(store, task)
4622
- : latestTaskReviewContractAnchor(store, task, taskFinalContract);
4623
- return queueTaskReviewRound(store, task, anchor.item, anchor.candidate.id, config, taskCandidate, options, now, establishedRound?.requestedBy ?? "policy", taskFinalContract);
4443
+ return queueTaskReviewRound(store, task, config, taskCandidate, options, now, establishedRound?.requestedBy ?? "policy", taskFinalContract);
4624
4444
  }
4625
4445
  function resumablePendingFinalTaskReview(store, task, round, config, taskCandidate, taskFinalContract, options) {
4626
4446
  if (taskFinalContract === undefined || round.taskFinalReviewContract === undefined) {
@@ -4685,22 +4505,6 @@ function assertPendingFinalReviewWorkspaceEvidence(store, task, round) {
4685
4505
  }
4686
4506
  }
4687
4507
  }
4688
- function latestTaskReviewContractAnchor(store, task, taskFinalContract) {
4689
- const anchor = store.listWorkItems(task.id)
4690
- .flatMap((item) => {
4691
- const candidate = governingWorkItemCandidate(item);
4692
- return candidate !== undefined && sameTaskFinalReviewContract(candidate.taskFinalReviewContract, taskFinalContract)
4693
- ? [{ item, candidate }]
4694
- : [];
4695
- })
4696
- .sort((left, right) => (left.item.updatedAt.localeCompare(right.item.updatedAt)
4697
- || left.item.id.localeCompare(right.item.id)))
4698
- .at(-1);
4699
- if (anchor === undefined) {
4700
- throw usageError(`Task ${task.id} has no WorkItem Candidate to anchor its final Review.`);
4701
- }
4702
- return anchor;
4703
- }
4704
4508
  /**
4705
4509
  * Leader-only retry of an exact failed Task-final review Run. The old failed
4706
4510
  * Run remains the attempt trail, while the semantic ReviewRound is reset to
@@ -4743,15 +4547,7 @@ function retryFailedReviewRun(previous, store, options, now) {
4743
4547
  }
4744
4548
  const currentTaskCandidate = actualTaskReviewCandidateForMutation(tx, task, options);
4745
4549
  if (!isSameTaskReviewCandidate(currentTaskCandidate, round.taskCandidate)) {
4746
- throw usageError(`Task-final ReviewRound ${round.id} no longer matches the latest committed Integration heads.`);
4747
- }
4748
- const item = tx.getWorkItem(task.id, round.workItemId);
4749
- if (item === null) {
4750
- throw dataError(`Work item not found for run ${run.id}: ${round.workItemId}.`);
4751
- }
4752
- const candidate = item.candidates.find(({ id }) => id === round.candidateId);
4753
- if (candidate === undefined) {
4754
- throw dataError(`ReviewRound Candidate not found: ${round.candidateId}.`);
4550
+ throw usageError(`Task-final ReviewRound ${round.id} no longer matches the frozen Task heads.`);
4755
4551
  }
4756
4552
  const taskRounds = reviewRoundsByIdentity(tx.listReviewRounds(task.id)
4757
4553
  .filter((entry) => (entry.scope ?? "work-item") === "task"));
@@ -4885,8 +4681,7 @@ function retryFailedReviewRun(previous, store, options, now) {
4885
4681
  tx.saveReviewRound(task.id, resetRound);
4886
4682
  recordTaskEvent(tx, task.id, "run.review-retried", {
4887
4683
  runId: run.id,
4888
- reviewRoundId: round.id,
4889
- candidateId: candidate.id
4684
+ reviewRoundId: round.id
4890
4685
  }, now);
4891
4686
  return { round: resetRound, previousRun: run, created: true };
4892
4687
  });
@@ -5300,8 +5095,8 @@ function yieldRun(args, store, options) {
5300
5095
  const panelGroup = round.executionGroup;
5301
5096
  recordTaskEvent(tx, task.id, "review-group-ready", {
5302
5097
  reviewRoundId: round.id,
5303
- workItemId: round.workItemId,
5304
- candidateId: round.candidateId,
5098
+ ...(round.workItemId === undefined ? {} : { workItemId: round.workItemId }),
5099
+ ...(round.candidateId === undefined ? {} : { candidateId: round.candidateId }),
5305
5100
  executionGroupId: panelGroup.id,
5306
5101
  terminalLanes: String(panelGroup.lanes
5307
5102
  .filter(({ status }) => ["yielded", "completed", "failed"].includes(status)).length),
@@ -5315,8 +5110,8 @@ function yieldRun(args, store, options) {
5315
5110
  }
5316
5111
  recordTaskEvent(tx, task.id, "review.completed", {
5317
5112
  reviewRoundId: round.id,
5318
- workItemId: round.workItemId,
5319
- candidateId: round.candidateId,
5113
+ ...(round.workItemId === undefined ? {} : { workItemId: round.workItemId }),
5114
+ ...(round.candidateId === undefined ? {} : { candidateId: round.candidateId }),
5320
5115
  reviewBaseCommit: round.reviewBaseCommit,
5321
5116
  evidenceCommit: round.evidenceCommit ?? "none",
5322
5117
  checks: round.checks?.map(({ name, outcome }) => `${name}:${outcome}`)
@@ -5614,18 +5409,12 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
5614
5409
  }
5615
5410
  throw usageError(`ReviewRound workspace is not ready: ${round.id}.`);
5616
5411
  }
5617
- const item = tx.getWorkItem(taskId, round.workItemId);
5618
- if (item === null)
5619
- throw dataError(`ReviewRound Work Item not found: ${round.workItemId}.`);
5620
- const candidate = item.candidates.find(({ id }) => id === round.candidateId);
5621
- if (candidate === undefined) {
5622
- throw dataError(`ReviewRound Candidate not found: ${round.candidateId}.`);
5623
- }
5624
5412
  const taskScope = (round.scope ?? "work-item") === "task";
5413
+ let item;
5414
+ let candidate;
5625
5415
  if (taskScope) {
5626
- // A Task-scoped final ReviewRound freezes the integrated Task heads in
5627
- // its taskCandidate. The WorkItem Candidate snapshot is irrelevant: the
5628
- // authoritative review base is the first Project's committed head.
5416
+ // A Task-scoped final ReviewRound is anchored directly to its frozen
5417
+ // Task candidate. It deliberately has no synthetic WorkItem Candidate.
5629
5418
  if (round.taskCandidate === undefined) {
5630
5419
  throw new TaskFinalReviewDispatchDriftError(`Task ReviewRound ${round.id} has no frozen Task candidate.`);
5631
5420
  }
@@ -5633,8 +5422,21 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
5633
5422
  throw new TaskFinalReviewDispatchDriftError(`Task ReviewRound base does not match its frozen Task candidate: ${round.id}.`);
5634
5423
  }
5635
5424
  }
5636
- else if (candidate.gitSnapshot?.reviewBaseCommit !== round.reviewBaseCommit) {
5637
- throw usageError(`ReviewRound Candidate snapshot changed: ${round.id}.`);
5425
+ else {
5426
+ if (round.workItemId === undefined || round.candidateId === undefined) {
5427
+ throw dataError(`WorkItem ReviewRound has no Candidate anchor: ${round.id}.`);
5428
+ }
5429
+ item = tx.getWorkItem(taskId, round.workItemId) ?? undefined;
5430
+ if (item === undefined) {
5431
+ throw dataError(`ReviewRound Work Item not found: ${round.workItemId}.`);
5432
+ }
5433
+ candidate = item.candidates.find(({ id }) => id === round.candidateId);
5434
+ if (candidate === undefined) {
5435
+ throw dataError(`ReviewRound Candidate not found: ${round.candidateId}.`);
5436
+ }
5437
+ if (candidate.gitSnapshot?.reviewBaseCommit !== round.reviewBaseCommit) {
5438
+ throw usageError(`ReviewRound Candidate snapshot changed: ${round.id}.`);
5439
+ }
5638
5440
  }
5639
5441
  const task = requireTask(tx, taskId);
5640
5442
  if ((round.scope ?? "work-item") === "task") {
@@ -5671,7 +5473,7 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
5671
5473
  && frozenCommits.get(project.projectId) !== project.commit;
5672
5474
  });
5673
5475
  throw new TaskFinalReviewDispatchDriftError(committedIntegrationMoved
5674
- ? "Task-final ReviewRound frozen integrated heads changed for its Project set."
5476
+ ? "Task-final ReviewRound frozen Task heads changed for its Project set."
5675
5477
  : `Final ReviewRound ${round.id} freezes a candidate that is no longer `
5676
5478
  + "the current Task candidate.");
5677
5479
  }
@@ -5732,9 +5534,11 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
5732
5534
  throw new TaskFinalReviewDispatchDriftError(`Task ReviewRound frozen Project heads changed: ${round.id}.`);
5733
5535
  }
5734
5536
  }
5735
- const candidateLabel = candidate.source.type === "run"
5736
- ? `candidate Run ${candidate.source.runId}`
5737
- : `revision ${candidate.workItemRevision}`;
5537
+ const candidateLabel = taskScope
5538
+ ? "frozen Task candidate"
5539
+ : candidate.source.type === "run"
5540
+ ? `candidate Run ${candidate.source.runId}`
5541
+ : `revision ${candidate.workItemRevision}`;
5738
5542
  const frozenHeads = taskScope
5739
5543
  ? round.taskCandidate.projects
5740
5544
  .map(({ projectId, commit }) => `${projectId}@${commit}`)
@@ -5772,22 +5576,26 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
5772
5576
  .map(({ projectId }) => (`yui project show ${projectId}; yui project knowledge list ${projectId}`))
5773
5577
  .join(" | ");
5774
5578
  const rawInput = [
5775
- `Review ${scopeLabel} WorkItem ${item.id} ${candidateLabel}.`,
5579
+ taskScope
5580
+ ? `Review the ${scopeLabel} ${candidateLabel}.`
5581
+ : `Review ${scopeLabel} WorkItem ${item.id} ${candidateLabel}.`,
5776
5582
  `ReviewRound: ${round.id}`,
5777
5583
  `Review scope: ${taskScope ? "task" : "work-item"}`,
5778
5584
  `Review base commit: ${round.reviewBaseCommit}`,
5779
5585
  ...(taskScope
5780
- ? [`Frozen integrated Task heads: ${frozenHeads}`]
5586
+ ? [`Frozen Task heads: ${frozenHeads}`]
5781
5587
  : [`Candidate snapshot base: ${round.reviewBaseCommit}`]),
5782
5588
  `Project Policy pointers: ${projectPolicyPointers || "none"}`,
5783
5589
  `Review workspace source: exact workspace attached to this Reviewer Lane`,
5784
- `Candidate summary: ${candidate.summary}`,
5785
- `Acceptance criteria: ${item.acceptance.length === 0 ? "none" : item.acceptance.join("; ")}`,
5590
+ `Candidate summary: ${taskScope ? task.title : candidate.summary}`,
5591
+ `Acceptance criteria: ${taskScope
5592
+ ? "Task objective, maintained decisions, and Project Policy"
5593
+ : item.acceptance.length === 0 ? "none" : item.acceptance.join("; ")}`,
5786
5594
  ...(taskScope ? [deltaContext !== "" ? deltaContext : findingContext] : []),
5787
5595
  "Start from the user's core outcome and the WorkItem intent. The candidate summary is a pointer, not proof: inspect the complete relevant change, callers, and proportionate checks.",
5788
5596
  "Keep Yui Core lifecycle safety, generic Reviewer behavior, Project Policy/Knowledge, and the Task Contract separate. Follow Project Policy pointers from the dispatch context for project-specific checks.",
5789
5597
  ...(round.scope === "task" && round.deltaRecheck === undefined
5790
- ? ["This is the one final Task Review: inspect every bound Project at the frozen integrated heads, and report only reachable, material, actionable P1/P2 findings or bounded verification gaps."]
5598
+ ? ["This is the one final Task Review: inspect every bound Project at the frozen Task heads, and report only reachable, material, actionable P1/P2 findings or bounded verification gaps."]
5791
5599
  : []),
5792
5600
  "You may freely edit source/tests, run local build or test commands, and optionally commit diagnostic evidence only inside this ReviewRound-owned workspace.",
5793
5601
  "Do not push, integrate, mutate Task state, touch the Candidate or Worker workspace, another Task/workspace, a stable checkout, or the real Yui control-plane home.",
@@ -5848,7 +5656,7 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
5848
5656
  action: "review-round",
5849
5657
  subject: {
5850
5658
  taskId,
5851
- workItemId: item.id,
5659
+ ...(item === undefined ? {} : { workItemId: item.id }),
5852
5660
  reviewRoundId: round.id,
5853
5661
  executionGroupId: runningGroup.id,
5854
5662
  executionLaneId: lane.id
@@ -5864,7 +5672,7 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
5864
5672
  workspace: laneWorkspace
5865
5673
  }, now);
5866
5674
  createdRuns.push(createAgentRun(runId, taskId, laneReviewer.name, roleAgentSessionResumeMode(sessions, effective.agentId, effective), assignment, now, {
5867
- workItemId: item.id,
5675
+ ...(item === undefined ? {} : { workItemId: item.id }),
5868
5676
  purpose: "review",
5869
5677
  reviewRoundId: round.id,
5870
5678
  executionGroupId: runningGroup.id,
@@ -5901,7 +5709,7 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
5901
5709
  taskId,
5902
5710
  roleName: unboundRun.roleName,
5903
5711
  purpose: "review",
5904
- workItemId: item.id,
5712
+ ...(item === undefined ? {} : { workItemId: item.id }),
5905
5713
  reviewRoundId: round.id
5906
5714
  }, now);
5907
5715
  const created = withAgentRunContextSnapshot(unboundRun, contextSnapshotRef(snapshot), contextSnapshotDeltaRefIds(tx, snapshot));
@@ -5912,7 +5720,7 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
5912
5720
  tx.saveRole(taskId, updateRoleStatus(laneReviewer, "running", now));
5913
5721
  enqueueWork(tx, roleMailbox(taskId, laneReviewer.name), "review-requested", now, [
5914
5722
  runRef(taskId, created.id),
5915
- workItemRef(taskId, item.id)
5723
+ ...(item === undefined ? [] : [workItemRef(taskId, item.id)])
5916
5724
  ]);
5917
5725
  recordTaskEvent(tx, taskId, "run.review-dispatched", runLaunchEventPayload(created), now);
5918
5726
  }
@@ -5934,7 +5742,7 @@ export function failPendingReviewRound(taskId, reviewRoundId, summary, store, op
5934
5742
  const terminal = finishReviewRound(round, "failed", summary, now);
5935
5743
  tx.saveReviewRound(taskId, terminal);
5936
5744
  enqueueWork(tx, leaderMailbox(taskId), "review-failed", now, [
5937
- workItemRef(taskId, round.workItemId)
5745
+ ...(round.workItemId === undefined ? [] : [workItemRef(taskId, round.workItemId)])
5938
5746
  ]);
5939
5747
  return terminal;
5940
5748
  });
@@ -6068,7 +5876,7 @@ function requirePublishedTreeAuthorization(store, proof) {
6068
5876
  if (authorization === undefined) {
6069
5877
  throw usageError(`Published-tree completion requires explicit user or global Operator authorization for `
6070
5878
  + `${proof.taskId}/${proof.publicationId} at ${proof.reviewRoundId === undefined
6071
- ? "the direct Task-main head"
5879
+ ? "the Leader-owned Task-main head"
6072
5880
  : `Task-final Review ${proof.reviewRoundId}`}.`);
6073
5881
  }
6074
5882
  return authorization;
@@ -6337,11 +6145,6 @@ function parseTaskPriority(value) {
6337
6145
  return value;
6338
6146
  throw usageError(`Invalid Task priority: ${value}.`);
6339
6147
  }
6340
- function parseTaskDelivery(value) {
6341
- if (value === "direct" || value === "integrated")
6342
- return value;
6343
- throw usageError(`Task delivery is invalid: ${value}. Use direct or integrated.`);
6344
- }
6345
6148
  function parseTaskTags(value) {
6346
6149
  const tags = [...new Set(value.split(",").map((tag) => tag.trim()).filter(Boolean))];
6347
6150
  if (tags.length === 0)