@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
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { isReviewFindingBlocking } from "../review/reviewFinding.js";
|
|
3
|
-
import { deltaRecheckBlocksAcceptance } from "../review/reviewRound.js";
|
|
4
|
-
import { isSemanticReviewRound } from "../review/reviewOutcomeClassifier.js";
|
|
5
|
-
import { reviewFindingLedgerWriteFailedFromEvents } from "../review/reviewFindingLedger.js";
|
|
6
|
-
import { resolveRecordedTaskFinalReviewContract } from "../review/taskFinalReviewContractResolution.js";
|
|
7
|
-
import { sameTaskFinalReviewContract } from "../review/taskFinalReviewContract.js";
|
|
1
|
+
import { governingWorkItemDeliveries, integrationAttemptRequiresSettlement, workItemDeliverySettled } from "../integration/deliveryObligation.js";
|
|
8
2
|
const ACTIVE_JOB_STATUSES = new Set([
|
|
9
3
|
"queued",
|
|
10
4
|
"running",
|
|
@@ -17,13 +11,10 @@ const UNRESOLVED_INTEGRATION_STATUSES = new Set([
|
|
|
17
11
|
]);
|
|
18
12
|
const TERMINAL_REVIEW_STATUSES = new Set(["completed", "failed"]);
|
|
19
13
|
const TERMINAL_LANE_STATUSES = new Set(["completed", "failed", "skipped"]);
|
|
20
|
-
export function projectCompletionReadiness(facts
|
|
14
|
+
export function projectCompletionReadiness(facts) {
|
|
21
15
|
const blockers = [];
|
|
22
16
|
const advisories = [];
|
|
23
17
|
const { task } = facts;
|
|
24
|
-
const findingsGate = options.findingsGate ?? true;
|
|
25
|
-
const taskFinalReviewContract = resolveRecordedTaskFinalReviewContract(task.id, facts.workItems, facts.reviewRounds)?.effective;
|
|
26
|
-
const taskFinalReviewRequired = taskFinalReviewContract !== undefined;
|
|
27
18
|
// A pending/running Task-final Review must be resumed or blocked first.
|
|
28
19
|
for (const round of facts.reviewRounds) {
|
|
29
20
|
if ((round.scope ?? "work-item") !== "task")
|
|
@@ -39,36 +30,6 @@ export function projectCompletionReadiness(facts, options = {}) {
|
|
|
39
30
|
: `wait for Reviewer Turn on ${round.id} to finish`
|
|
40
31
|
});
|
|
41
32
|
}
|
|
42
|
-
// Issue 07: only the latest completed Task-final Review can define whether
|
|
43
|
-
// the current review lineage is accepted. Historical delta findings and
|
|
44
|
-
// escalations remain audit evidence, but a later completed full Review (or
|
|
45
|
-
// accepted delta) supersedes them instead of blocking completion forever.
|
|
46
|
-
const latestCompletedTaskReview = facts.reviewRounds
|
|
47
|
-
.filter((round) => ((round.scope ?? "work-item") === "task"
|
|
48
|
-
&& (taskFinalReviewContract === undefined || sameTaskFinalReviewContract(round.taskFinalReviewContract, taskFinalReviewContract))
|
|
49
|
-
&& isSemanticReviewRound(round, {
|
|
50
|
-
listTurns: () => facts.turns,
|
|
51
|
-
listReviewFindings: () => facts.reviewFindings,
|
|
52
|
-
listEvents: () => facts.events
|
|
53
|
-
})))
|
|
54
|
-
.slice()
|
|
55
|
-
.sort((left, right) => (left.createdAt.localeCompare(right.createdAt)
|
|
56
|
-
|| left.id.localeCompare(right.id, undefined, { numeric: true })))
|
|
57
|
-
.at(-1);
|
|
58
|
-
if (taskFinalReviewRequired
|
|
59
|
-
&& latestCompletedTaskReview !== undefined
|
|
60
|
-
&& deltaRecheckBlocksAcceptance(latestCompletedTaskReview)) {
|
|
61
|
-
const round = latestCompletedTaskReview;
|
|
62
|
-
const disposition = round.deltaRecheck.disposition;
|
|
63
|
-
blockers.push({
|
|
64
|
-
code: "delta-recheck-not-accepted",
|
|
65
|
-
ref: ref("review-round", round.id),
|
|
66
|
-
reason: `Delta-recheck ${round.id} disposition is ${disposition}; the head is not accepted.`,
|
|
67
|
-
fix: disposition === "requires-full-review"
|
|
68
|
-
? `yui task review request ${task.id} --role <global-role>`
|
|
69
|
-
: `repair the finding and request a new Task-final Review`
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
33
|
// Open Input requests block protocol convergence.
|
|
73
34
|
for (const request of facts.openInputRequests) {
|
|
74
35
|
blockers.push({
|
|
@@ -104,34 +65,16 @@ export function projectCompletionReadiness(facts, options = {}) {
|
|
|
104
65
|
fix: `wait for DurableJob ${job.id} to finish, or cancel it`
|
|
105
66
|
});
|
|
106
67
|
}
|
|
107
|
-
// Integration obligations follow
|
|
108
|
-
// each independent delivery unit.
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
for (const item of facts.workItems) {
|
|
112
|
-
if (item.status !== "completed")
|
|
113
|
-
continue;
|
|
114
|
-
const candidate = item.candidates?.at(-1);
|
|
115
|
-
const hasGitDelivery = candidate?.workspace !== undefined
|
|
116
|
-
|| candidate?.gitSnapshot !== undefined;
|
|
117
|
-
if (hasGitDelivery
|
|
118
|
-
&& !deliveryChangeSets.some((changeSet) => changeSet.workItemId === item.id)) {
|
|
119
|
-
blockers.push({
|
|
120
|
-
code: "integration-evidence-missing",
|
|
121
|
-
ref: ref("work-item", item.id),
|
|
122
|
-
reason: `Completed WorkItem ${item.id} has an uncaptured Git delivery.`,
|
|
123
|
-
fix: `yui task work capture ${task.id}/${item.id}`
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
for (const changeSet of deliveryChangeSets) {
|
|
128
|
-
if (changeSetDeliverySettled(changeSet, facts.integrations, facts.integrationQueueEntries))
|
|
68
|
+
// Integration obligations follow the exact start/result commits of the
|
|
69
|
+
// Candidate that currently governs each independent delivery unit.
|
|
70
|
+
for (const delivery of governingWorkItemDeliveries(facts.workItems)) {
|
|
71
|
+
if (workItemDeliverySettled(delivery, facts.integrations))
|
|
129
72
|
continue;
|
|
130
73
|
blockers.push({
|
|
131
74
|
code: "integration-evidence-missing",
|
|
132
|
-
ref: ref("
|
|
133
|
-
reason: `
|
|
134
|
-
fix: `yui task integration start ${task.id} --
|
|
75
|
+
ref: ref("work-item", delivery.workItemId),
|
|
76
|
+
reason: `WorkItem ${delivery.workItemId} result for Project ${delivery.projectId} is not part of a committed Integration.`,
|
|
77
|
+
fix: `yui task integration start ${task.id} --work-item ${delivery.workItemId} --project ${delivery.projectId} --strategy <ff|cherry-pick|merge|manual>`
|
|
135
78
|
});
|
|
136
79
|
}
|
|
137
80
|
// Current delivery Attempts and any Attempt that may still be writing must
|
|
@@ -139,7 +82,7 @@ export function projectCompletionReadiness(facts, options = {}) {
|
|
|
139
82
|
for (const integration of facts.integrations) {
|
|
140
83
|
if (!UNRESOLVED_INTEGRATION_STATUSES.has(integration.status))
|
|
141
84
|
continue;
|
|
142
|
-
if (!integrationAttemptRequiresSettlement(integration
|
|
85
|
+
if (!integrationAttemptRequiresSettlement(integration))
|
|
143
86
|
continue;
|
|
144
87
|
blockers.push({
|
|
145
88
|
code: "unresolved-integration",
|
|
@@ -148,8 +91,9 @@ export function projectCompletionReadiness(facts, options = {}) {
|
|
|
148
91
|
fix: `yui task integration continue ${task.id}/${integration.id}`
|
|
149
92
|
});
|
|
150
93
|
}
|
|
151
|
-
//
|
|
152
|
-
|
|
94
|
+
// Legacy queue entries may still launch an Integration and therefore must
|
|
95
|
+
// settle even though ChangeSets are no longer delivery authority.
|
|
96
|
+
for (const entry of facts.integrationQueueEntries) {
|
|
153
97
|
if (entry.status === "committed" || entry.status === "superseded")
|
|
154
98
|
continue;
|
|
155
99
|
blockers.push({
|
|
@@ -169,34 +113,6 @@ export function projectCompletionReadiness(facts, options = {}) {
|
|
|
169
113
|
if (disposition?.kind === "advisory")
|
|
170
114
|
advisories.push(disposition.value);
|
|
171
115
|
}
|
|
172
|
-
// Review finding ledger gate (enforce mode only).
|
|
173
|
-
// The transactional completion path runs this gate after final-review
|
|
174
|
-
// preparation: a pending/running Task-final Review is expected to resolve
|
|
175
|
-
// `fixed-pending-review` findings, so the gate only blocks when no Review
|
|
176
|
-
// is active. Mirror that timing here so the projection never blocks a
|
|
177
|
-
// completion attempt that is about to request the resolving Review.
|
|
178
|
-
const activeTaskReview = facts.reviewRounds.some((round) => ((round.scope ?? "work-item") === "task"
|
|
179
|
-
&& (round.status === "pending" || round.status === "running")));
|
|
180
|
-
if (findingsGate && facts.reviewFindingLedgerMode === "enforce" && !activeTaskReview) {
|
|
181
|
-
if (reviewFindingLedgerWriteFailedFromEvents(facts.events)) {
|
|
182
|
-
blockers.push({
|
|
183
|
-
code: "finding-ledger-unavailable",
|
|
184
|
-
ref: ref("task", task.id),
|
|
185
|
-
reason: "The Review finding ledger was unavailable while reconciling a semantic Review.",
|
|
186
|
-
fix: "recover the ledger and reconcile the Round before completing the Task"
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
for (const finding of facts.reviewFindings) {
|
|
190
|
-
if (!isReviewFindingBlocking(finding))
|
|
191
|
-
continue;
|
|
192
|
-
blockers.push({
|
|
193
|
-
code: "open-review-finding",
|
|
194
|
-
ref: ref("review-finding", finding.id),
|
|
195
|
-
reason: `Open ${finding.severity.toUpperCase()} finding ${finding.id} is undispositioned.`,
|
|
196
|
-
fix: `yui task review finding dispose ${task.id}/${finding.id}`
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
116
|
const sorted = [...blockers].sort((left, right) => {
|
|
201
117
|
const codeOrder = left.code.localeCompare(right.code);
|
|
202
118
|
if (codeOrder !== 0)
|
|
@@ -271,7 +187,10 @@ function workspaceCompletionDisposition(facts, taskId, workspace) {
|
|
|
271
187
|
}
|
|
272
188
|
case "execution-lane": {
|
|
273
189
|
const lane = findExecutionLane(facts, owner.executionGroupId, owner.executionLaneId);
|
|
274
|
-
|
|
190
|
+
const terminal = lane !== undefined && (lane.disposition !== undefined
|
|
191
|
+
? lane.disposition !== "open"
|
|
192
|
+
: lane.status !== undefined && TERMINAL_LANE_STATUSES.has(lane.status));
|
|
193
|
+
if (lane !== undefined && !terminal)
|
|
275
194
|
return null;
|
|
276
195
|
const value = {
|
|
277
196
|
code: "execution-lane-workspace-undisposed",
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { isSemanticReviewRound } from "../review/reviewOutcomeClassifier.js";
|
|
1
|
+
import { isCompletedTaskReviewEvidenceFromTurns } from "../review/reviewAcceptance.js";
|
|
3
2
|
const OPEN_WORK_ITEM_STATUSES = new Set(["pending", "running", "awaiting_acceptance"]);
|
|
4
3
|
export function detectDeliveryDuplicates(facts, intent) {
|
|
5
4
|
switch (intent.kind) {
|
|
@@ -84,29 +83,28 @@ function detectWorkItemDuplicates(facts, scope) {
|
|
|
84
83
|
return duplicates;
|
|
85
84
|
}
|
|
86
85
|
function detectIntegrationDuplicates(facts, intent) {
|
|
87
|
-
const wanted = new Set(intent.changeSetIds);
|
|
88
86
|
const duplicates = [];
|
|
89
87
|
for (const attempt of facts.integrations) {
|
|
90
88
|
if (attempt.projectId !== intent.projectId)
|
|
91
89
|
continue;
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
&&
|
|
90
|
+
const sameSource = attempt.source.kind === "work-item"
|
|
91
|
+
&& attempt.source.workItemId === intent.workItemId
|
|
92
|
+
&& attempt.source.resultCommit === intent.resultCommit;
|
|
95
93
|
const ref = { kind: "integration-attempt", id: attempt.id };
|
|
96
|
-
if (
|
|
94
|
+
if (sameSource && attempt.status === "committed") {
|
|
97
95
|
duplicates.push({
|
|
98
96
|
severity: "exact",
|
|
99
|
-
reason: `Integration ${attempt.id} already committed this exact
|
|
97
|
+
reason: `Integration ${attempt.id} already committed this exact WorkItem result`,
|
|
100
98
|
refs: [ref],
|
|
101
99
|
reuseCommand: `yui task integration show ${facts.task.id}/${attempt.id}`
|
|
102
100
|
});
|
|
103
101
|
continue;
|
|
104
102
|
}
|
|
105
|
-
if (
|
|
103
|
+
if (sameSource
|
|
106
104
|
&& (attempt.status === "running" || attempt.status === "validating")) {
|
|
107
105
|
duplicates.push({
|
|
108
106
|
severity: "suspected",
|
|
109
|
-
reason: `Integration ${attempt.id} is already ${attempt.status} for this
|
|
107
|
+
reason: `Integration ${attempt.id} is already ${attempt.status} for this WorkItem result`,
|
|
110
108
|
refs: [ref],
|
|
111
109
|
reuseCommand: `yui task integration show ${facts.task.id}/${attempt.id}`
|
|
112
110
|
});
|
|
@@ -129,7 +127,7 @@ function detectReviewDuplicates(facts, intent) {
|
|
|
129
127
|
if (!sameCandidate)
|
|
130
128
|
continue;
|
|
131
129
|
const ref = { kind: "review-round", id: round.id };
|
|
132
|
-
if (
|
|
130
|
+
if (isCompletedTaskReviewEvidenceFromTurns(round, facts.reviewOutcomeEvidence?.turns ?? [])) {
|
|
133
131
|
duplicates.push({
|
|
134
132
|
severity: "exact",
|
|
135
133
|
reason: `Task-final Review ${round.id} already attests this exact head`,
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
export function assertDraftTaskExecutionFree(store, task) {
|
|
2
|
+
if (task.status !== "draft") {
|
|
3
|
+
throw new Error(`Task is not a Draft: ${task.id}/${task.status}.`);
|
|
4
|
+
}
|
|
5
|
+
const executionFact = firstDraftExecutionFact(store, task);
|
|
6
|
+
if (executionFact !== undefined) {
|
|
7
|
+
throw new Error(`Draft Task ${task.id} has execution facts (${executionFact}); `
|
|
8
|
+
+ "retire it and create a clean Draft instead of editing runtime history.");
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export function validateDraftWorkItemEdit(store, task, candidate) {
|
|
12
|
+
assertDraftTaskExecutionFree(store, task);
|
|
13
|
+
const workItems = store.listWorkItems(task.id).map((item) => (item.id === candidate.id ? candidate : item));
|
|
14
|
+
const facts = draftPlanFacts(store, task, workItems);
|
|
15
|
+
validateWorkItemReferences(facts, candidate);
|
|
16
|
+
assertDraftWorkItemDependencyGraph(facts.workItems, candidate);
|
|
17
|
+
}
|
|
18
|
+
export function validateDraftTaskForActivation(store, task) {
|
|
19
|
+
assertDraftTaskExecutionFree(store, task);
|
|
20
|
+
const facts = draftPlanFacts(store, task, store.listWorkItems(task.id));
|
|
21
|
+
const current = facts.workItems.filter(({ status }) => status !== "retired");
|
|
22
|
+
for (const item of current)
|
|
23
|
+
validateWorkItemReferences(facts, item);
|
|
24
|
+
assertDraftWorkItemDependencyGraph(current);
|
|
25
|
+
}
|
|
26
|
+
/** Derive the first invalid edge in the current Draft dependency graph. */
|
|
27
|
+
export function draftWorkItemDependencyIssue(workItems, onlyItem) {
|
|
28
|
+
const current = workItems.filter(({ status }) => status !== "retired");
|
|
29
|
+
const byId = new Map(current.map((item) => [item.id, item]));
|
|
30
|
+
for (const item of onlyItem === undefined ? current : [onlyItem]) {
|
|
31
|
+
const dependencyId = item.dependsOn.find((id) => !byId.has(id));
|
|
32
|
+
if (dependencyId !== undefined) {
|
|
33
|
+
return { kind: "missing-or-retired", workItemId: item.id, dependencyId };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const visiting = new Set();
|
|
37
|
+
const visited = new Set();
|
|
38
|
+
const visit = (item) => {
|
|
39
|
+
visiting.add(item.id);
|
|
40
|
+
for (const dependencyId of item.dependsOn) {
|
|
41
|
+
if (visiting.has(dependencyId)) {
|
|
42
|
+
return { kind: "cycle", workItemId: item.id, dependencyId };
|
|
43
|
+
}
|
|
44
|
+
if (visited.has(dependencyId))
|
|
45
|
+
continue;
|
|
46
|
+
const dependency = byId.get(dependencyId);
|
|
47
|
+
if (dependency === undefined)
|
|
48
|
+
continue;
|
|
49
|
+
const issue = visit(dependency);
|
|
50
|
+
if (issue !== undefined)
|
|
51
|
+
return issue;
|
|
52
|
+
}
|
|
53
|
+
visiting.delete(item.id);
|
|
54
|
+
visited.add(item.id);
|
|
55
|
+
return undefined;
|
|
56
|
+
};
|
|
57
|
+
for (const item of current) {
|
|
58
|
+
if (visited.has(item.id))
|
|
59
|
+
continue;
|
|
60
|
+
const issue = visit(item);
|
|
61
|
+
if (issue !== undefined)
|
|
62
|
+
return issue;
|
|
63
|
+
}
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
function draftPlanFacts(store, task, workItems) {
|
|
67
|
+
const projects = new Map();
|
|
68
|
+
for (const binding of task.projectBindings) {
|
|
69
|
+
const project = store.getProject(binding.projectId);
|
|
70
|
+
if (project === null) {
|
|
71
|
+
throw new Error(`Task Project not found: ${binding.projectId}.`);
|
|
72
|
+
}
|
|
73
|
+
if (project.status !== "active") {
|
|
74
|
+
throw new Error(`Task Project is not active: ${project.id}/${project.status}.`);
|
|
75
|
+
}
|
|
76
|
+
projects.set(project.id, project);
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
task,
|
|
80
|
+
workItems,
|
|
81
|
+
roleNames: new Set(store.listRoles(task.id).map(({ name }) => name)),
|
|
82
|
+
projects
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function validateWorkItemReferences(facts, item) {
|
|
86
|
+
if (item.assignee !== undefined && !facts.roleNames.has(item.assignee)) {
|
|
87
|
+
throw new Error(`Work Item assignee Role not found: ${item.id}/${item.assignee}.`);
|
|
88
|
+
}
|
|
89
|
+
for (const projectId of item.writeProjectIds) {
|
|
90
|
+
if (!facts.projects.has(projectId)) {
|
|
91
|
+
throw new Error(`Work Item writable Project is not bound: ${item.id}/${projectId}.`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
for (const baseRef of item.baseRefs ?? []) {
|
|
95
|
+
if (!item.writeProjectIds.includes(baseRef.projectId)) {
|
|
96
|
+
throw new Error(`Work Item base-ref Project is not writable: ${item.id}/${baseRef.projectId}.`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function assertDraftWorkItemDependencyGraph(workItems, onlyItem) {
|
|
101
|
+
const issue = draftWorkItemDependencyIssue(workItems, onlyItem);
|
|
102
|
+
if (issue === undefined)
|
|
103
|
+
return;
|
|
104
|
+
if (issue.kind === "missing-or-retired") {
|
|
105
|
+
throw new Error(`Work Item dependency is missing or retired: ${issue.workItemId}/${issue.dependencyId}.`);
|
|
106
|
+
}
|
|
107
|
+
throw new Error(`Work Item dependency cycle detected: ${issue.workItemId}/${issue.dependencyId}.`);
|
|
108
|
+
}
|
|
109
|
+
function firstDraftExecutionFact(store, task) {
|
|
110
|
+
if (task.workspaceIdentity !== undefined || task.cwd !== undefined)
|
|
111
|
+
return "Task workspace";
|
|
112
|
+
const hostOwner = store.listSessionOwners().find(({ owner }) => (owner.scope === "task" && owner.taskId === task.id));
|
|
113
|
+
if (hostOwner !== undefined)
|
|
114
|
+
return `Host owner (${hostOwner.runtimeGenerationId})`;
|
|
115
|
+
if (store.listTurns(task.id).length > 0)
|
|
116
|
+
return "Turn";
|
|
117
|
+
if (store.listRoleSessionSets(task.id).length > 0)
|
|
118
|
+
return "Role Session";
|
|
119
|
+
if (store.listDurableJobs(task.id).length > 0)
|
|
120
|
+
return "DurableJob";
|
|
121
|
+
if (store.listManagedWorkspaces(task.id).length > 0)
|
|
122
|
+
return "managed Workspace";
|
|
123
|
+
if (store.listReviewRounds(task.id).length > 0)
|
|
124
|
+
return "ReviewRound";
|
|
125
|
+
if (store.listIntegrationAttempts(task.id).length > 0)
|
|
126
|
+
return "Integration Attempt";
|
|
127
|
+
if (store.listChangeSets(task.id).length > 0)
|
|
128
|
+
return "ChangeSet";
|
|
129
|
+
const itemWithExecution = store.listWorkItems(task.id).find((item) => (item.executionGroups.length > 0
|
|
130
|
+
|| item.currentExecutionGroupId !== undefined
|
|
131
|
+
|| item.candidates.length > 0
|
|
132
|
+
|| item.workspaceDisposition !== undefined
|
|
133
|
+
|| !["pending", "retired"].includes(item.status)));
|
|
134
|
+
if (itemWithExecution !== undefined) {
|
|
135
|
+
return `Work Item execution (${itemWithExecution.id})`;
|
|
136
|
+
}
|
|
137
|
+
const runtimeEvent = store.listEvents(task.id).find(({ type }) => (type.startsWith("runtime.")
|
|
138
|
+
|| type.startsWith("turn.")
|
|
139
|
+
|| type.startsWith("review.")
|
|
140
|
+
|| type.startsWith("integration.")));
|
|
141
|
+
return runtimeEvent === undefined ? undefined : `event ${runtimeEvent.id}/${runtimeEvent.type}`;
|
|
142
|
+
}
|