@zq-silk/yui 0.8.9 → 0.10.0
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 +48 -46
- package/README.md +72 -42
- package/dist/cli/commandCatalog.js +16 -8
- package/dist/cli.js +27 -32
- package/dist/commands/executionAuditCommands.js +2 -2
- package/dist/commands/globalRoleCommands.js +0 -12
- package/dist/commands/sessionCommands.js +116 -0
- package/dist/commands/taskBaseCommands.js +1 -11
- package/dist/commands/taskCommands.js +123 -340
- package/dist/commands/taskCompletionGate.js +15 -12
- package/dist/commands/taskContextCommand.js +18 -11
- package/dist/commands/taskNextActionCommand.js +4 -5
- package/dist/commands/taskWorkspaceCommands.js +2 -2
- package/dist/controller/clientRuntime.js +65 -0
- package/dist/controller/fileSchedulerStoreAdapter.js +2 -49
- package/dist/doctor/doctor.js +16 -12
- package/dist/execution/executionGroup.js +0 -3
- package/dist/executor/agentAdapter.js +39 -42
- package/dist/executor/agentExecutor.js +4 -2
- package/dist/executor/codexConfigConflict.js +40 -16
- package/dist/executor/effectiveLaunch.js +33 -3
- package/dist/executor/fileRoleLaunchPlanner.js +15 -24
- package/dist/integration/deliveryObligation.js +72 -0
- package/dist/integration/gitIntegrationService.js +1 -1
- package/dist/lifecycle/exactRunTerminalization.js +12 -8
- package/dist/observability/orchestrationMetrics.js +12 -26
- package/dist/profile/agentProfile.js +1 -1
- package/dist/repository/taskBaseFreshness.js +5 -11
- package/dist/repository/taskWorkspaceCoordinator.js +2 -0
- package/dist/repository/taskWorkspacePreparer.js +173 -26
- package/dist/review/reviewRound.js +41 -24
- package/dist/role/role.js +0 -9
- package/dist/runtime/{firstProgressStopLoss.js → firstProgressAdvisory.js} +7 -21
- package/dist/scheduler/leaderWakeupProcessor.js +0 -25
- package/dist/setup/setupCommand.js +0 -5
- package/dist/storage/migration/productionRegistry.js +138 -0
- package/dist/storage/sqliteStore.js +2 -1
- package/dist/storage/taskStore.js +27 -27
- package/dist/storage/upgrade/upgradeOrchestrator.js +35 -7
- package/dist/task/completionReadiness.js +35 -25
- package/dist/task/nextAction.js +70 -78
- package/dist/task/task.js +12 -19
- package/dist/web/assets/client/components.js +3 -1
- package/dist/web/assets/client/i18n.js +6 -0
- package/dist/web/webSnapshot.js +0 -3
- package/i18n/README.zh-CN.md +33 -24
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +55 -52
- package/skills/yui-operator/SKILL.md +31 -40
- package/skills/yui-reviewer/SKILL.md +18 -12
- package/skills/yui-worker/SKILL.md +5 -3
|
@@ -20,6 +20,8 @@ const PROJECT_KNOWLEDGE_PROPOSALS_FROM_VERSION = 3;
|
|
|
20
20
|
const PROJECT_KNOWLEDGE_PROPOSALS_TO_VERSION = 4;
|
|
21
21
|
const TASK_FROM_VERSION = 3;
|
|
22
22
|
const TASK_TO_VERSION = 4;
|
|
23
|
+
const TASK_INTENT_FROM_VERSION = 4;
|
|
24
|
+
const TASK_INTENT_TO_VERSION = 5;
|
|
23
25
|
const WORK_ITEM_FROM_VERSION = 6;
|
|
24
26
|
const WORK_ITEM_TO_VERSION = 7;
|
|
25
27
|
const WORK_ITEM_GIT_SNAPSHOT_FROM_VERSION = 7;
|
|
@@ -45,6 +47,8 @@ const REVIEW_ROUND_FROM_VERSION = 2;
|
|
|
45
47
|
const REVIEW_ROUND_TO_VERSION = 3;
|
|
46
48
|
const REVIEW_ROUND_GIT_SNAPSHOT_FROM_VERSION = 3;
|
|
47
49
|
const REVIEW_ROUND_GIT_SNAPSHOT_TO_VERSION = 4;
|
|
50
|
+
const REVIEW_ROUND_TASK_ANCHOR_FROM_VERSION = 4;
|
|
51
|
+
const REVIEW_ROUND_TASK_ANCHOR_TO_VERSION = 5;
|
|
48
52
|
const ACTIVE_RUN_POINTER_FROM_VERSION = 1;
|
|
49
53
|
const ACTIVE_RUN_POINTER_TO_VERSION = 2;
|
|
50
54
|
const ACTIVE_RUN_POINTER_NAMESPACE_FROM_VERSION = 2;
|
|
@@ -136,6 +140,7 @@ export function createProductionStorageRegistry() {
|
|
|
136
140
|
.registerOfflineMigration(configV2Step())
|
|
137
141
|
.registerCompatible(projectKnowledgeProposalsStep())
|
|
138
142
|
.registerOfflineMigration(taskWorkspaceIdentityStep())
|
|
143
|
+
.registerOfflineMigration(taskIntentStep())
|
|
139
144
|
.registerOfflineMigration(recordFamilyStep("workItem", WORK_ITEM_FROM_VERSION, WORK_ITEM_TO_VERSION, "workItems"))
|
|
140
145
|
.registerOfflineMigration(recordFamilyStep("workItem", WORK_ITEM_GIT_SNAPSHOT_FROM_VERSION, WORK_ITEM_GIT_SNAPSHOT_TO_VERSION, "workItems"))
|
|
141
146
|
.registerOfflineMigration(workItemExecutionGroupHistoryStep())
|
|
@@ -146,6 +151,7 @@ export function createProductionStorageRegistry() {
|
|
|
146
151
|
.registerOfflineMigration(messageWakePolicyStep())
|
|
147
152
|
.registerOfflineMigration(recordFamilyStep("reviewRound", REVIEW_ROUND_FROM_VERSION, REVIEW_ROUND_TO_VERSION, "reviewRounds"))
|
|
148
153
|
.registerOfflineMigration(recordFamilyStep("reviewRound", REVIEW_ROUND_GIT_SNAPSHOT_FROM_VERSION, REVIEW_ROUND_GIT_SNAPSHOT_TO_VERSION, "reviewRounds"))
|
|
154
|
+
.registerOfflineMigration(reviewRoundTaskAnchorStep())
|
|
149
155
|
.registerOfflineMigration(recordFamilyStep("activeRunPointer", ACTIVE_RUN_POINTER_FROM_VERSION, ACTIVE_RUN_POINTER_TO_VERSION, "activeRuns"))
|
|
150
156
|
.registerOfflineMigration(managedWorkspaceFamilyStep())
|
|
151
157
|
.registerOfflineMigration(activeRunPointerNamespaceStep())
|
|
@@ -1546,6 +1552,65 @@ function recordFamilyStep(recordKind, fromVersion, toVersion, taskMapKey) {
|
|
|
1546
1552
|
declaredEffects: []
|
|
1547
1553
|
};
|
|
1548
1554
|
}
|
|
1555
|
+
function reviewRoundTaskAnchorStep() {
|
|
1556
|
+
return {
|
|
1557
|
+
axis: "record",
|
|
1558
|
+
recordKind: "reviewRound",
|
|
1559
|
+
fromVersion: REVIEW_ROUND_TASK_ANCHOR_FROM_VERSION,
|
|
1560
|
+
toVersion: REVIEW_ROUND_TASK_ANCHOR_TO_VERSION,
|
|
1561
|
+
preconditions: (snapshot) => requireRecordFamilyVersion(snapshot, "reviewRound", REVIEW_ROUND_TASK_ANCHOR_FROM_VERSION, "reviewRounds"),
|
|
1562
|
+
transform: migrateReviewRoundTaskAnchors,
|
|
1563
|
+
declaredEffects: []
|
|
1564
|
+
};
|
|
1565
|
+
}
|
|
1566
|
+
function migrateReviewRoundTaskAnchors(snapshot) {
|
|
1567
|
+
requireRecordFamilyVersion(snapshot, "reviewRound", REVIEW_ROUND_TASK_ANCHOR_FROM_VERSION, "reviewRounds");
|
|
1568
|
+
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
1569
|
+
const schemaManifest = {
|
|
1570
|
+
...snapshot.schemaManifest,
|
|
1571
|
+
recordVersions: {
|
|
1572
|
+
...manifestVersions,
|
|
1573
|
+
reviewRound: REVIEW_ROUND_TASK_ANCHOR_TO_VERSION
|
|
1574
|
+
}
|
|
1575
|
+
};
|
|
1576
|
+
if (snapshot.state === null)
|
|
1577
|
+
return { schemaManifest, state: null };
|
|
1578
|
+
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
1579
|
+
const nextTasks = {};
|
|
1580
|
+
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
1581
|
+
const aggregate = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
1582
|
+
const rawRounds = aggregate.reviewRounds;
|
|
1583
|
+
if (rawRounds === undefined) {
|
|
1584
|
+
nextTasks[taskId] = { ...aggregate };
|
|
1585
|
+
continue;
|
|
1586
|
+
}
|
|
1587
|
+
const rounds = asObject(rawRounds, `reviewRound map ${taskId}`);
|
|
1588
|
+
const nextRounds = {};
|
|
1589
|
+
for (const [roundId, rawRound] of Object.entries(rounds)) {
|
|
1590
|
+
const round = asObject(rawRound, `reviewRound ${taskId}/${roundId}`);
|
|
1591
|
+
if (round.scope !== "task") {
|
|
1592
|
+
nextRounds[roundId] = {
|
|
1593
|
+
...round,
|
|
1594
|
+
schemaVersion: REVIEW_ROUND_TASK_ANCHOR_TO_VERSION
|
|
1595
|
+
};
|
|
1596
|
+
continue;
|
|
1597
|
+
}
|
|
1598
|
+
const workItemId = requiredMigrationText(round.workItemId, `reviewRound ${taskId}/${roundId} WorkItem id`);
|
|
1599
|
+
const candidateId = requiredMigrationText(round.candidateId, `reviewRound ${taskId}/${roundId} Candidate id`);
|
|
1600
|
+
const { workItemId: _workItemId, candidateId: _candidateId, schemaVersion: _schemaVersion, ...retained } = round;
|
|
1601
|
+
nextRounds[roundId] = {
|
|
1602
|
+
...retained,
|
|
1603
|
+
schemaVersion: REVIEW_ROUND_TASK_ANCHOR_TO_VERSION,
|
|
1604
|
+
legacyAnchor: { workItemId, candidateId }
|
|
1605
|
+
};
|
|
1606
|
+
}
|
|
1607
|
+
nextTasks[taskId] = { ...aggregate, reviewRounds: nextRounds };
|
|
1608
|
+
}
|
|
1609
|
+
return {
|
|
1610
|
+
schemaManifest,
|
|
1611
|
+
state: { ...snapshot.state, tasks: nextTasks }
|
|
1612
|
+
};
|
|
1613
|
+
}
|
|
1549
1614
|
function agentRunContextProtocolStep() {
|
|
1550
1615
|
return {
|
|
1551
1616
|
axis: "record",
|
|
@@ -2747,6 +2812,79 @@ function migrateTaskV3ToV4(snapshot) {
|
|
|
2747
2812
|
state: { ...snapshot.state, tasks: nextTasks }
|
|
2748
2813
|
};
|
|
2749
2814
|
}
|
|
2815
|
+
/**
|
|
2816
|
+
* Task v5 replaces the stored delivery topology switch with an optional,
|
|
2817
|
+
* Project-defined intent. Historical topology is retained as audit context,
|
|
2818
|
+
* but no current workflow decision reads it.
|
|
2819
|
+
*/
|
|
2820
|
+
function taskIntentStep() {
|
|
2821
|
+
return {
|
|
2822
|
+
axis: "record",
|
|
2823
|
+
recordKind: "task",
|
|
2824
|
+
fromVersion: TASK_INTENT_FROM_VERSION,
|
|
2825
|
+
toVersion: TASK_INTENT_TO_VERSION,
|
|
2826
|
+
preconditions: requireTaskV4Family,
|
|
2827
|
+
transform: migrateTaskV4ToV5,
|
|
2828
|
+
declaredEffects: []
|
|
2829
|
+
};
|
|
2830
|
+
}
|
|
2831
|
+
function requireTaskV4Family(snapshot) {
|
|
2832
|
+
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
2833
|
+
if (manifestVersions.task !== TASK_INTENT_FROM_VERSION) {
|
|
2834
|
+
throw new Error(`Record task migration requires manifest version ${TASK_INTENT_FROM_VERSION}.`);
|
|
2835
|
+
}
|
|
2836
|
+
if (snapshot.state === null)
|
|
2837
|
+
return;
|
|
2838
|
+
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
2839
|
+
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
2840
|
+
const aggregate = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
2841
|
+
if (aggregate.task === undefined)
|
|
2842
|
+
continue;
|
|
2843
|
+
const record = asObject(aggregate.task, `Task ${taskId}`);
|
|
2844
|
+
if (record.schemaVersion !== TASK_INTENT_FROM_VERSION) {
|
|
2845
|
+
throw new Error(`Task ${taskId} must use schemaVersion ${TASK_INTENT_FROM_VERSION}.`);
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2849
|
+
function migrateTaskV4ToV5(snapshot) {
|
|
2850
|
+
requireTaskV4Family(snapshot);
|
|
2851
|
+
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
2852
|
+
const schemaManifest = {
|
|
2853
|
+
...snapshot.schemaManifest,
|
|
2854
|
+
recordVersions: { ...manifestVersions, task: TASK_INTENT_TO_VERSION }
|
|
2855
|
+
};
|
|
2856
|
+
if (snapshot.state === null)
|
|
2857
|
+
return { schemaManifest, state: null };
|
|
2858
|
+
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
2859
|
+
const nextTasks = {};
|
|
2860
|
+
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
2861
|
+
const aggregate = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
2862
|
+
if (aggregate.task === undefined) {
|
|
2863
|
+
nextTasks[taskId] = { ...aggregate };
|
|
2864
|
+
continue;
|
|
2865
|
+
}
|
|
2866
|
+
const task = asObject(aggregate.task, `Task ${taskId}`);
|
|
2867
|
+
const { requireIntegration, schemaVersion: _schemaVersion, ...retained } = task;
|
|
2868
|
+
const projectBindings = Array.isArray(task.projectBindings)
|
|
2869
|
+
? task.projectBindings
|
|
2870
|
+
: [];
|
|
2871
|
+
const legacyDeliveryPath = projectBindings.length === 0
|
|
2872
|
+
? undefined
|
|
2873
|
+
: requireIntegration === true ? "integrated" : "direct";
|
|
2874
|
+
nextTasks[taskId] = {
|
|
2875
|
+
...aggregate,
|
|
2876
|
+
task: {
|
|
2877
|
+
...retained,
|
|
2878
|
+
schemaVersion: TASK_INTENT_TO_VERSION,
|
|
2879
|
+
...(legacyDeliveryPath === undefined ? {} : { legacyDeliveryPath })
|
|
2880
|
+
}
|
|
2881
|
+
};
|
|
2882
|
+
}
|
|
2883
|
+
return {
|
|
2884
|
+
schemaManifest,
|
|
2885
|
+
state: { ...snapshot.state, tasks: nextTasks }
|
|
2886
|
+
};
|
|
2887
|
+
}
|
|
2750
2888
|
/**
|
|
2751
2889
|
* A version bump is deliverable only when the shared planner resolves the full
|
|
2752
2890
|
* adjacent path. This also covers target-only record families as explicit 0->1
|
|
@@ -722,11 +722,12 @@ export class SqliteTaskStore {
|
|
|
722
722
|
id: task.id,
|
|
723
723
|
status: task.status,
|
|
724
724
|
projectBindings: task.projectBindings,
|
|
725
|
-
|
|
725
|
+
type: task.type
|
|
726
726
|
},
|
|
727
727
|
workItems: this.#sortById(this.#listPayload("work_items", "task_id = ?", [taskId]), (item) => item.id),
|
|
728
728
|
changeSets: this.#sortById(this.#listPayload("change_sets", "task_id = ?", [taskId]), (changeSet) => changeSet.id),
|
|
729
729
|
integrations: this.#sortById(this.#listPayload("integration_attempts", "task_id = ?", [taskId]), (attempt) => attempt.id),
|
|
730
|
+
integrationQueueEntries: this.#sortById(this.#listPayload("integration_queue", "task_id = ?", [taskId]), (entry) => entry.id),
|
|
730
731
|
reviewRounds: this.#sortById(this.#listPayload("review_rounds", "task_id = ?", [taskId]), (round) => round.id),
|
|
731
732
|
taskFinalReviewContractEvents: events
|
|
732
733
|
.filter((event) => event.type === TASK_FINAL_REVIEW_CONTRACT_REBOUND_EVENT),
|
|
@@ -62,13 +62,13 @@ export const CURRENT_PROJECT_SCHEMA_VERSION = 4;
|
|
|
62
62
|
export const CURRENT_AGENT_PROFILE_SCHEMA_VERSION = 2;
|
|
63
63
|
export const CURRENT_GLOBAL_ROLE_SCHEMA_VERSION = 3;
|
|
64
64
|
export const CURRENT_GLOBAL_ROLE_SESSION_SET_SCHEMA_VERSION = 3;
|
|
65
|
-
export const CURRENT_TASK_SCHEMA_VERSION =
|
|
65
|
+
export const CURRENT_TASK_SCHEMA_VERSION = 5;
|
|
66
66
|
export const CURRENT_TASK_BRIEF_SCHEMA_VERSION = 2;
|
|
67
67
|
export const CURRENT_CONTEXT_SNAPSHOT_SCHEMA_VERSION = 1;
|
|
68
68
|
export const CURRENT_TASK_ROLE_SCHEMA_VERSION = 3;
|
|
69
69
|
export const CURRENT_MANAGED_WORKSPACE_SCHEMA_VERSION = 2;
|
|
70
70
|
export const CURRENT_WORK_ITEM_SCHEMA_VERSION = 9;
|
|
71
|
-
export const CURRENT_REVIEW_ROUND_SCHEMA_VERSION =
|
|
71
|
+
export const CURRENT_REVIEW_ROUND_SCHEMA_VERSION = 5;
|
|
72
72
|
export const CURRENT_CHANGE_SET_SCHEMA_VERSION = 3;
|
|
73
73
|
export const CURRENT_INTEGRATION_ATTEMPT_SCHEMA_VERSION = 4;
|
|
74
74
|
export const CURRENT_MESSAGE_SCHEMA_VERSION = 3;
|
|
@@ -417,11 +417,12 @@ export class FileTaskStore {
|
|
|
417
417
|
id: aggregate.task.id,
|
|
418
418
|
status: aggregate.task.status,
|
|
419
419
|
projectBindings: aggregate.task.projectBindings,
|
|
420
|
-
|
|
420
|
+
type: aggregate.task.type
|
|
421
421
|
},
|
|
422
422
|
workItems: values(aggregate.workItems, "id"),
|
|
423
423
|
changeSets: values(aggregate.changeSets, "id"),
|
|
424
424
|
integrations: values(aggregate.integrationAttempts, "id"),
|
|
425
|
+
integrationQueueEntries: values(aggregate.integrationQueue, "id"),
|
|
425
426
|
reviewRounds: values(aggregate.reviewRounds, "id"),
|
|
426
427
|
taskFinalReviewContractEvents: events
|
|
427
428
|
.filter((event) => event.type === TASK_FINAL_REVIEW_CONTRACT_REBOUND_EVENT),
|
|
@@ -1026,18 +1027,16 @@ export class FileTaskStore {
|
|
|
1026
1027
|
throw new StorageRecordError(`ReviewRound belongs to another Task: ${stored.taskId}.`);
|
|
1027
1028
|
}
|
|
1028
1029
|
const aggregate = this.#requireTaskForWrite(taskId);
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
&& !sameTaskFinalReviewContract(stored.taskFinalReviewContract, candidate.taskFinalReviewContract)) {
|
|
1040
|
-
throw new StorageRecordError(`Task ReviewRound contract does not match its Candidate: ${stored.id}.`);
|
|
1030
|
+
if ((stored.scope ?? "work-item") === "work-item") {
|
|
1031
|
+
const item = aggregate.workItems[stored.workItemId];
|
|
1032
|
+
if (item === undefined) {
|
|
1033
|
+
throw new StorageRecordError(`ReviewRound Work Item not found: ${stored.workItemId}.`);
|
|
1034
|
+
}
|
|
1035
|
+
const candidate = item.candidates.find(({ id }) => id === stored.candidateId);
|
|
1036
|
+
if (candidate === undefined) {
|
|
1037
|
+
throw new StorageRecordError(`ReviewRound Candidate not found: ${stored.candidateId}.`);
|
|
1038
|
+
}
|
|
1039
|
+
assertWorkItemCandidateReferences(aggregate, item, candidate, `ReviewRound candidate ${stored.id}`);
|
|
1041
1040
|
}
|
|
1042
1041
|
if (stored.reviewerRunId !== undefined) {
|
|
1043
1042
|
const reviewerRun = aggregate.agentRuns[stored.reviewerRunId];
|
|
@@ -3349,24 +3348,23 @@ function validateCanonicalTaskReferences(state, aggregate) {
|
|
|
3349
3348
|
}
|
|
3350
3349
|
}
|
|
3351
3350
|
for (const round of Object.values(aggregate.reviewRounds)) {
|
|
3352
|
-
const item = aggregate.workItems[round.workItemId];
|
|
3353
|
-
if (item === undefined) {
|
|
3354
|
-
throw new StorageRecordError(`ReviewRound references are invalid: ${round.id}.`);
|
|
3355
|
-
}
|
|
3356
|
-
const candidate = item.candidates.find(({ id }) => id === round.candidateId);
|
|
3357
|
-
if (candidate === undefined) {
|
|
3358
|
-
throw new StorageRecordError(`ReviewRound Candidate not found: ${round.candidateId}.`);
|
|
3359
|
-
}
|
|
3360
|
-
assertWorkItemCandidateReferences(aggregate, item, candidate, `ReviewRound candidate ${round.id}`);
|
|
3361
3351
|
if ((round.scope ?? "work-item") === "task") {
|
|
3362
3352
|
const frozenProjects = round.taskCandidate?.projects ?? [];
|
|
3363
3353
|
if (frozenProjects.length !== boundProjects.size
|
|
3364
3354
|
|| frozenProjects.some(({ projectId }) => !boundProjects.has(projectId))) {
|
|
3365
3355
|
throw new StorageRecordError(`Task ReviewRound Projects do not match Task scope: ${round.id}.`);
|
|
3366
3356
|
}
|
|
3367
|
-
|
|
3368
|
-
|
|
3357
|
+
}
|
|
3358
|
+
else {
|
|
3359
|
+
const item = aggregate.workItems[round.workItemId];
|
|
3360
|
+
if (item === undefined) {
|
|
3361
|
+
throw new StorageRecordError(`ReviewRound references are invalid: ${round.id}.`);
|
|
3362
|
+
}
|
|
3363
|
+
const candidate = item.candidates.find(({ id }) => id === round.candidateId);
|
|
3364
|
+
if (candidate === undefined) {
|
|
3365
|
+
throw new StorageRecordError(`ReviewRound Candidate not found: ${round.candidateId}.`);
|
|
3369
3366
|
}
|
|
3367
|
+
assertWorkItemCandidateReferences(aggregate, item, candidate, `ReviewRound candidate ${round.id}`);
|
|
3370
3368
|
}
|
|
3371
3369
|
if (round.reviewerRunId !== undefined) {
|
|
3372
3370
|
const reviewerRun = aggregate.agentRuns[round.reviewerRunId];
|
|
@@ -3496,7 +3494,9 @@ function assertManagedWorkspaceReferences(aggregate, workspace, label, workItemO
|
|
|
3496
3494
|
if (workspace.entries.some(({ access }) => access !== "write")) {
|
|
3497
3495
|
throw new StorageRecordError(`${label} ReviewRound workspace must be writable: ${taskId}/${round.id}.`);
|
|
3498
3496
|
}
|
|
3499
|
-
const item =
|
|
3497
|
+
const item = round.workItemId === undefined
|
|
3498
|
+
? undefined
|
|
3499
|
+
: aggregate.workItems[round.workItemId];
|
|
3500
3500
|
const candidate = item?.candidates.find(({ id }) => id === round.candidateId);
|
|
3501
3501
|
const frozenProjects = round.scope === "task"
|
|
3502
3502
|
? round.taskCandidate?.projects
|
|
@@ -298,7 +298,7 @@ function describeOfflineBlockers(inventory) {
|
|
|
298
298
|
return `${index + 1}. ${identity.length === 0 ? "identity=unknown" : identity} ` +
|
|
299
299
|
`reason=${reason}`;
|
|
300
300
|
});
|
|
301
|
-
return `Offline migration
|
|
301
|
+
return `Offline Home migration cannot start while ${inventory.total} runtime obligation(s) remain. ` +
|
|
302
302
|
lines.join("; ");
|
|
303
303
|
}
|
|
304
304
|
async function readOfflineInventory(options, home) {
|
|
@@ -315,15 +315,43 @@ async function readOfflineInventory(options, home) {
|
|
|
315
315
|
}
|
|
316
316
|
function offlineInventoryBlocker(inventory, sceneUnchanged) {
|
|
317
317
|
const lifecycleOnly = inventory.blockers.every(({ reason }) => (reason === "pending-mailbox" || reason === "pending-inbox"));
|
|
318
|
+
const runtimeUnknown = inventory.blockers.some(({ reason }) => (reason === "native-session-unknown"));
|
|
319
|
+
const action = lifecycleOnly
|
|
320
|
+
? (sceneUnchanged
|
|
321
|
+
? "No binary, Controller, fence, or Home change was made. Wait for the listed lifecycle "
|
|
322
|
+
+ "events to settle, then re-run `yui update`."
|
|
323
|
+
: "The Home was not switched. Wait for the listed lifecycle events to settle, then "
|
|
324
|
+
+ "re-run `yui update`.")
|
|
325
|
+
: runtimeUnknown
|
|
326
|
+
? (sceneUnchanged
|
|
327
|
+
? "Offline Home migration could not confirm that every native Session is stopped. Run "
|
|
328
|
+
+ "`yui controller status --verbose` from a normal shell and inspect the reported "
|
|
329
|
+
+ "runtime ownership. Use `yui controller cleanup` only for resources it classifies "
|
|
330
|
+
+ "for cleanup, then re-run `yui update`; do not kill unknown processes blindly. No "
|
|
331
|
+
+ "binary, Controller, fence, or Home change was made."
|
|
332
|
+
: "The Home was not switched because native Session state could not be confirmed. Run "
|
|
333
|
+
+ "`yui controller status --verbose`, inspect the reported ownership, and use "
|
|
334
|
+
+ "`yui controller cleanup` only for resources it classifies for cleanup before "
|
|
335
|
+
+ "retrying; do not kill unknown processes blindly.")
|
|
336
|
+
: (sceneUnchanged
|
|
337
|
+
? "This version requires an offline Home migration with every managed Agent Session "
|
|
338
|
+
+ "stopped. Let the listed Turns or Runs finish. If the installed `yui` supports "
|
|
339
|
+
+ "`session stop --all`, run `yui session stop --all` from a normal shell; otherwise "
|
|
340
|
+
+ "exit every listed managed Session manually. Then re-run `yui update`. No binary, "
|
|
341
|
+
+ "Controller, fence, or Home change was made."
|
|
342
|
+
: "The Home was not switched. Let the listed Turns or Runs finish. If the installed "
|
|
343
|
+
+ "`yui` supports `session stop --all`, run `yui session stop --all` from a normal "
|
|
344
|
+
+ "shell; otherwise exit every listed managed Session manually. Then re-run "
|
|
345
|
+
+ "`yui update`.");
|
|
318
346
|
return {
|
|
319
347
|
outcome: "blocked",
|
|
320
|
-
stage: lifecycleOnly
|
|
348
|
+
stage: lifecycleOnly
|
|
349
|
+
? "drain-incomplete"
|
|
350
|
+
: runtimeUnknown
|
|
351
|
+
? "runtime-unknown"
|
|
352
|
+
: "active-sessions",
|
|
321
353
|
message: describeOfflineBlockers(inventory),
|
|
322
|
-
action
|
|
323
|
-
? "No binary, Controller, fence, or Home change was made. Keep working; when every " +
|
|
324
|
-
"listed runtime obligation is clear, re-run `yui update` so preflight is repeated."
|
|
325
|
-
: "The Home was not switched. Let every listed runtime obligation settle, then re-run " +
|
|
326
|
-
"`yui update`; do not kill, reset, rebind, or retry the blocked runtime blindly.",
|
|
354
|
+
action,
|
|
327
355
|
blockers: inventory.blockers,
|
|
328
356
|
retryCommand: "yui update",
|
|
329
357
|
...(sceneUnchanged ? { sceneUnchanged: true } : {})
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { changeSetDeliverySettled, governingChangeSets, integrationAttemptRequiresSettlement, latestGoverningQueueEntries } from "../integration/deliveryObligation.js";
|
|
1
2
|
import { isReviewFindingBlocking } from "../review/reviewFinding.js";
|
|
2
3
|
import { deltaRecheckBlocksAcceptance } from "../review/reviewRound.js";
|
|
3
4
|
import { isSemanticReviewRound } from "../review/reviewOutcomeClassifier.js";
|
|
4
5
|
import { reviewFindingLedgerWriteFailedFromEvents } from "../review/reviewFindingLedger.js";
|
|
6
|
+
import { resolveRecordedTaskFinalReviewContract } from "../review/taskFinalReviewContractRebind.js";
|
|
5
7
|
import { blockingProviderContinuations } from "../runtime/runtimeContinuationProjection.js";
|
|
6
8
|
const ACTIVE_JOB_STATUSES = new Set([
|
|
7
9
|
"queued",
|
|
@@ -20,6 +22,7 @@ export function projectCompletionReadiness(facts, options = {}) {
|
|
|
20
22
|
const advisories = [];
|
|
21
23
|
const { task } = facts;
|
|
22
24
|
const findingsGate = options.findingsGate ?? true;
|
|
25
|
+
const taskFinalReviewRequired = resolveRecordedTaskFinalReviewContract(task.id, facts.workItems, facts.reviewRounds, facts.taskFinalReviewContractEvents) !== undefined;
|
|
23
26
|
// A pending/running Task-final Review must be resumed or blocked first.
|
|
24
27
|
for (const round of facts.reviewRounds) {
|
|
25
28
|
if ((round.scope ?? "work-item") !== "task")
|
|
@@ -49,7 +52,8 @@ export function projectCompletionReadiness(facts, options = {}) {
|
|
|
49
52
|
.sort((left, right) => (left.createdAt.localeCompare(right.createdAt)
|
|
50
53
|
|| left.id.localeCompare(right.id, undefined, { numeric: true })))
|
|
51
54
|
.at(-1);
|
|
52
|
-
if (
|
|
55
|
+
if (taskFinalReviewRequired
|
|
56
|
+
&& latestCompletedTaskReview !== undefined
|
|
53
57
|
&& deltaRecheckBlocksAcceptance(latestCompletedTaskReview)) {
|
|
54
58
|
const round = latestCompletedTaskReview;
|
|
55
59
|
const disposition = round.deltaRecheck.disposition;
|
|
@@ -97,37 +101,43 @@ export function projectCompletionReadiness(facts, options = {}) {
|
|
|
97
101
|
fix: `wait for DurableJob ${job.id} to finish, or cancel it`
|
|
98
102
|
});
|
|
99
103
|
}
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
if (
|
|
111
|
-
|
|
112
|
-
code: "integration-evidence-missing",
|
|
113
|
-
ref: ref("task", task.id),
|
|
114
|
-
reason: `Task ${task.id} requires at least one ChangeSet before completion.`,
|
|
115
|
-
fix: `yui task work capture ${task.id}/<work-item>`
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
if (!facts.integrations.some(({ status }) => status === "committed")) {
|
|
104
|
+
// Integration obligations follow only the Candidate that currently governs
|
|
105
|
+
// each independent delivery unit. Superseded Candidate history remains
|
|
106
|
+
// visible without keeping the Task open forever.
|
|
107
|
+
const deliveryChangeSets = governingChangeSets(facts.workItems, facts.changeSets);
|
|
108
|
+
for (const item of facts.workItems) {
|
|
109
|
+
if (item.status !== "completed")
|
|
110
|
+
continue;
|
|
111
|
+
const candidate = item.candidates?.at(-1);
|
|
112
|
+
const hasGitDelivery = candidate?.workspace !== undefined
|
|
113
|
+
|| candidate?.gitSnapshot !== undefined;
|
|
114
|
+
if (hasGitDelivery
|
|
115
|
+
&& !deliveryChangeSets.some((changeSet) => changeSet.workItemId === item.id)) {
|
|
119
116
|
blockers.push({
|
|
120
117
|
code: "integration-evidence-missing",
|
|
121
|
-
ref: ref("
|
|
122
|
-
reason: `
|
|
123
|
-
fix: `yui task
|
|
118
|
+
ref: ref("work-item", item.id),
|
|
119
|
+
reason: `Completed WorkItem ${item.id} has an uncaptured Git delivery.`,
|
|
120
|
+
fix: `yui task work capture ${task.id}/${item.id}`
|
|
124
121
|
});
|
|
125
122
|
}
|
|
126
123
|
}
|
|
127
|
-
|
|
124
|
+
for (const changeSet of deliveryChangeSets) {
|
|
125
|
+
if (changeSetDeliverySettled(changeSet, facts.integrations, facts.integrationQueueEntries))
|
|
126
|
+
continue;
|
|
127
|
+
blockers.push({
|
|
128
|
+
code: "integration-evidence-missing",
|
|
129
|
+
ref: ref("change-set", changeSet.id),
|
|
130
|
+
reason: `ChangeSet ${changeSet.id} is not part of a committed Integration.`,
|
|
131
|
+
fix: `yui task integration start ${task.id} --project ${changeSet.projectId} --change-set ${changeSet.id}`
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
// Current delivery Attempts and any Attempt that may still be writing must
|
|
135
|
+
// settle. Historical blocked Attempts remain audit evidence only.
|
|
128
136
|
for (const integration of facts.integrations) {
|
|
129
137
|
if (!UNRESOLVED_INTEGRATION_STATUSES.has(integration.status))
|
|
130
138
|
continue;
|
|
139
|
+
if (!integrationAttemptRequiresSettlement(integration, deliveryChangeSets))
|
|
140
|
+
continue;
|
|
131
141
|
blockers.push({
|
|
132
142
|
code: "unresolved-integration",
|
|
133
143
|
ref: ref("integration-attempt", integration.id),
|
|
@@ -136,7 +146,7 @@ export function projectCompletionReadiness(facts, options = {}) {
|
|
|
136
146
|
});
|
|
137
147
|
}
|
|
138
148
|
// Unsettled integration queue entries must commit or be superseded.
|
|
139
|
-
for (const entry of facts.integrationQueueEntries) {
|
|
149
|
+
for (const entry of latestGoverningQueueEntries(deliveryChangeSets, facts.integrationQueueEntries)) {
|
|
140
150
|
if (entry.status === "committed" || entry.status === "superseded")
|
|
141
151
|
continue;
|
|
142
152
|
blockers.push({
|