@zq-silk/yui 0.14.1 → 0.15.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 +91 -47
- package/README.md +275 -145
- 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/updateCommand.js +17 -9
- package/dist/cli/updateOrchestrator.js +87 -19
- package/dist/cli/updatePorts.js +79 -13
- package/dist/cli/upgradeCommand.js +105 -23
- 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/controllerCommands.js +1 -1
- 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/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 +6 -5
- package/dist/doctor/doctor.js +37 -14
- 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 +16 -82
- 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/observability/runtimeIdentity.js +48 -50
- package/dist/profile/agentProfile.js +60 -15
- package/dist/profile/agentProfileRuntime.js +100 -0
- package/dist/release/releaseWorkflowPorts.js +5 -68
- package/dist/release/runtimeRelease.js +9 -1
- 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 +22 -296
- 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/currentTaskStore.js +6 -4
- package/dist/storage/sqliteSchema.js +135 -80
- package/dist/storage/sqliteStore.js +69 -52
- package/dist/storage/storageSchema.js +92 -230
- package/dist/storage/storageVersions.js +12 -13
- package/dist/storage/taskStore.js +8 -8
- package/dist/storage/upgrade/upgradeOrchestrator.js +276 -85
- 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/version.js +3 -3
- 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 +9 -9
- package/i18n/README.zh-CN.md +189 -74
- 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 +22 -0
- 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/storage/upgrade/recordVersions.js +0 -82
- package/dist/task/repairWave.js +0 -150
|
@@ -1,117 +1,305 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { basename, dirname, join } from "node:path";
|
|
2
|
+
import { copyFileSync, existsSync, mkdirSync, rmSync } from "node:fs";
|
|
3
|
+
import Database from "better-sqlite3";
|
|
4
|
+
import { RUNTIME_OBSERVATION_TASK_EVENT, createRuntimeObservation } from "../../runtime/runtimeObservation.js";
|
|
5
|
+
import { RUNTIME_PROCESS_EXIT_TASK_EVENT, validateRuntimeProcessExitObservation } from "../../runtime/processExitObservation.js";
|
|
6
|
+
import { validateAgentProfile } from "../../profile/agentProfile.js";
|
|
7
|
+
import { validateRoleSessionSet } from "../../executor/agentExecutor.js";
|
|
8
|
+
import { validateReviewRound } from "../../review/reviewRound.js";
|
|
9
|
+
import { validateTurn } from "../../turn/turn.js";
|
|
10
|
+
import { validateWorkItem } from "../../workItem/workItem.js";
|
|
3
11
|
import { SqliteTaskStore } from "../sqliteStore.js";
|
|
4
|
-
import {
|
|
5
|
-
|
|
12
|
+
import { migrateSqliteSchema, storageMigrationPlan } from "../sqliteSchema.js";
|
|
13
|
+
import { CURRENT_DATABASE_FILENAME, inspectStorageSchema } from "../storageSchema.js";
|
|
14
|
+
import { CURRENT_STORAGE_VERSION, MIN_SUPPORTED_STORAGE_VERSION } from "../storageVersions.js";
|
|
6
15
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
16
|
+
* Upgrade one valid Home through the complete linear migration chain.
|
|
17
|
+
*
|
|
18
|
+
* Supported earlier versions are readable only here. Ordinary stores still
|
|
19
|
+
* admit exactly {@link CURRENT_STORAGE_VERSION}; no old-shape normalizer or
|
|
20
|
+
* dual read path enters runtime code. Execute mode is an offline primitive:
|
|
21
|
+
* its caller must own the maintenance fence and keep the Controller quiesced
|
|
22
|
+
* for the whole backup, migration, and validation interval.
|
|
9
23
|
*/
|
|
10
24
|
export async function runStorageUpgrade(options) {
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return blocked(classification, "uninitialized", "Yui storage is not initialized for this Home.", "Run `yui setup` with a new Home.");
|
|
25
|
+
const state = inspectStorageSchema(options.home);
|
|
26
|
+
if (state.status === "uninitialized") {
|
|
27
|
+
return blocked({ ...corruptedClassification("Storage is not initialized."), uninitialized: true }, "uninitialized", "Yui storage is not initialized for this Home.", "Run `yui setup` with a new Home.");
|
|
15
28
|
}
|
|
16
|
-
if (
|
|
17
|
-
return blocked(
|
|
29
|
+
if (state.status === "invalid") {
|
|
30
|
+
return blocked(corruptedClassification(state.detail), "corruption", `Storage is invalid: ${state.detail}`, "Preserve this Home for diagnosis and restore it from a known-good backup.");
|
|
18
31
|
}
|
|
19
|
-
if (
|
|
20
|
-
|
|
32
|
+
if (state.status === "unsupported") {
|
|
33
|
+
const classification = unsupportedClassification(state);
|
|
34
|
+
return blocked(classification, "unsupported", classification.classification.verdict === "NEEDS_NEW_VERSION"
|
|
35
|
+
? classification.classification.blocker.message
|
|
36
|
+
: "Storage is unsupported.", classification.classification.verdict === "NEEDS_NEW_VERSION"
|
|
37
|
+
? classification.classification.blocker.action
|
|
38
|
+
: "Use a compatible Yui release.");
|
|
21
39
|
}
|
|
22
|
-
if (
|
|
23
|
-
return blocked(corruptedClassification(classification, "The current SQLite database is missing."), "corruption", "The current SQLite Home is incomplete: yui.db is missing.", "Preserve this Home for diagnosis and initialize a new Home.");
|
|
24
|
-
}
|
|
25
|
-
try {
|
|
26
|
-
const store = new SqliteTaskStore(options.home);
|
|
40
|
+
if (state.status === "current") {
|
|
27
41
|
try {
|
|
28
|
-
|
|
42
|
+
validateCurrentStore(options.home);
|
|
29
43
|
}
|
|
30
|
-
|
|
31
|
-
|
|
44
|
+
catch (error) {
|
|
45
|
+
return blocked(corruptedClassification(messageOf(error)), "corruption", `Current storage validation failed: ${messageOf(error)}`, "Preserve this Home for diagnosis and restore it from a known-good backup.");
|
|
46
|
+
}
|
|
47
|
+
const classification = currentClassification();
|
|
48
|
+
if (options.mode === "update-preflight") {
|
|
49
|
+
return {
|
|
50
|
+
outcome: "update-preflight",
|
|
51
|
+
status: "already-current",
|
|
52
|
+
stepCount: 0,
|
|
53
|
+
steps: [],
|
|
54
|
+
classification
|
|
55
|
+
};
|
|
32
56
|
}
|
|
57
|
+
return {
|
|
58
|
+
outcome: "already-current",
|
|
59
|
+
classification,
|
|
60
|
+
report: {
|
|
61
|
+
outcome: "already-current",
|
|
62
|
+
mode: options.mode,
|
|
63
|
+
sourceVersion: CURRENT_STORAGE_VERSION,
|
|
64
|
+
targetVersion: CURRENT_STORAGE_VERSION,
|
|
65
|
+
steps: []
|
|
66
|
+
}
|
|
67
|
+
};
|
|
33
68
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
return blocked(corruptedClassification(
|
|
69
|
+
const plan = storageMigrationPlan(state.currentVersion);
|
|
70
|
+
if (plan === null) {
|
|
71
|
+
return blocked(corruptedClassification(`No complete migration path exists from ${state.currentVersion} `
|
|
72
|
+
+ `to ${CURRENT_STORAGE_VERSION}.`), "corruption", "The storage migration registry is incomplete.", "Install a Yui release that carries the complete migration chain.");
|
|
37
73
|
}
|
|
74
|
+
const classification = migratableClassification(state.currentVersion);
|
|
38
75
|
if (options.mode === "update-preflight") {
|
|
39
76
|
return {
|
|
40
77
|
outcome: "update-preflight",
|
|
41
|
-
status: "
|
|
42
|
-
stepCount:
|
|
78
|
+
status: "migration-ready",
|
|
79
|
+
stepCount: plan.length,
|
|
80
|
+
steps: plan,
|
|
43
81
|
classification
|
|
44
82
|
};
|
|
45
83
|
}
|
|
46
|
-
|
|
47
|
-
outcome: "already-current",
|
|
48
|
-
mode: options.mode,
|
|
49
|
-
source: options.latest,
|
|
50
|
-
target: options.latest
|
|
51
|
-
};
|
|
52
|
-
return { outcome: "already-current", classification, report };
|
|
53
|
-
}
|
|
54
|
-
function classifyCurrentHome(schema, latest) {
|
|
55
|
-
const base = {
|
|
56
|
-
latestLayoutVersion: latest.layout,
|
|
57
|
-
latestAggregateVersion: latest.aggregate
|
|
58
|
-
};
|
|
59
|
-
if (schema.status === "uninitialized") {
|
|
84
|
+
if (options.mode === "dry-run") {
|
|
60
85
|
return {
|
|
61
|
-
|
|
62
|
-
classification
|
|
63
|
-
|
|
86
|
+
outcome: "upgrade-plan",
|
|
87
|
+
classification,
|
|
88
|
+
report: {
|
|
89
|
+
outcome: "upgrade-plan",
|
|
90
|
+
mode: "dry-run",
|
|
91
|
+
sourceVersion: state.currentVersion,
|
|
92
|
+
targetVersion: CURRENT_STORAGE_VERSION,
|
|
93
|
+
steps: plan
|
|
94
|
+
}
|
|
64
95
|
};
|
|
65
96
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
97
|
+
let backupPath;
|
|
98
|
+
try {
|
|
99
|
+
backupPath = await createDatabaseBackup(options.home, state.currentVersion, options.now ?? new Date());
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
return {
|
|
103
|
+
outcome: "failed",
|
|
104
|
+
stage: "backup",
|
|
105
|
+
message: `Storage backup failed: ${messageOf(error)}`,
|
|
106
|
+
action: "Storage was not modified. Resolve the backup path, permissions, or free-space problem "
|
|
107
|
+
+ "and rerun `yui upgrade`.",
|
|
108
|
+
classification,
|
|
109
|
+
sceneUnchanged: true
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
let migrationCommitted = false;
|
|
113
|
+
try {
|
|
114
|
+
const database = new Database(join(options.home, CURRENT_DATABASE_FILENAME));
|
|
115
|
+
try {
|
|
116
|
+
database.pragma("journal_mode = WAL");
|
|
117
|
+
database.pragma("synchronous = FULL");
|
|
118
|
+
database.pragma("foreign_keys = ON");
|
|
119
|
+
database.pragma("busy_timeout = 5000");
|
|
120
|
+
migrateSqliteSchema(database, { mode: "apply" });
|
|
121
|
+
migrationCommitted = true;
|
|
122
|
+
}
|
|
123
|
+
finally {
|
|
124
|
+
database.close();
|
|
125
|
+
}
|
|
126
|
+
validateCurrentStore(options.home);
|
|
127
|
+
rmSync(join(options.home, "schema.json"), { force: true });
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
const restoration = migrationCommitted
|
|
131
|
+
? tryRestoreDatabaseBackup(options.home, backupPath)
|
|
132
|
+
: { restored: true };
|
|
92
133
|
return {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
134
|
+
outcome: "failed",
|
|
135
|
+
stage: "migration",
|
|
136
|
+
message: `Storage migration failed: ${messageOf(error)}`,
|
|
137
|
+
action: restoration.restored
|
|
138
|
+
? "The original database was restored from the timestamped backup. "
|
|
139
|
+
+ "Resolve the reported problem and rerun `yui upgrade`."
|
|
140
|
+
: "Automatic restore also failed. Keep the Home quiesced and restore "
|
|
141
|
+
+ `${backupPath} manually before retrying. Restore error: ${restoration.error}`,
|
|
142
|
+
backupPath,
|
|
143
|
+
classification,
|
|
144
|
+
sceneUnchanged: restoration.restored
|
|
102
145
|
};
|
|
103
146
|
}
|
|
147
|
+
const finalState = inspectStorageSchema(options.home);
|
|
148
|
+
if (finalState.status !== "current") {
|
|
149
|
+
const restoration = tryRestoreDatabaseBackup(options.home, backupPath);
|
|
150
|
+
return {
|
|
151
|
+
outcome: "failed",
|
|
152
|
+
stage: "migration",
|
|
153
|
+
message: `Storage migration did not reach version ${CURRENT_STORAGE_VERSION}.`,
|
|
154
|
+
action: restoration.restored
|
|
155
|
+
? "The original database was restored from the timestamped backup. "
|
|
156
|
+
+ "Inspect the migration registry before retrying."
|
|
157
|
+
: "Automatic restore also failed. Keep the Home quiesced and restore "
|
|
158
|
+
+ `${backupPath} manually before retrying. Restore error: ${restoration.error}`,
|
|
159
|
+
backupPath,
|
|
160
|
+
classification,
|
|
161
|
+
sceneUnchanged: restoration.restored
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
outcome: "upgraded",
|
|
166
|
+
classification: currentClassification(),
|
|
167
|
+
report: {
|
|
168
|
+
outcome: "upgraded",
|
|
169
|
+
mode: "execute",
|
|
170
|
+
sourceVersion: state.currentVersion,
|
|
171
|
+
targetVersion: CURRENT_STORAGE_VERSION,
|
|
172
|
+
steps: plan,
|
|
173
|
+
backupPath
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function validateCurrentStore(home) {
|
|
178
|
+
const store = new SqliteTaskStore(home);
|
|
179
|
+
try {
|
|
180
|
+
store.getConfig();
|
|
181
|
+
for (const profile of store.listAgentProfiles())
|
|
182
|
+
validateAgentProfile(profile);
|
|
183
|
+
for (const sessions of store.listGlobalRoleSessionSets()) {
|
|
184
|
+
validateRoleSessionSet(sessions);
|
|
185
|
+
}
|
|
186
|
+
for (const owner of store.listSessionOwners()) {
|
|
187
|
+
if (owner.schemaVersion !== 2
|
|
188
|
+
|| typeof owner.runtimeGenerationId !== "string"
|
|
189
|
+
|| Object.hasOwn(owner, "launchId")) {
|
|
190
|
+
throw new Error("Session owner runtime generation identity is invalid.");
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
for (const taskId of store.listTasks().map(({ id }) => id)) {
|
|
194
|
+
for (const item of store.listWorkItems(taskId))
|
|
195
|
+
validateWorkItem(item);
|
|
196
|
+
for (const round of store.listReviewRounds(taskId))
|
|
197
|
+
validateReviewRound(round);
|
|
198
|
+
for (const turn of store.listTurns(taskId))
|
|
199
|
+
validateTurn(turn);
|
|
200
|
+
for (const sessions of store.listRoleSessionSets(taskId)) {
|
|
201
|
+
validateRoleSessionSet(sessions);
|
|
202
|
+
}
|
|
203
|
+
for (const event of store.listEvents(taskId)) {
|
|
204
|
+
if (event.type === RUNTIME_OBSERVATION_TASK_EVENT) {
|
|
205
|
+
createRuntimeObservation(JSON.parse(event.payload.observation ?? ""));
|
|
206
|
+
}
|
|
207
|
+
else if (event.type === RUNTIME_PROCESS_EXIT_TASK_EVENT) {
|
|
208
|
+
validateRuntimeProcessExitObservation(JSON.parse(event.payload.observation ?? ""));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const quickCheck = store.databaseHandle().pragma("quick_check", { simple: true });
|
|
213
|
+
if (quickCheck !== "ok") {
|
|
214
|
+
throw new Error(`SQLite quick_check failed: ${String(quickCheck)}.`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
finally {
|
|
218
|
+
store.close();
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
async function createDatabaseBackup(home, sourceVersion, now) {
|
|
222
|
+
const source = join(home, CURRENT_DATABASE_FILENAME);
|
|
223
|
+
if (!existsSync(source))
|
|
224
|
+
throw new Error("The authoritative yui.db is missing.");
|
|
225
|
+
const backupDirectory = join(dirname(home), `${basename(home)}-backups`);
|
|
226
|
+
mkdirSync(backupDirectory, { recursive: true, mode: 0o700 });
|
|
227
|
+
const timestamp = now.toISOString().replaceAll(":", "-");
|
|
228
|
+
const backupPath = join(backupDirectory, `pre-storage-v${sourceVersion}-${timestamp}.db`);
|
|
229
|
+
const database = new Database(source, { readonly: true, fileMustExist: true });
|
|
230
|
+
try {
|
|
231
|
+
await database.backup(backupPath);
|
|
232
|
+
}
|
|
233
|
+
finally {
|
|
234
|
+
database.close();
|
|
235
|
+
}
|
|
236
|
+
return backupPath;
|
|
237
|
+
}
|
|
238
|
+
function restoreDatabaseBackup(home, backupPath) {
|
|
239
|
+
const databasePath = join(home, CURRENT_DATABASE_FILENAME);
|
|
240
|
+
rmSync(`${databasePath}-wal`, { force: true });
|
|
241
|
+
rmSync(`${databasePath}-shm`, { force: true });
|
|
242
|
+
copyFileSync(backupPath, databasePath);
|
|
243
|
+
}
|
|
244
|
+
function tryRestoreDatabaseBackup(home, backupPath) {
|
|
245
|
+
try {
|
|
246
|
+
restoreDatabaseBackup(home, backupPath);
|
|
247
|
+
return { restored: true };
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
return { restored: false, error: messageOf(error) };
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
function currentClassification() {
|
|
104
254
|
return {
|
|
105
|
-
...base,
|
|
106
255
|
classification: { verdict: "USABLE", status: "current" },
|
|
107
|
-
|
|
108
|
-
|
|
256
|
+
storageVersion: CURRENT_STORAGE_VERSION,
|
|
257
|
+
currentStorageVersion: CURRENT_STORAGE_VERSION,
|
|
258
|
+
minimumSupportedStorageVersion: MIN_SUPPORTED_STORAGE_VERSION
|
|
109
259
|
};
|
|
110
260
|
}
|
|
111
|
-
function
|
|
261
|
+
function migratableClassification(storageVersion) {
|
|
112
262
|
return {
|
|
113
|
-
|
|
114
|
-
|
|
263
|
+
classification: { verdict: "MIGRATABLE", status: "migration-ready" },
|
|
264
|
+
storageVersion,
|
|
265
|
+
currentStorageVersion: CURRENT_STORAGE_VERSION,
|
|
266
|
+
minimumSupportedStorageVersion: MIN_SUPPORTED_STORAGE_VERSION
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
function unsupportedClassification(state) {
|
|
270
|
+
const future = state.direction === "newer";
|
|
271
|
+
const message = future
|
|
272
|
+
? `Storage version ${state.currentVersion} is newer than this CLI supports `
|
|
273
|
+
+ `(${CURRENT_STORAGE_VERSION}).`
|
|
274
|
+
: `Storage version ${state.currentVersion} is older than the minimum supported `
|
|
275
|
+
+ `migration version ${MIN_SUPPORTED_STORAGE_VERSION}.`;
|
|
276
|
+
const action = future
|
|
277
|
+
? "Use a newer Yui release."
|
|
278
|
+
: "Preserve this Home for use with its matching historical Yui release, "
|
|
279
|
+
+ "or initialize a new Home with Yui 0.15.0 or later.";
|
|
280
|
+
return {
|
|
281
|
+
classification: {
|
|
282
|
+
verdict: "NEEDS_NEW_VERSION",
|
|
283
|
+
status: "unsupported",
|
|
284
|
+
blocker: {
|
|
285
|
+
reason: future ? "future-version" : "below-minimum",
|
|
286
|
+
found: state.currentVersion,
|
|
287
|
+
current: CURRENT_STORAGE_VERSION,
|
|
288
|
+
minimum: MIN_SUPPORTED_STORAGE_VERSION,
|
|
289
|
+
message,
|
|
290
|
+
action
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
storageVersion: state.currentVersion,
|
|
294
|
+
currentStorageVersion: CURRENT_STORAGE_VERSION,
|
|
295
|
+
minimumSupportedStorageVersion: MIN_SUPPORTED_STORAGE_VERSION
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
function corruptedClassification(detail) {
|
|
299
|
+
return {
|
|
300
|
+
classification: { verdict: "CORRUPTED", status: "unsupported", detail },
|
|
301
|
+
currentStorageVersion: CURRENT_STORAGE_VERSION,
|
|
302
|
+
minimumSupportedStorageVersion: MIN_SUPPORTED_STORAGE_VERSION
|
|
115
303
|
};
|
|
116
304
|
}
|
|
117
305
|
function blocked(classification, stage, message, action) {
|
|
@@ -124,3 +312,6 @@ function blocked(classification, stage, message, action) {
|
|
|
124
312
|
sceneUnchanged: true
|
|
125
313
|
};
|
|
126
314
|
}
|
|
315
|
+
function messageOf(error) {
|
|
316
|
+
return error instanceof Error ? error.message : String(error);
|
|
317
|
+
}
|
|
@@ -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`,
|