@zq-silk/yui 0.13.9 → 0.14.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.
Files changed (229) hide show
  1. package/ARCHITECTURE.md +69 -428
  2. package/README.md +108 -298
  3. package/dist/agent/managedRuntimeEnvironment.js +2 -2
  4. package/dist/cli/commandCatalog.js +39 -77
  5. package/dist/cli/interactionCandidates.js +4 -4
  6. package/dist/cli/interactionPolicy.js +7 -10
  7. package/dist/cli/invocationRouter.js +1 -1
  8. package/dist/cli/updateCommand.js +6 -27
  9. package/dist/cli/updateOrchestrator.js +63 -484
  10. package/dist/cli/updatePorts.js +15 -333
  11. package/dist/cli/upgradeCommand.js +8 -65
  12. package/dist/cli.js +57 -242
  13. package/dist/commands/configCommands.js +11 -62
  14. package/dist/commands/executionAuditCommands.js +23 -23
  15. package/dist/commands/globalRoleCommands.js +2 -7
  16. package/dist/commands/jobCommands.js +1 -4
  17. package/dist/commands/projectCommands.js +5 -5
  18. package/dist/commands/releaseCommands.js +3 -10
  19. package/dist/commands/resourcesCommands.js +2 -2
  20. package/dist/commands/sessionCommands.js +2 -2
  21. package/dist/commands/taskActor.js +31 -39
  22. package/dist/commands/taskChangeSetCommands.js +5 -12
  23. package/dist/commands/taskCommands.js +464 -1249
  24. package/dist/commands/taskContextCommand.js +35 -36
  25. package/dist/commands/taskExecutionCommands.js +33 -46
  26. package/dist/commands/taskInputCommands.js +20 -42
  27. package/dist/commands/taskNextActionCommand.js +9 -9
  28. package/dist/commands/taskOverviewCommand.js +56 -95
  29. package/dist/commands/taskRoleRuntimeStatus.js +77 -107
  30. package/dist/commands/taskUpstreamCommands.js +3 -3
  31. package/dist/commands/taskWorkspaceCommands.js +7 -79
  32. package/dist/commands/telemetryCommands.js +32 -124
  33. package/dist/commands/workflowCommands.js +3 -3
  34. package/dist/completion/fileCompletionManager.js +1 -1
  35. package/dist/config/configCatalog.js +4 -6
  36. package/dist/config/yuiConfig.js +6 -43
  37. package/dist/context/dispatchContext.js +14 -30
  38. package/dist/context/roleSessionContext.js +2 -2
  39. package/dist/context/sessionBootstrapManifest.js +2 -2
  40. package/dist/context/{runContextPack.js → turnContextPack.js} +58 -73
  41. package/dist/context/turnInputContract.js +204 -0
  42. package/dist/context/wakeNotification.js +2 -2
  43. package/dist/controller/agentRuntimeObserver.js +18 -18
  44. package/dist/controller/clientRuntime.js +13 -20
  45. package/dist/controller/controller.js +89 -116
  46. package/dist/controller/fileSchedulerStoreAdapter.js +511 -742
  47. package/dist/controller/jobControl.js +17 -17
  48. package/dist/controller/resourceInventory.js +6 -6
  49. package/dist/controller/resourceInventoryLinux.js +38 -59
  50. package/dist/controller/resourceInventoryRpc.js +3 -3
  51. package/dist/controller/resourceInventoryWorker.js +1 -1
  52. package/dist/controller/runtime.js +46 -53
  53. package/dist/controller/runtimeEventInbox.js +23 -31
  54. package/dist/controller/runtimeEventProcessor.js +15 -15
  55. package/dist/controller/{runtimeHookRunFence.js → runtimeHookTurnFence.js} +87 -81
  56. package/dist/controller/runtimeLaunchCoordinator.js +3 -3
  57. package/dist/controller/runtimeObservationHook.js +11 -8
  58. package/dist/controller/sessionNotify.js +11 -11
  59. package/dist/controller/structuredProviderObservation.js +89 -16
  60. package/dist/coordination/workMailbox.js +309 -579
  61. package/dist/coordination/workMailboxQueue.js +5 -6
  62. package/dist/core/controllerClient.js +1 -91
  63. package/dist/core/controllerServer.js +3 -6
  64. package/dist/doctor/doctor.js +51 -314
  65. package/dist/execution/candidateConvergence.js +3 -3
  66. package/dist/execution/executionGroup.js +32 -33
  67. package/dist/execution/executionHealth.js +36 -38
  68. package/dist/execution/resourceBroker.js +12 -12
  69. package/dist/executor/agentExecutor.js +30 -265
  70. package/dist/executor/effectiveLaunch.js +9 -19
  71. package/dist/executor/executorRegistry.js +43 -17
  72. package/dist/executor/fileRoleLaunchPlanner.js +56 -56
  73. package/dist/executor/launchPlan.js +3 -3
  74. package/dist/executor/workspacePreflightClassification.js +16 -16
  75. package/dist/grant/capabilityGrant.js +20 -25
  76. package/dist/input/inputRequest.js +12 -12
  77. package/dist/integration/changeSet.js +9 -13
  78. package/dist/integration/changeSetManifest.js +1 -1
  79. package/dist/integration/gitIntegrationService.js +2 -2
  80. package/dist/integration/integrationQueueService.js +5 -21
  81. package/dist/integration/overlapDiagnostics.js +2 -2
  82. package/dist/interaction/operatorPresentation.js +1 -1
  83. package/dist/job/jobRunner.js +1 -1
  84. package/dist/lifecycle/canonicalLifecycleEvent.js +25 -25
  85. package/dist/lifecycle/{exactRunTerminalization.js → exactTurnTerminalization.js} +116 -232
  86. package/dist/message/message.js +6 -6
  87. package/dist/observability/executionAudit.js +74 -86
  88. package/dist/observability/faultClassification.js +7 -24
  89. package/dist/observability/orchestrationMetrics.js +5 -5
  90. package/dist/observability/runtimeIdentity.js +34 -101
  91. package/dist/profile/agentProfile.js +1 -1
  92. package/dist/release/cliHomeReleaseFence.js +3 -3
  93. package/dist/release/releaseWorkflowEngine.js +6 -10
  94. package/dist/release/releaseWorkflowPorts.js +2 -2
  95. package/dist/release/workflowFileLock.js +1 -1
  96. package/dist/repository/checkoutSwap.js +1 -1
  97. package/dist/repository/gitWorkspace.js +0 -13
  98. package/dist/repository/project.js +1 -1
  99. package/dist/repository/projectMaintenanceLock.js +1 -2
  100. package/dist/repository/taskBaseFreshness.js +13 -13
  101. package/dist/repository/taskWorkspaceCoordinator.js +21 -25
  102. package/dist/repository/taskWorkspaceIdentity.js +8 -21
  103. package/dist/repository/taskWorkspacePreparer.js +63 -620
  104. package/dist/resources/autoResourceGc.js +3 -3
  105. package/dist/resources/liveReferences.js +1 -1
  106. package/dist/resources/resourceGc.js +1 -1
  107. package/dist/resources/sqliteResourceRegistry.js +1 -1
  108. package/dist/review/reviewDecision.js +7 -7
  109. package/dist/review/reviewFindingLedger.js +1 -1
  110. package/dist/review/reviewOutcomeClassifier.js +35 -50
  111. package/dist/review/reviewRound.js +14 -25
  112. package/dist/review/reviewerAvailability.js +4 -4
  113. package/dist/review/taskFinalReviewContractResolution.js +41 -0
  114. package/dist/role/role.js +6 -3
  115. package/dist/runtime/agentDriver.js +4 -0
  116. package/dist/runtime/agentHost.js +151 -36
  117. package/dist/runtime/builtinAgentDrivers.js +79 -2
  118. package/dist/runtime/codexAppServerRuntime.js +122 -0
  119. package/dist/runtime/continuationManager.js +3 -5
  120. package/dist/runtime/exactControlPlane.js +17 -42
  121. package/dist/runtime/firstProgressAdvisory.js +1 -1
  122. package/dist/runtime/index.js +2 -2
  123. package/dist/runtime/launchDiagnostics.js +2 -2
  124. package/dist/runtime/lifecycleReservation.js +4 -4
  125. package/dist/runtime/processExitObservation.js +1 -1
  126. package/dist/runtime/processExitOutbox.js +0 -32
  127. package/dist/runtime/promptEnvelope.js +9 -9
  128. package/dist/runtime/providerContinuation.js +2 -2
  129. package/dist/runtime/providerContinuationReconciliationService.js +2 -2
  130. package/dist/runtime/providerRuntimeIdentity.js +76 -24
  131. package/dist/runtime/recentTurnIds.js +38 -0
  132. package/dist/runtime/runtimeContinuationProjection.js +2 -2
  133. package/dist/runtime/runtimeHealthPolicy.js +1 -1
  134. package/dist/runtime/runtimeObservation.js +58 -21
  135. package/dist/runtime/runtimeProjection.js +19 -27
  136. package/dist/runtime/sessionLaunchRequest.js +2 -2
  137. package/dist/runtime/structuredProviderHost.js +177 -12
  138. package/dist/runtime/tmuxAdapters.js +46 -11
  139. package/dist/scheduler/activeRoleTurnDelivery.js +245 -0
  140. package/dist/scheduler/leaderWakeupProcessor.js +147 -445
  141. package/dist/scheduler/operatorEvent.js +4 -6
  142. package/dist/scheduler/resourceQueueProjection.js +71 -0
  143. package/dist/scheduler/{roleRunLiveness.js → roleTurnLiveness.js} +24 -28
  144. package/dist/scheduler/{roleRunStall.js → roleTurnStall.js} +162 -247
  145. package/dist/scheduler/taskExecutionProjection.js +99 -99
  146. package/dist/scheduler/taskObservabilityProjection.js +10 -10
  147. package/dist/scheduler/taskWake.js +4 -4
  148. package/dist/scheduler/wakeReason.js +6 -10
  149. package/dist/scheduler/wakeupQueue.js +0 -9
  150. package/dist/setup/setupCommand.js +2 -2
  151. package/dist/storage/currentTaskStore.js +46 -0
  152. package/dist/storage/persistenceWorker.js +1 -1
  153. package/dist/storage/sqliteSchema.js +81 -485
  154. package/dist/storage/sqliteStore.js +206 -365
  155. package/dist/storage/storageSchema.js +1 -15
  156. package/dist/storage/storageVersions.js +8 -9
  157. package/dist/storage/storeRpc.js +10 -34
  158. package/dist/storage/taskStore.js +63 -3415
  159. package/dist/storage/upgrade/recordVersions.js +55 -149
  160. package/dist/storage/upgrade/upgradeOrchestrator.js +89 -1752
  161. package/dist/task/completionReadiness.js +5 -5
  162. package/dist/task/deliveryGuard.js +10 -10
  163. package/dist/task/nextAction.js +74 -77
  164. package/dist/task/task.js +2 -4
  165. package/dist/task/taskRecordReference.js +3 -3
  166. package/dist/task/taskRecordRetirement.js +1 -1
  167. package/dist/telemetry/sqliteTelemetryStore.js +53 -53
  168. package/dist/telemetry/telemetryConfig.js +14 -14
  169. package/dist/telemetry/telemetryWiring.js +8 -14
  170. package/dist/turn/turn.js +356 -0
  171. package/dist/{run/runIdentity.js → turn/turnIdentity.js} +4 -4
  172. package/dist/verification/gateArtifactStore.js +2 -2
  173. package/dist/web/assets/client/app.js +1 -1
  174. package/dist/web/assets/client/components.js +29 -54
  175. package/dist/web/assets/client/i18n.js +17 -37
  176. package/dist/web/assets/client/view.js +37 -37
  177. package/dist/web/assets/shell.js +1 -1
  178. package/dist/web/assets/styles/cards.js +5 -5
  179. package/dist/web/assets/styles/widgets.js +1 -2
  180. package/dist/web/webSnapshot.js +65 -60
  181. package/dist/workItem/workItem.js +11 -11
  182. package/dist/workspace/workItemChangeSetManager.js +1 -1
  183. package/docs/task-local-identity.md +5 -5
  184. package/i18n/README.zh-CN.md +41 -54
  185. package/package.json +1 -1
  186. package/skills/yui-leader/SKILL.md +85 -83
  187. package/skills/yui-operator/SKILL.md +16 -16
  188. package/skills/yui-reviewer/SKILL.md +10 -10
  189. package/skills/yui-runtime/SKILL.md +17 -18
  190. package/skills/yui-worker/SKILL.md +16 -18
  191. package/dist/context/runContextContract.js +0 -162
  192. package/dist/executor/turnCompletion.js +0 -126
  193. package/dist/review/taskFinalReviewContractEvent.js +0 -1
  194. package/dist/review/taskFinalReviewContractRebind.js +0 -367
  195. package/dist/run/agentRun.js +0 -356
  196. package/dist/run/rejectedYieldAttempt.js +0 -221
  197. package/dist/run/runControlRequest.js +0 -50
  198. package/dist/run/yieldReceipt.js +0 -65
  199. package/dist/runtime/turnCompletion.js +0 -3
  200. package/dist/scheduler/actionability.js +0 -323
  201. package/dist/scheduler/activeRoleRunDelivery.js +0 -868
  202. package/dist/scheduler/activeTaskProgress.js +0 -126
  203. package/dist/storage/compatibleTaskStore.js +0 -248
  204. package/dist/storage/migration/baseline.js +0 -78
  205. package/dist/storage/migration/classifier.js +0 -51
  206. package/dist/storage/migration/compatibleCodec.js +0 -53
  207. package/dist/storage/migration/engine.js +0 -147
  208. package/dist/storage/migration/index.js +0 -33
  209. package/dist/storage/migration/planner.js +0 -154
  210. package/dist/storage/migration/productionRegistry.js +0 -3931
  211. package/dist/storage/migration/registry.js +0 -169
  212. package/dist/storage/migration/report.js +0 -54
  213. package/dist/storage/migration/types.js +0 -31
  214. package/dist/storage/upgrade/homeClassification.js +0 -353
  215. package/dist/storage/upgrade/homeMigrationTarget.js +0 -595
  216. package/dist/storage/upgrade/migrationReceipt.js +0 -67
  217. package/dist/storage/upgrade/offlineUpgradeInventory.js +0 -453
  218. package/dist/storage/upgrade/productionMigrationRegistry.js +0 -6
  219. package/dist/storage/upgrade/pseudoLayoutRepair.js +0 -241
  220. package/dist/storage/upgrade/recordVersionScan.js +0 -176
  221. package/dist/storage/upgrade/sqliteFileSet.js +0 -50
  222. package/dist/storage/upgrade/sqliteMigrationTarget.js +0 -375
  223. package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +0 -305
  224. package/dist/storage/upgrade/sqliteStateMigration.js +0 -918
  225. package/dist/storage/upgrade/switchProgress.js +0 -80
  226. package/dist/storage/upgrade/upgradeReceipt.js +0 -161
  227. package/dist/storage/upgradeCoordination.js +0 -187
  228. package/dist/storage/upgradeFence.js +0 -366
  229. package/dist/telemetry/telemetryCompaction.js +0 -252
