@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
@@ -0,0 +1,454 @@
1
+ import { isDeepStrictEqual } from "node:util";
2
+ import { normalizedUniqueIdentities, normalizedUniqueText, requireIdentity, requirePositiveInteger, requireText, requireTimestamp } from "../domain/validation.js";
3
+ import { validateContextSnapshotRef } from "../context/contextSnapshot.js";
4
+ import { validateEffectiveLaunchSnapshot } from "../executor/effectiveLaunch.js";
5
+ import { MAX_SYNTHESIS_SOURCE_TURNS } from "../context/sourceTurnContext.js";
6
+ export const WORK_ITEM_EXECUTION_GROUP_SCHEMA_VERSION = 2;
7
+ export const WORK_ITEM_EXECUTION_LANE_SCHEMA_VERSION = 2;
8
+ export const WORK_ITEM_EXECUTION_ASSIGNMENT_SCHEMA_VERSION = 1;
9
+ export const MINIMUM_WORK_ITEM_SYNTHESIS_RESULTS = 2;
10
+ export const EXECUTION_GROUP_SCHEMA_VERSION = WORK_ITEM_EXECUTION_GROUP_SCHEMA_VERSION;
11
+ export const EXECUTION_LANE_SCHEMA_VERSION = WORK_ITEM_EXECUTION_LANE_SCHEMA_VERSION;
12
+ export const EXECUTION_ASSIGNMENT_SCHEMA_VERSION = WORK_ITEM_EXECUTION_ASSIGNMENT_SCHEMA_VERSION;
13
+ export const MINIMUM_SYNTHESIS_RESULTS = MINIMUM_WORK_ITEM_SYNTHESIS_RESULTS;
14
+ export const MAXIMUM_SYNTHESIS_RESULTS = MAX_SYNTHESIS_SOURCE_TURNS;
15
+ export function createWorkItemExecutionAssignment(input) {
16
+ return validateWorkItemExecutionAssignment({
17
+ schemaVersion: WORK_ITEM_EXECUTION_ASSIGNMENT_SCHEMA_VERSION,
18
+ ...input
19
+ });
20
+ }
21
+ export function createReviewExecutionAssignment(input) {
22
+ return validateReviewExecutionAssignment({
23
+ schemaVersion: EXECUTION_ASSIGNMENT_SCHEMA_VERSION,
24
+ ...input
25
+ });
26
+ }
27
+ export function createExecutionGroup(id, taskId, assignment, laneInputs, now) {
28
+ if (laneInputs.length < MINIMUM_SYNTHESIS_RESULTS) {
29
+ throw new Error("A replicated ExecutionGroup requires at least two Lanes.");
30
+ }
31
+ if (laneInputs.length > MAXIMUM_SYNTHESIS_RESULTS) {
32
+ throw new Error(`A replicated ExecutionGroup supports at most ${MAXIMUM_SYNTHESIS_RESULTS} Lanes.`);
33
+ }
34
+ const groupId = requireIdentity(id, "ExecutionGroup id");
35
+ const normalizedTaskId = requireIdentity(taskId, "Task id");
36
+ const frozenAssignment = validateExecutionAssignment(assignment);
37
+ if (frozenAssignment.taskId !== normalizedTaskId) {
38
+ throw new Error("ExecutionGroup Assignment belongs to another Task.");
39
+ }
40
+ const timestamp = now.toISOString();
41
+ return validateExecutionGroup({
42
+ schemaVersion: EXECUTION_GROUP_SCHEMA_VERSION,
43
+ id: groupId,
44
+ taskId: normalizedTaskId,
45
+ assignment: frozenAssignment,
46
+ lanes: laneInputs.map((lane, index) => ({
47
+ schemaVersion: EXECUTION_LANE_SCHEMA_VERSION,
48
+ id: `${groupId}-lane-${index + 1}`,
49
+ groupId,
50
+ ordinal: index + 1,
51
+ roleName: lane.roleName,
52
+ ...(lane.effective === undefined ? {} : { effective: lane.effective }),
53
+ ...(lane.workspace === undefined ? {} : { workspace: lane.workspace }),
54
+ ...(lane.currentTurnId === undefined ? {} : { currentTurnId: lane.currentTurnId }),
55
+ disposition: "open",
56
+ createdAt: timestamp,
57
+ updatedAt: timestamp
58
+ })),
59
+ createdAt: timestamp,
60
+ updatedAt: timestamp
61
+ });
62
+ }
63
+ export function createWorkItemExecutionGroup(id, taskId, assignment, laneInputs, now) {
64
+ const group = createExecutionGroup(id, taskId, assignment, laneInputs, now);
65
+ return validateWorkItemExecutionGroup(group);
66
+ }
67
+ export function updateExecutionLane(group, laneId, patch, now) {
68
+ validateExecutionGroup(group);
69
+ const index = group.lanes.findIndex(({ id }) => id === laneId);
70
+ if (index < 0)
71
+ throw new Error(`ExecutionGroup Lane not found: ${laneId}.`);
72
+ const current = group.lanes[index];
73
+ if (current.disposition !== "open") {
74
+ throw new Error(`Terminal ExecutionLane is immutable: ${laneId}.`);
75
+ }
76
+ const disposition = patch.disposition ?? current.disposition;
77
+ const currentTurnId = patch.currentTurnId ?? current.currentTurnId;
78
+ const successfulTurnId = patch.successfulTurnId ?? current.successfulTurnId;
79
+ const effective = patch.effective ?? current.effective;
80
+ const workspace = patch.workspace ?? current.workspace;
81
+ if ((effective === undefined) !== (workspace === undefined)) {
82
+ throw new Error("ExecutionLane launch facts are incomplete.");
83
+ }
84
+ if (disposition === "succeeded") {
85
+ if (successfulTurnId === undefined || successfulTurnId !== currentTurnId) {
86
+ throw new Error("A succeeded Lane must identify its current successful Turn.");
87
+ }
88
+ }
89
+ else if (successfulTurnId !== undefined) {
90
+ throw new Error("Only a succeeded Lane may identify a successful Turn.");
91
+ }
92
+ const timestamp = now.toISOString();
93
+ const updated = {
94
+ ...current,
95
+ ...(effective === undefined ? {} : { effective }),
96
+ ...(workspace === undefined ? {} : { workspace }),
97
+ ...(currentTurnId === undefined ? {} : { currentTurnId }),
98
+ ...(successfulTurnId === undefined ? {} : { successfulTurnId }),
99
+ disposition,
100
+ updatedAt: timestamp,
101
+ ...(disposition === "open" ? {} : { endedAt: timestamp })
102
+ };
103
+ const candidate = validateExecutionGroup({
104
+ ...group,
105
+ lanes: group.lanes.map((lane, laneIndex) => laneIndex === index ? updated : lane),
106
+ updatedAt: timestamp
107
+ });
108
+ assertExecutionGroupTransition(group, candidate);
109
+ return candidate;
110
+ }
111
+ export function updateWorkItemExecutionLane(group, laneId, patch, now) {
112
+ return validateWorkItemExecutionGroup(updateExecutionLane(group, laneId, patch, now));
113
+ }
114
+ /**
115
+ * Reopen only failed Lanes for an explicit retry of the same semantic
116
+ * execution. Successful results remain settled, while each failed Lane
117
+ * returns to the same state produced by a naturally failed Turn: open and
118
+ * still pointing at the failed attempt that dispatch must replace.
119
+ *
120
+ * This is the sole terminal-to-open Lane transition. Normal Lane updates keep
121
+ * terminal records immutable.
122
+ */
123
+ export function retryFailedExecutionLanes(group, now) {
124
+ validateExecutionGroup(group);
125
+ if (!group.lanes.some(({ disposition }) => disposition === "failed"))
126
+ return group;
127
+ const timestamp = now.toISOString();
128
+ return validateExecutionGroup({
129
+ ...group,
130
+ lanes: group.lanes.map((lane) => {
131
+ if (lane.disposition !== "failed")
132
+ return lane;
133
+ const { endedAt: _endedAt, ...retryable } = lane;
134
+ return {
135
+ ...retryable,
136
+ disposition: "open",
137
+ updatedAt: timestamp
138
+ };
139
+ }),
140
+ updatedAt: timestamp
141
+ });
142
+ }
143
+ export function validateWorkItemExecutionAssignment(assignment) {
144
+ rejectUnknownFields(assignment, [
145
+ "schemaVersion",
146
+ "input",
147
+ "objective",
148
+ "acceptance",
149
+ "contextSnapshotRef",
150
+ "taskId",
151
+ "workItemId",
152
+ "workItemRevision",
153
+ "projects",
154
+ "dependencyFacts"
155
+ ], "WorkItem ExecutionAssignment");
156
+ if (assignment.schemaVersion !== WORK_ITEM_EXECUTION_ASSIGNMENT_SCHEMA_VERSION) {
157
+ throw new Error("WorkItem ExecutionAssignment schemaVersion is invalid.");
158
+ }
159
+ requireText(assignment.input, "ExecutionAssignment input");
160
+ requireText(assignment.objective, "ExecutionAssignment objective");
161
+ normalizedUniqueText(assignment.acceptance, "ExecutionAssignment acceptance criterion");
162
+ validateContextSnapshotRef(assignment.contextSnapshotRef);
163
+ requireIdentity(assignment.taskId, "ExecutionAssignment Task id");
164
+ requireIdentity(assignment.workItemId, "ExecutionAssignment WorkItem id");
165
+ requirePositiveInteger(assignment.workItemRevision, "ExecutionAssignment WorkItem revision");
166
+ if (!Array.isArray(assignment.projects)) {
167
+ throw new Error("ExecutionAssignment projects are invalid.");
168
+ }
169
+ const projectIds = assignment.projects.map((project) => {
170
+ rejectUnknownFields(project, [
171
+ "projectId",
172
+ "baseCommit"
173
+ ], "ExecutionAssignment Project base");
174
+ const { projectId, baseCommit } = project;
175
+ requireIdentity(projectId, "ExecutionAssignment Project id");
176
+ requireCommit(baseCommit, "ExecutionAssignment Project base commit");
177
+ return projectId;
178
+ });
179
+ normalizedUniqueIdentities(projectIds, "ExecutionAssignment Project");
180
+ if (!Array.isArray(assignment.dependencyFacts)) {
181
+ throw new Error("ExecutionAssignment dependency facts are invalid.");
182
+ }
183
+ const dependencyIds = assignment.dependencyFacts.map((dependency) => {
184
+ rejectUnknownFields(dependency, [
185
+ "workItemId",
186
+ "revision"
187
+ ], "ExecutionAssignment dependency fact");
188
+ const { workItemId, revision } = dependency;
189
+ requireIdentity(workItemId, "ExecutionAssignment dependency WorkItem id");
190
+ requirePositiveInteger(revision, "ExecutionAssignment dependency revision");
191
+ return workItemId;
192
+ });
193
+ normalizedUniqueIdentities(dependencyIds, "ExecutionAssignment dependency WorkItem");
194
+ return assignment;
195
+ }
196
+ export function validateReviewExecutionAssignment(assignment) {
197
+ rejectUnknownFields(assignment, [
198
+ "schemaVersion",
199
+ "input",
200
+ "objective",
201
+ "acceptance",
202
+ "contextSnapshotRef",
203
+ "taskId",
204
+ "reviewRoundId",
205
+ "reviewBaseCommit",
206
+ "scope",
207
+ "workItemId",
208
+ "candidateId",
209
+ "projects"
210
+ ], "Review ExecutionAssignment");
211
+ if (assignment.schemaVersion !== EXECUTION_ASSIGNMENT_SCHEMA_VERSION) {
212
+ throw new Error("Review ExecutionAssignment schemaVersion is invalid.");
213
+ }
214
+ requireText(assignment.input, "ExecutionAssignment input");
215
+ requireText(assignment.objective, "ExecutionAssignment objective");
216
+ normalizedUniqueText(assignment.acceptance, "ExecutionAssignment acceptance criterion");
217
+ validateContextSnapshotRef(assignment.contextSnapshotRef);
218
+ requireIdentity(assignment.taskId, "ExecutionAssignment Task id");
219
+ requireIdentity(assignment.reviewRoundId, "ExecutionAssignment ReviewRound id");
220
+ requireCommit(assignment.reviewBaseCommit, "ExecutionAssignment Review base commit");
221
+ if (assignment.scope !== "work-item" && assignment.scope !== "task") {
222
+ throw new Error("Review ExecutionAssignment scope is invalid.");
223
+ }
224
+ if (assignment.scope === "work-item") {
225
+ requireIdentity(assignment.workItemId ?? "", "ExecutionAssignment WorkItem id");
226
+ requireIdentity(assignment.candidateId ?? "", "ExecutionAssignment Candidate id");
227
+ }
228
+ else if (assignment.workItemId !== undefined || assignment.candidateId !== undefined) {
229
+ throw new Error("A Task Review ExecutionAssignment cannot identify a WorkItem Candidate.");
230
+ }
231
+ validateExecutionProjects(assignment.projects);
232
+ return assignment;
233
+ }
234
+ export function validateExecutionAssignment(assignment) {
235
+ return "reviewRoundId" in assignment
236
+ ? validateReviewExecutionAssignment(assignment)
237
+ : validateWorkItemExecutionAssignment(assignment);
238
+ }
239
+ export function validateExecutionGroup(group) {
240
+ rejectUnknownFields(group, [
241
+ "schemaVersion",
242
+ "id",
243
+ "taskId",
244
+ "assignment",
245
+ "lanes",
246
+ "createdAt",
247
+ "updatedAt"
248
+ ], "ExecutionGroup");
249
+ if (group.schemaVersion !== EXECUTION_GROUP_SCHEMA_VERSION) {
250
+ throw new Error("ExecutionGroup schemaVersion is invalid.");
251
+ }
252
+ requireIdentity(group.id, "ExecutionGroup id");
253
+ requireIdentity(group.taskId, "ExecutionGroup Task id");
254
+ const assignment = validateExecutionAssignment(group.assignment);
255
+ if (assignment.taskId !== group.taskId) {
256
+ throw new Error("ExecutionGroup Assignment Task does not match its Group.");
257
+ }
258
+ if (!Array.isArray(group.lanes) || group.lanes.length < MINIMUM_SYNTHESIS_RESULTS) {
259
+ throw new Error("A replicated ExecutionGroup requires at least two Lanes.");
260
+ }
261
+ if (group.lanes.length > MAXIMUM_SYNTHESIS_RESULTS) {
262
+ throw new Error(`A replicated ExecutionGroup supports at most ${MAXIMUM_SYNTHESIS_RESULTS} Lanes.`);
263
+ }
264
+ const ids = new Set();
265
+ const roles = new Set();
266
+ const assignmentProjectIds = assignment.projects.map(({ projectId }) => projectId).sort();
267
+ group.lanes.forEach((lane, index) => {
268
+ validateWorkItemExecutionLane(lane, group.id, index + 1);
269
+ if (lane.workspace !== undefined && (!isDeepStrictEqual([...lane.workspace.writableProjectIds].sort(), assignmentProjectIds) || !isDeepStrictEqual([...lane.effective.writeProjectIds].sort(), assignmentProjectIds))) {
270
+ throw new Error(`ExecutionLane Project scope differs from its Assignment: ${lane.id}.`);
271
+ }
272
+ if (ids.has(lane.id))
273
+ throw new Error(`ExecutionGroup Lane is duplicated: ${lane.id}.`);
274
+ if (roles.has(lane.roleName)) {
275
+ throw new Error(`ExecutionGroup Lane Role is duplicated: ${lane.roleName}.`);
276
+ }
277
+ ids.add(lane.id);
278
+ roles.add(lane.roleName);
279
+ });
280
+ requireTimestamp(group.createdAt, "ExecutionGroup createdAt");
281
+ requireTimestamp(group.updatedAt, "ExecutionGroup updatedAt");
282
+ if (Date.parse(group.updatedAt) < Date.parse(group.createdAt)) {
283
+ throw new Error("ExecutionGroup updatedAt precedes createdAt.");
284
+ }
285
+ return group;
286
+ }
287
+ export function validateWorkItemExecutionGroup(group) {
288
+ validateExecutionGroup(group);
289
+ validateWorkItemExecutionAssignment(group.assignment);
290
+ if (group.lanes.some(({ effective, workspace }) => (effective === undefined || workspace === undefined))) {
291
+ throw new Error("A WorkItem ExecutionGroup requires prepared Lane launch facts.");
292
+ }
293
+ return group;
294
+ }
295
+ export function assertWorkItemExecutionGroupTransition(existing, candidate) {
296
+ validateWorkItemExecutionGroup(existing);
297
+ validateWorkItemExecutionGroup(candidate);
298
+ assertExecutionGroupTransition(existing, candidate);
299
+ }
300
+ export function assertExecutionGroupTransition(existing, candidate) {
301
+ validateExecutionGroup(existing);
302
+ validateExecutionGroup(candidate);
303
+ if (isDeepStrictEqual(existing, candidate))
304
+ return;
305
+ if (existing.id !== candidate.id
306
+ || existing.taskId !== candidate.taskId
307
+ || existing.createdAt !== candidate.createdAt
308
+ || !isDeepStrictEqual(existing.assignment, candidate.assignment)
309
+ || existing.lanes.length !== candidate.lanes.length) {
310
+ throw new Error(`ExecutionGroup identity or Assignment changed: ${existing.id}.`);
311
+ }
312
+ if (Date.parse(candidate.updatedAt) < Date.parse(existing.updatedAt)) {
313
+ throw new Error(`ExecutionGroup time moved backwards: ${existing.id}.`);
314
+ }
315
+ existing.lanes.forEach((lane, index) => {
316
+ const next = candidate.lanes[index];
317
+ const launchFactsChanged = lane.effective === undefined
318
+ ? (next.effective === undefined) !== (next.workspace === undefined)
319
+ : !isDeepStrictEqual(lane.effective, next.effective)
320
+ || !isDeepStrictEqual(lane.workspace, next.workspace);
321
+ if (lane.id !== next.id
322
+ || lane.groupId !== next.groupId
323
+ || lane.ordinal !== next.ordinal
324
+ || lane.roleName !== next.roleName
325
+ || lane.createdAt !== next.createdAt
326
+ || launchFactsChanged) {
327
+ throw new Error(`ExecutionLane identity changed: ${lane.id}.`);
328
+ }
329
+ if (lane.disposition !== "open" && !isDeepStrictEqual(lane, next)) {
330
+ throw new Error(`Terminal ExecutionLane is immutable: ${lane.id}.`);
331
+ }
332
+ });
333
+ }
334
+ export function workItemExecutionGroupSettled(group) {
335
+ validateWorkItemExecutionGroup(group);
336
+ return executionGroupSettled(group);
337
+ }
338
+ export function executionGroupSettled(group) {
339
+ validateExecutionGroup(group);
340
+ return group.lanes.every(({ disposition }) => disposition !== "open");
341
+ }
342
+ export function summarizeExecutionGroup(group) {
343
+ validateExecutionGroup(group);
344
+ return {
345
+ groupId: group.id,
346
+ kind: "replicated",
347
+ laneCount: group.lanes.length,
348
+ openLaneCount: group.lanes.filter(({ disposition }) => disposition === "open").length,
349
+ succeededLaneCount: group.lanes.filter(({ disposition }) => disposition === "succeeded").length,
350
+ failedLaneCount: group.lanes.filter(({ disposition }) => disposition === "failed").length,
351
+ laneSummaries: group.lanes.map((lane) => ({
352
+ laneId: lane.id,
353
+ roleName: lane.roleName,
354
+ ordinal: lane.ordinal,
355
+ disposition: lane.disposition,
356
+ ...(lane.currentTurnId === undefined ? {} : { currentTurnId: lane.currentTurnId }),
357
+ ...(lane.successfulTurnId === undefined ? {} : { successfulTurnId: lane.successfulTurnId }),
358
+ ...(lane.effective === undefined ? {} : { effective: lane.effective })
359
+ }))
360
+ };
361
+ }
362
+ function validateExecutionProjects(projects) {
363
+ if (!Array.isArray(projects) || projects.length === 0) {
364
+ throw new Error("ExecutionAssignment projects are invalid.");
365
+ }
366
+ const projectIds = projects.map((project) => {
367
+ rejectUnknownFields(project, [
368
+ "projectId",
369
+ "baseCommit"
370
+ ], "ExecutionAssignment Project base");
371
+ const { projectId, baseCommit } = project;
372
+ requireIdentity(projectId, "ExecutionAssignment Project id");
373
+ requireCommit(baseCommit, "ExecutionAssignment Project base commit");
374
+ return projectId;
375
+ });
376
+ normalizedUniqueIdentities(projectIds, "ExecutionAssignment Project");
377
+ return projects;
378
+ }
379
+ function validateWorkItemExecutionLane(lane, groupId, ordinal) {
380
+ rejectUnknownFields(lane, [
381
+ "schemaVersion",
382
+ "id",
383
+ "groupId",
384
+ "ordinal",
385
+ "roleName",
386
+ "effective",
387
+ "workspace",
388
+ "currentTurnId",
389
+ "successfulTurnId",
390
+ "disposition",
391
+ "createdAt",
392
+ "updatedAt",
393
+ "endedAt"
394
+ ], "WorkItem ExecutionLane");
395
+ if (lane.schemaVersion !== WORK_ITEM_EXECUTION_LANE_SCHEMA_VERSION) {
396
+ throw new Error("WorkItem ExecutionLane schemaVersion is invalid.");
397
+ }
398
+ requireIdentity(lane.id, "ExecutionLane id");
399
+ if (lane.groupId !== groupId)
400
+ throw new Error("ExecutionLane Group does not match.");
401
+ if (lane.ordinal !== ordinal)
402
+ throw new Error("ExecutionLane ordinal is invalid.");
403
+ requireIdentity(lane.roleName, "ExecutionLane Role name");
404
+ if ((lane.effective === undefined) !== (lane.workspace === undefined)) {
405
+ throw new Error("ExecutionLane launch facts are incomplete.");
406
+ }
407
+ if (lane.effective !== undefined && lane.workspace !== undefined) {
408
+ validateEffectiveLaunchSnapshot(lane.effective);
409
+ rejectUnknownFields(lane.workspace, [
410
+ "root",
411
+ "writableProjectIds"
412
+ ], "ExecutionLane workspace");
413
+ requireText(lane.workspace.root, "ExecutionLane workspace root");
414
+ normalizedUniqueIdentities(lane.workspace.writableProjectIds, "ExecutionLane writable Project");
415
+ }
416
+ if (lane.currentTurnId !== undefined && lane.effective === undefined) {
417
+ throw new Error("A dispatched ExecutionLane requires launch facts.");
418
+ }
419
+ if (lane.currentTurnId !== undefined)
420
+ requireIdentity(lane.currentTurnId, "ExecutionLane current Turn id");
421
+ if (lane.successfulTurnId !== undefined)
422
+ requireIdentity(lane.successfulTurnId, "ExecutionLane successful Turn id");
423
+ if (!["open", "succeeded", "failed"].includes(lane.disposition)) {
424
+ throw new Error(`ExecutionLane disposition is invalid: ${String(lane.disposition)}.`);
425
+ }
426
+ if (lane.disposition === "succeeded") {
427
+ if (lane.currentTurnId === undefined || lane.successfulTurnId !== lane.currentTurnId) {
428
+ throw new Error("A succeeded Lane must identify its current successful Turn.");
429
+ }
430
+ }
431
+ else if (lane.successfulTurnId !== undefined) {
432
+ throw new Error("Only a succeeded Lane may identify a successful Turn.");
433
+ }
434
+ requireTimestamp(lane.createdAt, "ExecutionLane createdAt");
435
+ requireTimestamp(lane.updatedAt, "ExecutionLane updatedAt");
436
+ if (lane.disposition === "open") {
437
+ if (lane.endedAt !== undefined)
438
+ throw new Error("An open ExecutionLane cannot have endedAt.");
439
+ }
440
+ else {
441
+ requireTimestamp(lane.endedAt ?? "", "ExecutionLane endedAt");
442
+ }
443
+ }
444
+ function rejectUnknownFields(value, allowed, label) {
445
+ const unknown = Object.keys(value).filter((field) => !allowed.includes(field));
446
+ if (unknown.length > 0)
447
+ throw new Error(`${label} has unknown fields: ${unknown.join(", ")}.`);
448
+ }
449
+ function requireCommit(value, label) {
450
+ const normalized = requireText(value, label);
451
+ if (!/^[0-9a-f]{40}$/u.test(normalized))
452
+ throw new Error(`${label} is invalid.`);
453
+ return normalized;
454
+ }