@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.
- package/ARCHITECTURE.md +64 -35
- package/README.md +258 -125
- package/dist/agent/argumentPolicy.js +2 -1
- package/dist/agent/managedRuntimeEnvironment.js +5 -8
- package/dist/cli/commandCatalog.js +74 -109
- package/dist/cli/helpRenderer.js +1 -2
- package/dist/cli/interactionCandidates.js +10 -0
- package/dist/cli/interactionPolicy.js +76 -37
- package/dist/cli/roleWizard.js +5 -6
- package/dist/cli/updateOrchestrator.js +10 -8
- package/dist/cli/updatePorts.js +17 -13
- package/dist/cli/upgradeCommand.js +2 -5
- package/dist/cli.js +268 -231
- package/dist/commands/agentCommands.js +13 -0
- package/dist/commands/configCommands.js +7 -19
- package/dist/commands/configOverview.js +3 -2
- package/dist/commands/durableJobCommands.js +1 -1
- package/dist/commands/executionAuditCommands.js +4 -9
- package/dist/commands/globalRoleCommands.js +17 -0
- package/dist/commands/profileCommands.js +200 -30
- package/dist/commands/sessionCommands.js +1 -1
- package/dist/commands/taskActor.js +29 -127
- package/dist/commands/taskChangeSetCommands.js +1 -3
- package/dist/commands/taskCommands.js +1771 -2107
- package/dist/commands/taskCompletionGate.js +27 -103
- package/dist/commands/taskContextCommand.js +47 -80
- package/dist/commands/taskExecutionCommands.js +9 -27
- package/dist/commands/taskInputCommands.js +10 -10
- package/dist/commands/taskIntegrationCommands.js +69 -61
- package/dist/commands/taskIntegrationQueueCommands.js +1 -1
- package/dist/commands/taskNextActionCommand.js +8 -29
- package/dist/commands/taskOverviewCommand.js +25 -3
- package/dist/commands/taskPublicationCommands.js +151 -85
- package/dist/commands/taskPublicationVerifyCommand.js +181 -0
- package/dist/commands/taskRemoteDeliveryCommand.js +134 -0
- package/dist/commands/taskRoleRuntimeStatus.js +5 -6
- package/dist/commands/taskUpstreamCommands.js +79 -95
- package/dist/config/configCatalog.js +1 -1
- package/dist/context/contextSnapshot.js +17 -2
- package/dist/context/sessionBootstrapManifest.js +18 -1
- package/dist/context/sourceTurnContext.js +30 -0
- package/dist/context/turnContextPack.js +107 -48
- package/dist/context/turnInputContract.js +11 -4
- package/dist/context/wakeNotification.js +84 -22
- package/dist/controller/agentRuntimeObserver.js +9 -12
- package/dist/controller/clientRuntime.js +13 -13
- package/dist/controller/controller.js +11 -11
- package/dist/controller/fileSchedulerStoreAdapter.js +262 -219
- package/dist/controller/jobControl.js +11 -10
- package/dist/controller/resourceInventory.js +6 -6
- package/dist/controller/resourceInventoryLinux.js +2 -2
- package/dist/controller/runtime.js +18 -61
- package/dist/controller/runtimeEventInbox.js +57 -34
- package/dist/controller/runtimeEventProcessor.js +36 -96
- package/dist/controller/runtimeHookTurnFence.js +33 -57
- package/dist/controller/runtimeLaunchCoordinator.js +44 -40
- package/dist/controller/runtimeObservationHook.js +18 -12
- package/dist/controller/sessionNotify.js +43 -28
- package/dist/controller/sessionOwnerReconciliation.js +21 -21
- package/dist/controller/structuredProviderObservation.js +13 -16
- package/dist/coordination/workMailboxQueue.js +132 -0
- package/dist/core/controllerServer.js +1 -0
- package/dist/domain/agentResultTransport.js +60 -0
- package/dist/execution/executionHealth.js +111 -118
- package/dist/execution/reviewMainTurn.js +161 -0
- package/dist/execution/workItemExecution.js +454 -0
- package/dist/execution/workItemExecutionProjection.js +309 -0
- package/dist/execution/workItemMainTurn.js +164 -0
- package/dist/executor/agentExecutor.js +19 -19
- package/dist/executor/executorRegistry.js +7 -28
- package/dist/executor/fileRoleLaunchPlanner.js +51 -83
- package/dist/external/githubPublicationVerifier.js +114 -0
- package/dist/external/gitlabPublicationVerifier.js +158 -0
- package/dist/external/pinnedCommandRunner.js +73 -0
- package/dist/integration/changeSetManifest.js +0 -3
- package/dist/integration/deliveryObligation.js +43 -6
- package/dist/integration/gitIntegrationService.js +284 -101
- package/dist/integration/integrationAttempt.js +64 -14
- package/dist/integration/integrationQueueEntry.js +5 -35
- package/dist/integration/integrationQueueService.js +65 -194
- package/dist/lifecycle/canonicalLifecycleEvent.js +16 -15
- package/dist/lifecycle/exactTurnTerminalization.js +100 -97
- package/dist/message/message.js +16 -0
- package/dist/observability/executionAudit.js +4 -25
- package/dist/observability/faultClassification.js +58 -70
- package/dist/observability/orchestrationMetrics.js +8 -50
- package/dist/profile/agentProfile.js +60 -15
- package/dist/profile/agentProfileRuntime.js +100 -0
- package/dist/release/releaseWorkflowPorts.js +5 -68
- package/dist/repository/executionLaneGitSnapshot.js +80 -0
- package/dist/repository/gitWorkspace.js +9 -67
- package/dist/repository/taskBaseFreshness.js +3 -1
- package/dist/repository/taskWorkspaceCoordinator.js +32 -4
- package/dist/repository/taskWorkspacePreparer.js +252 -411
- package/dist/resources/liveReferences.js +6 -55
- package/dist/resources/resourceDiscovery.js +1 -30
- package/dist/resources/resourceRegistrar.js +0 -23
- package/dist/review/deltaRecheck.js +8 -65
- package/dist/review/reviewAcceptance.js +22 -19
- package/dist/review/reviewConfig.js +0 -10
- package/dist/review/reviewDecision.js +2 -2
- package/dist/review/reviewRound.js +147 -273
- package/dist/review/reviewerAvailability.js +13 -23
- package/dist/role/role.js +3 -0
- package/dist/runtime/agentDriverObservation.js +1 -1
- package/dist/runtime/agentHost.js +51 -51
- package/dist/runtime/builtinAgentDrivers.js +9 -2
- package/dist/runtime/codexAppServerRuntime.js +23 -5
- package/dist/runtime/exactControlPlane.js +2 -267
- package/dist/runtime/firstProgressAdvisory.js +1 -1
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/launchBroker.js +11 -11
- package/dist/runtime/lifecycleReservation.js +2 -2
- package/dist/runtime/managedCaller.js +111 -0
- package/dist/runtime/ports.js +16 -3
- package/dist/runtime/preallocatedNativeSession.js +2 -2
- package/dist/runtime/processExitObservation.js +3 -2
- package/dist/runtime/providerContinuationReconciliationService.js +1 -1
- package/dist/runtime/runtimeBinding.js +1 -1
- package/dist/runtime/runtimeObservation.js +33 -16
- package/dist/runtime/runtimeSessionCandidate.js +2 -2
- package/dist/runtime/runtimeStopReceipt.js +11 -11
- package/dist/runtime/sessionLaunchRequest.js +2 -2
- package/dist/runtime/sessionOwnerIdentity.js +9 -9
- package/dist/runtime/sessionOwnerRegistry.js +13 -13
- package/dist/runtime/sessionReconciliation.js +4 -4
- package/dist/runtime/sessionTerminationGuard.js +9 -9
- package/dist/runtime/sessionTokenMetrics.js +5 -7
- package/dist/runtime/structuredProviderHost.js +5 -5
- package/dist/runtime/taskRuntimeIsolation.js +24 -24
- package/dist/runtime/tmuxAdapters.js +32 -30
- package/dist/scheduler/activeRoleTurnDelivery.js +37 -15
- package/dist/scheduler/leaderWakeupProcessor.js +78 -55
- package/dist/scheduler/roleTurnLiveness.js +6 -6
- package/dist/scheduler/roleTurnStall.js +8 -9
- package/dist/scheduler/taskExecutionProjection.js +19 -41
- package/dist/scheduler/taskObservabilityProjection.js +75 -122
- package/dist/storage/sqliteSchema.js +1 -21
- package/dist/storage/sqliteStore.js +62 -47
- package/dist/storage/storageSchema.js +1 -8
- package/dist/storage/storageVersions.js +7 -4
- package/dist/storage/taskStore.js +8 -8
- package/dist/storage/upgrade/upgradeOrchestrator.js +112 -83
- package/dist/task/completionReadiness.js +17 -98
- package/dist/task/deliveryGuard.js +9 -11
- package/dist/task/draftPlan.js +142 -0
- package/dist/task/nextAction.js +179 -477
- package/dist/task/publicationVerification.js +1 -0
- package/dist/task/remoteDelivery.js +233 -0
- package/dist/task/task.js +108 -4
- package/dist/task/taskRecordReference.js +0 -1
- package/dist/turn/turn.js +109 -16
- package/dist/verification/verificationGateService.js +0 -26
- package/dist/web/assets/client/components.js +99 -121
- package/dist/web/assets/client/i18n.js +136 -58
- package/dist/web/assets/client/view.js +44 -0
- package/dist/web/assets/styles/cards.js +3 -15
- package/dist/web/assets/styles/widgets.js +4 -0
- package/dist/web/webSnapshot.js +29 -14
- package/dist/workItem/dependencyGate.js +34 -0
- package/dist/workItem/workItem.js +99 -266
- package/dist/workspace/workItemChangeSetManager.js +15 -59
- package/docs/task-local-identity.md +1 -1
- package/i18n/README.zh-CN.md +165 -59
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +272 -782
- package/skills/yui-operator/SKILL.md +190 -365
- package/skills/yui-reviewer/SKILL.md +40 -33
- package/skills/yui-runtime/SKILL.md +27 -6
- package/skills/yui-worker/SKILL.md +19 -6
- package/dist/execution/candidateConvergence.js +0 -623
- package/dist/execution/executionGroup.js +0 -854
- package/dist/execution/resourceBroker.js +0 -426
- package/dist/executor/launchPlan.js +0 -16
- package/dist/review/reviewFinding.js +0 -240
- package/dist/review/reviewFindingLedger.js +0 -550
- package/dist/review/reviewOutcomeClassifier.js +0 -255
- package/dist/scheduler/resourceQueueProjection.js +0 -71
- 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
|
+
}
|