@@ -1,126 +0,0 @@
1
- import { selectedActiveSchedulerTasks, isSchedulerTaskWorkspaceReady } from "./ports.js";
2
- import { queueLeaderWakeup } from "./wakeupQueue.js";
3
- import { wakeReason } from "./wakeReason.js";
4
- import { projectTaskExecution } from "./taskExecutionProjection.js";
5
- import { collectTaskActionability, computeActionabilityDigest, decideOrphanWake, hasDispatchableQueuedResourceLane } from "./actionability.js";
6
- import { operationalTaskRecords } from "../task/taskRecordRetirement.js";
7
- /**
8
- * Repairs an active Task that has no durable owner capable of advancing it.
9
- * This is a low-frequency safety net; normal transitions enqueue their own
10
- * Role mailbox signal in the same transaction.
11
- *
12
- * Issue 05: a `task-orphaned` wake is suppressed when the last Leader Run
13
- * ended waiting/blocked and its observed actionability digest equals the
14
- * current digest. Suppression is silent — no Message, Event, or progress
15
- * record is written — because a periodic scan that changes nothing is Yui
16
- * engineering behavior with no Task-visible effect. Computation errors fail
17
- * open: the Task is woken once and the error is recorded so it can never be
18
- * silently starved.
19
- */
20
- export function repairOrphanedActiveTasks(store, now, selection) {
21
- const repaired = [];
22
- for (const task of selectedActiveSchedulerTasks(store, selection)) {
23
- const roles = store.listRoles(task.id);
24
- const activeRuns = roles.flatMap((role) => {
25
- const run = store.getActiveAgentRun(task.id, role.name);
26
- return run === null ? [] : [run];
27
- });
28
- const leaderTarget = { kind: "role", taskId: task.id, roleName: "leader" };
29
- const leaderMailbox = store.getWorkMailbox(leaderTarget);
30
- const projection = projectTaskExecution({
31
- task,
32
- roles,
33
- runs: activeRuns,
34
- pendingWakeup: store.getPendingWakeup(task.id),
35
- leaderMailbox,
36
- leaderFailure: store.getLeaderFailure(task.id)
37
- });
38
- const queuedAlongsideActiveSibling = hasDispatchableQueuedResourceLane(store, task.id)
39
- && !activeRuns.some(({ roleName }) => roleName === "leader")
40
- && activeRuns.some(({ roleName }) => roleName !== "leader")
41
- && projection.status === "waiting-on-agents";
42
- if (store.hasOpenInputRequest(task.id)
43
- || store.getLeaderFailure(task.id) !== null
44
- || (projection.status !== "needs-leader-action" && !queuedAlongsideActiveSibling)
45
- || hasUnclaimedLeaderWork(store, task.id, leaderMailbox)) {
46
- continue;
47
- }
48
- // Digest admission covers both an ownerless Task and a resource-queued
49
- // Task with active siblings. The latter wakes only when capacity/deadline
50
- // actionability changes, so repeated full scans remain silent.
51
- if (projection.reason === "no-executor" || queuedAlongsideActiveSibling) {
52
- if (admitOrphanWake(store, task.id, now) === "suppress")
53
- continue;
54
- }
55
- const taskWorkspace = store.getTaskWorkspace(task.id);
56
- if (!isSchedulerTaskWorkspaceReady(task, taskWorkspace)) {
57
- store.queueTaskProgress(task.id, "task-orphaned", now);
58
- }
59
- if (leaderMailbox?.processing !== null && leaderMailbox?.processing !== undefined) {
60
- const recovered = store.releaseLeaderWakeupAndEnqueue === undefined
61
- ? store.releaseWorkMailbox(leaderTarget, leaderMailbox.processing.batchId)
62
- && (queueLeaderWakeup(store, task.id, wakeReason("task-orphaned"), now), true)
63
- : store.releaseLeaderWakeupAndEnqueue(task.id, leaderMailbox.processing.batchId, wakeReason("task-orphaned"), now);
64
- if (recovered) {
65
- repaired.push(task.id);
66
- }
67
- continue;
68
- }
69
- queueLeaderWakeup(store, task.id, wakeReason("task-orphaned"), now);
70
- repaired.push(task.id);
71
- }
72
- return repaired;
73
- }
74
- /**
75
- * Decide whether one `task-orphaned` scan should wake the Leader. Returns
76
- * `"suppress"` without writing anything when the digest is unchanged since
77
- * the last waiting/blocked Leader Run. Computation errors fail open.
78
- */
79
- function admitOrphanWake(store, taskId, now) {
80
- let digest;
81
- try {
82
- const input = collectTaskActionability(store, taskId, now);
83
- digest = computeActionabilityDigest(input);
84
- }
85
- catch (error) {
86
- // Fail open: wake once and record the computation error so the Task can
87
- // never be silently starved by a broken projection.
88
- store.queueTaskProgress(taskId, "actionability-unknown", new Date());
89
- return "wake";
90
- }
91
- const lastLeaderRun = findLastLeaderRun(store, taskId);
92
- const decision = decideOrphanWake({ currentDigest: digest, lastLeaderRun });
93
- return decision.kind === "suppress" ? "suppress" : "wake";
94
- }
95
- /**
96
- * The most recent Leader Run (terminal or active). Active Runs are returned
97
- * so the admission check never suppresses while a Leader is still running.
98
- */
99
- function findLastLeaderRun(store, taskId) {
100
- const runs = operationalTaskRecords(store.listAgentRuns?.(taskId) ?? [], store.listEvents?.(taskId) ?? [], "agent-run");
101
- let latest = null;
102
- for (const run of runs) {
103
- if (run.roleName !== "leader")
104
- continue;
105
- if (latest === null || run.createdAt > latest.createdAt)
106
- latest = run;
107
- }
108
- if (latest === null)
109
- return null;
110
- return {
111
- status: latest.status,
112
- ...(latest.disposition === undefined ? {} : { disposition: latest.disposition }),
113
- ...(latest.observedActionabilityDigest === undefined
114
- ? {}
115
- : { observedActionabilityDigest: latest.observedActionabilityDigest })
116
- };
117
- }
118
- function hasUnclaimedLeaderWork(store, taskId, leaderMailbox) {
119
- if (store.getPendingWakeup(taskId) !== null)
120
- return true;
121
- if (leaderMailbox === null)
122
- return false;
123
- return leaderMailbox.processing === null
124
- && (leaderMailbox.pending.normal !== null
125
- || leaderMailbox.pending.userCorrection !== null);
126
- }
@@ -1,248 +0,0 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
- import { join } from "node:path";
3
- import { StorageCompatibilityError, loadCompatibleSnapshot } from "./migration/index.js";
4
- import { createProductionStorageRegistry } from "./migration/productionRegistry.js";
5
- import { FileTaskStore, STORAGE_STATE_FILE, stateFileFingerprint, StorageRecordError, validateCurrentStorageStateSnapshot } from "./taskStore.js";
6
- import { readSqliteHomeIdentity, SqliteTaskStore } from "./sqliteStore.js";
7
- import { COMMITTED_DATABASE_FILENAME } from "./upgrade/sqliteStateMigration.js";
8
- import { validateHomeIdentity } from "../repository/homeIdentity.js";
9
- import { CURRENT_STORAGE_LAYOUT_VERSION, ensureStorageSchema, inspectStorageSchema, readStorageSchemaManifest, STORAGE_SCHEMA_FILE } from "./storageSchema.js";
10
- import { classifyHome } from "./upgrade/homeClassification.js";
11
- import { inspectSnapshotVersionState } from "./upgrade/homeMigrationTarget.js";
12
- import { latestStorageVersionState } from "./upgrade/recordVersions.js";
13
- export { createProductionStorageRegistry } from "./migration/productionRegistry.js";
14
- /**
15
- * Initialize a brand-new Home with the current authoritative backend, or open
16
- * an existing Home through the same compatibility classification as every
17
- * ordinary command. Setup is the one ordinary flow that is also responsible
18
- * for creating the initial manifest.
19
- */
20
- export function initializeCompatibleTaskStore(home, options = {}) {
21
- if (inspectStorageSchema(home).status === "uninitialized") {
22
- ensureStorageSchema(home);
23
- if (CURRENT_STORAGE_LAYOUT_VERSION >= 7) {
24
- return new SqliteTaskStore(home);
25
- }
26
- }
27
- return openCompatibleFileTaskStore(home, options);
28
- }
29
- /**
30
- * Open current or explicitly compatible-old storage. Compatible records are
31
- * normalized in memory through strict old-shape validators; FileTaskStore then
32
- * runs its one current parser and its existing writer emits only current bytes.
33
- *
34
- * A current Home is opened from a single fingerprint-fenced `state.json`
35
- * snapshot: the same bytes drive the record-version inspection, the strict
36
- * shape/reference validation, and the returned store's initial cache seed, so
37
- * the open never re-reads the (large) state merely to construct or first use
38
- * the store.
39
- */
40
- export function openCompatibleFileTaskStore(home, options = {}) {
41
- const registry = options.registry ?? createProductionStorageRegistry();
42
- const latest = options.latest ?? latestStorageVersionState();
43
- const schema = inspectStorageSchema(home);
44
- // Issue 01: a layout-7 Home's authoritative backend is SQLite WAL. When
45
- // `yui.db` exists, open it directly — never fall back to a FileTaskStore
46
- // over a `state.json` that the repair may already have archived.
47
- if (!options.forceStateSource
48
- &&
49
- (schema.status === "current" || schema.status === "unsupported")
50
- && schema.currentLayoutVersion >= 7
51
- && existsSync(join(home, COMMITTED_DATABASE_FILENAME))) {
52
- return new SqliteTaskStore(home);
53
- }
54
- if (schema.status === "current") {
55
- return openCurrentFileTaskStore(home, latest);
56
- }
57
- const classification = classifyHome({ home, registry, latest });
58
- switch (classification.classification.status) {
59
- case "current":
60
- case "needs-storage-repair":
61
- // A pseudo-layout-7 Home whose aggregate axis is also older cannot be
62
- // normalized in memory: the compatible loader (requireCompatibleStorageSchema)
63
- // only bypasses record-axis mismatches, not aggregate/layout ones. Such a
64
- // Home needs the offline migration path (repair + aggregate migration in
65
- // one upgrade), so surface the same migration-required error the pure
66
- // version classifier would have produced, rather than a lazy strict-schema
67
- // error from the first store read.
68
- if (classification.classification.status === "needs-storage-repair"
69
- && classification.aggregateVersion !== undefined
70
- && classification.aggregateVersion < latest.aggregate) {
71
- throw new StorageCompatibilityError("Storage requires an offline migration. Re-run `yui update` when active Sessions are clear.");
72
- }
73
- // A pseudo-layout-7 Home (manifest 7, no yui.db) may also carry
74
- // compatible-old record versions, so open with the same normalization
75
- // path as `compatible-old` rather than the strict current gate.
76
- return new FileTaskStore(home, {
77
- normalizeState: (raw) => normalizeState(home, raw, registry, latest)
78
- });
79
- case "compatible-old":
80
- return new FileTaskStore(home, {
81
- normalizeState: (raw) => normalizeState(home, raw, registry, latest)
82
- });
83
- case "migration-required":
84
- throw new StorageCompatibilityError("Storage requires an offline migration. Re-run `yui update` when active Sessions are clear.");
85
- case "unsupported":
86
- throw new StorageCompatibilityError(describeUnsupported(classification.classification));
87
- }
88
- }
89
- /**
90
- * Read the one authoritative durable Home identity without creating runtime
91
- * state. SQLite is opened read-only; file-backed Homes read only the immutable
92
- * identity field, so an aggregate that requires offline migration can still
93
- * authenticate its already-running Controller before the migration stops it.
94
- */
95
- export function readCompatibleHomeIdentity(home, options = {}) {
96
- const schema = inspectStorageSchema(home);
97
- if (!options.forceStateSource
98
- && (schema.status === "current" || schema.status === "unsupported")
99
- && schema.currentLayoutVersion >= 7
100
- && existsSync(join(home, COMMITTED_DATABASE_FILENAME))) {
101
- return readSqliteHomeIdentity(home, COMMITTED_DATABASE_FILENAME);
102
- }
103
- if (!existsSync(join(home, STORAGE_STATE_FILE))) {
104
- throw new StorageRecordError("Durable Home identity is missing.");
105
- }
106
- let parsed;
107
- try {
108
- parsed = JSON.parse(readFileSync(join(home, STORAGE_STATE_FILE), "utf8"));
109
- }
110
- catch {
111
- throw new StorageRecordError("Durable Home identity state is invalid JSON.");
112
- }
113
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
114
- throw new StorageRecordError("Durable Home identity state is invalid.");
115
- }
116
- try {
117
- return validateHomeIdentity(parsed.homeIdentity);
118
- }
119
- catch {
120
- throw new StorageRecordError("Durable Home identity is invalid.");
121
- }
122
- }
123
- /**
124
- * The one-snapshot open for a Home whose manifest is already current. A single
125
- * fingerprint-fenced read of `state.json` supplies:
126
- * - the record-version inspection (structural scan cross-checked against the
127
- * durable manifest, never the strict loader),
128
- * - the strict shape/reference validation (the store's own parser, run while
129
- * seeding its cache), and
130
- * - the returned store's initial cache seed under the same fingerprint.
131
- *
132
- * A fingerprint that drifts across the read retries once and then fails
133
- * closed; a later external writer still invalidates the seeded cache on the
134
- * store's next read. Corruption surfaces as the same `StorageCompatibilityError`
135
- * diagnosis the classifier produced before this fast path existed.
136
- */
137
- function openCurrentFileTaskStore(home, latest) {
138
- const statePath = join(home, STORAGE_STATE_FILE);
139
- if (!existsSync(statePath)) {
140
- // No state.json yet: the store's own missing-file path seeds an empty
141
- // state without reading anything.
142
- return new FileTaskStore(home);
143
- }
144
- const manifest = readStorageSchemaManifest(home);
145
- const snapshot = readFingerprintFencedState(statePath);
146
- let parsed;
147
- try {
148
- parsed = JSON.parse(snapshot.raw);
149
- }
150
- catch (error) {
151
- throw new StorageCompatibilityError(`Invalid state.json: state.json is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
152
- }
153
- if (!isRecord(parsed)) {
154
- throw new StorageCompatibilityError("Invalid state.json: state.json is not a JSON object.");
155
- }
156
- const inspection = inspectSnapshotVersionState(Object.freeze({ schemaManifest: manifest, state: parsed }), latest);
157
- if ("corruption" in inspection) {
158
- throw new StorageCompatibilityError(`Invalid state.json: ${inspection.corruption.detail}`);
159
- }
160
- try {
161
- return new FileTaskStore(home, {
162
- initialStateSnapshot: { fingerprint: snapshot.fingerprint, raw: snapshot.raw }
163
- });
164
- }
165
- catch (error) {
166
- if (error instanceof StorageRecordError) {
167
- throw new StorageCompatibilityError(`Invalid state.json: ${error.message}`);
168
- }
169
- throw error;
170
- }
171
- }
172
- /**
173
- * Read `state.json` once, fenced by the same fingerprint the store's read cache
174
- * is keyed on. A fingerprint that changes between the before and after stat
175
- * means a concurrent writer is mid-update: retry once (bounded), then fail
176
- * closed rather than seed a store from bytes that may be torn.
177
- */
178
- export function readFingerprintFencedState(statePath, io = {}) {
179
- const fingerprint = io.fingerprint ?? stateFileFingerprint;
180
- const read = io.read ?? ((path) => readFileSync(path, "utf8"));
181
- for (let attempt = 0; attempt < 2; attempt += 1) {
182
- const before = fingerprint(statePath);
183
- const raw = read(statePath);
184
- const after = fingerprint(statePath);
185
- if (before === after)
186
- return { raw, fingerprint: after };
187
- }
188
- throw new StorageCompatibilityError("state.json changed while opening; a concurrent writer is updating the Home. Retry the command.");
189
- }
190
- /**
191
- * Eagerly prove that a compatible-old Home reaches the strict current parser.
192
- * Staged update/upgrade preflight uses this before any Controller or binary
193
- * action; ordinary commands may keep the store's normal lazy-read behavior.
194
- */
195
- export function validateCompatibleFileTaskStore(home, options = {}) {
196
- openCompatibleFileTaskStore(home, { ...options, forceStateSource: true }).getConfig();
197
- }
198
- function normalizeState(home, raw, registry, latest) {
199
- let state;
200
- try {
201
- state = JSON.parse(raw);
202
- }
203
- catch (error) {
204
- throw new StorageCompatibilityError(`Compatible state is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
205
- }
206
- if (!isRecord(state)) {
207
- throw new StorageCompatibilityError("Compatible state must be a JSON object.");
208
- }
209
- const schemaManifest = JSON.parse(readFileSync(join(home, STORAGE_SCHEMA_FILE), "utf8"));
210
- if (!isRecord(schemaManifest)) {
211
- throw new StorageCompatibilityError("Compatible schema manifest must be a JSON object.");
212
- }
213
- const snapshot = { schemaManifest, state };
214
- const source = versionsOf(snapshot, latest);
215
- const normalized = loadCompatibleSnapshot({
216
- registry,
217
- source,
218
- latest,
219
- snapshot,
220
- inspectVersions: (candidate) => versionsOf(candidate, latest),
221
- validateCurrent: (candidate) => {
222
- if (candidate.state === null) {
223
- throw new StorageCompatibilityError("Compatible state unexpectedly disappeared.");
224
- }
225
- validateCurrentStorageStateSnapshot(candidate.state);
226
- }
227
- });
228
- return `${JSON.stringify(normalized.state)}\n`;
229
- }
230
- function versionsOf(snapshot, latest) {
231
- const inspection = inspectSnapshotVersionState(snapshot, latest);
232
- if ("corruption" in inspection) {
233
- throw new StorageCompatibilityError(inspection.corruption.detail);
234
- }
235
- return inspection.source;
236
- }
237
- function describeUnsupported(classification) {
238
- if (classification.verdict === "CORRUPTED") {
239
- return `Invalid state.json: ${classification.detail}`;
240
- }
241
- if (classification.verdict === "NEEDS_NEW_VERSION") {
242
- return classification.blocker.message;
243
- }
244
- return "Storage is unsupported by this Yui release.";
245
- }
246
- function isRecord(value) {
247
- return typeof value === "object" && value !== null && !Array.isArray(value);
248
- }
@@ -1,78 +0,0 @@
1
- import { CURRENT_AGGREGATE_SCHEMA_VERSION, CURRENT_STORAGE_LAYOUT_VERSION } from "../storageVersions.js";
2
- import { currentRecordVersions } from "../upgrade/recordVersions.js";
3
- export const BASELINE_STORAGE_LAYOUT_VERSION = 6;
4
- export const BASELINE_AGGREGATE_SCHEMA_VERSION = 16;
5
- const BASELINE_RECORD_DESCRIPTORS = Object.freeze({
6
- config: descriptor(1, "state.json#/config"),
7
- configuredAgent: descriptor(2, "state.json#/configuredAgents"),
8
- project: descriptor(2, "state.json#/projects"),
9
- agentProfile: descriptor(2, "state.json#/agentProfiles"),
10
- globalRole: descriptor(3, "state.json#/globalRoles"),
11
- globalRoleSessionSet: descriptor(3, "state.json#/globalRoleSessionSets"),
12
- storedTask: descriptor(14, "state.json#/tasks/*"),
13
- task: descriptor(3, "state.json#/tasks/*/task"),
14
- taskBrief: descriptor(2, "state.json#/tasks/*/brief"),
15
- taskRole: descriptor(3, "state.json#/tasks/*/roles"),
16
- managedWorkspace: descriptor(1, "state.json#/tasks/*/managedWorkspaces"),
17
- taskRoleSessionSet: descriptor(4, "state.json#/tasks/*/roleSessionSets"),
18
- workItem: descriptor(6, "state.json#/tasks/*/workItems"),
19
- agentRun: descriptor(5, "state.json#/tasks/*/agentRuns"),
20
- reviewRound: descriptor(2, "state.json#/tasks/*/reviewRounds"),
21
- changeSet: descriptor(2, "state.json#/tasks/*/changeSets"),
22
- integrationAttempt: descriptor(2, "state.json#/tasks/*/integrationAttempts"),
23
- activeRunPointer: descriptor(1, "state.json#/tasks/*/activeRuns"),
24
- message: descriptor(2, "state.json#/tasks/*/messages"),
25
- inputRequest: descriptor(2, "state.json#/tasks/*/inputRequests"),
26
- decision: descriptor(1, "state.json#/tasks/*/decisions"),
27
- milestone: descriptor(1, "state.json#/tasks/*/milestones"),
28
- event: descriptor(2, "state.json#/tasks/*/events"),
29
- leaderFailure: descriptor(1, "state.json#/tasks/*/leaderFailure"),
30
- operatorNotification: descriptor(1, "state.json#/tasks/*/operatorNotification"),
31
- workMailbox: descriptor(1, "state.json#/mailboxes")
32
- });
33
- /** Frozen baseline record-family version map retained as a public delivery contract. */
34
- export const BASELINE_RECORD_VERSIONS = Object.freeze(Object.fromEntries(Object.entries(BASELINE_RECORD_DESCRIPTORS).map(([kind, entry]) => [kind, entry.version])));
35
- export function baselineStorageVersionState() {
36
- return Object.freeze({
37
- layout: BASELINE_STORAGE_LAYOUT_VERSION,
38
- aggregate: BASELINE_AGGREGATE_SCHEMA_VERSION,
39
- record: Object.freeze({ ...BASELINE_RECORD_DESCRIPTORS })
40
- });
41
- }
42
- /** Constant spelling used by delivery-gate callers and historical tests. */
43
- export const BASELINE_STORAGE_VERSION_STATE = baselineStorageVersionState();
44
- /**
45
- * Fail the delivery gate if the immutable baseline is ahead of the live schema,
46
- * loses a record family, or changes a locator without an explicit scalar
47
- * storage boundary. The production registry gate separately proves the full
48
- * executable baseline-to-current transition path.
49
- */
50
- export function assertBaselineConsistency(current = {
51
- layout: CURRENT_STORAGE_LAYOUT_VERSION,
52
- aggregate: CURRENT_AGGREGATE_SCHEMA_VERSION,
53
- record: currentRecordVersions()
54
- }) {
55
- if (BASELINE_STORAGE_LAYOUT_VERSION > current.layout) {
56
- throw new Error(`Baseline layout ${BASELINE_STORAGE_LAYOUT_VERSION} exceeds current layout ${current.layout}.`);
57
- }
58
- if (BASELINE_AGGREGATE_SCHEMA_VERSION > current.aggregate) {
59
- throw new Error(`Baseline aggregate ${BASELINE_AGGREGATE_SCHEMA_VERSION} exceeds current aggregate ${current.aggregate}.`);
60
- }
61
- const scalarVersionAdvanced = current.layout > BASELINE_STORAGE_LAYOUT_VERSION
62
- || current.aggregate > BASELINE_AGGREGATE_SCHEMA_VERSION;
63
- for (const [kind, baselineEntry] of Object.entries(BASELINE_RECORD_DESCRIPTORS)) {
64
- const currentEntry = current.record[kind];
65
- if (!Number.isSafeInteger(baselineEntry.version) || baselineEntry.version < 1) {
66
- throw new Error(`Baseline record family '${kind}' has invalid version ${String(baselineEntry.version)}.`);
67
- }
68
- if (currentEntry === undefined || baselineEntry.version > currentEntry.version) {
69
- throw new Error(`Baseline record family '${kind}' is ahead of or missing from current.`);
70
- }
71
- if (baselineEntry.path !== currentEntry.path && !scalarVersionAdvanced) {
72
- throw new Error(`Baseline record family '${kind}' path drift requires a layout or aggregate version change.`);
73
- }
74
- }
75
- }
76
- function descriptor(version, path) {
77
- return Object.freeze({ version, path });
78
- }
@@ -1,51 +0,0 @@
1
- /**
2
- * The pure four-state compatibility classifier.
3
- *
4
- * Given the source and target (latest-supported) version states, an EMPTY-or-
5
- * populated registry, and an optional caller-supplied corruption signal, it
6
- * returns exactly one verdict:
7
- *
8
- * - `USABLE` — every axis is already at the current version.
9
- * - `COMPATIBLE` — strictly older record axes whose complete adjacent
10
- * path consists only of compatible normalizations.
11
- * - `MIGRATABLE` — at least one adjacent hop requires offline migration,
12
- * and every axis has a complete deterministic path.
13
- * - `NEEDS_NEW_VERSION` — any axis is newer than supported (`future-version`),
14
- * or older with a broken/absent step path (`missing-step`). Carries a precise
15
- * reason + action. Under an EMPTY registry, any strictly-older version lands
16
- * here via a missing transition contract.
17
- * - `CORRUPTED` — only when the caller reports real JSON/structural/
18
- * reference-invariant damage. The classifier never infers corruption from
19
- * version numbers.
20
- *
21
- * There are no baseline/pre-release special cases: compatibility is decided
22
- * solely by explicit registry step-paths, never by version magnitude or semver.
23
- */
24
- import { planMigration } from "./planner.js";
25
- /**
26
- * Classify a storage target. Pure: no I/O, no mutation, deterministic.
27
- *
28
- * `corruption` is supplied by the caller when parsing/structural/reference
29
- * validation has already failed; when present it takes precedence, because a
30
- * corrupted target's version numbers cannot be trusted.
31
- */
32
- export function classifyStorage(registry, source, target, corruption) {
33
- if (corruption?.corrupted === true) {
34
- return { verdict: "CORRUPTED", status: "unsupported", detail: corruption.detail };
35
- }
36
- const plan = planMigration(registry, source, target);
37
- switch (plan.kind) {
38
- case "no-op":
39
- return { verdict: "USABLE", status: "current" };
40
- case "runnable":
41
- return plan.path === "compatible"
42
- ? { verdict: "COMPATIBLE", status: "compatible-old", stepCount: plan.steps.length }
43
- : { verdict: "MIGRATABLE", status: "migration-required", stepCount: plan.steps.length };
44
- case "blocked":
45
- return {
46
- verdict: "NEEDS_NEW_VERSION",
47
- status: "unsupported",
48
- blocker: plan.blocker
49
- };
50
- }
51
- }
@@ -1,53 +0,0 @@
1
- import { isDeepStrictEqual } from "node:util";
2
- import { planMigration } from "./planner.js";
3
- export class StorageCompatibilityError extends Error {
4
- constructor(message) {
5
- super(message);
6
- this.name = "StorageCompatibilityError";
7
- }
8
- }
9
- /**
10
- * Apply an all-compatible declaration chain in memory. This never writes the
11
- * source. It rejects offline paths, missing declarations/steps, non-fresh
12
- * normalizers, and output which does not pass the strict current-model gate.
13
- */
14
- export function loadCompatibleSnapshot(options) {
15
- const plan = planMigration(options.registry, options.source, options.latest);
16
- if (plan.kind === "blocked") {
17
- throw new StorageCompatibilityError(plan.blocker.message);
18
- }
19
- if (plan.kind === "runnable" && plan.path !== "compatible") {
20
- throw new StorageCompatibilityError("Storage requires an offline migration and cannot be normalized by the compatible loader.");
21
- }
22
- let current = options.snapshot;
23
- if (plan.kind === "runnable") {
24
- for (const planned of plan.steps) {
25
- planned.step.preconditions(current);
26
- const next = planned.step.transform(current);
27
- if (next === current && typeof next === "object" && next !== null) {
28
- throw new StorageCompatibilityError(`Compatible normalizer ${planned.axis}` +
29
- `${planned.recordKind ? `/${planned.recordKind}` : ""} ` +
30
- `${planned.fromVersion}->${planned.toVersion} returned its input object.`);
31
- }
32
- current = next;
33
- }
34
- }
35
- assertCurrentVersions(options.inspectVersions(current), options.latest, "normalized model");
36
- options.validateCurrent(current);
37
- return current;
38
- }
39
- /**
40
- * Encode only the current record contract. There is intentionally no old
41
- * writer or dual-write branch: a non-current encoding fails before persistence.
42
- */
43
- export function writeCurrentSnapshot(options) {
44
- const snapshot = options.encode(options.model);
45
- assertCurrentVersions(options.inspectVersions(snapshot), options.latest, "writer output");
46
- options.validateCurrent(snapshot);
47
- return snapshot;
48
- }
49
- function assertCurrentVersions(actual, latest, label) {
50
- if (!isDeepStrictEqual(actual, latest)) {
51
- throw new StorageCompatibilityError(`Compatible ${label} is not at the current storage versions; old-version writing is forbidden.`);
52
- }
53
- }