@zq-silk/yui 0.14.0 → 0.14.2

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 (179) hide show
  1. package/ARCHITECTURE.md +64 -35
  2. package/README.md +258 -125
  3. package/dist/agent/argumentPolicy.js +2 -1
  4. package/dist/agent/managedRuntimeEnvironment.js +5 -8
  5. package/dist/cli/commandCatalog.js +74 -109
  6. package/dist/cli/helpRenderer.js +1 -2
  7. package/dist/cli/interactionCandidates.js +10 -0
  8. package/dist/cli/interactionPolicy.js +76 -37
  9. package/dist/cli/roleWizard.js +5 -6
  10. package/dist/cli/updateOrchestrator.js +10 -8
  11. package/dist/cli/updatePorts.js +17 -13
  12. package/dist/cli/upgradeCommand.js +2 -5
  13. package/dist/cli.js +268 -231
  14. package/dist/commands/agentCommands.js +13 -0
  15. package/dist/commands/configCommands.js +7 -19
  16. package/dist/commands/configOverview.js +3 -2
  17. package/dist/commands/durableJobCommands.js +1 -1
  18. package/dist/commands/executionAuditCommands.js +4 -9
  19. package/dist/commands/globalRoleCommands.js +17 -0
  20. package/dist/commands/profileCommands.js +200 -30
  21. package/dist/commands/sessionCommands.js +1 -1
  22. package/dist/commands/taskActor.js +29 -127
  23. package/dist/commands/taskChangeSetCommands.js +1 -3
  24. package/dist/commands/taskCommands.js +1771 -2107
  25. package/dist/commands/taskCompletionGate.js +27 -103
  26. package/dist/commands/taskContextCommand.js +47 -80
  27. package/dist/commands/taskExecutionCommands.js +9 -27
  28. package/dist/commands/taskInputCommands.js +10 -10
  29. package/dist/commands/taskIntegrationCommands.js +69 -61
  30. package/dist/commands/taskIntegrationQueueCommands.js +1 -1
  31. package/dist/commands/taskNextActionCommand.js +8 -29
  32. package/dist/commands/taskOverviewCommand.js +25 -3
  33. package/dist/commands/taskPublicationCommands.js +151 -85
  34. package/dist/commands/taskPublicationVerifyCommand.js +181 -0
  35. package/dist/commands/taskRemoteDeliveryCommand.js +134 -0
  36. package/dist/commands/taskRoleRuntimeStatus.js +5 -6
  37. package/dist/commands/taskUpstreamCommands.js +79 -95
  38. package/dist/config/configCatalog.js +1 -1
  39. package/dist/context/contextSnapshot.js +17 -2
  40. package/dist/context/sessionBootstrapManifest.js +18 -1
  41. package/dist/context/sourceTurnContext.js +30 -0
  42. package/dist/context/turnContextPack.js +107 -48
  43. package/dist/context/turnInputContract.js +11 -4
  44. package/dist/context/wakeNotification.js +84 -22
  45. package/dist/controller/agentRuntimeObserver.js +9 -12
  46. package/dist/controller/clientRuntime.js +13 -13
  47. package/dist/controller/controller.js +11 -11
  48. package/dist/controller/fileSchedulerStoreAdapter.js +262 -219
  49. package/dist/controller/jobControl.js +11 -10
  50. package/dist/controller/resourceInventory.js +6 -6
  51. package/dist/controller/resourceInventoryLinux.js +2 -2
  52. package/dist/controller/runtime.js +18 -61
  53. package/dist/controller/runtimeEventInbox.js +57 -34
  54. package/dist/controller/runtimeEventProcessor.js +36 -96
  55. package/dist/controller/runtimeHookTurnFence.js +33 -57
  56. package/dist/controller/runtimeLaunchCoordinator.js +44 -40
  57. package/dist/controller/runtimeObservationHook.js +18 -12
  58. package/dist/controller/sessionNotify.js +43 -28
  59. package/dist/controller/sessionOwnerReconciliation.js +21 -21
  60. package/dist/controller/structuredProviderObservation.js +13 -16
  61. package/dist/coordination/workMailboxQueue.js +132 -0
  62. package/dist/core/controllerServer.js +1 -0
  63. package/dist/domain/agentResultTransport.js +60 -0
  64. package/dist/execution/executionHealth.js +111 -118
  65. package/dist/execution/reviewMainTurn.js +161 -0
  66. package/dist/execution/workItemExecution.js +454 -0
  67. package/dist/execution/workItemExecutionProjection.js +309 -0
  68. package/dist/execution/workItemMainTurn.js +164 -0
  69. package/dist/executor/agentExecutor.js +19 -19
  70. package/dist/executor/executorRegistry.js +7 -28
  71. package/dist/executor/fileRoleLaunchPlanner.js +51 -83
  72. package/dist/external/githubPublicationVerifier.js +114 -0
  73. package/dist/external/gitlabPublicationVerifier.js +158 -0
  74. package/dist/external/pinnedCommandRunner.js +73 -0
  75. package/dist/integration/changeSetManifest.js +0 -3
  76. package/dist/integration/deliveryObligation.js +43 -6
  77. package/dist/integration/gitIntegrationService.js +284 -101
  78. package/dist/integration/integrationAttempt.js +64 -14
  79. package/dist/integration/integrationQueueEntry.js +5 -35
  80. package/dist/integration/integrationQueueService.js +65 -194
  81. package/dist/lifecycle/canonicalLifecycleEvent.js +16 -15
  82. package/dist/lifecycle/exactTurnTerminalization.js +100 -97
  83. package/dist/message/message.js +16 -0
  84. package/dist/observability/executionAudit.js +4 -25
  85. package/dist/observability/faultClassification.js +58 -70
  86. package/dist/observability/orchestrationMetrics.js +8 -50
  87. package/dist/profile/agentProfile.js +60 -15
  88. package/dist/profile/agentProfileRuntime.js +100 -0
  89. package/dist/release/releaseWorkflowPorts.js +5 -68
  90. package/dist/repository/executionLaneGitSnapshot.js +80 -0
  91. package/dist/repository/gitWorkspace.js +9 -67
  92. package/dist/repository/taskBaseFreshness.js +3 -1
  93. package/dist/repository/taskWorkspaceCoordinator.js +32 -4
  94. package/dist/repository/taskWorkspacePreparer.js +252 -411
  95. package/dist/resources/liveReferences.js +6 -55
  96. package/dist/resources/resourceDiscovery.js +1 -30
  97. package/dist/resources/resourceRegistrar.js +0 -23
  98. package/dist/review/deltaRecheck.js +8 -65
  99. package/dist/review/reviewAcceptance.js +22 -19
  100. package/dist/review/reviewConfig.js +0 -10
  101. package/dist/review/reviewDecision.js +2 -2
  102. package/dist/review/reviewRound.js +147 -273
  103. package/dist/review/reviewerAvailability.js +13 -23
  104. package/dist/role/role.js +3 -0
  105. package/dist/runtime/agentDriverObservation.js +1 -1
  106. package/dist/runtime/agentHost.js +51 -51
  107. package/dist/runtime/builtinAgentDrivers.js +9 -2
  108. package/dist/runtime/codexAppServerRuntime.js +23 -5
  109. package/dist/runtime/exactControlPlane.js +2 -267
  110. package/dist/runtime/firstProgressAdvisory.js +1 -1
  111. package/dist/runtime/index.js +1 -1
  112. package/dist/runtime/launchBroker.js +11 -11
  113. package/dist/runtime/lifecycleReservation.js +2 -2
  114. package/dist/runtime/managedCaller.js +111 -0
  115. package/dist/runtime/ports.js +16 -3
  116. package/dist/runtime/preallocatedNativeSession.js +2 -2
  117. package/dist/runtime/processExitObservation.js +3 -2
  118. package/dist/runtime/providerContinuationReconciliationService.js +1 -1
  119. package/dist/runtime/runtimeBinding.js +1 -1
  120. package/dist/runtime/runtimeObservation.js +33 -16
  121. package/dist/runtime/runtimeSessionCandidate.js +2 -2
  122. package/dist/runtime/runtimeStopReceipt.js +11 -11
  123. package/dist/runtime/sessionLaunchRequest.js +2 -2
  124. package/dist/runtime/sessionOwnerIdentity.js +9 -9
  125. package/dist/runtime/sessionOwnerRegistry.js +13 -13
  126. package/dist/runtime/sessionReconciliation.js +4 -4
  127. package/dist/runtime/sessionTerminationGuard.js +9 -9
  128. package/dist/runtime/sessionTokenMetrics.js +5 -7
  129. package/dist/runtime/structuredProviderHost.js +5 -5
  130. package/dist/runtime/taskRuntimeIsolation.js +24 -24
  131. package/dist/runtime/tmuxAdapters.js +32 -30
  132. package/dist/scheduler/activeRoleTurnDelivery.js +37 -15
  133. package/dist/scheduler/leaderWakeupProcessor.js +78 -55
  134. package/dist/scheduler/roleTurnLiveness.js +6 -6
  135. package/dist/scheduler/roleTurnStall.js +8 -9
  136. package/dist/scheduler/taskExecutionProjection.js +19 -41
  137. package/dist/scheduler/taskObservabilityProjection.js +75 -122
  138. package/dist/storage/sqliteSchema.js +1 -21
  139. package/dist/storage/sqliteStore.js +62 -47
  140. package/dist/storage/storageSchema.js +1 -8
  141. package/dist/storage/storageVersions.js +7 -4
  142. package/dist/storage/taskStore.js +8 -8
  143. package/dist/storage/upgrade/upgradeOrchestrator.js +112 -83
  144. package/dist/task/completionReadiness.js +17 -98
  145. package/dist/task/deliveryGuard.js +9 -11
  146. package/dist/task/draftPlan.js +142 -0
  147. package/dist/task/nextAction.js +179 -477
  148. package/dist/task/publicationVerification.js +1 -0
  149. package/dist/task/remoteDelivery.js +233 -0
  150. package/dist/task/task.js +108 -4
  151. package/dist/task/taskRecordReference.js +0 -1
  152. package/dist/turn/turn.js +109 -16
  153. package/dist/verification/verificationGateService.js +0 -26
  154. package/dist/web/assets/client/components.js +99 -121
  155. package/dist/web/assets/client/i18n.js +136 -58
  156. package/dist/web/assets/client/view.js +44 -0
  157. package/dist/web/assets/styles/cards.js +3 -15
  158. package/dist/web/assets/styles/widgets.js +4 -0
  159. package/dist/web/webSnapshot.js +29 -14
  160. package/dist/workItem/dependencyGate.js +34 -0
  161. package/dist/workItem/workItem.js +99 -266
  162. package/dist/workspace/workItemChangeSetManager.js +15 -59
  163. package/docs/task-local-identity.md +1 -1
  164. package/i18n/README.zh-CN.md +165 -59
  165. package/package.json +1 -1
  166. package/skills/yui-leader/SKILL.md +272 -782
  167. package/skills/yui-operator/SKILL.md +190 -365
  168. package/skills/yui-reviewer/SKILL.md +40 -33
  169. package/skills/yui-runtime/SKILL.md +27 -6
  170. package/skills/yui-worker/SKILL.md +19 -6
  171. package/dist/execution/candidateConvergence.js +0 -623
  172. package/dist/execution/executionGroup.js +0 -854
  173. package/dist/execution/resourceBroker.js +0 -426
  174. package/dist/executor/launchPlan.js +0 -16
  175. package/dist/review/reviewFinding.js +0 -240
  176. package/dist/review/reviewFindingLedger.js +0 -550
  177. package/dist/review/reviewOutcomeClassifier.js +0 -255
  178. package/dist/scheduler/resourceQueueProjection.js +0 -71
  179. package/dist/task/repairWave.js +0 -150
