@zq-silk/yui 0.12.4 → 0.13.1
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/dist/cli/updatePorts.js +18 -2
- package/dist/cli.js +3 -6
- package/dist/commands/durableJobCommands.js +5 -17
- package/dist/commands/executionAuditCommands.js +1 -1
- package/dist/commands/taskActor.js +17 -61
- package/dist/commands/taskCommands.js +44 -166
- package/dist/commands/taskIntegrationCommands.js +7 -9
- package/dist/commands/taskIntegrationQueueCommands.js +9 -11
- package/dist/commands/taskPublicationCommands.js +0 -4
- package/dist/commands/taskWorkspaceCommands.js +1 -1
- package/dist/controller/jobClient.js +5 -8
- package/dist/controller/jobControl.js +59 -105
- package/dist/integration/gitIntegrationService.js +3 -3
- package/dist/integration/integrationAttempt.js +13 -9
- package/dist/integration/integrationQueueService.js +7 -7
- package/dist/milestone/milestone.js +9 -3
- package/dist/review/deltaRecheck.js +1 -1
- package/dist/review/reviewFinding.js +3 -3
- package/dist/review/reviewRound.js +11 -8
- package/dist/storage/migration/productionRegistry.js +9 -0
- package/dist/storage/taskStore.js +9 -8
- package/dist/storage/upgrade/upgradeOrchestrator.js +2 -2
- package/dist/storage/upgradeCoordination.js +6 -5
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +4 -0
- package/skills/yui-operator/SKILL.md +19 -13
|
@@ -30,7 +30,7 @@ import { buildDeltaRecheckDispatchContext, verifyDeltaRecheckDiff } from "../rev
|
|
|
30
30
|
import { isAcceptedTaskReviewBaseline } from "../review/reviewAcceptance.js";
|
|
31
31
|
import { projectReviewerAvailability } from "../review/reviewerAvailability.js";
|
|
32
32
|
import { buildTaskFinalReviewFindingContext, dispositionReviewFinding, planRepairGroups, reconcileReviewFindings, reconcileReviewFindingsAfterReview } from "../review/reviewFindingLedger.js";
|
|
33
|
-
import {
|
|
33
|
+
import { TASK_CONTROL_FINDING_DISPOSITIONS } from "../review/reviewFinding.js";
|
|
34
34
|
import { createTaskBrief, updateTaskBrief } from "../brief/taskBrief.js";
|
|
35
35
|
import { createDecision, supersedeDecision } from "../decision/decision.js";
|
|
36
36
|
import { createMilestone } from "../milestone/milestone.js";
|
|
@@ -42,7 +42,6 @@ import { blockingProviderContinuations, projectProviderContinuations, runOwnsBlo
|
|
|
42
42
|
import { runtimeObservationFromTaskEvent } from "../runtime/runtimeObservation.js";
|
|
43
43
|
import { addTaskProjectBinding, archiveTask, completeTask, createTask, retireTask, reopenTask, updateTaskMetadata } from "../task/task.js";
|
|
44
44
|
import { resolveTaskRecordReference } from "../task/taskRecordReference.js";
|
|
45
|
-
import { TASK_COMPLETION_PUBLISHED_TREE_AUTHORIZED_EVENT } from "../task/publicationReference.js";
|
|
46
45
|
import { projectCompletionReadiness } from "../task/completionReadiness.js";
|
|
47
46
|
import { assertProjectActive, resolveProject } from "../repository/project.js";
|
|
48
47
|
import { acquireProjectMaintenanceLocks } from "../repository/projectMaintenanceLock.js";
|
|
@@ -203,9 +202,6 @@ export function preflightTaskCompletion(taskId, store, options = {}, request = {
|
|
|
203
202
|
.filter((entry) => entry.run !== null);
|
|
204
203
|
const leaderEntry = activeRuns.find(({ role }) => role.name === LEADER_ROLE);
|
|
205
204
|
if (leaderEntry !== undefined) {
|
|
206
|
-
if (actor !== "leader") {
|
|
207
|
-
throw usageError(`Task ${task.id} has an active Leader run.`);
|
|
208
|
-
}
|
|
209
205
|
if (leaderEntry.run.workItemId !== undefined) {
|
|
210
206
|
throw usageError(`Task ${task.id} has running work: ${leaderEntry.run.workItemId}.`);
|
|
211
207
|
}
|
|
@@ -381,9 +377,7 @@ function taskProjectCommand(args, store, options) {
|
|
|
381
377
|
const updated = store.transaction((tx) => {
|
|
382
378
|
const task = requireTask(tx, parsed.positionals[0]);
|
|
383
379
|
assertTaskOpen(task);
|
|
384
|
-
|
|
385
|
-
throw usageError("Only the Task Leader may add a Project to an active Task.");
|
|
386
|
-
}
|
|
380
|
+
taskActor(tx, options, task.id);
|
|
387
381
|
const project = resolveProject(tx.listProjects(), parsed.positionals[1]);
|
|
388
382
|
if (project === null)
|
|
389
383
|
throw usageError(`Project not found: ${parsed.positionals[1]}.`);
|
|
@@ -723,8 +717,7 @@ function completeTaskCommand(args, store, options) {
|
|
|
723
717
|
changed: false,
|
|
724
718
|
runtimeCleanupTargets: [],
|
|
725
719
|
completionAdvisories: [],
|
|
726
|
-
finalReview: undefined
|
|
727
|
-
publishedTreeAuthorization: undefined
|
|
720
|
+
finalReview: undefined
|
|
728
721
|
};
|
|
729
722
|
}
|
|
730
723
|
const taskFinalContract = preflight.taskFinalReviewContract;
|
|
@@ -734,28 +727,6 @@ function completeTaskCommand(args, store, options) {
|
|
|
734
727
|
const publishedTreeProof = request.acceptedPublishedTreePublicationId === undefined
|
|
735
728
|
? undefined
|
|
736
729
|
: assertTaskCompletionPublishedTreeProof(tx, task, request.acceptedPublishedTreePublicationId, options.completionPublishedTreeProof, actualTaskCandidate);
|
|
737
|
-
const requiresContractHandoff = publishedTreeProof !== undefined
|
|
738
|
-
&& taskFinalContract !== undefined;
|
|
739
|
-
if (requiresContractHandoff && actor !== "leader") {
|
|
740
|
-
const existing = matchingPublishedTreeAuthorization(tx, publishedTreeProof);
|
|
741
|
-
const event = existing ?? recordTaskEventRecord(tx, task.id, TASK_COMPLETION_PUBLISHED_TREE_AUTHORIZED_EVENT, publishedTreeAuthorizationPayload(actor, publishedTreeProof), now);
|
|
742
|
-
enqueueWork(tx, leaderMailbox(task.id), wakeReason("published-tree-authorized", event.id), now, [eventRef(task.id, event.id)]);
|
|
743
|
-
return {
|
|
744
|
-
task,
|
|
745
|
-
changed: false,
|
|
746
|
-
runtimeCleanupTargets: [],
|
|
747
|
-
completionAdvisories: [],
|
|
748
|
-
finalReview: undefined,
|
|
749
|
-
publishedTreeAuthorization: {
|
|
750
|
-
event,
|
|
751
|
-
proof: publishedTreeProof,
|
|
752
|
-
created: existing === undefined
|
|
753
|
-
}
|
|
754
|
-
};
|
|
755
|
-
}
|
|
756
|
-
if (publishedTreeProof !== undefined && actor === "leader") {
|
|
757
|
-
requirePublishedTreeAuthorization(tx, publishedTreeProof);
|
|
758
|
-
}
|
|
759
730
|
const roles = tx.listRoles(task.id);
|
|
760
731
|
const activeRuns = roles
|
|
761
732
|
.map((role) => ({ role, run: tx.getActiveAgentRun(task.id, role.name) }))
|
|
@@ -766,9 +737,6 @@ function completeTaskCommand(args, store, options) {
|
|
|
766
737
|
// the post-Review readiness fence below.
|
|
767
738
|
const leaderEntry = activeRuns.find(({ role }) => role.name === LEADER_ROLE);
|
|
768
739
|
if (leaderEntry !== undefined) {
|
|
769
|
-
if (actor !== "leader") {
|
|
770
|
-
throw usageError(`Task ${task.id} has an active Leader run.`);
|
|
771
|
-
}
|
|
772
740
|
if (leaderEntry.run.workItemId !== undefined) {
|
|
773
741
|
throw usageError(`Task ${task.id} has running work: ${leaderEntry.run.workItemId}.`);
|
|
774
742
|
}
|
|
@@ -799,7 +767,6 @@ function completeTaskCommand(args, store, options) {
|
|
|
799
767
|
runtimeCleanupTargets: [],
|
|
800
768
|
completionAdvisories: [],
|
|
801
769
|
finalReview,
|
|
802
|
-
publishedTreeAuthorization: undefined
|
|
803
770
|
};
|
|
804
771
|
}
|
|
805
772
|
// Issue 06: re-validate the full completion readiness inside the
|
|
@@ -863,21 +830,9 @@ function completeTaskCommand(args, store, options) {
|
|
|
863
830
|
changed: true,
|
|
864
831
|
runtimeCleanupTargets,
|
|
865
832
|
completionAdvisories: readiness.advisories,
|
|
866
|
-
finalReview: undefined
|
|
867
|
-
publishedTreeAuthorization: undefined
|
|
833
|
+
finalReview: undefined
|
|
868
834
|
};
|
|
869
835
|
});
|
|
870
|
-
if (result.publishedTreeAuthorization !== undefined) {
|
|
871
|
-
notifyMailbox(options.runtime, leaderMailbox(result.task.id), result.task.id);
|
|
872
|
-
const authorization = result.publishedTreeAuthorization;
|
|
873
|
-
return output(authorization.created
|
|
874
|
-
? `Authorized published-tree completion for ${result.task.id} as ${authorization.event.id}; exact Task Leader completion is required.\n`
|
|
875
|
-
: `Published-tree completion is already authorized for ${result.task.id} as ${authorization.event.id}; exact Task Leader completion is required.\n`, {
|
|
876
|
-
task: result.task,
|
|
877
|
-
authorizationEvent: authorization.event,
|
|
878
|
-
publishedTreeProof: authorization.proof
|
|
879
|
-
});
|
|
880
|
-
}
|
|
881
836
|
if (result.changed) {
|
|
882
837
|
for (const target of result.runtimeCleanupTargets) {
|
|
883
838
|
// Cleanup owns an independent lifecycle lane. Never fall back to the
|
|
@@ -1147,10 +1102,7 @@ export function parseTaskArchiveArguments(args) {
|
|
|
1147
1102
|
export function validateTaskArchiveRequest(args, store, options = {}) {
|
|
1148
1103
|
const request = parseTaskArchiveArguments(args);
|
|
1149
1104
|
const task = requireTask(store, request.taskId);
|
|
1150
|
-
|
|
1151
|
-
if (actor === "leader") {
|
|
1152
|
-
throw usageError("Only the global Operator may archive a Task from a managed Session.");
|
|
1153
|
-
}
|
|
1105
|
+
taskActor(store, options, task.id);
|
|
1154
1106
|
if (task.status !== "archived"
|
|
1155
1107
|
&& task.status !== "completed"
|
|
1156
1108
|
&& task.status !== "retired") {
|
|
@@ -1954,9 +1906,7 @@ function updateWorkScope(args, store, options) {
|
|
|
1954
1906
|
const updated = store.transaction((tx) => {
|
|
1955
1907
|
const item = requireWorkItem(tx, parsed.positionals[0], options);
|
|
1956
1908
|
const task = requireTask(tx, item.taskId);
|
|
1957
|
-
|
|
1958
|
-
throw usageError("Only the Task Leader may change a Work Item Project scope.");
|
|
1959
|
-
}
|
|
1909
|
+
taskActor(tx, options, task.id);
|
|
1960
1910
|
if (tx.getActiveAgentRun(task.id, item.assignee ?? "") !== null) {
|
|
1961
1911
|
throw usageError(`Stop the active Work Item Run before changing scope: ${item.id}.`);
|
|
1962
1912
|
}
|
|
@@ -2004,9 +1954,7 @@ function updateWork(args, store, options) {
|
|
|
2004
1954
|
const task = requireTask(tx, current.taskId);
|
|
2005
1955
|
assertTaskOpen(task);
|
|
2006
1956
|
if (current.assignee === undefined) {
|
|
2007
|
-
|
|
2008
|
-
throw usageError(`Only the Task Leader may update unassigned Work Item execution: ${current.id}.`);
|
|
2009
|
-
}
|
|
1957
|
+
taskActor(tx, options, task.id);
|
|
2010
1958
|
if (status === "running") {
|
|
2011
1959
|
assertWorkItemDependenciesCompleted(tx, current);
|
|
2012
1960
|
}
|
|
@@ -2199,9 +2147,8 @@ function dispatchWork(args, store, options) {
|
|
|
2199
2147
|
+ `The Task Leader must run "yui task work update ${item.id} running", `
|
|
2200
2148
|
+ "then execute it directly or create native subagents in the Leader Session.");
|
|
2201
2149
|
}
|
|
2202
|
-
if (expanding
|
|
2203
|
-
|
|
2204
|
-
}
|
|
2150
|
+
if (expanding)
|
|
2151
|
+
taskActor(tx, options, task.id);
|
|
2205
2152
|
assertWorkItemDependenciesCompleted(tx, item);
|
|
2206
2153
|
const workspace = tx.getWorkItemWorkspace(task.id, item.id);
|
|
2207
2154
|
if (workspace?.owner.type === "work-item"
|
|
@@ -2581,9 +2528,7 @@ function resolveWorkExecutionGroup(args, store, options) {
|
|
|
2581
2528
|
const task = requireTask(tx, item.taskId);
|
|
2582
2529
|
if (task.status !== "active")
|
|
2583
2530
|
throw usageError(inactiveTaskMessage(task, "resolving an ExecutionGroup"));
|
|
2584
|
-
|
|
2585
|
-
throw usageError("Only the Task Leader may resolve an ExecutionGroup.");
|
|
2586
|
-
}
|
|
2531
|
+
taskActor(tx, options, task.id);
|
|
2587
2532
|
let group = currentWorkItemExecutionGroup(item);
|
|
2588
2533
|
if (group === undefined
|
|
2589
2534
|
|| (group.stage === undefined
|
|
@@ -2815,9 +2760,7 @@ function acceptWork(args, store, options) {
|
|
|
2815
2760
|
if (task.status !== "active") {
|
|
2816
2761
|
throw usageError(`Task is not active: ${task.id}/${task.status}.`);
|
|
2817
2762
|
}
|
|
2818
|
-
|
|
2819
|
-
throw usageError("Only the Task Leader may accept a Work Item.");
|
|
2820
|
-
}
|
|
2763
|
+
const actor = taskActor(tx, options, task.id);
|
|
2821
2764
|
if (item.status !== "awaiting_acceptance") {
|
|
2822
2765
|
throw usageError(`Work Item is not awaiting acceptance: ${item.id}/${item.status}.`);
|
|
2823
2766
|
}
|
|
@@ -2876,9 +2819,9 @@ function acceptWork(args, store, options) {
|
|
|
2876
2819
|
...(candidate.source.type === "run"
|
|
2877
2820
|
? { runId: candidate.source.runId }
|
|
2878
2821
|
: { workItemRevision: String(candidate.workItemRevision) }),
|
|
2879
|
-
acceptedBy:
|
|
2822
|
+
acceptedBy: actor,
|
|
2880
2823
|
summary,
|
|
2881
|
-
...leaderActionEventPayload(tx, item.taskId, options)
|
|
2824
|
+
...(actor === "leader" ? leaderActionEventPayload(tx, item.taskId, options) : {})
|
|
2882
2825
|
}, now);
|
|
2883
2826
|
return completed;
|
|
2884
2827
|
});
|
|
@@ -2938,9 +2881,7 @@ function rejectWork(args, store, options) {
|
|
|
2938
2881
|
if (task.status !== "active") {
|
|
2939
2882
|
throw usageError(`Task is not active: ${task.id}/${task.status}.`);
|
|
2940
2883
|
}
|
|
2941
|
-
|
|
2942
|
-
throw usageError("Only the Task Leader may reject a Work Item.");
|
|
2943
|
-
}
|
|
2884
|
+
const actor = taskActor(tx, options, task.id);
|
|
2944
2885
|
if (item.status !== "awaiting_acceptance") {
|
|
2945
2886
|
throw usageError(`Work Item is not awaiting acceptance: ${item.id}/${item.status}.`);
|
|
2946
2887
|
}
|
|
@@ -2954,7 +2895,7 @@ function rejectWork(args, store, options) {
|
|
|
2954
2895
|
recordTaskEvent(tx, item.taskId, "work.rejected", {
|
|
2955
2896
|
workItemId: item.id,
|
|
2956
2897
|
candidateId: candidate.id,
|
|
2957
|
-
rejectedBy:
|
|
2898
|
+
rejectedBy: actor,
|
|
2958
2899
|
summary
|
|
2959
2900
|
}, now);
|
|
2960
2901
|
return failed;
|
|
@@ -3097,9 +3038,7 @@ function reviewWork(args, store, options) {
|
|
|
3097
3038
|
if (task.status !== "active") {
|
|
3098
3039
|
throw usageError(`Task is not active: ${task.id}/${task.status}.`);
|
|
3099
3040
|
}
|
|
3100
|
-
|
|
3101
|
-
throw usageError("Only the Task Leader may request a Work Item review.");
|
|
3102
|
-
}
|
|
3041
|
+
const requestedBy = taskActor(tx, options, task.id);
|
|
3103
3042
|
if (item.status !== "awaiting_acceptance") {
|
|
3104
3043
|
throw usageError(`Work Item is not awaiting acceptance: ${item.id}/${item.status}.`);
|
|
3105
3044
|
}
|
|
@@ -3126,7 +3065,7 @@ function reviewWork(args, store, options) {
|
|
|
3126
3065
|
}
|
|
3127
3066
|
throw usageError(`ReviewRound is already active: ${activeRound.id}/${activeRound.status}.`);
|
|
3128
3067
|
}
|
|
3129
|
-
const queued = queueReviewRound(tx, item, config,
|
|
3068
|
+
const queued = queueReviewRound(tx, item, config, requestedBy, now);
|
|
3130
3069
|
return { ...queued, resumed: false };
|
|
3131
3070
|
});
|
|
3132
3071
|
if (result.run !== null) {
|
|
@@ -3140,7 +3079,7 @@ function reviewWork(args, store, options) {
|
|
|
3140
3079
|
: output(`Review requested as ${result.round.id}\n`, { reviewRound: result.round });
|
|
3141
3080
|
}
|
|
3142
3081
|
/**
|
|
3143
|
-
*
|
|
3082
|
+
* Task-control recovery for a failed Task-final ReviewRound that never
|
|
3144
3083
|
* created a Reviewer Run. This is deliberately separate from `task run retry`:
|
|
3145
3084
|
* that command requires an exact failed Agent Run and remains the only retry
|
|
3146
3085
|
* path for a failed provider execution. Here the old terminal Round is an
|
|
@@ -3290,9 +3229,7 @@ function resolveReviewExecutionGroup(args, store, options) {
|
|
|
3290
3229
|
const task = requireTask(tx, round.taskId);
|
|
3291
3230
|
if (task.status !== "active")
|
|
3292
3231
|
throw usageError(inactiveTaskMessage(task, "resolving a Review ExecutionGroup"));
|
|
3293
|
-
|
|
3294
|
-
throw usageError("Only the Task Leader may resolve a Reviewer ExecutionGroup.");
|
|
3295
|
-
}
|
|
3232
|
+
taskActor(tx, options, task.id);
|
|
3296
3233
|
const group = round.executionGroup;
|
|
3297
3234
|
if (group === undefined
|
|
3298
3235
|
|| (group.lanes.length < 2 && group.strategy.mode !== "adaptive")) {
|
|
@@ -3411,7 +3348,7 @@ function disposeReviewFindingCommand(args, store, options) {
|
|
|
3411
3348
|
const parsed = parseTail(args, new Set(["--disposition", "--work-item", "--commit", "--verification", "--note", "--superseded-by"]), usage);
|
|
3412
3349
|
exactPositionals(parsed.positionals, 1, usage);
|
|
3413
3350
|
const disposition = requiredOption(parsed.options, "--disposition");
|
|
3414
|
-
if (!
|
|
3351
|
+
if (!TASK_CONTROL_FINDING_DISPOSITIONS.includes(disposition)) {
|
|
3415
3352
|
throw usageError(`Review finding disposition is invalid: ${disposition}.`);
|
|
3416
3353
|
}
|
|
3417
3354
|
const now = clock(options);
|
|
@@ -3423,12 +3360,12 @@ function disposeReviewFindingCommand(args, store, options) {
|
|
|
3423
3360
|
const task = requireTask(tx, reference.taskId);
|
|
3424
3361
|
if (task.status !== "active")
|
|
3425
3362
|
throw usageError(inactiveTaskMessage(task, "dispositioning a review finding"));
|
|
3426
|
-
|
|
3427
|
-
throw usageError("Only the Task Leader may disposition a review finding.");
|
|
3428
|
-
}
|
|
3363
|
+
const actor = taskActor(tx, options, task.id);
|
|
3429
3364
|
const command = {
|
|
3430
3365
|
disposition,
|
|
3431
|
-
by:
|
|
3366
|
+
by: actor === "leader"
|
|
3367
|
+
? taskLeaderActionRunId(tx, task.id, options.environment, options.yuiHome) ?? actor
|
|
3368
|
+
: actor,
|
|
3432
3369
|
...(parsed.options.get("--note") === undefined ? {} : { note: parsed.options.get("--note") }),
|
|
3433
3370
|
...(parsed.options.get("--work-item") === undefined ? {} : { workItemId: parsed.options.get("--work-item") }),
|
|
3434
3371
|
...(parsed.options.get("--commit") === undefined ? {} : { commit: parsed.options.get("--commit") }),
|
|
@@ -3460,9 +3397,7 @@ function planReviewRepairWave(args, store, options) {
|
|
|
3460
3397
|
if (currentTask.status !== "active") {
|
|
3461
3398
|
throw usageError(inactiveTaskMessage(currentTask, "creating a review repair wave"));
|
|
3462
3399
|
}
|
|
3463
|
-
|
|
3464
|
-
throw usageError("Only the Task Leader may create a review repair wave.");
|
|
3465
|
-
}
|
|
3400
|
+
taskActor(tx, options, currentTask.id);
|
|
3466
3401
|
const openItems = tx.listWorkItems(currentTask.id)
|
|
3467
3402
|
.filter((item) => item.status === "pending" || item.status === "running");
|
|
3468
3403
|
return groups.map((group) => {
|
|
@@ -3561,9 +3496,7 @@ function requestTaskReviewRound(args, store, options) {
|
|
|
3561
3496
|
const task = requireTask(tx, parsed.positionals[0]);
|
|
3562
3497
|
if (task.status !== "active")
|
|
3563
3498
|
throw usageError(`Task is not active: ${task.id}.`);
|
|
3564
|
-
|
|
3565
|
-
throw usageError("Only the Task Leader may request a Task-final Review.");
|
|
3566
|
-
}
|
|
3499
|
+
const requestedBy = taskActor(tx, options, task.id);
|
|
3567
3500
|
if (task.projectBindings.length === 0) {
|
|
3568
3501
|
throw usageError(`Task ${task.id} has no bound Projects for a Task-final Review.`);
|
|
3569
3502
|
}
|
|
@@ -3715,8 +3648,8 @@ function requestTaskReviewRound(args, store, options) {
|
|
|
3715
3648
|
deltaRecord = validateDeltaRecheckRequest(tx, task.id, provenance.candidate, options.deltaRecheckPreflight);
|
|
3716
3649
|
}
|
|
3717
3650
|
let created = deltaRecord === undefined
|
|
3718
|
-
? createTaskReviewRound(tx.nextReviewRoundId(task.id), task.id, reviewerRoleName,
|
|
3719
|
-
: createTaskDeltaReviewRound(tx.nextReviewRoundId(task.id), task.id, reviewerRoleName,
|
|
3651
|
+
? createTaskReviewRound(tx.nextReviewRoundId(task.id), task.id, reviewerRoleName, requestedBy, provenance.candidate, now, taskFinalContract)
|
|
3652
|
+
: createTaskDeltaReviewRound(tx.nextReviewRoundId(task.id), task.id, reviewerRoleName, requestedBy, provenance.candidate, deltaRecord, now, taskFinalContract);
|
|
3720
3653
|
let group = createExecutionGroup(`execution-group-${created.id}`, task.id, {
|
|
3721
3654
|
purpose: "review",
|
|
3722
3655
|
target: executionTargetForReviewRound(task, created),
|
|
@@ -3792,9 +3725,7 @@ function forceFreshTaskReviewRound(args, store, options) {
|
|
|
3792
3725
|
const task = requireTask(tx, reference.taskId);
|
|
3793
3726
|
if (task.status !== "active")
|
|
3794
3727
|
throw usageError(`Task is not active: ${task.id}.`);
|
|
3795
|
-
|
|
3796
|
-
throw usageError("Only the Task Leader may force a fresh Task-final ReviewRound.");
|
|
3797
|
-
}
|
|
3728
|
+
const requestedBy = taskActor(tx, options, task.id);
|
|
3798
3729
|
if ((source.scope ?? "work-item") !== "task") {
|
|
3799
3730
|
throw usageError(`ReviewRound ${source.id} is not a Task-final ReviewRound.`);
|
|
3800
3731
|
}
|
|
@@ -3868,7 +3799,7 @@ function forceFreshTaskReviewRound(args, store, options) {
|
|
|
3868
3799
|
reviewer = createTaskRole(tx, task, source.reviewerRoleName, undefined, now, source.reviewerRoleName);
|
|
3869
3800
|
tx.saveRole(task.id, reviewer);
|
|
3870
3801
|
}
|
|
3871
|
-
let created = createTaskReviewRound(tx.nextReviewRoundId(task.id), task.id, source.reviewerRoleName,
|
|
3802
|
+
let created = createTaskReviewRound(tx.nextReviewRoundId(task.id), task.id, source.reviewerRoleName, requestedBy, source.taskCandidate, now, taskFinalContract);
|
|
3872
3803
|
const group = createExecutionGroup(`execution-group-${created.id}`, task.id, {
|
|
3873
3804
|
purpose: "review",
|
|
3874
3805
|
target: executionTargetForReviewRound(task, created),
|
|
@@ -3884,7 +3815,8 @@ function forceFreshTaskReviewRound(args, store, options) {
|
|
|
3884
3815
|
reviewerRoleName: created.reviewerRoleName,
|
|
3885
3816
|
taskCandidate: JSON.stringify(created.taskCandidate),
|
|
3886
3817
|
reason: "source-round-terminal-without-semantic-review",
|
|
3887
|
-
|
|
3818
|
+
requestedBy,
|
|
3819
|
+
...(requestedBy === "leader" ? leaderActionEventPayload(tx, task.id, options) : {})
|
|
3888
3820
|
}, now);
|
|
3889
3821
|
return { round: created, source, created: true };
|
|
3890
3822
|
});
|
|
@@ -4004,9 +3936,7 @@ function retryFailedTaskReviewRound(args, store, options) {
|
|
|
4004
3936
|
const task = requireTask(tx, reference.taskId);
|
|
4005
3937
|
if (task.status !== "active")
|
|
4006
3938
|
throw usageError(`Task is not active: ${task.id}.`);
|
|
4007
|
-
|
|
4008
|
-
throw usageError("Only the Task Leader may retry a failed Task-final ReviewRound.");
|
|
4009
|
-
}
|
|
3939
|
+
const requestedBy = taskActor(tx, options, task.id);
|
|
4010
3940
|
if ((round.scope ?? "work-item") !== "task") {
|
|
4011
3941
|
throw usageError(`ReviewRound ${round.id} is not a failed Task-final ReviewRound.`);
|
|
4012
3942
|
}
|
|
@@ -4088,7 +4018,7 @@ function retryFailedTaskReviewRound(args, store, options) {
|
|
|
4088
4018
|
// Issue 06: infra retry resets the same semantic Round to pending instead
|
|
4089
4019
|
// of manufacturing a new Round, so Round count and finding identity stay
|
|
4090
4020
|
// stable across execution-attempt failures.
|
|
4091
|
-
const resetRound = retryTaskReviewRound(round);
|
|
4021
|
+
const resetRound = retryTaskReviewRound(round, requestedBy);
|
|
4092
4022
|
tx.saveReviewRound(task.id, resetRound);
|
|
4093
4023
|
recordTaskEvent(tx, task.id, "review.task-final-retried", {
|
|
4094
4024
|
reviewRoundId: round.id
|
|
@@ -4350,9 +4280,7 @@ function settleStaleFinalReviewRun(args, store, options) {
|
|
|
4350
4280
|
const task = requireTask(tx, run.taskId);
|
|
4351
4281
|
if (task.status !== "active")
|
|
4352
4282
|
throw usageError(`Task is not active: ${task.id}.`);
|
|
4353
|
-
|
|
4354
|
-
throw usageError("Only the Task Leader may settle a stale final review Run.");
|
|
4355
|
-
}
|
|
4283
|
+
const actor = taskActor(tx, options, task.id);
|
|
4356
4284
|
const round = tx.getReviewRound(task.id, run.reviewRoundId);
|
|
4357
4285
|
if (round === null) {
|
|
4358
4286
|
throw dataError(`ReviewRound not found for run ${run.id}: ${run.reviewRoundId}.`);
|
|
@@ -4461,7 +4389,9 @@ function settleStaleFinalReviewRun(args, store, options) {
|
|
|
4461
4389
|
runId: run.id,
|
|
4462
4390
|
reviewRoundId: round.id,
|
|
4463
4391
|
previousTaskCandidate: JSON.stringify(round.taskCandidate),
|
|
4464
|
-
currentTaskCandidate: JSON.stringify(currentTaskCandidate)
|
|
4392
|
+
currentTaskCandidate: JSON.stringify(currentTaskCandidate),
|
|
4393
|
+
settledBy: actor,
|
|
4394
|
+
...(actor === "leader" ? leaderActionEventPayload(tx, task.id, options) : {})
|
|
4465
4395
|
}, now);
|
|
4466
4396
|
return { run, round: terminal, changed: true };
|
|
4467
4397
|
});
|
|
@@ -5012,7 +4942,7 @@ function assertPendingFinalReviewWorkspaceEvidence(store, task, round) {
|
|
|
5012
4942
|
}
|
|
5013
4943
|
}
|
|
5014
4944
|
/**
|
|
5015
|
-
*
|
|
4945
|
+
* Task-control retry of an exact failed Task-final review Run. The old failed
|
|
5016
4946
|
* Run remains the attempt trail, while the semantic ReviewRound is reset to
|
|
5017
4947
|
* pending under its existing identity. Every identity and frozen-head fence is
|
|
5018
4948
|
* checked inside one transaction so a partial fail-old-without-reset state can
|
|
@@ -5039,9 +4969,7 @@ function retryFailedReviewRun(previous, store, options, now) {
|
|
|
5039
4969
|
throw usageError(`Review Run ${run.id} still owns an unsettled Provider continuation; `
|
|
5040
4970
|
+ "reconcile it before retrying the Lane.");
|
|
5041
4971
|
}
|
|
5042
|
-
|
|
5043
|
-
throw usageError("Only the Task Leader may retry a failed final review Run.");
|
|
5044
|
-
}
|
|
4972
|
+
const requestedBy = taskActor(tx, options, task.id);
|
|
5045
4973
|
const round = tx.getReviewRound(task.id, run.reviewRoundId);
|
|
5046
4974
|
if (round === null) {
|
|
5047
4975
|
throw dataError(`ReviewRound not found for run ${run.id}: ${run.reviewRoundId}.`);
|
|
@@ -5221,7 +5149,7 @@ function retryFailedReviewRun(previous, store, options, now) {
|
|
|
5221
5149
|
// Issue 06: infra retry resets the same semantic Round to pending instead
|
|
5222
5150
|
// of manufacturing a new Round, so Round count and finding identity stay
|
|
5223
5151
|
// stable across execution-attempt failures.
|
|
5224
|
-
const resetRound = retryTaskReviewRound(roundToReset, run.executionLaneId);
|
|
5152
|
+
const resetRound = retryTaskReviewRound(roundToReset, requestedBy, run.executionLaneId);
|
|
5225
5153
|
tx.saveReviewRound(task.id, resetRound);
|
|
5226
5154
|
recordTaskEvent(tx, task.id, "run.review-retried", {
|
|
5227
5155
|
runId: run.id,
|
|
@@ -5266,9 +5194,7 @@ function recoverRun(args, store, options) {
|
|
|
5266
5194
|
const task = requireTask(tx, active.taskId);
|
|
5267
5195
|
if (task.status !== "active")
|
|
5268
5196
|
throw usageError(inactiveTaskMessage(task, "recovering a run"));
|
|
5269
|
-
|
|
5270
|
-
throw usageError("Only the Task Leader may control exact Agent Run recovery.");
|
|
5271
|
-
}
|
|
5197
|
+
taskActor(tx, options, task.id);
|
|
5272
5198
|
const roleName = parsed.options.get("--role") ?? active.roleName;
|
|
5273
5199
|
if (roleName !== active.roleName) {
|
|
5274
5200
|
throw usageError(`Recovery Role does not match Run ${active.id}: ${roleName}.`);
|
|
@@ -6559,52 +6485,6 @@ function recordTaskEventRecord(store, taskId, type, payload, now) {
|
|
|
6559
6485
|
store.saveEvent(taskId, event);
|
|
6560
6486
|
return event;
|
|
6561
6487
|
}
|
|
6562
|
-
function publishedTreeAuthorizationPayload(actor, proof) {
|
|
6563
|
-
return {
|
|
6564
|
-
by: actor,
|
|
6565
|
-
projectId: proof.projectId,
|
|
6566
|
-
publicationId: proof.publicationId,
|
|
6567
|
-
...(proof.reviewRoundId === undefined
|
|
6568
|
-
? { reviewAnchor: publishedTreeReviewAnchor(proof) }
|
|
6569
|
-
: { reviewRoundId: proof.reviewRoundId }),
|
|
6570
|
-
localCommit: proof.localCommit,
|
|
6571
|
-
remoteCommit: proof.remoteCommit,
|
|
6572
|
-
tree: proof.tree
|
|
6573
|
-
};
|
|
6574
|
-
}
|
|
6575
|
-
function matchingPublishedTreeAuthorization(store, proof) {
|
|
6576
|
-
const events = store.listEvents(proof.taskId);
|
|
6577
|
-
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
6578
|
-
const event = events[index];
|
|
6579
|
-
if (event.type === "task.completed" || event.type === "task.reopened")
|
|
6580
|
-
return undefined;
|
|
6581
|
-
if (event.type === TASK_COMPLETION_PUBLISHED_TREE_AUTHORIZED_EVENT
|
|
6582
|
-
&& (event.payload.by === "user" || event.payload.by === "operator")
|
|
6583
|
-
&& event.payload.projectId === proof.projectId
|
|
6584
|
-
&& event.payload.publicationId === proof.publicationId
|
|
6585
|
-
&& (event.payload.reviewAnchor ?? event.payload.reviewRoundId)
|
|
6586
|
-
=== publishedTreeReviewAnchor(proof)
|
|
6587
|
-
&& event.payload.localCommit === proof.localCommit
|
|
6588
|
-
&& event.payload.remoteCommit === proof.remoteCommit
|
|
6589
|
-
&& event.payload.tree === proof.tree) {
|
|
6590
|
-
return event;
|
|
6591
|
-
}
|
|
6592
|
-
}
|
|
6593
|
-
return undefined;
|
|
6594
|
-
}
|
|
6595
|
-
function requirePublishedTreeAuthorization(store, proof) {
|
|
6596
|
-
const authorization = matchingPublishedTreeAuthorization(store, proof);
|
|
6597
|
-
if (authorization === undefined) {
|
|
6598
|
-
throw usageError(`Published-tree completion requires explicit user or global Operator authorization for `
|
|
6599
|
-
+ `${proof.taskId}/${proof.publicationId} at ${proof.reviewRoundId === undefined
|
|
6600
|
-
? "the Leader-owned Task-main head"
|
|
6601
|
-
: `Task-final Review ${proof.reviewRoundId}`}.`);
|
|
6602
|
-
}
|
|
6603
|
-
return authorization;
|
|
6604
|
-
}
|
|
6605
|
-
function publishedTreeReviewAnchor(proof) {
|
|
6606
|
-
return proof.reviewRoundId ?? "direct";
|
|
6607
|
-
}
|
|
6608
6488
|
/** Keeps a free-text run-fact note bounded so an event payload stays compact. */
|
|
6609
6489
|
function truncateEventNote(note) {
|
|
6610
6490
|
const normalized = note.trim();
|
|
@@ -7083,10 +6963,8 @@ function taskMilestoneCommand(args, store, options) {
|
|
|
7083
6963
|
const result = store.transaction((tx) => {
|
|
7084
6964
|
const task = requireTask(tx, parsed.positionals[0]);
|
|
7085
6965
|
assertTaskOpen(task);
|
|
7086
|
-
|
|
7087
|
-
|
|
7088
|
-
}
|
|
7089
|
-
const milestone = createMilestone(tx.nextMilestoneId(task.id), task.id, title, summary, now);
|
|
6966
|
+
const actor = taskActor(tx, options, task.id);
|
|
6967
|
+
const milestone = createMilestone(tx.nextMilestoneId(task.id), task.id, title, summary, actor, now);
|
|
7090
6968
|
tx.saveMilestone(task.id, milestone);
|
|
7091
6969
|
recordTaskEvent(tx, task.id, "milestone.added", {
|
|
7092
6970
|
milestoneId: milestone.id,
|
|
@@ -183,7 +183,7 @@ async function runIntegration(store, home, integration, now, options) {
|
|
|
183
183
|
const output = result.status === "committed"
|
|
184
184
|
? `Integrated ${result.attempt.changeSetIds.join(", ")} into ${result.attempt.targetRef} with CAS (${result.attempt.id})\n`
|
|
185
185
|
: result.status === "blocked"
|
|
186
|
-
? `Integration ${result.attempt.id} requires a
|
|
186
|
+
? `Integration ${result.attempt.id} requires a Task Agent resolution decision in ${result.workspace.path}\n`
|
|
187
187
|
: result.status === "checks-running"
|
|
188
188
|
? `Integration ${result.attempt.id} checks are running as DurableJob ${result.job.id}; run 'yui task integration continue ${result.attempt.taskId}/${result.attempt.id}' when the job finishes\n`
|
|
189
189
|
: `Integration ${result.attempt.id} failed; target ref was not advanced\n`;
|
|
@@ -201,20 +201,18 @@ function resolveDecision(args, store, now, environment, home) {
|
|
|
201
201
|
const resolved = store.transaction((tx) => {
|
|
202
202
|
const integration = requireIntegration(tx, parsed.positionals[0], environment);
|
|
203
203
|
const task = requireActiveIntegrationTask(tx, integration);
|
|
204
|
-
|
|
205
|
-
throw usageError("Only the Task Leader can resolve an Integration conflict.");
|
|
206
|
-
}
|
|
204
|
+
const actor = taskLocalActor(tx, environment, task.id, home);
|
|
207
205
|
const updated = recordResolutionDecision(integration, {
|
|
208
206
|
action: selectedOption,
|
|
209
207
|
rationale
|
|
210
|
-
}, now());
|
|
208
|
+
}, actor, now());
|
|
211
209
|
tx.saveIntegrationAttempt(updated.taskId, updated);
|
|
212
210
|
return updated;
|
|
213
211
|
});
|
|
214
212
|
return {
|
|
215
213
|
output: selectedOption === "manual-resolution"
|
|
216
|
-
? `
|
|
217
|
-
: `
|
|
214
|
+
? `Selected manual resolution for ${resolved.id}; resolve its candidate worktree, then run integration continue\n`
|
|
215
|
+
: `Rejected Integration ${resolved.id}\n`,
|
|
218
216
|
data: { integration: resolved }
|
|
219
217
|
};
|
|
220
218
|
}
|
|
@@ -275,8 +273,8 @@ function supersedeIntegrationCommand(args, store, now, environment, home) {
|
|
|
275
273
|
const reason = parsed.one.get("--reason");
|
|
276
274
|
if (reason === undefined)
|
|
277
275
|
throw usageError(usage);
|
|
278
|
-
//
|
|
279
|
-
//
|
|
276
|
+
// Superseding a committed Integration rewrites delivery-baseline evidence
|
|
277
|
+
// and audit history, so it remains an explicit Task-control decision.
|
|
280
278
|
taskLocalActor(store, environment, integration.taskId, home);
|
|
281
279
|
// A queue-backed committed Attempt cannot be superseded: the queue entry
|
|
282
280
|
// would remain in its current status while its Attempt becomes "superseded",
|
|
@@ -37,7 +37,7 @@ async function enqueue(args, store, options, home) {
|
|
|
37
37
|
if (parsed.positionals.length !== 1)
|
|
38
38
|
throw usageError(usage);
|
|
39
39
|
const task = requireActiveTask(store, parsed.positionals[0]);
|
|
40
|
-
|
|
40
|
+
requireTaskControlActor(store, options.environment, task.id, home);
|
|
41
41
|
const projectRef = parsed.one.get("--project");
|
|
42
42
|
const changeSetId = parsed.one.get("--change-set");
|
|
43
43
|
if (projectRef === undefined || changeSetId === undefined)
|
|
@@ -120,7 +120,7 @@ async function process(args, store, home, options) {
|
|
|
120
120
|
if (parsed.positionals.length !== 1)
|
|
121
121
|
throw usageError(usage);
|
|
122
122
|
const task = requireActiveTask(store, parsed.positionals[0]);
|
|
123
|
-
|
|
123
|
+
requireTaskControlActor(store, options.environment, task.id, home);
|
|
124
124
|
const limitValue = parsed.one.get("--limit");
|
|
125
125
|
const limit = limitValue === undefined ? undefined : Number.parseInt(limitValue, 10);
|
|
126
126
|
if (limitValue !== undefined && (!Number.isSafeInteger(limit) || limit < 1)) {
|
|
@@ -154,11 +154,11 @@ function supersede(args, store, options, home) {
|
|
|
154
154
|
if (parsed.positionals.length !== 1)
|
|
155
155
|
throw usageError(usage);
|
|
156
156
|
const entry = requireQueueEntry(store, parsed.positionals[0], options.environment);
|
|
157
|
-
|
|
157
|
+
const actor = requireTaskControlActor(store, options.environment, entry.taskId, home);
|
|
158
158
|
const reason = parsed.one.get("--reason");
|
|
159
159
|
if (reason === undefined)
|
|
160
160
|
throw usageError(usage);
|
|
161
|
-
const superseded = supersedeIntegrationQueueEntry(store, entry.taskId, entry.id, reason, options.now ?? (() => new Date()));
|
|
161
|
+
const superseded = supersedeIntegrationQueueEntry(store, entry.taskId, entry.id, reason, actor, options.now ?? (() => new Date()));
|
|
162
162
|
return {
|
|
163
163
|
output: `Superseded ${superseded.id}: ${reason}\n`,
|
|
164
164
|
data: { entry: superseded }
|
|
@@ -169,8 +169,8 @@ function requeue(args, store, options, home) {
|
|
|
169
169
|
if (args.length !== 1)
|
|
170
170
|
throw usageError(usage);
|
|
171
171
|
const entry = requireQueueEntry(store, args[0], options.environment);
|
|
172
|
-
|
|
173
|
-
const waiting = requeueIntegrationQueueEntry(store, entry.taskId, entry.id, options.now ?? (() => new Date()));
|
|
172
|
+
const actor = requireTaskControlActor(store, options.environment, entry.taskId, home);
|
|
173
|
+
const waiting = requeueIntegrationQueueEntry(store, entry.taskId, entry.id, actor, options.now ?? (() => new Date()));
|
|
174
174
|
return {
|
|
175
175
|
output: `Requeued ${waiting.id}; it will be processed again on the next queue run\n`,
|
|
176
176
|
data: { entry: waiting }
|
|
@@ -181,7 +181,7 @@ async function reconcile(args, store, options, home) {
|
|
|
181
181
|
if (args.length !== 1)
|
|
182
182
|
throw usageError(usage);
|
|
183
183
|
const entry = requireQueueEntry(store, args[0], options.environment);
|
|
184
|
-
|
|
184
|
+
requireTaskControlActor(store, options.environment, entry.taskId, home);
|
|
185
185
|
const committed = await reconcileIntegrationQueueEntry(store, entry.taskId, entry.id, new NodeGitWorkspace(), options.now ?? (() => new Date()));
|
|
186
186
|
return {
|
|
187
187
|
output: `Reconciled ${committed.id} as committed -> ${committed.targetAfter ?? "-"}\n`,
|
|
@@ -221,8 +221,6 @@ function requireQueueEntry(store, value, environment) {
|
|
|
221
221
|
}
|
|
222
222
|
return entry;
|
|
223
223
|
}
|
|
224
|
-
function
|
|
225
|
-
|
|
226
|
-
throw usageError("Only the Task Leader can change the integration queue.");
|
|
227
|
-
}
|
|
224
|
+
function requireTaskControlActor(store, environment, taskId, home) {
|
|
225
|
+
return taskLocalActor(store, environment, taskId, home);
|
|
228
226
|
}
|
|
@@ -34,10 +34,6 @@ function addPublication(args, store, options) {
|
|
|
34
34
|
throw usageError(`Project is not bound to Task ${task.id}: ${projectReference}.`, usage);
|
|
35
35
|
}
|
|
36
36
|
const actor = taskActor(options.environment, task.id);
|
|
37
|
-
if (actor === "leader") {
|
|
38
|
-
throw usageError("A managed Task Leader cannot record external publication evidence; "
|
|
39
|
-
+ "use a user or global Operator session.", usage);
|
|
40
|
-
}
|
|
41
37
|
const state = enumOption(parsed.options, "--state", STATES, usage) ?? "open";
|
|
42
38
|
const verification = parseVerification(parsed, usage);
|
|
43
39
|
const input = {
|
|
@@ -135,7 +135,7 @@ function replaceTaskCommand(args, store, options) {
|
|
|
135
135
|
}
|
|
136
136
|
const now = options.now?.() ?? new Date();
|
|
137
137
|
const milestone = createMilestone(store.nextMilestoneId(replacement.id), replacement.id, `Replaces ${old.id}`, `Replacement Task for ${old.id} (${old.status}); the original Task, its refs, `
|
|
138
|
-
+ "and its evidence are preserved and were not rewritten.", now);
|
|
138
|
+
+ "and its evidence are preserved and were not rewritten.", "user", now);
|
|
139
139
|
store.saveMilestone(replacement.id, milestone);
|
|
140
140
|
return {
|
|
141
141
|
output: `${created.output}\nRecorded replacement of ${old.id} on ${replacement.id} `
|
|
@@ -68,16 +68,13 @@ function parseJobResult(value) {
|
|
|
68
68
|
* list/show paths do not use this port and stay lean.
|
|
69
69
|
*/
|
|
70
70
|
export function createControllerIntegrationJobPort(home, clientOptions = {}) {
|
|
71
|
-
const { store, ...controllerOptions } = clientOptions;
|
|
72
71
|
return {
|
|
73
72
|
async startCheckJob(input) {
|
|
74
73
|
const owner = {
|
|
75
74
|
kind: "integration-attempt",
|
|
76
75
|
integrationAttemptId: input.integrationId
|
|
77
76
|
};
|
|
78
|
-
|
|
79
|
-
// identity. A user-scope caller carries a verified Leader assertion.
|
|
80
|
-
const caller = resolveJobCaller(controllerOptions.environment, input.taskId, store);
|
|
77
|
+
const caller = resolveJobCaller(clientOptions.environment, input.taskId);
|
|
81
78
|
const { job } = await startDurableJob(home, {
|
|
82
79
|
taskId: input.taskId,
|
|
83
80
|
owner,
|
|
@@ -87,16 +84,16 @@ export function createControllerIntegrationJobPort(home, clientOptions = {}) {
|
|
|
87
84
|
env: input.env,
|
|
88
85
|
steps: input.steps,
|
|
89
86
|
caller
|
|
90
|
-
},
|
|
87
|
+
}, clientOptions);
|
|
91
88
|
return job;
|
|
92
89
|
},
|
|
93
90
|
async getJob(taskId, jobId) {
|
|
94
|
-
return getDurableJob(home, taskId, jobId,
|
|
91
|
+
return getDurableJob(home, taskId, jobId, clientOptions);
|
|
95
92
|
},
|
|
96
93
|
async cancelJob(taskId, jobId) {
|
|
97
94
|
// rr8/rr12: Bind the cancel request to the caller's managed identity.
|
|
98
|
-
const caller = resolveJobCaller(
|
|
99
|
-
await cancelDurableJob(home, taskId, jobId, caller,
|
|
95
|
+
const caller = resolveJobCaller(clientOptions.environment, taskId);
|
|
96
|
+
await cancelDurableJob(home, taskId, jobId, caller, clientOptions);
|
|
100
97
|
}
|
|
101
98
|
};
|
|
102
99
|
}
|