@zq-silk/yui 0.14.0 → 0.14.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +64 -35
- package/README.md +258 -125
- package/dist/agent/argumentPolicy.js +2 -1
- package/dist/agent/managedRuntimeEnvironment.js +5 -8
- package/dist/cli/commandCatalog.js +74 -109
- package/dist/cli/helpRenderer.js +1 -2
- package/dist/cli/interactionCandidates.js +10 -0
- package/dist/cli/interactionPolicy.js +76 -37
- package/dist/cli/roleWizard.js +5 -6
- package/dist/cli/updateOrchestrator.js +10 -8
- package/dist/cli/updatePorts.js +17 -13
- package/dist/cli/upgradeCommand.js +2 -5
- package/dist/cli.js +268 -231
- package/dist/commands/agentCommands.js +13 -0
- package/dist/commands/configCommands.js +7 -19
- package/dist/commands/configOverview.js +3 -2
- package/dist/commands/durableJobCommands.js +1 -1
- package/dist/commands/executionAuditCommands.js +4 -9
- package/dist/commands/globalRoleCommands.js +17 -0
- package/dist/commands/profileCommands.js +200 -30
- package/dist/commands/sessionCommands.js +1 -1
- package/dist/commands/taskActor.js +29 -127
- package/dist/commands/taskChangeSetCommands.js +1 -3
- package/dist/commands/taskCommands.js +1771 -2107
- package/dist/commands/taskCompletionGate.js +27 -103
- package/dist/commands/taskContextCommand.js +47 -80
- package/dist/commands/taskExecutionCommands.js +9 -27
- package/dist/commands/taskInputCommands.js +10 -10
- package/dist/commands/taskIntegrationCommands.js +69 -61
- package/dist/commands/taskIntegrationQueueCommands.js +1 -1
- package/dist/commands/taskNextActionCommand.js +8 -29
- package/dist/commands/taskOverviewCommand.js +25 -3
- package/dist/commands/taskPublicationCommands.js +151 -85
- package/dist/commands/taskPublicationVerifyCommand.js +181 -0
- package/dist/commands/taskRemoteDeliveryCommand.js +134 -0
- package/dist/commands/taskRoleRuntimeStatus.js +5 -6
- package/dist/commands/taskUpstreamCommands.js +79 -95
- package/dist/config/configCatalog.js +1 -1
- package/dist/context/contextSnapshot.js +17 -2
- package/dist/context/sessionBootstrapManifest.js +18 -1
- package/dist/context/sourceTurnContext.js +30 -0
- package/dist/context/turnContextPack.js +107 -48
- package/dist/context/turnInputContract.js +11 -4
- package/dist/context/wakeNotification.js +84 -22
- package/dist/controller/agentRuntimeObserver.js +9 -12
- package/dist/controller/clientRuntime.js +13 -13
- package/dist/controller/controller.js +11 -11
- package/dist/controller/fileSchedulerStoreAdapter.js +262 -219
- package/dist/controller/jobControl.js +11 -10
- package/dist/controller/resourceInventory.js +6 -6
- package/dist/controller/resourceInventoryLinux.js +2 -2
- package/dist/controller/runtime.js +18 -61
- package/dist/controller/runtimeEventInbox.js +57 -34
- package/dist/controller/runtimeEventProcessor.js +36 -96
- package/dist/controller/runtimeHookTurnFence.js +33 -57
- package/dist/controller/runtimeLaunchCoordinator.js +44 -40
- package/dist/controller/runtimeObservationHook.js +18 -12
- package/dist/controller/sessionNotify.js +43 -28
- package/dist/controller/sessionOwnerReconciliation.js +21 -21
- package/dist/controller/structuredProviderObservation.js +13 -16
- package/dist/coordination/workMailboxQueue.js +132 -0
- package/dist/core/controllerServer.js +1 -0
- package/dist/domain/agentResultTransport.js +60 -0
- package/dist/execution/executionHealth.js +111 -118
- package/dist/execution/reviewMainTurn.js +161 -0
- package/dist/execution/workItemExecution.js +454 -0
- package/dist/execution/workItemExecutionProjection.js +309 -0
- package/dist/execution/workItemMainTurn.js +164 -0
- package/dist/executor/agentExecutor.js +19 -19
- package/dist/executor/executorRegistry.js +7 -28
- package/dist/executor/fileRoleLaunchPlanner.js +51 -83
- package/dist/external/githubPublicationVerifier.js +114 -0
- package/dist/external/gitlabPublicationVerifier.js +158 -0
- package/dist/external/pinnedCommandRunner.js +73 -0
- package/dist/integration/changeSetManifest.js +0 -3
- package/dist/integration/deliveryObligation.js +43 -6
- package/dist/integration/gitIntegrationService.js +284 -101
- package/dist/integration/integrationAttempt.js +64 -14
- package/dist/integration/integrationQueueEntry.js +5 -35
- package/dist/integration/integrationQueueService.js +65 -194
- package/dist/lifecycle/canonicalLifecycleEvent.js +16 -15
- package/dist/lifecycle/exactTurnTerminalization.js +100 -97
- package/dist/message/message.js +16 -0
- package/dist/observability/executionAudit.js +4 -25
- package/dist/observability/faultClassification.js +58 -70
- package/dist/observability/orchestrationMetrics.js +8 -50
- package/dist/profile/agentProfile.js +60 -15
- package/dist/profile/agentProfileRuntime.js +100 -0
- package/dist/release/releaseWorkflowPorts.js +5 -68
- package/dist/repository/executionLaneGitSnapshot.js +80 -0
- package/dist/repository/gitWorkspace.js +9 -67
- package/dist/repository/taskBaseFreshness.js +3 -1
- package/dist/repository/taskWorkspaceCoordinator.js +32 -4
- package/dist/repository/taskWorkspacePreparer.js +252 -411
- package/dist/resources/liveReferences.js +6 -55
- package/dist/resources/resourceDiscovery.js +1 -30
- package/dist/resources/resourceRegistrar.js +0 -23
- package/dist/review/deltaRecheck.js +8 -65
- package/dist/review/reviewAcceptance.js +22 -19
- package/dist/review/reviewConfig.js +0 -10
- package/dist/review/reviewDecision.js +2 -2
- package/dist/review/reviewRound.js +147 -273
- package/dist/review/reviewerAvailability.js +13 -23
- package/dist/role/role.js +3 -0
- package/dist/runtime/agentDriverObservation.js +1 -1
- package/dist/runtime/agentHost.js +51 -51
- package/dist/runtime/builtinAgentDrivers.js +9 -2
- package/dist/runtime/codexAppServerRuntime.js +23 -5
- package/dist/runtime/exactControlPlane.js +2 -267
- package/dist/runtime/firstProgressAdvisory.js +1 -1
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/launchBroker.js +11 -11
- package/dist/runtime/lifecycleReservation.js +2 -2
- package/dist/runtime/managedCaller.js +111 -0
- package/dist/runtime/ports.js +16 -3
- package/dist/runtime/preallocatedNativeSession.js +2 -2
- package/dist/runtime/processExitObservation.js +3 -2
- package/dist/runtime/providerContinuationReconciliationService.js +1 -1
- package/dist/runtime/runtimeBinding.js +1 -1
- package/dist/runtime/runtimeObservation.js +33 -16
- package/dist/runtime/runtimeSessionCandidate.js +2 -2
- package/dist/runtime/runtimeStopReceipt.js +11 -11
- package/dist/runtime/sessionLaunchRequest.js +2 -2
- package/dist/runtime/sessionOwnerIdentity.js +9 -9
- package/dist/runtime/sessionOwnerRegistry.js +13 -13
- package/dist/runtime/sessionReconciliation.js +4 -4
- package/dist/runtime/sessionTerminationGuard.js +9 -9
- package/dist/runtime/sessionTokenMetrics.js +5 -7
- package/dist/runtime/structuredProviderHost.js +5 -5
- package/dist/runtime/taskRuntimeIsolation.js +24 -24
- package/dist/runtime/tmuxAdapters.js +32 -30
- package/dist/scheduler/activeRoleTurnDelivery.js +37 -15
- package/dist/scheduler/leaderWakeupProcessor.js +78 -55
- package/dist/scheduler/roleTurnLiveness.js +6 -6
- package/dist/scheduler/roleTurnStall.js +8 -9
- package/dist/scheduler/taskExecutionProjection.js +19 -41
- package/dist/scheduler/taskObservabilityProjection.js +75 -122
- package/dist/storage/sqliteSchema.js +1 -21
- package/dist/storage/sqliteStore.js +62 -47
- package/dist/storage/storageSchema.js +1 -8
- package/dist/storage/storageVersions.js +7 -4
- package/dist/storage/taskStore.js +8 -8
- package/dist/storage/upgrade/upgradeOrchestrator.js +112 -83
- package/dist/task/completionReadiness.js +17 -98
- package/dist/task/deliveryGuard.js +9 -11
- package/dist/task/draftPlan.js +142 -0
- package/dist/task/nextAction.js +179 -477
- package/dist/task/publicationVerification.js +1 -0
- package/dist/task/remoteDelivery.js +233 -0
- package/dist/task/task.js +108 -4
- package/dist/task/taskRecordReference.js +0 -1
- package/dist/turn/turn.js +109 -16
- package/dist/verification/verificationGateService.js +0 -26
- package/dist/web/assets/client/components.js +99 -121
- package/dist/web/assets/client/i18n.js +136 -58
- package/dist/web/assets/client/view.js +44 -0
- package/dist/web/assets/styles/cards.js +3 -15
- package/dist/web/assets/styles/widgets.js +4 -0
- package/dist/web/webSnapshot.js +29 -14
- package/dist/workItem/dependencyGate.js +34 -0
- package/dist/workItem/workItem.js +99 -266
- package/dist/workspace/workItemChangeSetManager.js +15 -59
- package/docs/task-local-identity.md +1 -1
- package/i18n/README.zh-CN.md +165 -59
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +272 -782
- package/skills/yui-operator/SKILL.md +190 -365
- package/skills/yui-reviewer/SKILL.md +40 -33
- package/skills/yui-runtime/SKILL.md +27 -6
- package/skills/yui-worker/SKILL.md +19 -6
- package/dist/execution/candidateConvergence.js +0 -623
- package/dist/execution/executionGroup.js +0 -854
- package/dist/execution/resourceBroker.js +0 -426
- package/dist/executor/launchPlan.js +0 -16
- package/dist/review/reviewFinding.js +0 -240
- package/dist/review/reviewFindingLedger.js +0 -550
- package/dist/review/reviewOutcomeClassifier.js +0 -255
- package/dist/scheduler/resourceQueueProjection.js +0 -71
- package/dist/task/repairWave.js +0 -150
|
@@ -40,8 +40,7 @@ import Database from "better-sqlite3";
|
|
|
40
40
|
import { consumePendingBatch, mailboxTargetKey, validateWorkMailbox } from "../coordination/workMailbox.js";
|
|
41
41
|
import { validateContextSnapshot } from "../context/contextSnapshot.js";
|
|
42
42
|
import { compareRuntimeSessionCandidates, projectRuntimeSessionCandidate } from "../runtime/runtimeSessionCandidate.js";
|
|
43
|
-
import {
|
|
44
|
-
import { reviewFindingLedgerMode } from "../review/reviewFindingLedger.js";
|
|
43
|
+
import { validateReviewRound } from "../review/reviewRound.js";
|
|
45
44
|
import { generateHomeIdentity, validateHomeIdentity } from "../repository/homeIdentity.js";
|
|
46
45
|
import { validateIntegrationQueueEntry } from "../integration/integrationQueueEntry.js";
|
|
47
46
|
import { validDurableJobTransition, validateDurableJob } from "../job/durableJob.js";
|
|
@@ -53,6 +52,7 @@ import { CURRENT_CONFIG_SCHEMA_VERSION, CURRENT_WORK_MAILBOX_SCHEMA_VERSION, exe
|
|
|
53
52
|
import { publicationExternalKey } from "../task/publicationReference.js";
|
|
54
53
|
import { gateArtifactKey, validateGateArtifact } from "../verification/gateArtifact.js";
|
|
55
54
|
import { inspectSqliteSchemaMigrations, migrateSqliteSchema, SqliteSchemaMigrationError, SQLITE_AGGREGATE_VERSION, SQLITE_LAYOUT_VERSION, TELEMETRY_KEEP_PER_GENERATION, TELEMETRY_TURN_CAP } from "./sqliteSchema.js";
|
|
55
|
+
import { StorageSchemaError } from "./storageSchema.js";
|
|
56
56
|
/** Read the immutable Home identity without opening a writable Store connection. */
|
|
57
57
|
export function readSqliteHomeIdentity(rootDir, databaseFilename = "yui.db") {
|
|
58
58
|
const database = new Database(join(rootDir, databaseFilename), {
|
|
@@ -123,6 +123,10 @@ export class SqliteTaskStore {
|
|
|
123
123
|
mkdirSync(rootDir, { recursive: true, mode: 0o700 });
|
|
124
124
|
const filename = _options.databaseFilename ?? "yui.db";
|
|
125
125
|
const databasePath = join(rootDir, filename);
|
|
126
|
+
// Remember whether the database file existed before this connection opened
|
|
127
|
+
// it. A store that creates the file is bootstrapping a fresh database and
|
|
128
|
+
// may seed its singleton rows; a store that opens an existing file missing
|
|
129
|
+
// those rows has found corruption, not a bootstrap opportunity.
|
|
126
130
|
const databaseExisted = existsSync(databasePath);
|
|
127
131
|
this.#db = new Database(databasePath);
|
|
128
132
|
try {
|
|
@@ -140,8 +144,7 @@ export class SqliteTaskStore {
|
|
|
140
144
|
version: schema.currentVersion,
|
|
141
145
|
checksum: schema.currentChecksum
|
|
142
146
|
};
|
|
143
|
-
this.#
|
|
144
|
-
this.#seedConfig();
|
|
147
|
+
this.#ensureSeedRows(databaseExisted);
|
|
145
148
|
}
|
|
146
149
|
catch (error) {
|
|
147
150
|
this.#db.close();
|
|
@@ -158,6 +161,34 @@ export class SqliteTaskStore {
|
|
|
158
161
|
*/
|
|
159
162
|
databaseHandle() { return this.#db; }
|
|
160
163
|
// -- transaction primitives -------------------------------------------------
|
|
164
|
+
/**
|
|
165
|
+
* Guarantee the `home_meta` and `config` singleton rows exist.
|
|
166
|
+
*
|
|
167
|
+
* A fresh database created by this connection is bootstrapped with defaults.
|
|
168
|
+
* An existing database that is missing either row is corrupt —
|
|
169
|
+
* truncated, partially migrated, or hand-edited — and must fail closed
|
|
170
|
+
* instead of silently masking the damage with fresh defaults (Issue 01
|
|
171
|
+
* cross-issue handoff: INSERT OR IGNORE could hide migration corruption).
|
|
172
|
+
*/
|
|
173
|
+
#ensureSeedRows(databaseExisted) {
|
|
174
|
+
const hasHomeMeta = this.#db.prepare("SELECT 1 FROM home_meta WHERE id = 1").get() !== undefined;
|
|
175
|
+
const hasConfig = this.#db.prepare("SELECT 1 FROM config WHERE id = 1").get() !== undefined;
|
|
176
|
+
if (hasHomeMeta && hasConfig)
|
|
177
|
+
return;
|
|
178
|
+
if (databaseExisted) {
|
|
179
|
+
const missing = [
|
|
180
|
+
!hasHomeMeta ? "home_meta" : null,
|
|
181
|
+
!hasConfig ? "config" : null
|
|
182
|
+
].filter((value) => value !== null).join(", ");
|
|
183
|
+
throw new StorageSchemaError("STORAGE_SCHEMA_INVALID", `SQLite database is corrupt: required singleton row(s) missing (${missing}). `
|
|
184
|
+
+ "The database may be truncated or partially migrated. Restore yui.db from a "
|
|
185
|
+
+ "backup, or preserve this Home for diagnosis and initialize a new Home.");
|
|
186
|
+
}
|
|
187
|
+
if (!hasHomeMeta)
|
|
188
|
+
this.#seedHomeMeta();
|
|
189
|
+
if (!hasConfig)
|
|
190
|
+
this.#seedConfig();
|
|
191
|
+
}
|
|
161
192
|
#seedHomeMeta() {
|
|
162
193
|
const now = new Date().toISOString();
|
|
163
194
|
const identity = generateHomeIdentity(new Date());
|
|
@@ -731,9 +762,7 @@ export class SqliteTaskStore {
|
|
|
731
762
|
activeTurns: runs.filter((run) => run.status === "active"),
|
|
732
763
|
leaderTurns: runs.filter((run) => run.roleName === "leader"),
|
|
733
764
|
reviewOutcomeEvidence: {
|
|
734
|
-
turns: this.#sortById(this.#listPayload("turns", "task_id = ?", [taskId]).filter((run) => run.purpose === "review"), (run) => run.id)
|
|
735
|
-
reviewFindings: this.listReviewFindings(taskId),
|
|
736
|
-
events: events.filter((event) => event.type === "review.completed")
|
|
765
|
+
turns: this.#sortById(this.#listPayload("turns", "task_id = ?", [taskId]).filter((run) => run.purpose === "review"), (run) => run.id)
|
|
737
766
|
}
|
|
738
767
|
};
|
|
739
768
|
}
|
|
@@ -743,13 +772,9 @@ export class SqliteTaskStore {
|
|
|
743
772
|
return null;
|
|
744
773
|
return {
|
|
745
774
|
...base,
|
|
746
|
-
turns: operationalTaskRecords(this.listTurns(taskId), this.listEvents(taskId), "turn"),
|
|
747
775
|
managedWorkspaces: this.#sortById(this.#listPayload("managed_workspaces", "task_id = ?", [taskId]), (workspace) => managedWorkspaceKey(workspace.owner)),
|
|
748
776
|
durableJobs: this.#sortById(this.#listPayload("durable_jobs", "task_id = ?", [taskId]), (job) => job.id),
|
|
749
777
|
integrationQueueEntries: this.#sortById(this.#listPayload("integration_queue", "task_id = ?", [taskId]), (entry) => entry.id),
|
|
750
|
-
reviewFindings: this.#sortById(this.#listPayload("review_findings", "task_id = ?", [taskId]), (finding) => finding.id),
|
|
751
|
-
reviewFindingLedgerMode: reviewFindingLedgerMode(this.getConfig()),
|
|
752
|
-
events: this.#sortById(this.#listPayload("events", "task_id = ?", [taskId]), (event) => event.id)
|
|
753
778
|
};
|
|
754
779
|
}
|
|
755
780
|
/** Task ids flagged active in the catalog projection (the global active index). */
|
|
@@ -926,11 +951,11 @@ export class SqliteTaskStore {
|
|
|
926
951
|
role_name = excluded.role_name, agent_id = excluded.agent_id,
|
|
927
952
|
native_session_id = excluded.native_session_id,
|
|
928
953
|
provider_root_pid = excluded.provider_root_pid,
|
|
929
|
-
payload = excluded.payload, recorded_at = excluded.recorded_at`).run(identity.
|
|
954
|
+
payload = excluded.payload, recorded_at = excluded.recorded_at`).run(identity.runtimeGenerationId, owner.scope, owner.scope === "task" ? owner.taskId : null, owner.roleName, identity.agentId, identity.nativeSessionId ?? null, identity.providerRoot.pid, this.#json(identity), identity.recordedAt);
|
|
930
955
|
});
|
|
931
956
|
}
|
|
932
|
-
getSessionOwner(
|
|
933
|
-
const row = this.#db.prepare("SELECT payload FROM session_owners WHERE launch_id = ?").get(
|
|
957
|
+
getSessionOwner(runtimeGenerationId) {
|
|
958
|
+
const row = this.#db.prepare("SELECT payload FROM session_owners WHERE launch_id = ?").get(runtimeGenerationId);
|
|
934
959
|
return row === undefined ? null : this.#parse(row.payload);
|
|
935
960
|
}
|
|
936
961
|
listSessionOwners() {
|
|
@@ -942,9 +967,9 @@ export class SqliteTaskStore {
|
|
|
942
967
|
}
|
|
943
968
|
return this.#listPayload("session_owners", "scope = 'task' AND task_id = ? AND role_name = ?", [owner.taskId, owner.roleName]);
|
|
944
969
|
}
|
|
945
|
-
removeSessionOwner(
|
|
970
|
+
removeSessionOwner(runtimeGenerationId) {
|
|
946
971
|
this.#mutate(() => {
|
|
947
|
-
this.#db.prepare("DELETE FROM session_owners WHERE launch_id = ?").run(
|
|
972
|
+
this.#db.prepare("DELETE FROM session_owners WHERE launch_id = ?").run(runtimeGenerationId);
|
|
948
973
|
});
|
|
949
974
|
}
|
|
950
975
|
// -- task roles -------------------------------------------------------------
|
|
@@ -1058,7 +1083,7 @@ export class SqliteTaskStore {
|
|
|
1058
1083
|
? ""
|
|
1059
1084
|
: ` WHERE ${predicates.join(" AND ")}`;
|
|
1060
1085
|
const rows = this.#db.prepare(`SELECT scope, task_id, role_name, agent_id, adapter_id,
|
|
1061
|
-
native_session_id, launch_id, session_updated_at,
|
|
1086
|
+
native_session_id, launch_id AS runtime_generation_id, session_updated_at,
|
|
1062
1087
|
cleanup_required
|
|
1063
1088
|
FROM runtime_session_candidates${where}`).all(...parameters);
|
|
1064
1089
|
const candidates = rows.map((row) => ({
|
|
@@ -1068,7 +1093,7 @@ export class SqliteTaskStore {
|
|
|
1068
1093
|
agentId: row.agent_id,
|
|
1069
1094
|
adapterId: row.adapter_id,
|
|
1070
1095
|
nativeSessionId: row.native_session_id,
|
|
1071
|
-
...(row.
|
|
1096
|
+
...(row.runtime_generation_id === null ? {} : { runtimeGenerationId: row.runtime_generation_id }),
|
|
1072
1097
|
sessionUpdatedAt: row.session_updated_at,
|
|
1073
1098
|
cleanupRequired: row.cleanup_required === 1
|
|
1074
1099
|
})).sort(compareRuntimeSessionCandidates);
|
|
@@ -1123,7 +1148,7 @@ export class SqliteTaskStore {
|
|
|
1123
1148
|
json_extract(active_session, '$.agentId') AS agent_id,
|
|
1124
1149
|
json_extract(active_session, '$.adapterId') AS adapter_id,
|
|
1125
1150
|
json_extract(active_session, '$.nativeSessionId') AS native_session_id,
|
|
1126
|
-
json_extract(active_session, '$.
|
|
1151
|
+
json_extract(active_session, '$.runtimeGenerationId') AS runtime_generation_id,
|
|
1127
1152
|
CASE
|
|
1128
1153
|
WHEN json_extract(active_session, '$.status') = 'active'
|
|
1129
1154
|
THEN 1 ELSE 0
|
|
@@ -1131,7 +1156,7 @@ export class SqliteTaskStore {
|
|
|
1131
1156
|
json_extract(active_session, '$.updatedAt') AS session_updated_at,
|
|
1132
1157
|
CASE
|
|
1133
1158
|
WHEN json_extract(active_session, '$.status') = 'active'
|
|
1134
|
-
AND json_type(active_session, '$.
|
|
1159
|
+
AND json_type(active_session, '$.runtimeGenerationId') = 'text'
|
|
1135
1160
|
THEN 1 ELSE 0
|
|
1136
1161
|
END AS cleanup_required
|
|
1137
1162
|
FROM active`).get(...source.parameters);
|
|
@@ -1151,7 +1176,7 @@ export class SqliteTaskStore {
|
|
|
1151
1176
|
&& row.agent_id === candidate.agentId
|
|
1152
1177
|
&& row.adapter_id === candidate.adapterId
|
|
1153
1178
|
&& row.native_session_id === candidate.nativeSessionId
|
|
1154
|
-
&& (row.
|
|
1179
|
+
&& (row.runtime_generation_id ?? undefined) === candidate.runtimeGenerationId
|
|
1155
1180
|
&& row.is_active === 1
|
|
1156
1181
|
&& row.session_updated_at === candidate.sessionUpdatedAt
|
|
1157
1182
|
&& row.cleanup_required === (candidate.cleanupRequired ? 1 : 0);
|
|
@@ -1192,7 +1217,7 @@ export class SqliteTaskStore {
|
|
|
1192
1217
|
native_session_id = excluded.native_session_id,
|
|
1193
1218
|
launch_id = excluded.launch_id,
|
|
1194
1219
|
session_updated_at = excluded.session_updated_at,
|
|
1195
|
-
cleanup_required = excluded.cleanup_required`).run(candidate.owner.scope, taskId, candidate.owner.roleName, candidate.agentId, candidate.adapterId, candidate.nativeSessionId, candidate.
|
|
1220
|
+
cleanup_required = excluded.cleanup_required`).run(candidate.owner.scope, taskId, candidate.owner.roleName, candidate.agentId, candidate.adapterId, candidate.nativeSessionId, candidate.runtimeGenerationId ?? null, candidate.sessionUpdatedAt, candidate.cleanupRequired ? 1 : 0);
|
|
1196
1221
|
}
|
|
1197
1222
|
#deleteRuntimeSessionCandidate(owner) {
|
|
1198
1223
|
this.#db.prepare(`DELETE FROM runtime_session_candidates
|
|
@@ -1295,8 +1320,7 @@ export class SqliteTaskStore {
|
|
|
1295
1320
|
if (sameExternal.length > 0) {
|
|
1296
1321
|
const latest = latestSqlitePublicationReference(sameExternal);
|
|
1297
1322
|
if (stored.supersedes !== latest.id) {
|
|
1298
|
-
throw new StorageRecordError(`Publication external identity conflicts with ${latest.id};
|
|
1299
|
-
+ `explicitly supersede that record: ${key}.`);
|
|
1323
|
+
throw new StorageRecordError(`Publication external identity conflicts with current record ${latest.id}: ${key}.`);
|
|
1300
1324
|
}
|
|
1301
1325
|
}
|
|
1302
1326
|
else if (stored.supersedes !== undefined) {
|
|
@@ -1500,6 +1524,7 @@ export class SqliteTaskStore {
|
|
|
1500
1524
|
return this.#sortById(this.#listPayload("review_rounds", "task_id = ?", [taskId]), (round) => round.id);
|
|
1501
1525
|
}
|
|
1502
1526
|
saveReviewRound(taskId, round) {
|
|
1527
|
+
validateReviewRound(round);
|
|
1503
1528
|
if (round.taskId !== taskId)
|
|
1504
1529
|
throw new StorageRecordError(`Review round belongs to another Task: ${round.taskId}`);
|
|
1505
1530
|
this.#requireTask(taskId);
|
|
@@ -1509,27 +1534,6 @@ export class SqliteTaskStore {
|
|
|
1509
1534
|
payload = excluded.payload, updated_at = excluded.updated_at`).run(taskId, round.id, round.status, this.#json(round), this.#now());
|
|
1510
1535
|
});
|
|
1511
1536
|
}
|
|
1512
|
-
// -- review findings --------------------------------------------------------
|
|
1513
|
-
nextReviewFindingId(taskId) { return this.#nextTaskRecordId(taskId, "reviewFinding"); }
|
|
1514
|
-
getReviewFinding(taskId, findingId) {
|
|
1515
|
-
return this.#getPayload("review_findings", "task_id = ? AND finding_id = ?", [taskId, findingId]);
|
|
1516
|
-
}
|
|
1517
|
-
listReviewFindings(taskId) {
|
|
1518
|
-
return this.#sortById(this.#listPayload("review_findings", "task_id = ?", [taskId]), (finding) => finding.id);
|
|
1519
|
-
}
|
|
1520
|
-
saveReviewFinding(taskId, finding) {
|
|
1521
|
-
validateReviewFinding(finding);
|
|
1522
|
-
if (finding.taskId !== taskId) {
|
|
1523
|
-
throw new StorageRecordError(`Review finding belongs to another Task: ${finding.taskId}`);
|
|
1524
|
-
}
|
|
1525
|
-
this.#requireTask(taskId);
|
|
1526
|
-
this.#mutate(() => {
|
|
1527
|
-
this.#db.prepare(`INSERT INTO review_findings (task_id, finding_id, stable_key, severity, payload, updated_at)
|
|
1528
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
1529
|
-
ON CONFLICT(task_id, finding_id) DO UPDATE SET stable_key = excluded.stable_key,
|
|
1530
|
-
severity = excluded.severity, payload = excluded.payload, updated_at = excluded.updated_at`).run(taskId, finding.id, finding.stableKey, finding.severity, this.#json(finding), this.#now());
|
|
1531
|
-
});
|
|
1532
|
-
}
|
|
1533
1537
|
// -- active turns ----------------------------------------------------------
|
|
1534
1538
|
#saveActiveTurn(taskId, pointer, turnId) {
|
|
1535
1539
|
this.#mutate(() => {
|
|
@@ -1721,6 +1725,18 @@ export class SqliteTaskStore {
|
|
|
1721
1725
|
this.#observeHighWater(taskId, "message", seq);
|
|
1722
1726
|
});
|
|
1723
1727
|
}
|
|
1728
|
+
updateMessage(taskId, message) {
|
|
1729
|
+
if (message.taskId !== taskId) {
|
|
1730
|
+
throw new StorageRecordError(`Message belongs to another Task: ${message.taskId}`);
|
|
1731
|
+
}
|
|
1732
|
+
this.#requireTask(taskId);
|
|
1733
|
+
this.#mutate(() => {
|
|
1734
|
+
const result = this.#db.prepare(`UPDATE messages SET payload = ? WHERE task_id = ? AND message_id = ?`).run(this.#json(message), taskId, message.id);
|
|
1735
|
+
if (result.changes !== 1) {
|
|
1736
|
+
throw new StorageRecordError(`Message does not exist: ${taskId}/${message.id}`);
|
|
1737
|
+
}
|
|
1738
|
+
});
|
|
1739
|
+
}
|
|
1724
1740
|
listMessages(taskId) {
|
|
1725
1741
|
return this.#sortById(this.#listPayload("messages", "task_id = ?", [taskId]), (message) => message.id);
|
|
1726
1742
|
}
|
|
@@ -2109,10 +2125,9 @@ function validIntegrationQueueTransition(before, after) {
|
|
|
2109
2125
|
|| before.createdAt !== after.createdAt)
|
|
2110
2126
|
return false;
|
|
2111
2127
|
const allowed = {
|
|
2112
|
-
queued: ["queued", "running", "
|
|
2128
|
+
queued: ["queued", "running", "superseded"],
|
|
2113
2129
|
running: ["running", "conflicted", "committed"],
|
|
2114
2130
|
conflicted: ["conflicted", "running", "committed", "queued", "superseded"],
|
|
2115
|
-
validated: ["validated", "running", "queued", "superseded"],
|
|
2116
2131
|
committed: ["committed"],
|
|
2117
2132
|
superseded: ["superseded"]
|
|
2118
2133
|
};
|
|
@@ -136,9 +136,6 @@ export function ensureStorageSchema(rootDir, now = new Date()) {
|
|
|
136
136
|
}
|
|
137
137
|
requireInspectedSchema(state);
|
|
138
138
|
}
|
|
139
|
-
export function requireStorageSchema(rootDir) {
|
|
140
|
-
requireInspectedSchema(inspectStorageSchema(rootDir));
|
|
141
|
-
}
|
|
142
139
|
/** Persist the current three-axis manifest through the existing atomic-file seam. */
|
|
143
140
|
export function writeCurrentStorageManifest(rootDir, now = new Date()) {
|
|
144
141
|
const recordVersions = {};
|
|
@@ -176,7 +173,7 @@ function unsupportedVersion(current, required, component, recordFamily) {
|
|
|
176
173
|
? "Aggregate schema"
|
|
177
174
|
: `Record family '${recordFamily}'`;
|
|
178
175
|
if (current < required) {
|
|
179
|
-
return new StorageSchemaError("STORAGE_SCHEMA_UNSUPPORTED", `${label} ${current} is older than required ${component} version ${required};
|
|
176
|
+
return new StorageSchemaError("STORAGE_SCHEMA_UNSUPPORTED", `${label} ${current} is older than required ${component} version ${required}; use the matching Yui version or initialize a new Home.`);
|
|
180
177
|
}
|
|
181
178
|
return new StorageSchemaError("STORAGE_SCHEMA_UNSUPPORTED", `${label} ${current} is newer than supported ${component} version ${required}; use a newer Yui release.`);
|
|
182
179
|
}
|
|
@@ -229,10 +226,6 @@ export function parseStorageSchemaManifest(value) {
|
|
|
229
226
|
updatedAt: value.updatedAt
|
|
230
227
|
};
|
|
231
228
|
}
|
|
232
|
-
/** Read and strictly parse the durable manifest through the same contract. */
|
|
233
|
-
export function readStorageSchemaManifest(rootDir) {
|
|
234
|
-
return parseStorageManifest(readFileSync(join(rootDir, STORAGE_SCHEMA_FILE), "utf8"));
|
|
235
|
-
}
|
|
236
229
|
function parseJsonObject(raw, label) {
|
|
237
230
|
let value;
|
|
238
231
|
try {
|
|
@@ -9,9 +9,12 @@
|
|
|
9
9
|
* Version of the on-disk layout (`schema.json` plus the SQLite database).
|
|
10
10
|
*
|
|
11
11
|
* Layout 8 is the current SQLite WAL control-plane layout: the authoritative
|
|
12
|
-
* store is `yui.db`. Layout 8 is the only layout this release reads
|
|
13
|
-
*
|
|
12
|
+
* store is `yui.db`. Layout 8 is the only physical layout this release reads
|
|
13
|
+
* and writes. Historical aggregate contracts are rejected without mutation.
|
|
14
14
|
*/
|
|
15
15
|
export const CURRENT_STORAGE_LAYOUT_VERSION = 8;
|
|
16
|
-
/**
|
|
17
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Aggregate 31 makes Agent results opaque, removes parsed Review/finding
|
|
18
|
+
* authority, and intentionally has no upgrade path from earlier contracts.
|
|
19
|
+
*/
|
|
20
|
+
export const CURRENT_AGGREGATE_SCHEMA_VERSION = 31;
|
|
@@ -13,18 +13,18 @@ export const CURRENT_CONFIG_SCHEMA_VERSION = 6;
|
|
|
13
13
|
/** Current SQLite payload-family versions owned by this storage boundary. */
|
|
14
14
|
export const CURRENT_CONFIGURED_AGENT_SCHEMA_VERSION = 2;
|
|
15
15
|
export const CURRENT_PROJECT_SCHEMA_VERSION = 5;
|
|
16
|
-
export const CURRENT_AGENT_PROFILE_SCHEMA_VERSION =
|
|
16
|
+
export const CURRENT_AGENT_PROFILE_SCHEMA_VERSION = 3;
|
|
17
17
|
export const CURRENT_GLOBAL_ROLE_SCHEMA_VERSION = 3;
|
|
18
|
-
export const CURRENT_GLOBAL_ROLE_SESSION_SET_SCHEMA_VERSION =
|
|
19
|
-
export const CURRENT_TASK_SCHEMA_VERSION =
|
|
18
|
+
export const CURRENT_GLOBAL_ROLE_SESSION_SET_SCHEMA_VERSION = 5;
|
|
19
|
+
export const CURRENT_TASK_SCHEMA_VERSION = 7;
|
|
20
20
|
export const CURRENT_TASK_BRIEF_SCHEMA_VERSION = 2;
|
|
21
21
|
export const CURRENT_CONTEXT_SNAPSHOT_SCHEMA_VERSION = 1;
|
|
22
22
|
export const CURRENT_TASK_ROLE_SCHEMA_VERSION = 4;
|
|
23
23
|
export const CURRENT_MANAGED_WORKSPACE_SCHEMA_VERSION = 2;
|
|
24
|
-
export const CURRENT_WORK_ITEM_SCHEMA_VERSION =
|
|
25
|
-
export const CURRENT_REVIEW_ROUND_SCHEMA_VERSION =
|
|
24
|
+
export const CURRENT_WORK_ITEM_SCHEMA_VERSION = 15;
|
|
25
|
+
export const CURRENT_REVIEW_ROUND_SCHEMA_VERSION = 8;
|
|
26
26
|
export const CURRENT_CHANGE_SET_SCHEMA_VERSION = 4;
|
|
27
|
-
export const CURRENT_INTEGRATION_ATTEMPT_SCHEMA_VERSION =
|
|
27
|
+
export const CURRENT_INTEGRATION_ATTEMPT_SCHEMA_VERSION = 6;
|
|
28
28
|
export const CURRENT_MESSAGE_SCHEMA_VERSION = 3;
|
|
29
29
|
export const CURRENT_INPUT_REQUEST_SCHEMA_VERSION = 3;
|
|
30
30
|
export const CURRENT_DECISION_SCHEMA_VERSION = 1;
|
|
@@ -65,8 +65,8 @@ export function executionLaneActiveTurnKeyParts(key) {
|
|
|
65
65
|
/**
|
|
66
66
|
* Nested-record versions consumed by the current SQLite payload validators.
|
|
67
67
|
*/
|
|
68
|
-
export const CURRENT_TASK_ROLE_SESSION_SET_SCHEMA_VERSION =
|
|
69
|
-
export const CURRENT_TURN_SCHEMA_VERSION =
|
|
68
|
+
export const CURRENT_TASK_ROLE_SESSION_SET_SCHEMA_VERSION = 12;
|
|
69
|
+
export const CURRENT_TURN_SCHEMA_VERSION = 5;
|
|
70
70
|
export const CURRENT_INTEGRATION_QUEUE_SCHEMA_VERSION = 1;
|
|
71
71
|
export class StorageRecordError extends Error {
|
|
72
72
|
constructor(message) { super(message); this.name = "StorageRecordError"; }
|
|
@@ -1,126 +1,155 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { RUNTIME_OBSERVATION_TASK_EVENT, createRuntimeObservation } from "../../runtime/runtimeObservation.js";
|
|
4
|
+
import { RUNTIME_PROCESS_EXIT_TASK_EVENT, validateRuntimeProcessExitObservation } from "../../runtime/processExitObservation.js";
|
|
5
|
+
import { validateAgentProfile } from "../../profile/agentProfile.js";
|
|
6
|
+
import { validateRoleSessionSet } from "../../executor/agentExecutor.js";
|
|
7
|
+
import { validateReviewRound } from "../../review/reviewRound.js";
|
|
8
|
+
import { validateTurn } from "../../turn/turn.js";
|
|
9
|
+
import { validateWorkItem } from "../../workItem/workItem.js";
|
|
3
10
|
import { SqliteTaskStore } from "../sqliteStore.js";
|
|
4
11
|
import { inspectStorageSchema } from "../storageSchema.js";
|
|
5
12
|
const CURRENT_DATABASE_FILENAME = "yui.db";
|
|
6
13
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
14
|
+
* Validate the one supported storage contract.
|
|
15
|
+
*
|
|
16
|
+
* Aggregate 31 deliberately has no historical migration path. `upgrade`
|
|
17
|
+
* remains as a read-only compatibility/preflight surface for current Homes;
|
|
18
|
+
* every non-current contract is rejected without touching the Home.
|
|
9
19
|
*/
|
|
10
20
|
export async function runStorageUpgrade(options) {
|
|
11
21
|
const schema = inspectStorageSchema(options.home);
|
|
12
|
-
const classification = classifyCurrentHome(schema, options.latest);
|
|
13
22
|
if (schema.status === "uninitialized") {
|
|
14
|
-
return blocked(
|
|
23
|
+
return blocked({ ...currentClassification(options.latest), uninitialized: true }, "uninitialized", "Yui storage is not initialized for this Home.", "Run `yui setup` with a new Home.");
|
|
15
24
|
}
|
|
16
25
|
if (schema.status === "invalid") {
|
|
17
|
-
return blocked(
|
|
18
|
-
}
|
|
19
|
-
if (schema.status === "unsupported") {
|
|
20
|
-
return blocked(classification, "unsupported", `Storage contract is ${schema.direction}: ${schema.incompatibleComponent}.`, "Open the old Home only with its original Yui version to export a summary; then let the new Operator create a new Task in a new Home.");
|
|
26
|
+
return blocked(corruptedClassification(options.latest, schema.detail), "corruption", `Storage schema is invalid: ${schema.detail}`, "Preserve this Home for diagnosis and restore it from a known-good backup.");
|
|
21
27
|
}
|
|
22
28
|
if (!existsSync(join(options.home, CURRENT_DATABASE_FILENAME))) {
|
|
23
|
-
return blocked(corruptedClassification(
|
|
29
|
+
return blocked(corruptedClassification(options.latest, "The SQLite database is missing."), "corruption", "The SQLite Home is incomplete: yui.db is missing.", "Preserve this Home for diagnosis and restore it from a known-good backup.");
|
|
30
|
+
}
|
|
31
|
+
if (schema.status === "unsupported") {
|
|
32
|
+
const classification = unsupportedClassification(schema, options.latest);
|
|
33
|
+
return blocked(classification, "unsupported", "This Home does not exactly match the current storage contract; this release provides no migration path.", schema.direction === "newer"
|
|
34
|
+
? "Use a newer Yui release that supports this exact Home."
|
|
35
|
+
: "Open it with its matching Yui version, or initialize a new Home.");
|
|
24
36
|
}
|
|
25
37
|
try {
|
|
26
|
-
|
|
27
|
-
try {
|
|
28
|
-
store.getConfig();
|
|
29
|
-
}
|
|
30
|
-
finally {
|
|
31
|
-
store.close();
|
|
32
|
-
}
|
|
38
|
+
validateCurrentStore(options.home);
|
|
33
39
|
}
|
|
34
40
|
catch (error) {
|
|
35
|
-
|
|
36
|
-
return blocked(corruptedClassification(classification, message), "corruption", `Current storage validation failed: ${message}`, "Preserve this Home for diagnosis and initialize a new Home.");
|
|
41
|
+
return blocked(corruptedClassification(options.latest, messageOf(error)), "corruption", `Current storage validation failed: ${messageOf(error)}`, "Preserve this Home for diagnosis and restore it from a known-good backup.");
|
|
37
42
|
}
|
|
43
|
+
const classification = currentClassification(options.latest);
|
|
38
44
|
if (options.mode === "update-preflight") {
|
|
39
45
|
return {
|
|
40
46
|
outcome: "update-preflight",
|
|
41
47
|
status: "already-current",
|
|
42
48
|
stepCount: 0,
|
|
49
|
+
steps: [],
|
|
43
50
|
classification
|
|
44
51
|
};
|
|
45
52
|
}
|
|
46
|
-
|
|
53
|
+
return {
|
|
47
54
|
outcome: "already-current",
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
55
|
+
classification,
|
|
56
|
+
report: {
|
|
57
|
+
outcome: "already-current",
|
|
58
|
+
mode: options.mode,
|
|
59
|
+
source: options.latest,
|
|
60
|
+
target: options.latest
|
|
61
|
+
}
|
|
51
62
|
};
|
|
52
|
-
return { outcome: "already-current", classification, report };
|
|
53
63
|
}
|
|
54
|
-
function
|
|
55
|
-
const
|
|
64
|
+
function validateCurrentStore(home) {
|
|
65
|
+
const store = new SqliteTaskStore(home);
|
|
66
|
+
try {
|
|
67
|
+
store.getConfig();
|
|
68
|
+
for (const profile of store.listAgentProfiles())
|
|
69
|
+
validateAgentProfile(profile);
|
|
70
|
+
for (const sessions of store.listGlobalRoleSessionSets()) {
|
|
71
|
+
validateRoleSessionSet(sessions);
|
|
72
|
+
}
|
|
73
|
+
for (const owner of store.listSessionOwners()) {
|
|
74
|
+
if (owner.schemaVersion !== 2
|
|
75
|
+
|| typeof owner.runtimeGenerationId !== "string"
|
|
76
|
+
|| Object.hasOwn(owner, "launchId")) {
|
|
77
|
+
throw new Error("Session owner runtime generation identity is invalid.");
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
for (const taskId of store.listTasks().map(({ id }) => id)) {
|
|
81
|
+
for (const item of store.listWorkItems(taskId))
|
|
82
|
+
validateWorkItem(item);
|
|
83
|
+
for (const round of store.listReviewRounds(taskId))
|
|
84
|
+
validateReviewRound(round);
|
|
85
|
+
for (const turn of store.listTurns(taskId))
|
|
86
|
+
validateTurn(turn);
|
|
87
|
+
for (const sessions of store.listRoleSessionSets(taskId)) {
|
|
88
|
+
validateRoleSessionSet(sessions);
|
|
89
|
+
}
|
|
90
|
+
for (const event of store.listEvents(taskId)) {
|
|
91
|
+
if (event.type === RUNTIME_OBSERVATION_TASK_EVENT) {
|
|
92
|
+
createRuntimeObservation(JSON.parse(event.payload.observation ?? ""));
|
|
93
|
+
}
|
|
94
|
+
else if (event.type === RUNTIME_PROCESS_EXIT_TASK_EVENT) {
|
|
95
|
+
validateRuntimeProcessExitObservation(JSON.parse(event.payload.observation ?? ""));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
store.close();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function currentClassification(latest) {
|
|
105
|
+
return {
|
|
106
|
+
classification: { verdict: "USABLE", status: "current" },
|
|
107
|
+
layoutVersion: latest.layout,
|
|
108
|
+
aggregateVersion: latest.aggregate,
|
|
56
109
|
latestLayoutVersion: latest.layout,
|
|
57
110
|
latestAggregateVersion: latest.aggregate
|
|
58
111
|
};
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
reason: "future-version",
|
|
76
|
-
axis: schema.incompatibleComponent,
|
|
112
|
+
}
|
|
113
|
+
function unsupportedClassification(schema, latest) {
|
|
114
|
+
const axis = schema.incompatibleComponent;
|
|
115
|
+
const found = schema.currentVersion;
|
|
116
|
+
const supported = schema.latestVersion;
|
|
117
|
+
const future = schema.direction === "newer";
|
|
118
|
+
const action = future
|
|
119
|
+
? "Use a newer Yui release that supports this exact Home."
|
|
120
|
+
: "Open it with its matching Yui version, or initialize a new Home.";
|
|
121
|
+
return {
|
|
122
|
+
classification: {
|
|
123
|
+
verdict: "NEEDS_NEW_VERSION",
|
|
124
|
+
status: "unsupported",
|
|
125
|
+
blocker: {
|
|
126
|
+
reason: future ? "future-version" : "missing-step",
|
|
127
|
+
axis,
|
|
77
128
|
...(schema.recordFamily === undefined ? {} : { recordKind: schema.recordFamily }),
|
|
78
|
-
found:
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
129
|
+
...(future ? { found, supported } : { from: found, to: supported }),
|
|
130
|
+
message: future
|
|
131
|
+
? "The Home is newer than this Yui release."
|
|
132
|
+
: "This release provides no migration path for the older Home.",
|
|
133
|
+
action
|
|
82
134
|
}
|
|
83
|
-
|
|
84
|
-
reason: "missing-step",
|
|
85
|
-
axis: schema.incompatibleComponent,
|
|
86
|
-
...(schema.recordFamily === undefined ? {} : { recordKind: schema.recordFamily }),
|
|
87
|
-
from: schema.currentVersion,
|
|
88
|
-
to: schema.latestVersion,
|
|
89
|
-
message: "Historical storage contracts are not supported by this release.",
|
|
90
|
-
action: "Initialize a new Home."
|
|
91
|
-
};
|
|
92
|
-
return {
|
|
93
|
-
...base,
|
|
94
|
-
classification: {
|
|
95
|
-
verdict: "NEEDS_NEW_VERSION",
|
|
96
|
-
status: "unsupported",
|
|
97
|
-
blocker
|
|
98
|
-
},
|
|
99
|
-
layoutVersion: schema.currentLayoutVersion,
|
|
100
|
-
aggregateVersion: schema.currentAggregateSchemaVersion,
|
|
101
|
-
incompatibleComponent: schema.incompatibleComponent
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
return {
|
|
105
|
-
...base,
|
|
106
|
-
classification: { verdict: "USABLE", status: "current" },
|
|
135
|
+
},
|
|
107
136
|
layoutVersion: schema.currentLayoutVersion,
|
|
108
|
-
aggregateVersion: schema.currentAggregateSchemaVersion
|
|
137
|
+
aggregateVersion: schema.currentAggregateSchemaVersion,
|
|
138
|
+
latestLayoutVersion: latest.layout,
|
|
139
|
+
latestAggregateVersion: latest.aggregate,
|
|
140
|
+
incompatibleComponent: axis
|
|
109
141
|
};
|
|
110
142
|
}
|
|
111
|
-
function corruptedClassification(
|
|
143
|
+
function corruptedClassification(latest, detail) {
|
|
112
144
|
return {
|
|
113
|
-
|
|
114
|
-
|
|
145
|
+
classification: { verdict: "CORRUPTED", status: "unsupported", detail },
|
|
146
|
+
latestLayoutVersion: latest.layout,
|
|
147
|
+
latestAggregateVersion: latest.aggregate
|
|
115
148
|
};
|
|
116
149
|
}
|
|
117
150
|
function blocked(classification, stage, message, action) {
|
|
118
|
-
return {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
action,
|
|
123
|
-
classification,
|
|
124
|
-
sceneUnchanged: true
|
|
125
|
-
};
|
|
151
|
+
return { outcome: "blocked", stage, message, action, classification, sceneUnchanged: true };
|
|
152
|
+
}
|
|
153
|
+
function messageOf(error) {
|
|
154
|
+
return error instanceof Error ? error.message : String(error);
|
|
126
155
|
}
|