@zq-silk/yui 0.13.10 → 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (230) hide show
  1. package/ARCHITECTURE.md +69 -428
  2. package/README.md +113 -300
  3. package/dist/agent/argumentPolicy.js +2 -1
  4. package/dist/agent/managedRuntimeEnvironment.js +2 -2
  5. package/dist/cli/commandCatalog.js +39 -77
  6. package/dist/cli/interactionCandidates.js +4 -4
  7. package/dist/cli/interactionPolicy.js +7 -10
  8. package/dist/cli/invocationRouter.js +1 -1
  9. package/dist/cli/updateCommand.js +6 -27
  10. package/dist/cli/updateOrchestrator.js +63 -484
  11. package/dist/cli/updatePorts.js +15 -333
  12. package/dist/cli/upgradeCommand.js +8 -65
  13. package/dist/cli.js +57 -242
  14. package/dist/commands/configCommands.js +11 -62
  15. package/dist/commands/executionAuditCommands.js +23 -23
  16. package/dist/commands/globalRoleCommands.js +2 -7
  17. package/dist/commands/jobCommands.js +1 -4
  18. package/dist/commands/projectCommands.js +5 -5
  19. package/dist/commands/releaseCommands.js +3 -10
  20. package/dist/commands/resourcesCommands.js +2 -2
  21. package/dist/commands/sessionCommands.js +2 -2
  22. package/dist/commands/taskActor.js +31 -39
  23. package/dist/commands/taskChangeSetCommands.js +5 -12
  24. package/dist/commands/taskCommands.js +464 -1249
  25. package/dist/commands/taskContextCommand.js +35 -36
  26. package/dist/commands/taskExecutionCommands.js +33 -46
  27. package/dist/commands/taskInputCommands.js +20 -42
  28. package/dist/commands/taskNextActionCommand.js +9 -9
  29. package/dist/commands/taskOverviewCommand.js +56 -95
  30. package/dist/commands/taskRoleRuntimeStatus.js +77 -107
  31. package/dist/commands/taskUpstreamCommands.js +3 -3
  32. package/dist/commands/taskWorkspaceCommands.js +7 -79
  33. package/dist/commands/telemetryCommands.js +32 -124
  34. package/dist/commands/workflowCommands.js +3 -3
  35. package/dist/completion/fileCompletionManager.js +1 -1
  36. package/dist/config/configCatalog.js +4 -6
  37. package/dist/config/yuiConfig.js +6 -43
  38. package/dist/context/dispatchContext.js +14 -30
  39. package/dist/context/roleSessionContext.js +2 -2
  40. package/dist/context/sessionBootstrapManifest.js +20 -3
  41. package/dist/context/{runContextPack.js → turnContextPack.js} +58 -73
  42. package/dist/context/turnInputContract.js +204 -0
  43. package/dist/context/wakeNotification.js +2 -2
  44. package/dist/controller/agentRuntimeObserver.js +18 -18
  45. package/dist/controller/clientRuntime.js +13 -20
  46. package/dist/controller/controller.js +89 -116
  47. package/dist/controller/fileSchedulerStoreAdapter.js +511 -742
  48. package/dist/controller/jobControl.js +17 -17
  49. package/dist/controller/resourceInventory.js +6 -6
  50. package/dist/controller/resourceInventoryLinux.js +38 -59
  51. package/dist/controller/resourceInventoryRpc.js +3 -3
  52. package/dist/controller/resourceInventoryWorker.js +1 -1
  53. package/dist/controller/runtime.js +46 -53
  54. package/dist/controller/runtimeEventInbox.js +23 -31
  55. package/dist/controller/runtimeEventProcessor.js +15 -15
  56. package/dist/controller/{runtimeHookRunFence.js → runtimeHookTurnFence.js} +87 -81
  57. package/dist/controller/runtimeLaunchCoordinator.js +3 -3
  58. package/dist/controller/runtimeObservationHook.js +11 -8
  59. package/dist/controller/sessionNotify.js +11 -11
  60. package/dist/controller/structuredProviderObservation.js +89 -16
  61. package/dist/coordination/workMailbox.js +309 -579
  62. package/dist/coordination/workMailboxQueue.js +5 -6
  63. package/dist/core/controllerClient.js +1 -91
  64. package/dist/core/controllerServer.js +3 -6
  65. package/dist/doctor/doctor.js +51 -314
  66. package/dist/execution/candidateConvergence.js +3 -3
  67. package/dist/execution/executionGroup.js +32 -33
  68. package/dist/execution/executionHealth.js +36 -38
  69. package/dist/execution/resourceBroker.js +12 -12
  70. package/dist/executor/agentExecutor.js +30 -265
  71. package/dist/executor/effectiveLaunch.js +9 -19
  72. package/dist/executor/executorRegistry.js +43 -17
  73. package/dist/executor/fileRoleLaunchPlanner.js +91 -57
  74. package/dist/executor/launchPlan.js +3 -3
  75. package/dist/executor/workspacePreflightClassification.js +16 -16
  76. package/dist/grant/capabilityGrant.js +20 -25
  77. package/dist/input/inputRequest.js +12 -12
  78. package/dist/integration/changeSet.js +9 -13
  79. package/dist/integration/changeSetManifest.js +1 -1
  80. package/dist/integration/gitIntegrationService.js +2 -2
  81. package/dist/integration/integrationQueueService.js +5 -21
  82. package/dist/integration/overlapDiagnostics.js +2 -2
  83. package/dist/interaction/operatorPresentation.js +1 -1
  84. package/dist/job/jobRunner.js +1 -1
  85. package/dist/lifecycle/canonicalLifecycleEvent.js +25 -25
  86. package/dist/lifecycle/{exactRunTerminalization.js → exactTurnTerminalization.js} +116 -232
  87. package/dist/message/message.js +6 -6
  88. package/dist/observability/executionAudit.js +74 -86
  89. package/dist/observability/faultClassification.js +7 -24
  90. package/dist/observability/orchestrationMetrics.js +5 -5
  91. package/dist/observability/runtimeIdentity.js +34 -101
  92. package/dist/profile/agentProfile.js +1 -1
  93. package/dist/release/cliHomeReleaseFence.js +3 -3
  94. package/dist/release/releaseWorkflowEngine.js +6 -10
  95. package/dist/release/releaseWorkflowPorts.js +2 -2
  96. package/dist/release/workflowFileLock.js +1 -1
  97. package/dist/repository/checkoutSwap.js +1 -1
  98. package/dist/repository/gitWorkspace.js +0 -13
  99. package/dist/repository/project.js +1 -1
  100. package/dist/repository/projectMaintenanceLock.js +1 -2
  101. package/dist/repository/taskBaseFreshness.js +13 -13
  102. package/dist/repository/taskWorkspaceCoordinator.js +21 -25
  103. package/dist/repository/taskWorkspaceIdentity.js +8 -21
  104. package/dist/repository/taskWorkspacePreparer.js +63 -620
  105. package/dist/resources/autoResourceGc.js +3 -3
  106. package/dist/resources/liveReferences.js +1 -1
  107. package/dist/resources/resourceGc.js +1 -1
  108. package/dist/resources/sqliteResourceRegistry.js +1 -1
  109. package/dist/review/reviewDecision.js +7 -7
  110. package/dist/review/reviewFindingLedger.js +1 -1
  111. package/dist/review/reviewOutcomeClassifier.js +35 -50
  112. package/dist/review/reviewRound.js +14 -25
  113. package/dist/review/reviewerAvailability.js +4 -4
  114. package/dist/review/taskFinalReviewContractResolution.js +41 -0
  115. package/dist/role/role.js +6 -3
  116. package/dist/runtime/agentDriver.js +4 -0
  117. package/dist/runtime/agentHost.js +151 -36
  118. package/dist/runtime/builtinAgentDrivers.js +79 -2
  119. package/dist/runtime/codexAppServerRuntime.js +122 -0
  120. package/dist/runtime/continuationManager.js +3 -5
  121. package/dist/runtime/exactControlPlane.js +17 -42
  122. package/dist/runtime/firstProgressAdvisory.js +1 -1
  123. package/dist/runtime/index.js +2 -2
  124. package/dist/runtime/launchDiagnostics.js +2 -2
  125. package/dist/runtime/lifecycleReservation.js +4 -4
  126. package/dist/runtime/processExitObservation.js +1 -1
  127. package/dist/runtime/processExitOutbox.js +0 -32
  128. package/dist/runtime/promptEnvelope.js +9 -9
  129. package/dist/runtime/providerContinuation.js +2 -2
  130. package/dist/runtime/providerContinuationReconciliationService.js +2 -2
  131. package/dist/runtime/providerRuntimeIdentity.js +76 -24
  132. package/dist/runtime/recentTurnIds.js +38 -0
  133. package/dist/runtime/runtimeContinuationProjection.js +2 -2
  134. package/dist/runtime/runtimeHealthPolicy.js +1 -1
  135. package/dist/runtime/runtimeObservation.js +58 -21
  136. package/dist/runtime/runtimeProjection.js +19 -27
  137. package/dist/runtime/sessionLaunchRequest.js +2 -2
  138. package/dist/runtime/structuredProviderHost.js +177 -12
  139. package/dist/runtime/tmuxAdapters.js +46 -11
  140. package/dist/scheduler/activeRoleTurnDelivery.js +245 -0
  141. package/dist/scheduler/leaderWakeupProcessor.js +147 -445
  142. package/dist/scheduler/operatorEvent.js +4 -6
  143. package/dist/scheduler/resourceQueueProjection.js +71 -0
  144. package/dist/scheduler/{roleRunLiveness.js → roleTurnLiveness.js} +24 -28
  145. package/dist/scheduler/{roleRunStall.js → roleTurnStall.js} +162 -247
  146. package/dist/scheduler/taskExecutionProjection.js +99 -99
  147. package/dist/scheduler/taskObservabilityProjection.js +10 -10
  148. package/dist/scheduler/taskWake.js +4 -4
  149. package/dist/scheduler/wakeReason.js +6 -10
  150. package/dist/scheduler/wakeupQueue.js +0 -9
  151. package/dist/setup/setupCommand.js +2 -2
  152. package/dist/storage/currentTaskStore.js +46 -0
  153. package/dist/storage/persistenceWorker.js +1 -1
  154. package/dist/storage/sqliteSchema.js +81 -485
  155. package/dist/storage/sqliteStore.js +206 -365
  156. package/dist/storage/storageSchema.js +1 -15
  157. package/dist/storage/storageVersions.js +8 -9
  158. package/dist/storage/storeRpc.js +10 -34
  159. package/dist/storage/taskStore.js +63 -3415
  160. package/dist/storage/upgrade/recordVersions.js +55 -149
  161. package/dist/storage/upgrade/upgradeOrchestrator.js +89 -1752
  162. package/dist/task/completionReadiness.js +5 -5
  163. package/dist/task/deliveryGuard.js +10 -10
  164. package/dist/task/nextAction.js +74 -77
  165. package/dist/task/task.js +2 -4
  166. package/dist/task/taskRecordReference.js +3 -3
  167. package/dist/task/taskRecordRetirement.js +1 -1
  168. package/dist/telemetry/sqliteTelemetryStore.js +53 -53
  169. package/dist/telemetry/telemetryConfig.js +14 -14
  170. package/dist/telemetry/telemetryWiring.js +8 -14
  171. package/dist/turn/turn.js +356 -0
  172. package/dist/{run/runIdentity.js → turn/turnIdentity.js} +4 -4
  173. package/dist/verification/gateArtifactStore.js +2 -2
  174. package/dist/web/assets/client/app.js +1 -1
  175. package/dist/web/assets/client/components.js +29 -54
  176. package/dist/web/assets/client/i18n.js +17 -37
  177. package/dist/web/assets/client/view.js +37 -37
  178. package/dist/web/assets/shell.js +1 -1
  179. package/dist/web/assets/styles/cards.js +5 -5
  180. package/dist/web/assets/styles/widgets.js +1 -2
  181. package/dist/web/webSnapshot.js +65 -60
  182. package/dist/workItem/workItem.js +11 -11
  183. package/dist/workspace/workItemChangeSetManager.js +1 -1
  184. package/docs/task-local-identity.md +5 -5
  185. package/i18n/README.zh-CN.md +42 -55
  186. package/package.json +1 -1
  187. package/skills/yui-leader/SKILL.md +85 -83
  188. package/skills/yui-operator/SKILL.md +16 -16
  189. package/skills/yui-reviewer/SKILL.md +10 -10
  190. package/skills/yui-runtime/SKILL.md +22 -24
  191. package/skills/yui-worker/SKILL.md +16 -18
  192. package/dist/context/runContextContract.js +0 -162
  193. package/dist/executor/turnCompletion.js +0 -126
  194. package/dist/review/taskFinalReviewContractEvent.js +0 -1
  195. package/dist/review/taskFinalReviewContractRebind.js +0 -367
  196. package/dist/run/agentRun.js +0 -356
  197. package/dist/run/rejectedYieldAttempt.js +0 -221
  198. package/dist/run/runControlRequest.js +0 -50
  199. package/dist/run/yieldReceipt.js +0 -65
  200. package/dist/runtime/turnCompletion.js +0 -3
  201. package/dist/scheduler/actionability.js +0 -323
  202. package/dist/scheduler/activeRoleRunDelivery.js +0 -868
  203. package/dist/scheduler/activeTaskProgress.js +0 -126
  204. package/dist/storage/compatibleTaskStore.js +0 -248
  205. package/dist/storage/migration/baseline.js +0 -78
  206. package/dist/storage/migration/classifier.js +0 -51
  207. package/dist/storage/migration/compatibleCodec.js +0 -53
  208. package/dist/storage/migration/engine.js +0 -147
  209. package/dist/storage/migration/index.js +0 -33
  210. package/dist/storage/migration/planner.js +0 -154
  211. package/dist/storage/migration/productionRegistry.js +0 -3931
  212. package/dist/storage/migration/registry.js +0 -169
  213. package/dist/storage/migration/report.js +0 -54
  214. package/dist/storage/migration/types.js +0 -31
  215. package/dist/storage/upgrade/homeClassification.js +0 -353
  216. package/dist/storage/upgrade/homeMigrationTarget.js +0 -595
  217. package/dist/storage/upgrade/migrationReceipt.js +0 -67
  218. package/dist/storage/upgrade/offlineUpgradeInventory.js +0 -453
  219. package/dist/storage/upgrade/productionMigrationRegistry.js +0 -6
  220. package/dist/storage/upgrade/pseudoLayoutRepair.js +0 -241
  221. package/dist/storage/upgrade/recordVersionScan.js +0 -176
  222. package/dist/storage/upgrade/sqliteFileSet.js +0 -50
  223. package/dist/storage/upgrade/sqliteMigrationTarget.js +0 -375
  224. package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +0 -305
  225. package/dist/storage/upgrade/sqliteStateMigration.js +0 -926
  226. package/dist/storage/upgrade/switchProgress.js +0 -80
  227. package/dist/storage/upgrade/upgradeReceipt.js +0 -161
  228. package/dist/storage/upgradeCoordination.js +0 -187
  229. package/dist/storage/upgradeFence.js +0 -366
  230. 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
- }