@@ -2,10 +2,10 @@ import { requireIdentity, requireText, requireTimestamp } from "../domain/valida
2
2
  import { validateTaskRecordReference } from "../task/taskRecordReference.js";
3
3
  import { validateTaskFinalReviewContract } from "./taskFinalReviewContract.js";
4
4
  import { validateManagedWorkspace } from "../worktree/managedWorkspace.js";
5
- import { assertExecutionGroupTransition, resetReviewExecutionLane, validateExecutionGroup } from "../execution/executionGroup.js";
5
+ import { assertExecutionGroupTransition, retryFailedExecutionLanes, validateExecutionGroup, validateReviewExecutionAssignment } from "../execution/workItemExecution.js";
6
6
  export function createReviewRound(id, taskId, workItemId, candidateId, reviewerRoleName, requestedBy, reviewBaseCommit, now, executionGroup) {
7
7
  return validateReviewRound({
8
- schemaVersion: 6,
8
+ schemaVersion: 8,
9
9
  id: requireIdentity(id, "ReviewRound id"),
10
10
  taskId: requireIdentity(taskId, "Task id"),
11
11
  workItemId: requireIdentity(workItemId, "Work Item id"),
@@ -21,7 +21,7 @@ export function createReviewRound(id, taskId, workItemId, candidateId, reviewerR
21
21
  export function createTaskReviewRound(id, taskId, reviewerRoleName, requestedBy, taskCandidate, now, taskFinalReviewContract, executionGroup) {
22
22
  const candidate = validateTaskReviewCandidate(taskCandidate);
23
23
  return validateReviewRound({
24
- schemaVersion: 6,
24
+ schemaVersion: 8,
25
25
  id: requireIdentity(id, "ReviewRound id"),
26
26
  taskId: requireIdentity(taskId, "Task id"),
27
27
  reviewerRoleName: requireIdentity(reviewerRoleName, "Reviewer Role"),
@@ -47,7 +47,7 @@ export function createTaskReviewRound(id, taskId, reviewerRoleName, requestedBy,
47
47
  export function createTaskDeltaReviewRound(id, taskId, reviewerRoleName, requestedBy, taskCandidate, deltaRecheck, now, taskFinalReviewContract, executionGroup) {
48
48
  const candidate = validateTaskReviewCandidate(taskCandidate);
49
49
  return validateReviewRound({
50
- schemaVersion: 6,
50
+ schemaVersion: 8,
51
51
  id: requireIdentity(id, "ReviewRound id"),
52
52
  taskId: requireIdentity(taskId, "Task id"),
53
53
  reviewerRoleName: requireIdentity(reviewerRoleName, "Reviewer Role"),
@@ -80,7 +80,10 @@ export function attachReviewRoundWorkspace(round, workspace) {
80
80
  }
81
81
  export function startReviewRound(round, reviewerTurnId) {
82
82
  validateReviewRound(round);
83
- if (round.status !== "pending") {
83
+ if (round.status !== "pending"
84
+ && !(round.status === "running"
85
+ && round.executionGroup !== undefined
86
+ && round.reviewerTurnId === undefined)) {
84
87
  throw new Error(`ReviewRound cannot start from ${round.status}: ${round.id}.`);
85
88
  }
86
89
  if (round.workspace === undefined) {
@@ -92,74 +95,67 @@ export function startReviewRound(round, reviewerTurnId) {
92
95
  status: "running"
93
96
  });
94
97
  }
95
- export function finishReviewRound(round, status, summary, now, result = {}) {
98
+ export function startReplicatedReviewRound(round) {
99
+ validateReviewRound(round);
100
+ if (round.status !== "pending" || round.executionGroup === undefined) {
101
+ throw new Error(`ReviewRound cannot start replicated execution: ${round.id}.`);
102
+ }
103
+ if (round.workspace === undefined) {
104
+ throw new Error(`ReviewRound workspace is not ready: ${round.id}.`);
105
+ }
106
+ return validateReviewRound({ ...round, status: "running" });
107
+ }
108
+ export function finishReviewRound(round, status, now, failure) {
96
109
  validateReviewRound(round);
97
110
  if (round.status !== "pending" && round.status !== "running") {
98
111
  throw new Error(`ReviewRound is already terminal: ${round.id}.`);
99
112
  }
100
- const terminal = validateReviewRound({
101
- ...round,
102
- status,
103
- summary: requireText(summary, "Review summary"),
104
- report: requireText(result.report ?? summary, "Review report"),
105
- checks: validateChecks(result.checks ?? []),
106
- ...(result.evidenceCommit === undefined
107
- ? {}
108
- : { evidenceCommit: requireCommit(result.evidenceCommit, "Review evidence commit") }),
109
- endedAt: now.toISOString()
110
- });
111
- if (round.deltaRecheck === undefined) {
112
- if (result.deltaDisposition !== undefined || result.deltaReasoning !== undefined) {
113
- throw new Error(`Only a delta-recheck ReviewRound can carry a delta disposition: ${round.id}.`);
114
- }
115
- return terminal;
113
+ if (status === "completed" && failure !== undefined) {
114
+ throw new Error("A completed ReviewRound cannot carry failure metadata.");
116
115
  }
117
- if (status !== "completed") {
118
- // A failed delta Round is an infra attempt; it records no disposition and
119
- // the candidate stays unaccepted.
120
- return terminal;
116
+ if (status === "failed" && failure === undefined) {
117
+ throw new Error("A failed ReviewRound requires Core-owned failure metadata.");
121
118
  }
122
- // Fail closed: a completed delta Round without an explicit, valid
123
- // disposition remains non-accepting for Leader routing. Uncertainty never accepts.
124
- const disposition = result.deltaDisposition ?? "requires-full-review";
125
- const reasoning = result.deltaReasoning ?? result.report ?? summary;
126
119
  return validateReviewRound({
127
- ...terminal,
128
- deltaRecheck: validateDeltaRecheckRecord({
129
- ...round.deltaRecheck,
130
- disposition,
131
- reasoning: requireText(reasoning, "Delta recheck reasoning")
132
- })
120
+ ...round,
121
+ status,
122
+ ...(failure === undefined ? {} : { failure: validateReviewRoundFailure(failure) }),
123
+ endedAt: now.toISOString()
133
124
  });
134
125
  }
135
126
  /**
136
- * Issue 06: retry a failed Task-final execution attempt under the same semantic
137
- * Round identity. Turn history remains the attempt trail; the Round itself
138
- * returns to pending so infrastructure retries do not manufacture a new
139
- * semantic ReviewRound or duplicate findings.
127
+ * Retry a failed review execution attempt under the same semantic Round
128
+ * identity. Turn history remains the attempt trail; the Round itself returns
129
+ * to pending so infrastructure retries do not manufacture a new semantic
130
+ * ReviewRound or duplicate findings.
140
131
  */
141
- export function retryTaskReviewRound(round, requestedBy, executionLaneId) {
132
+ export function retryReviewRound(round, requestedBy, now) {
142
133
  validateReviewRound(round);
143
- if ((round.scope ?? "work-item") !== "task") {
144
- throw new Error(`Only a Task-final ReviewRound can be retried in place: ${round.id}.`);
145
- }
146
134
  if (round.status !== "failed") {
147
135
  throw new Error(`ReviewRound ${round.id} is not retryable from ${round.status}.`);
148
136
  }
149
- const retryExecutionGroup = round.executionGroup === undefined
150
- ? undefined
151
- : retryReviewExecutionGroup(round, executionLaneId);
137
+ const taskScope = (round.scope ?? "work-item") === "task";
152
138
  return validateReviewRound({
153
139
  schemaVersion: round.schemaVersion,
154
140
  id: round.id,
155
141
  taskId: round.taskId,
142
+ ...(round.scope === undefined ? {} : { scope: round.scope }),
143
+ ...(taskScope
144
+ ? {}
145
+ : {
146
+ workItemId: round.workItemId,
147
+ candidateId: round.candidateId
148
+ }),
156
149
  reviewerRoleName: round.reviewerRoleName,
157
150
  reviewBaseCommit: round.reviewBaseCommit,
158
- scope: "task",
159
- ...(round.taskCandidate === undefined ? {} : { taskCandidate: round.taskCandidate }),
160
- ...(round.taskFinalReviewContract === undefined
161
- ? {}
162
- : { taskFinalReviewContract: round.taskFinalReviewContract }),
151
+ ...(taskScope
152
+ ? {
153
+ taskCandidate: round.taskCandidate,
154
+ ...(round.taskFinalReviewContract === undefined
155
+ ? {}
156
+ : { taskFinalReviewContract: round.taskFinalReviewContract })
157
+ }
158
+ : {}),
163
159
  ...(round.deltaRecheck === undefined
164
160
  ? {}
165
161
  // A retried delta Round is still the same semantic recheck: the
@@ -176,129 +172,37 @@ export function retryTaskReviewRound(round, requestedBy, executionLaneId) {
176
172
  deletedLines: round.deltaRecheck.deletedLines
177
173
  })
178
174
  }),
179
- // Keep the historical attempt Group and Lane addressable from Turn
180
- // history while resetting the Lane for another dispatch attempt.
181
- ...(retryExecutionGroup === undefined ? {} : { executionGroup: retryExecutionGroup }),
175
+ // A replicated retry preserves settled successful Producers and reopens
176
+ // only failed Lanes. If every Producer succeeded, only the main Reviewer
177
+ // Turn is retried.
178
+ ...(round.executionGroup === undefined
179
+ ? {}
180
+ : { executionGroup: retryFailedExecutionLanes(round.executionGroup, now) }),
182
181
  requestedBy: validateReviewRequestSource(requestedBy),
183
182
  status: "pending",
184
183
  ...(round.workspace === undefined ? {} : { workspace: round.workspace }),
185
184
  createdAt: round.createdAt
186
185
  });
187
186
  }
188
- /** Keep a running panel Round active while retrying only its exact failed Lane. */
189
- export function retryRunningReviewExecutionLane(round, executionLaneId, turnId, now) {
187
+ /** Task-final compatibility wrapper for callers that require that scope. */
188
+ export function retryTaskReviewRound(round, requestedBy, now) {
189
+ if ((round.scope ?? "work-item") !== "task") {
190
+ throw new Error(`Only a Task-final ReviewRound can be retried in place: ${round.id}.`);
191
+ }
192
+ return retryReviewRound(round, requestedBy, now);
193
+ }
194
+ /** A failed producer attempt leaves its logical Lane open for another Turn. */
195
+ export function retryRunningReviewExecutionLane(round, executionLaneId, turnId) {
190
196
  validateReviewRound(round);
191
197
  if (round.status !== "running" || round.executionGroup === undefined) {
192
198
  throw new Error(`ReviewRound ${round.id} has no running ExecutionGroup.`);
193
199
  }
194
200
  const lane = round.executionGroup.lanes.find(({ id }) => id === executionLaneId);
195
- if (lane === undefined || lane.status !== "failed" || lane.turnId !== turnId) {
201
+ if (lane === undefined || lane.disposition !== "open" || lane.currentTurnId !== turnId) {
196
202
  throw new Error(`Review retry does not target the current failed Lane attempt: `
197
203
  + `${round.executionGroup.id}/${executionLaneId}/${turnId}.`);
198
204
  }
199
- return updateReviewExecutionGroup(round, retryReviewExecutionGroup(round, executionLaneId, now));
200
- }
201
- function retryReviewExecutionGroup(round, executionLaneId, retryAt) {
202
- const previous = round.executionGroup;
203
- if (executionLaneId !== undefined) {
204
- const exactLane = previous.lanes.find(({ id }) => id === executionLaneId);
205
- if (exactLane === undefined || exactLane.status !== "failed") {
206
- throw new Error(`Review retry Lane is not failed: ${previous.id}/${executionLaneId}.`);
207
- }
208
- }
209
- const now = retryAt ?? new Date(Date.parse(round.endedAt ?? round.createdAt));
210
- const lanes = previous.lanes.map((lane) => (lane.status === "failed"
211
- && (executionLaneId === undefined || lane.id === executionLaneId)
212
- ? resetReviewExecutionLane(previous, lane.id, now)
213
- : lane));
214
- return validateExecutionGroup({
215
- ...previous,
216
- lanes,
217
- updatedAt: now.toISOString()
218
- });
219
- }
220
- /** Accepts the Reviewer's complete report and extracts only optional known evidence. */
221
- export function parseReviewResultReport(value) {
222
- const report = requireText(value, "Review report");
223
- let parsed;
224
- try {
225
- parsed = JSON.parse(report);
226
- }
227
- catch {
228
- return { summary: report, report, checks: [] };
229
- }
230
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
231
- return { summary: report, report, checks: [] };
232
- }
233
- const record = parsed;
234
- const summary = typeof record.summary === "string" && record.summary.trim().length > 0
235
- ? requireText(record.summary, "Review summary")
236
- : report;
237
- const checks = extractChecks(record.checks);
238
- const findings = extractFindings(record.findings);
239
- const evidence = extractEvidence(record.evidence);
240
- const deltaDisposition = extractDeltaDisposition(record.deltaDisposition);
241
- return {
242
- summary,
243
- report,
244
- checks,
245
- ...(findings.length === 0 ? {} : { findings }),
246
- ...(evidence.length === 0 ? {} : { evidence }),
247
- ...(deltaDisposition === undefined ? {} : { deltaDisposition }),
248
- ...(typeof record.deltaReasoning !== "string"
249
- || record.deltaReasoning.trim().length === 0
250
- ? {}
251
- : { deltaReasoning: requireText(record.deltaReasoning, "Delta recheck reasoning") }),
252
- ...(typeof record.evidenceCommit !== "string"
253
- ? {}
254
- : {
255
- evidenceCommit: requireCommit(record.evidenceCommit, "Review evidence commit")
256
- })
257
- };
258
- }
259
- function extractDeltaDisposition(value) {
260
- if (value !== "equivalent-and-accepted"
261
- && value !== "finding"
262
- && value !== "requires-full-review") {
263
- return undefined;
264
- }
265
- return value;
266
- }
267
- function extractFindings(value) {
268
- if (!Array.isArray(value))
269
- return [];
270
- return value.flatMap((entry) => {
271
- if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
272
- return [];
273
- }
274
- const finding = entry;
275
- if (typeof finding.id !== "string"
276
- || (finding.severity !== "low" && finding.severity !== "medium"
277
- && finding.severity !== "high" && finding.severity !== "critical"
278
- && finding.severity !== "p1" && finding.severity !== "p2")
279
- || (finding.status !== "open" && finding.status !== "resolved")
280
- || typeof finding.summary !== "string") {
281
- return [];
282
- }
283
- return [{
284
- id: requireIdentity(finding.id, "Review finding id"),
285
- // Reviewers may use the product-level P1/P2 vocabulary. Execution
286
- // groups keep one canonical severity scale so resolution gates remain
287
- // stable across Worker and Reviewer reports.
288
- severity: finding.severity === "p1"
289
- ? "critical"
290
- : finding.severity === "p2"
291
- ? "high"
292
- : finding.severity,
293
- status: finding.status,
294
- summary: requireText(finding.summary, "Review finding summary")
295
- }];
296
- });
297
- }
298
- function extractEvidence(value) {
299
- if (!Array.isArray(value))
300
- return [];
301
- return value.flatMap((entry) => (typeof entry === "string" && entry.trim().length > 0 ? [entry.trim()] : []));
205
+ return round;
302
206
  }
303
207
  export function recordReviewWorkspaceDisposition(round, disposition, now) {
304
208
  validateReviewRound(round);
@@ -348,9 +252,8 @@ export function updateReviewExecutionGroup(round, executionGroup) {
348
252
  }
349
253
  return validateReviewRound({ ...round, executionGroup });
350
254
  }
351
- if (round.executionGroup.id !== executionGroup.id
352
- || JSON.stringify(round.executionGroup.target) !== JSON.stringify(executionGroup.target)) {
353
- throw new Error(`ReviewRound ExecutionGroup target is immutable: ${round.id}.`);
255
+ if (round.executionGroup.id !== executionGroup.id) {
256
+ throw new Error(`ReviewRound ExecutionGroup identity is immutable: ${round.id}.`);
354
257
  }
355
258
  if (JSON.stringify(round.executionGroup) === JSON.stringify(executionGroup))
356
259
  return round;
@@ -358,8 +261,30 @@ export function updateReviewExecutionGroup(round, executionGroup) {
358
261
  return validateReviewRound({ ...round, executionGroup });
359
262
  }
360
263
  export function validateReviewRound(round) {
361
- if (round.schemaVersion !== 6)
362
- throw new Error("ReviewRound must use schemaVersion 6.");
264
+ rejectUnknownFields(round, [
265
+ "schemaVersion",
266
+ "id",
267
+ "taskId",
268
+ "workItemId",
269
+ "candidateId",
270
+ "reviewerRoleName",
271
+ "reviewerTurnId",
272
+ "reviewBaseCommit",
273
+ "scope",
274
+ "taskCandidate",
275
+ "taskFinalReviewContract",
276
+ "deltaRecheck",
277
+ "executionGroup",
278
+ "workspace",
279
+ "requestedBy",
280
+ "status",
281
+ "failure",
282
+ "workspaceDisposition",
283
+ "createdAt",
284
+ "endedAt"
285
+ ], "ReviewRound");
286
+ if (round.schemaVersion !== 8)
287
+ throw new Error("ReviewRound must use schemaVersion 8.");
363
288
  validateTaskRecordReference({ taskId: round.taskId, localId: round.id }, "reviewRound");
364
289
  requireIdentity(round.reviewerRoleName, "Reviewer Role");
365
290
  requireCommit(round.reviewBaseCommit, "Review base commit");
@@ -424,29 +349,30 @@ export function validateReviewRound(round) {
424
349
  requireTimestamp(round.createdAt, "ReviewRound createdAt");
425
350
  const terminal = round.status === "completed" || round.status === "failed";
426
351
  if (terminal) {
427
- requireText(round.summary ?? "", "Review summary");
428
- requireText(round.report ?? "", "Review report");
429
- validateChecks(round.checks ?? []);
430
- if (round.evidenceCommit !== undefined) {
431
- // The exact commit on which the review's checks ran. Equals the base
432
- // when the reviewer ran checks on the frozen candidate tree; differs
433
- // when the reviewer committed diagnostics on top of it. A dirty
434
- // review with uncommitted changes records no evidenceCommit, since no
435
- // single commit captures the checked tree.
436
- requireCommit(round.evidenceCommit, "Review evidence commit");
352
+ if (round.status === "completed" && round.reviewerTurnId === undefined) {
353
+ throw new Error("A completed ReviewRound requires its exact main Reviewer Turn.");
354
+ }
355
+ if (round.status === "completed" && round.failure !== undefined) {
356
+ throw new Error("A completed ReviewRound cannot carry failure metadata.");
437
357
  }
358
+ if (round.status === "failed" && round.failure === undefined) {
359
+ throw new Error("A failed ReviewRound requires failure metadata.");
360
+ }
361
+ if (round.failure !== undefined)
362
+ validateReviewRoundFailure(round.failure);
438
363
  requireTimestamp(round.endedAt ?? "", "ReviewRound endedAt");
439
364
  }
440
- else if (round.summary !== undefined
441
- || round.report !== undefined
442
- || round.checks !== undefined
443
- || round.evidenceCommit !== undefined
365
+ else if (round.failure !== undefined
444
366
  || round.endedAt !== undefined) {
445
367
  throw new Error("An active ReviewRound cannot have terminal metadata.");
446
368
  }
369
+ if (round.status === "running" && round.workspace === undefined) {
370
+ throw new Error("A running ReviewRound requires its main Reviewer workspace.");
371
+ }
447
372
  if (round.status === "running"
448
- && (round.reviewerTurnId === undefined || round.workspace === undefined)) {
449
- throw new Error("A running ReviewRound requires a Reviewer Turn and workspace.");
373
+ && round.reviewerTurnId === undefined
374
+ && round.executionGroup === undefined) {
375
+ throw new Error("A running ReviewRound requires a direct Reviewer Turn or replicated Group.");
450
376
  }
451
377
  if (round.workspaceDisposition !== undefined) {
452
378
  if (!terminal || round.workspace === undefined) {
@@ -459,23 +385,19 @@ export function validateReviewRound(round) {
459
385
  }
460
386
  requireTimestamp(round.workspaceDisposition.recordedAt, "Review workspace disposition time");
461
387
  }
462
- if (round.deltaRecheck?.disposition !== undefined) {
463
- if (!terminal) {
464
- throw new Error("An active delta-recheck ReviewRound cannot have a disposition.");
465
- }
466
- if (round.deltaRecheck.reasoning === undefined
467
- || round.deltaRecheck.reasoning.trim().length === 0) {
468
- throw new Error("A terminal delta-recheck ReviewRound requires reasoning.");
469
- }
470
- if (round.deltaRecheck.escalatedToReviewRoundId !== undefined
471
- && round.deltaRecheck.disposition !== "requires-full-review") {
472
- throw new Error("Only a requires-full-review delta-recheck can record an escalation.");
473
- }
474
- }
475
388
  return round;
476
389
  }
477
390
  /** Validates a delta-recheck record's immutable identity and terminal fields. */
478
391
  export function validateDeltaRecheckRecord(record) {
392
+ rejectUnknownFields(record, [
393
+ "schemaVersion",
394
+ "previousReviewRoundId",
395
+ "previousBaseCommit",
396
+ "diffDigest",
397
+ "changedFiles",
398
+ "addedLines",
399
+ "deletedLines"
400
+ ], "Delta recheck record");
479
401
  if (record.schemaVersion !== 1) {
480
402
  throw new Error("Delta recheck record must use schemaVersion 1.");
481
403
  }
@@ -492,59 +414,43 @@ export function validateDeltaRecheckRecord(record) {
492
414
  || !Number.isInteger(record.deletedLines) || record.deletedLines < 0) {
493
415
  throw new Error("Delta recheck line counts are invalid.");
494
416
  }
495
- if (record.disposition !== undefined
496
- && record.disposition !== "equivalent-and-accepted"
497
- && record.disposition !== "finding"
498
- && record.disposition !== "requires-full-review") {
499
- throw new Error(`Delta recheck disposition is invalid: ${String(record.disposition)}.`);
500
- }
501
- if (record.reasoning !== undefined) {
502
- requireText(record.reasoning, "Delta recheck reasoning");
503
- }
504
- if (record.escalatedToReviewRoundId !== undefined) {
505
- requireIdentity(record.escalatedToReviewRoundId, "Delta recheck escalation ReviewRound id");
506
- }
507
417
  return record;
508
418
  }
509
419
  /** True when this Round is an Issue 07 delta-recheck. */
510
420
  export function isDeltaRecheckRound(round) {
511
421
  return round.deltaRecheck !== undefined;
512
422
  }
513
- /** True only when a delta Round explicitly accepted the new head. */
514
- export function deltaRecheckAccepted(round) {
515
- return round.deltaRecheck?.disposition === "equivalent-and-accepted";
516
- }
517
- /** True when a delta Round escalated to a full Review. */
518
- export function deltaRecheckEscalated(round) {
519
- return round.deltaRecheck?.disposition === "requires-full-review";
520
- }
521
- /**
522
- * True when a completed delta Round does NOT accept the new head. A `finding`
523
- * or `requires-full-review` disposition must keep the completion gate closed.
524
- */
525
- export function deltaRecheckBlocksAcceptance(round) {
526
- return round.deltaRecheck !== undefined
527
- && round.status === "completed"
528
- && round.deltaRecheck.disposition !== undefined
529
- && round.deltaRecheck.disposition !== "equivalent-and-accepted";
423
+ function validateReviewRoundFailure(failure) {
424
+ rejectUnknownFields(failure, [
425
+ "kind",
426
+ "message"
427
+ ], "ReviewRound failure");
428
+ if (failure.kind !== "dispatch"
429
+ && failure.kind !== "execution"
430
+ && failure.kind !== "quorum") {
431
+ throw new Error("ReviewRound failure kind is invalid.");
432
+ }
433
+ requireText(failure.message, "ReviewRound failure message");
434
+ return failure;
530
435
  }
531
436
  function validateReviewExecutionGroup(group, round) {
532
437
  validateExecutionGroup(group);
533
- if (group.taskId !== round.taskId || group.purpose !== "review") {
438
+ const assignment = validateReviewExecutionAssignment(group.assignment);
439
+ if (group.taskId !== round.taskId
440
+ || assignment.taskId !== round.taskId
441
+ || assignment.reviewRoundId !== round.id
442
+ || assignment.reviewBaseCommit !== round.reviewBaseCommit
443
+ || assignment.scope !== (round.scope ?? "work-item")) {
534
444
  throw new Error(`ReviewRound ExecutionGroup provenance is invalid: ${round.id}.`);
535
445
  }
536
- const expectedKind = (round.scope ?? "work-item") === "task"
537
- ? "task-final-review"
538
- : "work-item";
539
- if (group.target.kind !== expectedKind
540
- || group.target.taskId !== round.taskId) {
541
- throw new Error(`ReviewRound ExecutionGroup target is invalid: ${round.id}.`);
542
- }
543
- if (expectedKind === "work-item"
544
- && (group.target.workItemId !== round.workItemId
545
- || group.target.candidateId !== round.candidateId)) {
446
+ if ((round.scope ?? "work-item") === "work-item"
447
+ && (assignment.workItemId !== round.workItemId
448
+ || assignment.candidateId !== round.candidateId)) {
546
449
  throw new Error(`ReviewRound ExecutionGroup WorkItem target is invalid: ${round.id}.`);
547
450
  }
451
+ if (group.lanes.some(({ roleName }) => roleName === round.reviewerRoleName)) {
452
+ throw new Error(`The main Reviewer cannot also be a Producer Lane: ${round.id}.`);
453
+ }
548
454
  return group;
549
455
  }
550
456
  export function validateTaskReviewCandidate(candidate) {
@@ -563,26 +469,6 @@ export function validateTaskReviewCandidate(candidate) {
563
469
  }
564
470
  return { schemaVersion: 1, projects };
565
471
  }
566
- function extractChecks(value) {
567
- if (!Array.isArray(value))
568
- return [];
569
- const extracted = value.flatMap((entry) => {
570
- if (typeof entry !== "object" || entry === null || Array.isArray(entry))
571
- return [];
572
- const check = entry;
573
- if (typeof check.name !== "string"
574
- || (check.outcome !== "passed"
575
- && check.outcome !== "failed"
576
- && check.outcome !== "skipped"))
577
- return [];
578
- return [{
579
- name: check.name,
580
- outcome: check.outcome,
581
- ...(typeof check.details === "string" ? { details: check.details } : {})
582
- }];
583
- });
584
- return validateChecks(extracted);
585
- }
586
472
  function validateReviewWorkspace(round, workspace) {
587
473
  validateManagedWorkspace(workspace);
588
474
  if (workspace.owner.taskId !== round.taskId) {
@@ -600,24 +486,6 @@ function validateReviewWorkspace(round, workspace) {
600
486
  throw new Error(`ReviewRound workspace does not contain its review base: ${round.id}.`);
601
487
  }
602
488
  }
603
- function validateChecks(checks) {
604
- if (!Array.isArray(checks))
605
- throw new Error("Review checks are invalid.");
606
- return checks.map((check) => {
607
- const name = requireText(check.name, "Review check name");
608
- if (check.outcome !== "passed" && check.outcome !== "failed"
609
- && check.outcome !== "skipped") {
610
- throw new Error(`Review check outcome is invalid: ${String(check.outcome)}.`);
611
- }
612
- return {
613
- name,
614
- outcome: check.outcome,
615
- ...(check.details === undefined
616
- ? {}
617
- : { details: requireText(check.details, "Review check details") })
618
- };
619
- });
620
- }
621
489
  function requireCommit(value, label) {
622
490
  const commit = requireText(value, label).toLowerCase();
623
491
  if (!/^[a-f0-9]{40}(?:[a-f0-9]{24})?$/u.test(commit)) {
@@ -634,3 +502,9 @@ function validateReviewRequestSource(source) {
634
502
  }
635
503
  return source;
636
504
  }
505
+ function rejectUnknownFields(value, fields, label) {
506
+ const allowed = new Set(fields);
507
+ const unknown = Object.keys(value).find((field) => !allowed.has(field));
508
+ if (unknown !== undefined)
509
+ throw new Error(`${label} has unknown field: ${unknown}.`);
510
+ }
@@ -2,9 +2,8 @@ import { nextPendingBatch, mailboxHasWork } from "../coordination/workMailbox.js
2
2
  import { runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
3
3
  /** One authoritative read-only projection for a Task Reviewer Role slot. */
4
4
  export function projectReviewerAvailability(store, taskId, reviewerRoleName) {
5
- const active = store.getActiveTurn(taskId, reviewerRoleName)
6
- ?? store.listTurns(taskId).find((run) => (run.roleName === reviewerRoleName && run.status === "active"));
7
- if (active !== null && active !== undefined) {
5
+ const active = store.getActiveTurn(taskId, reviewerRoleName);
6
+ if (active !== null) {
8
7
  return {
9
8
  kind: "busy",
10
9
  reviewerRoleName,
@@ -18,8 +17,15 @@ export function projectReviewerAvailability(store, taskId, reviewerRoleName) {
18
17
  retryAfterSeconds: 5
19
18
  };
20
19
  }
21
- const activeRound = store.listReviewRounds(taskId).find((round) => ((round.scope ?? "work-item") === "task"
22
- && (round.status === "pending" || round.status === "running")
20
+ const runtimeMailbox = store.getWorkMailbox(runtimeLifecycleTarget({
21
+ scope: "task",
22
+ taskId,
23
+ roleName: reviewerRoleName
24
+ }));
25
+ if (runtimeMailbox !== null && mailboxHasWork(runtimeMailbox)) {
26
+ return runtimeLifecycleBusy(reviewerRoleName, runtimeMailbox);
27
+ }
28
+ const activeRound = store.listReviewRounds(taskId).find((round) => ((round.status === "pending" || round.status === "running")
23
29
  && (round.reviewerRoleName === reviewerRoleName
24
30
  || round.executionGroup?.lanes.some(({ roleName }) => roleName === reviewerRoleName) === true)));
25
31
  if (activeRound !== undefined) {
@@ -33,35 +39,19 @@ export function projectReviewerAvailability(store, taskId, reviewerRoleName) {
33
39
  retryAfterSeconds: 5
34
40
  };
35
41
  }
36
- const reviewerMailbox = store.getWorkMailbox({
37
- kind: "role",
38
- taskId,
39
- roleName: reviewerRoleName
40
- });
41
- if (reviewerMailbox !== null && mailboxHasWork(reviewerMailbox)) {
42
- return mailboxBusy(reviewerRoleName, "mailbox", reviewerMailbox);
43
- }
44
- const runtimeMailbox = store.getWorkMailbox(runtimeLifecycleTarget({
45
- scope: "task",
46
- taskId,
47
- roleName: reviewerRoleName
48
- }));
49
- if (runtimeMailbox !== null && mailboxHasWork(runtimeMailbox)) {
50
- return mailboxBusy(reviewerRoleName, "runtime-lifecycle", runtimeMailbox);
51
- }
52
42
  return {
53
43
  kind: "available",
54
44
  reviewerRoleName,
55
45
  retryable: true
56
46
  };
57
47
  }
58
- function mailboxBusy(reviewerRoleName, phase, mailbox) {
48
+ function runtimeLifecycleBusy(reviewerRoleName, mailbox) {
59
49
  const pending = nextPendingBatch(mailbox);
60
50
  const startedAt = mailbox.processing?.startedAt ?? pending?.firstQueuedAt;
61
51
  return {
62
52
  kind: "busy",
63
53
  reviewerRoleName,
64
- phase,
54
+ phase: "runtime-lifecycle",
65
55
  ...(startedAt === undefined ? {} : { startedAt }),
66
56
  retryable: true,
67
57
  retryAfterSeconds: 5
package/dist/role/role.js CHANGED
@@ -98,6 +98,9 @@ export function switchActiveRoleAgent(role, sessions, targetAgentId, runtime, no
98
98
  : {
99
99
  ...sessions,
100
100
  activeAgentId: normalizedTarget,
101
+ ...(sessions.owner.scope === "task" && fromAgentId !== normalizedTarget
102
+ ? { providerBinding: null }
103
+ : {}),
101
104
  updatedAt: timestamp
102
105
  };
103
106
  return {
@@ -40,7 +40,7 @@ function observation(input, mapped) {
40
40
  const eventId = hookEventId(input, mapped, requestOccurrenceId);
41
41
  const fence = { ...input.fence, ...mapped.fence };
42
42
  return createRuntimeObservation({
43
- schemaVersion: 2,
43
+ schemaVersion: 4,
44
44
  eventId,
45
45
  semanticKey: requestOccurrenceId === undefined
46
46
  ? runtimeObservationSemanticKey({