@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.
Files changed (179) hide show
  1. package/ARCHITECTURE.md +64 -35
  2. package/README.md +258 -125
  3. package/dist/agent/argumentPolicy.js +2 -1
  4. package/dist/agent/managedRuntimeEnvironment.js +5 -8
  5. package/dist/cli/commandCatalog.js +74 -109
  6. package/dist/cli/helpRenderer.js +1 -2
  7. package/dist/cli/interactionCandidates.js +10 -0
  8. package/dist/cli/interactionPolicy.js +76 -37
  9. package/dist/cli/roleWizard.js +5 -6
  10. package/dist/cli/updateOrchestrator.js +10 -8
  11. package/dist/cli/updatePorts.js +17 -13
  12. package/dist/cli/upgradeCommand.js +2 -5
  13. package/dist/cli.js +268 -231
  14. package/dist/commands/agentCommands.js +13 -0
  15. package/dist/commands/configCommands.js +7 -19
  16. package/dist/commands/configOverview.js +3 -2
  17. package/dist/commands/durableJobCommands.js +1 -1
  18. package/dist/commands/executionAuditCommands.js +4 -9
  19. package/dist/commands/globalRoleCommands.js +17 -0
  20. package/dist/commands/profileCommands.js +200 -30
  21. package/dist/commands/sessionCommands.js +1 -1
  22. package/dist/commands/taskActor.js +29 -127
  23. package/dist/commands/taskChangeSetCommands.js +1 -3
  24. package/dist/commands/taskCommands.js +1771 -2107
  25. package/dist/commands/taskCompletionGate.js +27 -103
  26. package/dist/commands/taskContextCommand.js +47 -80
  27. package/dist/commands/taskExecutionCommands.js +9 -27
  28. package/dist/commands/taskInputCommands.js +10 -10
  29. package/dist/commands/taskIntegrationCommands.js +69 -61
  30. package/dist/commands/taskIntegrationQueueCommands.js +1 -1
  31. package/dist/commands/taskNextActionCommand.js +8 -29
  32. package/dist/commands/taskOverviewCommand.js +25 -3
  33. package/dist/commands/taskPublicationCommands.js +151 -85
  34. package/dist/commands/taskPublicationVerifyCommand.js +181 -0
  35. package/dist/commands/taskRemoteDeliveryCommand.js +134 -0
  36. package/dist/commands/taskRoleRuntimeStatus.js +5 -6
  37. package/dist/commands/taskUpstreamCommands.js +79 -95
  38. package/dist/config/configCatalog.js +1 -1
  39. package/dist/context/contextSnapshot.js +17 -2
  40. package/dist/context/sessionBootstrapManifest.js +18 -1
  41. package/dist/context/sourceTurnContext.js +30 -0
  42. package/dist/context/turnContextPack.js +107 -48
  43. package/dist/context/turnInputContract.js +11 -4
  44. package/dist/context/wakeNotification.js +84 -22
  45. package/dist/controller/agentRuntimeObserver.js +9 -12
  46. package/dist/controller/clientRuntime.js +13 -13
  47. package/dist/controller/controller.js +11 -11
  48. package/dist/controller/fileSchedulerStoreAdapter.js +262 -219
  49. package/dist/controller/jobControl.js +11 -10
  50. package/dist/controller/resourceInventory.js +6 -6
  51. package/dist/controller/resourceInventoryLinux.js +2 -2
  52. package/dist/controller/runtime.js +18 -61
  53. package/dist/controller/runtimeEventInbox.js +57 -34
  54. package/dist/controller/runtimeEventProcessor.js +36 -96
  55. package/dist/controller/runtimeHookTurnFence.js +33 -57
  56. package/dist/controller/runtimeLaunchCoordinator.js +44 -40
  57. package/dist/controller/runtimeObservationHook.js +18 -12
  58. package/dist/controller/sessionNotify.js +43 -28
  59. package/dist/controller/sessionOwnerReconciliation.js +21 -21
  60. package/dist/controller/structuredProviderObservation.js +13 -16
  61. package/dist/coordination/workMailboxQueue.js +132 -0
  62. package/dist/core/controllerServer.js +1 -0
  63. package/dist/domain/agentResultTransport.js +60 -0
  64. package/dist/execution/executionHealth.js +111 -118
  65. package/dist/execution/reviewMainTurn.js +161 -0
  66. package/dist/execution/workItemExecution.js +454 -0
  67. package/dist/execution/workItemExecutionProjection.js +309 -0
  68. package/dist/execution/workItemMainTurn.js +164 -0
  69. package/dist/executor/agentExecutor.js +19 -19
  70. package/dist/executor/executorRegistry.js +7 -28
  71. package/dist/executor/fileRoleLaunchPlanner.js +51 -83
  72. package/dist/external/githubPublicationVerifier.js +114 -0
  73. package/dist/external/gitlabPublicationVerifier.js +158 -0
  74. package/dist/external/pinnedCommandRunner.js +73 -0
  75. package/dist/integration/changeSetManifest.js +0 -3
  76. package/dist/integration/deliveryObligation.js +43 -6
  77. package/dist/integration/gitIntegrationService.js +284 -101
  78. package/dist/integration/integrationAttempt.js +64 -14
  79. package/dist/integration/integrationQueueEntry.js +5 -35
  80. package/dist/integration/integrationQueueService.js +65 -194
  81. package/dist/lifecycle/canonicalLifecycleEvent.js +16 -15
  82. package/dist/lifecycle/exactTurnTerminalization.js +100 -97
  83. package/dist/message/message.js +16 -0
  84. package/dist/observability/executionAudit.js +4 -25
  85. package/dist/observability/faultClassification.js +58 -70
  86. package/dist/observability/orchestrationMetrics.js +8 -50
  87. package/dist/profile/agentProfile.js +60 -15
  88. package/dist/profile/agentProfileRuntime.js +100 -0
  89. package/dist/release/releaseWorkflowPorts.js +5 -68
  90. package/dist/repository/executionLaneGitSnapshot.js +80 -0
  91. package/dist/repository/gitWorkspace.js +9 -67
  92. package/dist/repository/taskBaseFreshness.js +3 -1
  93. package/dist/repository/taskWorkspaceCoordinator.js +32 -4
  94. package/dist/repository/taskWorkspacePreparer.js +252 -411
  95. package/dist/resources/liveReferences.js +6 -55
  96. package/dist/resources/resourceDiscovery.js +1 -30
  97. package/dist/resources/resourceRegistrar.js +0 -23
  98. package/dist/review/deltaRecheck.js +8 -65
  99. package/dist/review/reviewAcceptance.js +22 -19
  100. package/dist/review/reviewConfig.js +0 -10
  101. package/dist/review/reviewDecision.js +2 -2
  102. package/dist/review/reviewRound.js +147 -273
  103. package/dist/review/reviewerAvailability.js +13 -23
  104. package/dist/role/role.js +3 -0
  105. package/dist/runtime/agentDriverObservation.js +1 -1
  106. package/dist/runtime/agentHost.js +51 -51
  107. package/dist/runtime/builtinAgentDrivers.js +9 -2
  108. package/dist/runtime/codexAppServerRuntime.js +23 -5
  109. package/dist/runtime/exactControlPlane.js +2 -267
  110. package/dist/runtime/firstProgressAdvisory.js +1 -1
  111. package/dist/runtime/index.js +1 -1
  112. package/dist/runtime/launchBroker.js +11 -11
  113. package/dist/runtime/lifecycleReservation.js +2 -2
  114. package/dist/runtime/managedCaller.js +111 -0
  115. package/dist/runtime/ports.js +16 -3
  116. package/dist/runtime/preallocatedNativeSession.js +2 -2
  117. package/dist/runtime/processExitObservation.js +3 -2
  118. package/dist/runtime/providerContinuationReconciliationService.js +1 -1
  119. package/dist/runtime/runtimeBinding.js +1 -1
  120. package/dist/runtime/runtimeObservation.js +33 -16
  121. package/dist/runtime/runtimeSessionCandidate.js +2 -2
  122. package/dist/runtime/runtimeStopReceipt.js +11 -11
  123. package/dist/runtime/sessionLaunchRequest.js +2 -2
  124. package/dist/runtime/sessionOwnerIdentity.js +9 -9
  125. package/dist/runtime/sessionOwnerRegistry.js +13 -13
  126. package/dist/runtime/sessionReconciliation.js +4 -4
  127. package/dist/runtime/sessionTerminationGuard.js +9 -9
  128. package/dist/runtime/sessionTokenMetrics.js +5 -7
  129. package/dist/runtime/structuredProviderHost.js +5 -5
  130. package/dist/runtime/taskRuntimeIsolation.js +24 -24
  131. package/dist/runtime/tmuxAdapters.js +32 -30
  132. package/dist/scheduler/activeRoleTurnDelivery.js +37 -15
  133. package/dist/scheduler/leaderWakeupProcessor.js +78 -55
  134. package/dist/scheduler/roleTurnLiveness.js +6 -6
  135. package/dist/scheduler/roleTurnStall.js +8 -9
  136. package/dist/scheduler/taskExecutionProjection.js +19 -41
  137. package/dist/scheduler/taskObservabilityProjection.js +75 -122
  138. package/dist/storage/sqliteSchema.js +1 -21
  139. package/dist/storage/sqliteStore.js +62 -47
  140. package/dist/storage/storageSchema.js +1 -8
  141. package/dist/storage/storageVersions.js +7 -4
  142. package/dist/storage/taskStore.js +8 -8
  143. package/dist/storage/upgrade/upgradeOrchestrator.js +112 -83
  144. package/dist/task/completionReadiness.js +17 -98
  145. package/dist/task/deliveryGuard.js +9 -11
  146. package/dist/task/draftPlan.js +142 -0
  147. package/dist/task/nextAction.js +179 -477
  148. package/dist/task/publicationVerification.js +1 -0
  149. package/dist/task/remoteDelivery.js +233 -0
  150. package/dist/task/task.js +108 -4
  151. package/dist/task/taskRecordReference.js +0 -1
  152. package/dist/turn/turn.js +109 -16
  153. package/dist/verification/verificationGateService.js +0 -26
  154. package/dist/web/assets/client/components.js +99 -121
  155. package/dist/web/assets/client/i18n.js +136 -58
  156. package/dist/web/assets/client/view.js +44 -0
  157. package/dist/web/assets/styles/cards.js +3 -15
  158. package/dist/web/assets/styles/widgets.js +4 -0
  159. package/dist/web/webSnapshot.js +29 -14
  160. package/dist/workItem/dependencyGate.js +34 -0
  161. package/dist/workItem/workItem.js +99 -266
  162. package/dist/workspace/workItemChangeSetManager.js +15 -59
  163. package/docs/task-local-identity.md +1 -1
  164. package/i18n/README.zh-CN.md +165 -59
  165. package/package.json +1 -1
  166. package/skills/yui-leader/SKILL.md +272 -782
  167. package/skills/yui-operator/SKILL.md +190 -365
  168. package/skills/yui-reviewer/SKILL.md +40 -33
  169. package/skills/yui-runtime/SKILL.md +27 -6
  170. package/skills/yui-worker/SKILL.md +19 -6
  171. package/dist/execution/candidateConvergence.js +0 -623
  172. package/dist/execution/executionGroup.js +0 -854
  173. package/dist/execution/resourceBroker.js +0 -426
  174. package/dist/executor/launchPlan.js +0 -16
  175. package/dist/review/reviewFinding.js +0 -240
  176. package/dist/review/reviewFindingLedger.js +0 -550
  177. package/dist/review/reviewOutcomeClassifier.js +0 -255
  178. package/dist/scheduler/resourceQueueProjection.js +0 -71
  179. 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 { validateReviewFinding } from "../review/reviewFinding.js";
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.#seedHomeMeta();
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.launchId, owner.scope, owner.scope === "task" ? owner.taskId : null, owner.roleName, identity.agentId, identity.nativeSessionId ?? null, identity.providerRoot.pid, this.#json(identity), identity.recordedAt);
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(launchId) {
933
- const row = this.#db.prepare("SELECT payload FROM session_owners WHERE launch_id = ?").get(launchId);
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(launchId) {
970
+ removeSessionOwner(runtimeGenerationId) {
946
971
  this.#mutate(() => {
947
- this.#db.prepare("DELETE FROM session_owners WHERE launch_id = ?").run(launchId);
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.launch_id === null ? {} : { launchId: row.launch_id }),
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, '$.launchId') AS launch_id,
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, '$.launchId') = 'text'
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.launch_id ?? undefined) === candidate.launchId
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.launchId ?? null, candidate.sessionUpdatedAt, candidate.cleanupRequired ? 1 : 0);
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", "validated", "superseded"],
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}; no migration is available in this Yui release.`);
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 and writes;
13
- * older Homes are rejected and must not be upgraded in place.
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
- /** Version of the logical aggregates persisted in SQLite. */
17
- export const CURRENT_AGGREGATE_SCHEMA_VERSION = 21;
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 = 2;
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 = 4;
19
- export const CURRENT_TASK_SCHEMA_VERSION = 6;
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 = 13;
25
- export const CURRENT_REVIEW_ROUND_SCHEMA_VERSION = 6;
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 = 5;
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 = 11;
69
- export const CURRENT_TURN_SCHEMA_VERSION = 1;
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
- * Current-only storage admission. Historical Homes are preserved byte-for-byte
8
- * and rejected; Yui never repairs, normalizes, or upgrades them in place.
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(classification, "uninitialized", "Yui storage is not initialized for this Home.", "Run `yui setup` with a new Home.");
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(classification, "corruption", `Storage schema is invalid: ${schema.detail}`, "Preserve this Home for diagnosis and initialize a new Home.");
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(classification, "The current SQLite database is missing."), "corruption", "The current SQLite Home is incomplete: yui.db is missing.", "Preserve this Home for diagnosis and initialize a new Home.");
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
- const store = new SqliteTaskStore(options.home);
27
- try {
28
- store.getConfig();
29
- }
30
- finally {
31
- store.close();
32
- }
38
+ validateCurrentStore(options.home);
33
39
  }
34
40
  catch (error) {
35
- const message = error instanceof Error ? error.message : String(error);
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
- const report = {
53
+ return {
47
54
  outcome: "already-current",
48
- mode: options.mode,
49
- source: options.latest,
50
- target: options.latest
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 classifyCurrentHome(schema, latest) {
55
- const base = {
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
- if (schema.status === "uninitialized") {
60
- return {
61
- ...base,
62
- classification: { verdict: "USABLE", status: "current" },
63
- uninitialized: true
64
- };
65
- }
66
- if (schema.status === "invalid") {
67
- return corruptedClassification({
68
- ...base,
69
- classification: { verdict: "CORRUPTED", status: "unsupported", detail: schema.detail }
70
- }, schema.detail);
71
- }
72
- if (schema.status === "unsupported") {
73
- const blocker = schema.direction === "newer"
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: schema.currentVersion,
79
- supported: schema.latestVersion,
80
- message: "Historical storage contracts are not supported by this release.",
81
- action: "Initialize a new Home."
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(classification, detail) {
143
+ function corruptedClassification(latest, detail) {
112
144
  return {
113
- ...classification,
114
- classification: { verdict: "CORRUPTED", status: "unsupported", detail }
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
- outcome: "blocked",
120
- stage,
121
- message,
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
  }