@zq-silk/yui 0.16.1 → 1.0.0-alpha

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 (193) hide show
  1. package/ARCHITECTURE.md +12 -0
  2. package/ARCHITECTURE.zh-CN.md +8 -0
  3. package/README.md +9 -4
  4. package/dist/agent/agent.js +4 -9
  5. package/dist/agent/executionComponents.js +4 -4
  6. package/dist/agent/managedRuntimeEnvironment.js +0 -4
  7. package/dist/agentRun/agentRun.js +13 -23
  8. package/dist/artifacts/managedGit.js +3 -56
  9. package/dist/brief/taskBrief.js +3 -3
  10. package/dist/cli/agentConfigurationPicker.js +24 -21
  11. package/dist/cli/commandCatalog.js +18 -10
  12. package/dist/cli/roleWizard.js +41 -30
  13. package/dist/cli/updateCommand.js +35 -13
  14. package/dist/cli/updateOrchestrator.js +73 -44
  15. package/dist/cli/updatePorts.js +76 -86
  16. package/dist/cli/upgradeCommand.js +4 -8
  17. package/dist/cli.js +39 -33
  18. package/dist/commands/controllerCommands.js +2 -2
  19. package/dist/commands/executionAuditCommands.js +2 -2
  20. package/dist/commands/projectCommands.js +1 -1
  21. package/dist/commands/releaseCommands.js +9 -38
  22. package/dist/commands/taskCommands.js +91 -81
  23. package/dist/commands/taskCompletionGate.js +0 -117
  24. package/dist/commands/taskRoleRuntimeStatus.js +2 -2
  25. package/dist/commands/taskUpstreamCommands.js +93 -54
  26. package/dist/context/runContextPack.js +30 -54
  27. package/dist/context/runInputContract.js +9 -0
  28. package/dist/context/sessionBootstrapManifest.js +11 -19
  29. package/dist/controller/agentHostObservation.js +4 -2
  30. package/dist/controller/agentRuntimeObserver.js +4 -4
  31. package/dist/controller/clientRuntime.js +19 -44
  32. package/dist/controller/controller.js +24 -10
  33. package/dist/controller/fileSchedulerStoreAdapter.js +91 -24
  34. package/dist/controller/globalInputDelivery.js +31 -8
  35. package/dist/controller/globalRuntimeAttention.js +34 -0
  36. package/dist/controller/jobSupervisor.js +3 -3
  37. package/dist/controller/operatorNotification.js +31 -0
  38. package/dist/controller/providerRetryAdmission.js +3 -1
  39. package/dist/controller/providerRetryDelivery.js +136 -119
  40. package/dist/controller/runtime.js +2 -28
  41. package/dist/controller/sessionOwnerReconciliation.js +42 -24
  42. package/dist/controller/structuredProviderObservation.js +28 -1
  43. package/dist/controller/updateReconciliation.js +72 -20
  44. package/dist/coordination/workMailbox.js +4 -4
  45. package/dist/core/controllerIdentity.js +25 -0
  46. package/dist/core/controllerProcessIdentity.js +2 -2
  47. package/dist/core/controllerServer.js +4 -2
  48. package/dist/core/protocol.js +1 -1
  49. package/dist/doctor/doctor.js +2 -1
  50. package/dist/domain/validation.js +6 -0
  51. package/dist/event/taskEvent.js +3 -3
  52. package/dist/execution/workItemExecution.js +2 -2
  53. package/dist/executor/agentAdapter.js +28 -96
  54. package/dist/executor/agentConfigurationCatalog.js +13 -68
  55. package/dist/executor/agentConfigurationFields.js +120 -0
  56. package/dist/executor/agentConfigurationProbe.js +55 -80
  57. package/dist/executor/agentExecutor.js +11 -8
  58. package/dist/executor/effectiveLaunch.js +3 -3
  59. package/dist/executor/executorRegistry.js +0 -9
  60. package/dist/executor/fileRoleLaunchPlanner.js +12 -11
  61. package/dist/grant/capabilityGrant.js +2 -2
  62. package/dist/input/inputRequest.js +4 -4
  63. package/dist/integration/changeSet.js +3 -3
  64. package/dist/integration/integrationAttempt.js +3 -3
  65. package/dist/integration/integrationSourceApplication.js +1 -1
  66. package/dist/interaction/operatorPresentation.js +2 -1
  67. package/dist/job/durableJob.js +1 -1
  68. package/dist/job/jobRunner.js +2 -2
  69. package/dist/message/message.js +8 -6
  70. package/dist/message/messageContinuation.js +8 -9
  71. package/dist/milestone/milestone.js +3 -3
  72. package/dist/observability/executionAudit.js +1 -10
  73. package/dist/observability/runtimeIdentity.js +0 -23
  74. package/dist/output/agentConfigurationPresentation.js +4 -2
  75. package/dist/profile/agentProfile.js +3 -3
  76. package/dist/release/releaseHandover.js +2 -2
  77. package/dist/release/releaseIdempotencyStore.js +0 -23
  78. package/dist/release/releaseWorkflowPorts.js +8 -4
  79. package/dist/release/runtimeRelease.js +9 -7
  80. package/dist/repository/gitWorkspace.js +66 -19
  81. package/dist/repository/project.js +3 -3
  82. package/dist/repository/taskWorkspacePreparer.js +75 -68
  83. package/dist/resources/liveReferences.js +1 -1
  84. package/dist/resources/resourceRegistry.js +90 -44
  85. package/dist/resources/sqliteResourceRegistry.js +5 -7
  86. package/dist/review/reviewRound.js +5 -5
  87. package/dist/role/role.js +7 -8
  88. package/dist/runtime/acpProtocol.js +2 -3
  89. package/dist/runtime/acpSession.js +21 -5
  90. package/dist/runtime/agentDriverObservation.js +1 -1
  91. package/dist/runtime/agentEndpoint.js +4 -1
  92. package/dist/runtime/agentHost.js +25 -65
  93. package/dist/runtime/agentHostCleanup.js +85 -0
  94. package/dist/runtime/agentHostProtocol.js +3 -3
  95. package/dist/runtime/builtinAgentDrivers.js +46 -119
  96. package/dist/runtime/builtinTranscriptObserver.js +10 -6
  97. package/dist/runtime/codexAppServerRuntime.js +55 -89
  98. package/dist/runtime/codexInteractiveHost.js +1 -1
  99. package/dist/runtime/jsonLineChannel.js +35 -7
  100. package/dist/runtime/launchBroker.js +1 -1
  101. package/dist/runtime/processExitObservation.js +1 -1
  102. package/dist/runtime/providerContinuationReconciliationService.js +86 -38
  103. package/dist/runtime/providerRetry.js +15 -0
  104. package/dist/runtime/providerRuntimeIdentity.js +3 -3
  105. package/dist/runtime/providerRuntimeReconciler.js +12 -2
  106. package/dist/runtime/runtimeCoherence.js +7 -3
  107. package/dist/runtime/runtimeObservation.js +3 -3
  108. package/dist/runtime/sessionOwnerIdentity.js +1 -1
  109. package/dist/runtime/structuredProviderHost.js +106 -81
  110. package/dist/runtime/taskRuntimeIsolation.js +3 -3
  111. package/dist/runtime/tmuxAdapters.js +3 -3
  112. package/dist/scheduler/activeRoleRunDelivery.js +5 -1
  113. package/dist/scheduler/operatorInputNotificationProcessor.js +7 -17
  114. package/dist/scheduler/taskWake.js +1 -1
  115. package/dist/storage/baselineSchema.js +606 -0
  116. package/dist/storage/homeLayout.js +5 -16
  117. package/dist/storage/recordValidation.js +16 -4
  118. package/dist/storage/sqliteSchema.js +106 -1741
  119. package/dist/storage/sqliteStore.js +16 -14
  120. package/dist/storage/storageSchema.js +8 -7
  121. package/dist/storage/storageVersions.js +23 -16
  122. package/dist/storage/taskStore.js +3 -29
  123. package/dist/storage/upgrade/upgradeOrchestrator.js +14 -112
  124. package/dist/task/nextAction.js +33 -23
  125. package/dist/task/task.js +17 -8
  126. package/dist/task/taskActivation.js +5 -4
  127. package/dist/telemetry/sqliteTelemetryStore.js +2 -2
  128. package/dist/verification/gateArtifact.js +5 -3
  129. package/dist/verification/verificationPlan.js +6 -7
  130. package/dist/web/assets/assetManifest.js +2 -0
  131. package/dist/web/assets/client/app.js +72 -3
  132. package/dist/web/assets/client/components.js +2 -1
  133. package/dist/web/assets/client/i18n.js +2 -0
  134. package/dist/web/assets/client/taskSummary.js +345 -0
  135. package/dist/web/assets/client/taskSurface.js +44 -26
  136. package/dist/web/assets/client/view.js +33 -0
  137. package/dist/web/assets/shell.js +1 -0
  138. package/dist/web/assets/styles/cards.js +21 -0
  139. package/dist/web/webServer.js +39 -5
  140. package/dist/web/webSessions.js +165 -0
  141. package/dist/web/webSnapshot.js +23 -0
  142. package/dist/web/webTaskSurface.js +23 -0
  143. package/dist/workItem/workItem.js +6 -6
  144. package/dist/workspace/cleanupInspection.js +1 -9
  145. package/dist/worktree/managedWorkspace.js +3 -3
  146. package/docs/managed-turn-and-session-runtime.md +50 -7
  147. package/docs/managed-turn-and-session-runtime.zh-CN.md +36 -5
  148. package/docs/observability/README.md +47 -0
  149. package/docs/observability/README.zh-CN.md +37 -0
  150. package/docs/project-refresh.md +9 -0
  151. package/docs/project-refresh.zh-CN.md +8 -0
  152. package/docs/provider-retry.md +35 -0
  153. package/docs/release-workflow.md +70 -214
  154. package/docs/release-workflow.zh-CN.md +53 -157
  155. package/docs/roles-and-configuration.md +30 -0
  156. package/docs/roles-and-configuration.zh-CN.md +20 -0
  157. package/docs/sqlite-control-plane-design.md +48 -289
  158. package/docs/sqlite-control-plane-design.zh-CN.md +37 -53
  159. package/docs/storage-baseline.md +132 -0
  160. package/docs/storage-baseline.zh-CN.md +106 -0
  161. package/docs/task-delivery.md +110 -2
  162. package/docs/task-delivery.zh-CN.md +82 -2
  163. package/docs/task-discovery.md +9 -0
  164. package/docs/task-discovery.zh-CN.md +6 -0
  165. package/docs/testing/verification-levels.md +42 -164
  166. package/docs/testing/verification-levels.zh-CN.md +29 -115
  167. package/i18n/README.zh-CN.md +7 -4
  168. package/package.json +1 -1
  169. package/skills/yui-leader/SKILL.md +11 -0
  170. package/skills/yui-leader/references/execution.md +22 -0
  171. package/skills/yui-leader/references/planning.md +6 -2
  172. package/skills/yui-operator/SKILL.md +45 -13
  173. package/skills/yui-operator/references/task-delivery.md +99 -0
  174. package/skills/yui-runtime/SKILL.md +5 -3
  175. package/skills/yui-runtime/references/publication.md +56 -5
  176. package/skills/yui-runtime/references/recovery.md +12 -2
  177. package/dist/runtime/agentHostCompatibility.js +0 -127
  178. package/dist/storage/migrations/agentFailureContext.js +0 -22
  179. package/dist/storage/migrations/agentRunContract.js +0 -159
  180. package/dist/storage/migrations/artifactsToGit.js +0 -338
  181. package/dist/storage/migrations/collapseWorktreeLayout.js +0 -963
  182. package/dist/storage/migrations/currentInputContract.js +0 -86
  183. package/dist/storage/migrations/currentRuntimeContract.js +0 -228
  184. package/dist/storage/migrations/historicalVerificationPlan.js +0 -35
  185. package/dist/storage/migrations/integrationContinuation.js +0 -105
  186. package/dist/storage/migrations/narrowAgentFailureContext.js +0 -65
  187. package/dist/storage/migrations/notificationOnlyWakes.js +0 -74
  188. package/dist/storage/migrations/removeRuntimeGeneration.js +0 -207
  189. package/dist/storage/migrations/submitIntent.js +0 -126
  190. package/dist/storage/migrations/unifyHomeLayout.js +0 -925
  191. package/dist/storage/migrations/verificationPlanV1.js +0 -162
  192. package/dist/storage/migrations/verificationPolicy.js +0 -74
  193. package/dist/storage/migrations/workItemHistory.js +0 -46
@@ -1,4 +1,5 @@
1
1
  import { validateTaskRecordReference } from "../task/taskRecordReference.js";
2
+ import { requireKnownFields } from "../domain/validation.js";
2
3
  export const TASK_MESSAGE_KINDS = ["user", "operator", "role-result", "system"];
3
4
  /**
4
5
  * How a user/operator submission asks its Task to react (task-32 Requirement A).
@@ -24,7 +25,7 @@ export const TASK_MESSAGE_INPUT_CONTROL_OUTCOMES = ["pending", "accepted", "reje
24
25
  export function createTaskMessage(id, taskId, body, kind, author, now, context = {}) {
25
26
  validateKindAndAuthor(kind, author);
26
27
  const message = {
27
- schemaVersion: 3,
28
+ schemaVersion: 1,
28
29
  id: requireSafeIdentity(id, "Message id"),
29
30
  taskId: requireSafeIdentity(taskId, "Message Task id"),
30
31
  kind,
@@ -199,15 +200,16 @@ export function withSubmissionReceipt(message, receipt) {
199
200
  return updated;
200
201
  }
201
202
  export function validateTaskMessage(message) {
202
- if (message.schemaVersion !== 3)
203
- throw new Error("Task Message must use schemaVersion 3.");
203
+ if (message.schemaVersion !== 1)
204
+ throw new Error("Task Message must use schemaVersion 1.");
205
+ requireKnownFields(message, [
206
+ "schemaVersion", "id", "taskId", "kind", "author", "body", "intent", "submissionKey", "submissionReceipt",
207
+ "inputControl", "interruptThen", "control", "runId", "resultRef", "workItemId", "recipient", "continuation", "handovers", "createdAt"
208
+ ], "Task Message");
204
209
  validateTaskRecordReference({ taskId: message.taskId, localId: message.id }, "message");
205
210
  requireText(message.body, "Message body");
206
211
  validateKindAndAuthor(message.kind, message.author);
207
212
  normalizeAuthor(message.author);
208
- if (Object.hasOwn(message, "wakePolicy")) {
209
- throw new Error("Message contains a retired wake policy; use the storage upgrade boundary.");
210
- }
211
213
  if ((message.kind === "user" || message.kind === "operator") && message.intent === undefined) {
212
214
  throw new Error("User/operator Message intent is required.");
213
215
  }
@@ -1,7 +1,8 @@
1
1
  import { isDeepStrictEqual } from "node:util";
2
- import { createRun, withRunContextSnapshot } from "../agentRun/agentRun.js";
2
+ import { createRun } from "../agentRun/agentRun.js";
3
3
  import { createRunInput } from "../context/runInputContract.js";
4
4
  import { contextContentDigest, contextSnapshotRef, createContextSnapshot } from "../context/contextSnapshot.js";
5
+ import { readRunContextSnapshot } from "../context/runContextPack.js";
5
6
  import { roleSessionMayContinue } from "../executor/effectiveLaunch.js";
6
7
  import { enqueueRoleRunDispatch } from "../coordination/workMailboxQueue.js";
7
8
  import { createTaskEvent } from "../event/taskEvent.js";
@@ -265,12 +266,9 @@ export function prepareMessageContinuations(store, taskId, now, roleName) {
265
266
  continue;
266
267
  const previous = store.listRuns(taskId).filter((run) => run.roleName === owner.roleName
267
268
  && run.workItemId === owner.workItemId && run.reviewRoundId === owner.reviewRoundId).at(-1);
268
- const baselineRef = previous.inputs[0]?.input.contextSnapshotRef;
269
- const baseline = baselineRef === undefined ? null : store.getContextSnapshot(taskId, baselineRef.id);
270
- if (baseline === null || baseline.digest !== baselineRef.digest) {
271
- markNotDelivered(store, message, "assignment-context-unavailable");
272
- continue;
273
- }
269
+ // Missing or drifted evidence is diagnosed by the caller; never reconstruct
270
+ // an Assignment from today's Task facts.
271
+ const baseline = readRunContextSnapshot(store, previous);
274
272
  const runId = store.nextRunId(taskId);
275
273
  const round = owner.reviewRoundId === undefined ? null : store.getReviewRound(taskId, owner.reviewRoundId);
276
274
  const continuingRound = round === null ? null : (() => {
@@ -300,9 +298,10 @@ export function prepareMessageContinuations(store, taskId, now, roleName) {
300
298
  resources, refs: resources.map(({ ref }) => ref)
301
299
  });
302
300
  store.saveContextSnapshot(snapshot);
303
- const run = withRunContextSnapshot(createRun(runId, taskId, recipient.roleName, "resume", createRunInput({ source: { type: "yui", channel: "message-continuation" },
301
+ const run = createRun(runId, taskId, recipient.roleName, "resume", createRunInput({ source: { type: "yui", channel: "message-continuation" },
304
302
  directive: `Continue the same Assignment in its existing workspace. Read Messages ${batch.map((m) => m.id).join(", ")} from this exact Context. Message receipt is not implementation or acceptance.`,
305
- deltaRefIds: batch.map((entry) => entry.id) }), now, copyAssignment(owner)), contextSnapshotRef(snapshot), batch.map((entry) => entry.id));
303
+ contextSnapshotRef: contextSnapshotRef(snapshot),
304
+ deltaRefIds: batch.map((entry) => entry.id) }), now, copyAssignment(owner));
306
305
  store.saveRun(run);
307
306
  store.saveActiveRun(run);
308
307
  if (continuingRound !== null)
@@ -1,8 +1,8 @@
1
1
  import { validateTaskRecordReference } from "../task/taskRecordReference.js";
2
2
  import { requireTimestamp } from "../domain/validation.js";
3
3
  export function validateMilestone(milestone) {
4
- if (milestone.schemaVersion !== 2)
5
- throw new Error("Milestone must use schemaVersion 2.");
4
+ if (milestone.schemaVersion !== 1)
5
+ throw new Error("Milestone must use schemaVersion 1.");
6
6
  validateTaskRecordReference({ taskId: milestone.taskId, localId: milestone.id }, "milestone");
7
7
  requireText(milestone.title, "Milestone title");
8
8
  requireText(milestone.summary, "Milestone summary");
@@ -12,7 +12,7 @@ export function validateMilestone(milestone) {
12
12
  }
13
13
  export function createMilestone(id, taskId, title, summary, createdBy, now) {
14
14
  return {
15
- schemaVersion: 2,
15
+ schemaVersion: 1,
16
16
  id: validateTaskRecordReference({ taskId, localId: id }, "milestone").localId,
17
17
  taskId: requireSafeIdentity(taskId, "Task id"),
18
18
  title: requireText(title, "Milestone title"),
@@ -678,13 +678,6 @@ export function runExecutionAudit(home, options = {}, ports = createProductionEx
678
678
  })();
679
679
  const storage = (() => {
680
680
  try {
681
- let stateJsonBytes = UNSUPPORTED;
682
- try {
683
- stateJsonBytes = statSync(join(home, "state.json")).size;
684
- }
685
- catch {
686
- stateJsonBytes = UNSUPPORTED;
687
- }
688
681
  let databaseBytes = UNSUPPORTED;
689
682
  try {
690
683
  databaseBytes = statSync(join(home, "yui.db")).size;
@@ -693,15 +686,13 @@ export function runExecutionAudit(home, options = {}, ports = createProductionEx
693
686
  databaseBytes = UNSUPPORTED;
694
687
  }
695
688
  return ok({
696
- stateJsonBytes,
697
689
  databaseBytes,
698
690
  // The audit aggregates history; live db integrity is the status
699
691
  // command's job (it runs PRAGMA quick_check). Report presence/size
700
692
  // and the authoritative backend, not a duplicate health probe.
701
693
  databaseHealth: UNSUPPORTED,
702
694
  backend: resolveTaskStoreBackendForHome(home),
703
- runtimeDirBytes: ports.directorySize(join(home, "runtime")) ?? UNSUPPORTED,
704
- deploymentsBytes: ports.directorySize(join(home, "runtime", "deployments")) ?? UNSUPPORTED
695
+ runtimeDirBytes: ports.directorySize(join(home, "runtime")) ?? UNSUPPORTED
705
696
  });
706
697
  }
707
698
  catch (error) {
@@ -165,9 +165,6 @@ export function collectStorageIdentity(home, ports = createProductionStorageIden
165
165
  || storage.status === "unsupported"
166
166
  ? storage.currentVersion
167
167
  : UNSUPPORTED;
168
- const statePath = join(home, "state.json");
169
- const statePresent = ports.exists(statePath);
170
- const stateBytes = ports.fileSize(statePath);
171
168
  const dbPath = join(home, "yui.db");
172
169
  const dbPresent = ports.exists(dbPath);
173
170
  const dbBytes = ports.fileSize(dbPath);
@@ -221,14 +218,6 @@ export function collectStorageIdentity(home, ports = createProductionStorageIden
221
218
  remediation: "Preserve this Home for diagnosis and restore a known-good backup."
222
219
  });
223
220
  }
224
- if (ports.exists(join(home, "schema.json"))) {
225
- findings.push({
226
- code: "ignored-legacy-storage-manifest",
227
- severity: "warning",
228
- message: "schema.json is legacy metadata and is not a storage authority.",
229
- remediation: "This file is not used. Preserve or archive it separately after inspecting its contents."
230
- });
231
- }
232
221
  if (dbPresent && dbHealth !== null && dbHealth !== "ok") {
233
222
  findings.push({
234
223
  code: "database-unhealthy",
@@ -237,14 +226,6 @@ export function collectStorageIdentity(home, ports = createProductionStorageIden
237
226
  remediation: "Preserve this Home for diagnosis and initialize a new Home."
238
227
  });
239
228
  }
240
- if (statePresent) {
241
- findings.push({
242
- code: "ignored-historical-store",
243
- severity: "warning",
244
- message: "state.json is present but is not read or written by the current store.",
245
- remediation: "Keep it only as read-only historical evidence or archive it outside the active Home."
246
- });
247
- }
248
229
  const rawWorkerFlag = ports.env.YUI_STORE_WORKER;
249
230
  if (rawWorkerFlag !== undefined && !["0", "1", "false", "true"].includes(rawWorkerFlag.toLowerCase())) {
250
231
  findings.push({
@@ -261,10 +242,6 @@ export function collectStorageIdentity(home, ports = createProductionStorageIden
261
242
  minimumStorageVersion: MIN_SUPPORTED_STORAGE_VERSION,
262
243
  configuredBackend,
263
244
  workerEnabled,
264
- physicalStateJson: {
265
- present: statePresent,
266
- bytes: stateBytes ?? UNSUPPORTED
267
- },
268
245
  physicalDatabase: {
269
246
  present: dbPresent,
270
247
  bytes: dbBytes ?? UNSUPPORTED,
@@ -23,7 +23,9 @@ export function renderAgentConfigurationCatalog(resolved) {
23
23
  // query rather than a deliberate boundary, so it is shown either way.
24
24
  field.available === false
25
25
  ? `unavailable${field.reason === undefined ? "" : `: ${field.reason}`}`
26
- : field.reason === undefined ? "available" : `available: ${field.reason}`,
26
+ : field.available === true
27
+ ? `available${field.reason === undefined ? "" : `: ${field.reason}`}`
28
+ : field.reason ?? "Native support not confirmed",
27
29
  field.allowCustom ? "yes" : "no"
28
30
  ]);
29
31
  const sections = [
@@ -39,7 +41,7 @@ export function renderAgentConfigurationCatalog(resolved) {
39
41
  agentModelOptionsSummary(catalog),
40
42
  ...(fieldRows.length === 0
41
43
  ? []
42
- : [renderTable("Other runtime configuration", [
44
+ : [renderTable("Configuration fields (native observations and static adapter contracts)", [
43
45
  { header: "Field", minWidth: 12, maxWidth: 30 },
44
46
  { header: "Values", minWidth: 18, maxWidth: 58 },
45
47
  { header: "Status", minWidth: 10, maxWidth: 38 },
@@ -8,7 +8,7 @@ export const BUILTIN_PROFILE_IDS = Object.freeze([
8
8
  export function createAgentProfile(input, now) {
9
9
  const timestamp = now.toISOString();
10
10
  return validateAgentProfile({
11
- schemaVersion: 3,
11
+ schemaVersion: 1,
12
12
  id: requireIdentity(input.id, "Agent Profile id"),
13
13
  revision: 1,
14
14
  ...normalizeInput(input),
@@ -31,8 +31,8 @@ export function updateAgentProfile(profile, patch, now) {
31
31
  return validateAgentProfile(candidate);
32
32
  }
33
33
  export function validateAgentProfile(profile) {
34
- if (profile.schemaVersion !== 3) {
35
- throw new Error("AgentProfile must use schemaVersion 3.");
34
+ if (profile.schemaVersion !== 1) {
35
+ throw new Error("AgentProfile must use schemaVersion 1.");
36
36
  }
37
37
  requireIdentity(profile.id, "Agent Profile id");
38
38
  requirePositiveInteger(profile.revision, "Agent Profile revision");
@@ -131,14 +131,14 @@ async function activateLocked(ports, locked) {
131
131
  catch (rollbackError) {
132
132
  return {
133
133
  outcome: "aborted", phase: "preflight",
134
- message: `Host compatibility failed: ${messageOf(error)}; rollback failed: ${messageOf(rollbackError)}`,
134
+ message: `Storage preflight failed: ${messageOf(error)}; rollback failed: ${messageOf(rollbackError)}`,
135
135
  action: "No candidate was started and the release pointer is unchanged. Preserve the fence and inspect the old Controller.",
136
136
  recoverable: true
137
137
  };
138
138
  }
139
139
  return {
140
140
  outcome: "aborted", phase: "preflight",
141
- message: `Fenced compatibility preflight failed: ${messageOf(error)}`,
141
+ message: `Fenced storage preflight failed: ${messageOf(error)}`,
142
142
  action: "The old Controller is serving again; the active release and Home are unchanged.",
143
143
  recoverable: true
144
144
  };
@@ -38,41 +38,19 @@ import { writeTextFileAtomically } from "../storage/durableFile.js";
38
38
  * an unpersisted success into an ambiguous/fail-closed outcome; reporting
39
39
  * success for an effect whose dedup record did not land would let a crash
40
40
  * re-run the effect.
41
- *
42
- * Unsupported layouts
43
- * -------------------
44
- * The per-key `schemaVersion: 1` layout is the only supported format. A
45
- * legacy whole-map file (`release-idempotency.json`) from an unsupported
46
- * older layout is never auto-migrated: it fails closed with a bounded
47
- * diagnostic so the operator can inspect and clean it up deliberately.
48
41
  */
49
42
  export const RELEASE_IDEMPOTENCY_SCHEMA_VERSION = 1;
50
43
  const DIRECTORY_NAME = "release-idempotency";
51
- const LEGACY_FILE_NAME = "release-idempotency.json";
52
44
  export function createFileReleaseIdempotencyStore(home, now = () => new Date()) {
53
45
  const directory = join(home, DIRECTORY_NAME);
54
- const legacyPath = join(home, LEGACY_FILE_NAME);
55
46
  function recordPath(key) {
56
47
  // encodeURIComponent is injective and leaves only Linux-filename-safe
57
48
  // characters unescaped, so distinct keys never collide on disk.
58
49
  return join(directory, `${encodeURIComponent(key)}.json`);
59
50
  }
60
- /**
61
- * Fail closed when an unsupported legacy whole-map file is present.
62
- * The per-key layout is the only supported format; a whole-map file
63
- * from an unsupported older layout must not be silently trusted.
64
- */
65
- function rejectLegacyLayout() {
66
- if (existsSync(legacyPath)) {
67
- throw new Error(`unsupported legacy release idempotency layout: ${legacyPath} ` +
68
- `(whole-map file). Remove it after inspecting its contents; ` +
69
- `the per-key schemaVersion 1 layout is the only supported format.`);
70
- }
71
- }
72
51
  return {
73
52
  async load(key) {
74
53
  try {
75
- rejectLegacyLayout();
76
54
  const path = recordPath(key);
77
55
  if (!existsSync(path))
78
56
  return undefined;
@@ -88,7 +66,6 @@ export function createFileReleaseIdempotencyStore(home, now = () => new Date())
88
66
  throw new Error(`release idempotency store only records succeeded effects: ${effect.outcome}`);
89
67
  }
90
68
  try {
91
- rejectLegacyLayout();
92
69
  const record = Object.freeze({
93
70
  schemaVersion: RELEASE_IDEMPOTENCY_SCHEMA_VERSION,
94
71
  key,
@@ -2,6 +2,7 @@ import { createHash, randomBytes } from "node:crypto";
2
2
  import { chmod, mkdir, mkdtemp, readFile, rename, rm, writeFile } from "node:fs/promises";
3
3
  import { dirname, isAbsolute, join, resolve } from "node:path";
4
4
  import { runUpdate } from "../cli/updateOrchestrator.js";
5
+ import { renderUpdateResult } from "../cli/updateCommand.js";
5
6
  import { activatedControllerEntrypoint } from "../cli/updatePorts.js";
6
7
  import { runProjectCommand } from "../commands/projectCommands.js";
7
8
  import { restartFileTaskController, stopFileTaskController } from "../controller/clientRuntime.js";
@@ -116,11 +117,14 @@ export function createReleaseWorkflowPorts(deps) {
116
117
  // Best-effort persistence; the effect may still succeed.
117
118
  }
118
119
  const result = runUpdate(inspectedPorts, { home: deps.home });
120
+ // Preserve cleanup/restore receipts in the existing step log, even
121
+ // when the binary result is success or a later phase is blocked.
122
+ const updateLog = renderUpdateResult(result);
119
123
  if (result.outcome === "updated" || result.outcome === "already-current") {
120
124
  return {
121
125
  outcome: "succeeded",
122
126
  externalId: result.version,
123
- logs: [`cli-update: ${result.outcome} (${result.version})`]
127
+ logs: [updateLog]
124
128
  };
125
129
  }
126
130
  if (result.outcome === "aborted" && !result.recoverable) {
@@ -143,7 +147,7 @@ export function createReleaseWorkflowPorts(deps) {
143
147
  if (result.controllerOwnershipUnknown === true) {
144
148
  return {
145
149
  outcome: "timeout",
146
- logs: [`cli-update Controller ownership unknown at phase ${result.phase}: ${result.message}`]
150
+ logs: [updateLog]
147
151
  };
148
152
  }
149
153
  // P1-2 (rr22): persist the EXACT activation target (the resolved
@@ -154,13 +158,13 @@ export function createReleaseWorkflowPorts(deps) {
154
158
  return {
155
159
  outcome: "timeout",
156
160
  ...(identity === undefined ? {} : { externalIdentity: identity }),
157
- logs: [`cli-update ambiguous at phase ${result.phase}: ${result.message}`]
161
+ logs: [updateLog]
158
162
  };
159
163
  }
160
164
  return {
161
165
  outcome: "failed",
162
166
  error: `cli-update aborted at phase ${result.phase}: ${result.message}`,
163
- logs: [`cli-update: ${result.message}`]
167
+ logs: [updateLog]
164
168
  };
165
169
  }
166
170
  case "controller-replace": {
@@ -15,6 +15,7 @@
15
15
  * deterministic and recoverable.
16
16
  */
17
17
  import { createHash, randomBytes, randomUUID } from "node:crypto";
18
+ import { isStorageVersion, compareStorageVersions } from "../storage/storageVersions.js";
18
19
  import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, readlinkSync, rmSync, writeFileSync } from "node:fs";
19
20
  import { dirname, join, resolve } from "node:path";
20
21
  import { readLinuxProcessStartIdentity } from "../controller/domainIdentity.js";
@@ -133,7 +134,7 @@ export function writeActiveReleasePointer(home, pointer) {
133
134
  }
134
135
  /**
135
136
  * Resolves the Home's active release. Returns null when no pointer exists
136
- * (legacy/dev Home). Fails closed when the pointer names a release that is
137
+ * (no immutable release selected). Fails closed when the pointer names a release that is
137
138
  * missing or drifted.
138
139
  */
139
140
  export function resolveActiveRelease(home) {
@@ -478,7 +479,7 @@ function validatePointer(value) {
478
479
  function validateRuntimeIdentity(value) {
479
480
  if (value === null
480
481
  || typeof value !== "object"
481
- || value.schemaVersion !== 2
482
+ || value.schemaVersion !== 1
482
483
  || typeof value.version !== "string"
483
484
  || typeof value.executablePath !== "string"
484
485
  || value.executablePath === ""
@@ -487,15 +488,16 @@ function validateRuntimeIdentity(value) {
487
488
  || typeof value.cliRealpath !== "string"
488
489
  || typeof value.controllerRealpath !== "string"
489
490
  || !isPositiveInteger(value.controllerProtocolVersion)
490
- || !isPositiveInteger(value.storageVersion)
491
- || !isPositiveInteger(value.minimumStorageVersion)
492
- || value.minimumStorageVersion
493
- > value.storageVersion
491
+ || !isStorageVersion(value.storageVersion)
492
+ || !isStorageVersion(value.minimumStorageVersion)
493
+ || compareStorageVersions(value.minimumStorageVersion, value.storageVersion) > 0
494
494
  || typeof value.pid !== "number"
495
495
  || typeof value.processStartIdentity !== "string"
496
496
  || (value.mode !== "primary"
497
497
  && value.mode !== "candidate")
498
- || typeof value.dualOwner !== "boolean") {
498
+ || typeof value.dualOwner !== "boolean"
499
+ || value.storageBackend !== "sqlite"
500
+ || typeof value.workerEnabled !== "boolean") {
499
501
  throw new Error("Runtime identity receipt is invalid.");
500
502
  }
501
503
  return value;
@@ -348,6 +348,11 @@ export class NodeGitWorkspace {
348
348
  }
349
349
  await canonicalContainer(dirname(destination), true);
350
350
  const branch = input.branch === undefined ? undefined : safeRef(input.branch);
351
+ // Reserve exactly this operation's directory before invoking Git. A clone
352
+ // rejection because somebody else created the destination cannot authorize
353
+ // removal of that other directory.
354
+ await mkdir(destination, { mode: 0o700 });
355
+ const createdDirectory = await lstat(destination);
351
356
  try {
352
357
  await git([
353
358
  "clone",
@@ -363,12 +368,31 @@ export class NodeGitWorkspace {
363
368
  }
364
369
  await git(["-C", destination, "branch", "-M", "--", localBranch]);
365
370
  }
371
+ return await this.inspect(destination, "HEAD");
366
372
  }
367
373
  catch (error) {
368
- await rm(destination, { recursive: true, force: true });
374
+ try {
375
+ const current = await lstat(destination).catch(failure => {
376
+ if (isErrno(failure, "ENOENT"))
377
+ return undefined;
378
+ throw failure;
379
+ });
380
+ if (current !== undefined) {
381
+ if (!current.isDirectory() || current.dev !== createdDirectory.dev || current.ino !== createdDirectory.ino) {
382
+ throw new Error("Clone destination ownership changed; no direct deletion attempted.");
383
+ }
384
+ await rm(destination, { recursive: true });
385
+ }
386
+ }
387
+ catch (cleanupError) {
388
+ throw new AggregateError([error, cleanupError], `Clone failed at ${destination}: ${error instanceof Error ? error.message : String(error)}. `
389
+ + `Temporary compensation failed: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}. `
390
+ + "Cleanup effects are unconfirmed; inspect this exact destination before retrying.");
391
+ }
392
+ if (error instanceof Error)
393
+ error.message += ` Exact temporary clone directory removed or already absent: ${destination}.`;
369
394
  throw error;
370
395
  }
371
- return this.inspect(destination, "HEAD");
372
396
  }
373
397
  async ensureLocalBranch(repositoryPath, branch) {
374
398
  try {
@@ -822,31 +846,54 @@ export class NodeGitWorkspace {
822
846
  * worktree's own Git identity instead. Only call for unadopted worktrees
823
847
  * that are safe to discard (e.g. Lane preparation compensation).
824
848
  */
825
- async removeStrandedWorktree(path) {
826
- const kind = await pathKind(path);
827
- if (kind === undefined)
828
- return "missing";
829
- if (kind === "symlink")
830
- throw new Error("Stranded worktree path must not be a symbolic link.");
831
- // Inspect dirty state through the worktree's own Git, bypassing the
832
- // Project ownership check that fails after a catalog switch.
833
- const porcelain = await git(["-C", path, "status", "--porcelain=v1", "--untracked-files=all"]);
834
- if (porcelain.length > 0)
835
- return "dirty";
849
+ async removeStrandedWorktree(path, identity) {
850
+ let worktreeRemoved = false;
836
851
  try {
837
- await git(["-C", path, "worktree", "remove", "--force", "--", path]);
852
+ const expected = managedPath(resolve(identity.container), safeIdentity(identity.directory, "Project directory"));
853
+ if (path !== expected)
854
+ throw new Error("Unadopted worktree does not match its exact managed path.");
855
+ const kind = await pathKind(path);
856
+ if (kind === undefined)
857
+ return "missing";
858
+ if (kind === "symlink")
859
+ throw new Error("Stranded worktree path must not be a symbolic link.");
860
+ await canonicalContainer(identity.container, false);
861
+ if (!(await lstat(join(path, ".git"))).isFile()) {
862
+ throw new Error("Expected a linked worktree, not a standalone clone.");
863
+ }
864
+ const project = await this.inspect(path, "HEAD");
865
+ await assertOwnedWorktree(project, resolve(identity.container), path);
866
+ const branch = worktreeIdentity(identity.taskSegment, identity.roleName).branch;
867
+ await assertExpectedBranch(path, branch);
868
+ if (project.baseCommit !== identity.expectedCommit) {
869
+ throw new Error("Unadopted worktree HEAD changed since preparation; retained.");
870
+ }
871
+ if (!await this.inspectClean(path))
872
+ return "dirty";
873
+ // Use the worktree's proven common directory, never the changed catalog.
874
+ // Git still enforces locks/submodule restrictions. A Git error is not
875
+ // authority for direct filesystem deletion, even for unadopted work.
876
+ await git(["--git-dir", project.gitDirectory, "worktree", "remove", "--", path]);
877
+ worktreeRemoved = true;
878
+ if (identity.deleteBranch === true) {
879
+ await git(["--git-dir", project.gitDirectory, "update-ref", "-d", `refs/heads/${branch}`, identity.expectedCommit]);
880
+ }
881
+ return "removed";
838
882
  }
839
- catch {
840
- // The worktree's common-dir (old external repo) may itself be gone.
841
- // Remove the directory directly; the worktree is unadopted.
842
- await rm(path, { recursive: true, force: true });
883
+ catch (error) {
884
+ throw new Error(`Unadopted worktree cleanup failed at ${path}: ${error instanceof Error ? error.message : String(error)} `
885
+ + `Confirmed worktree removal: ${worktreeRemoved}; failed-command effects remain unconfirmed. `
886
+ + "No direct deletion was attempted. Inspect this exact path and its Git registration before choosing recovery.", { cause: error });
843
887
  }
844
- return "removed";
845
888
  }
846
889
  async removeTaskClone(input) {
847
890
  const state = await this.inspectTaskClone(input);
848
891
  if (state !== "clean")
849
892
  return state;
893
+ if (input.expectedCommit !== undefined
894
+ && await resolveRefCommit(input.path, "HEAD") !== input.expectedCommit) {
895
+ throw new Error(`Unadopted Task clone HEAD changed since preparation; retained at ${input.path}.`);
896
+ }
850
897
  await rm(managedPath(resolve(input.container), input.directory), { recursive: true });
851
898
  return "removed";
852
899
  }
@@ -8,7 +8,7 @@ export function managedProjectPath(home, projectId) {
8
8
  export function createProject(id, name, path, branches, now, metadata = {}) {
9
9
  const timestamp = now.toISOString();
10
10
  return validateProject({
11
- schemaVersion: 6,
11
+ schemaVersion: 1,
12
12
  id: requireIdentity(id, "Project id"),
13
13
  name: validateProjectName(name),
14
14
  aliases: normalizeAliases(metadata.aliases ?? [], name),
@@ -278,8 +278,8 @@ export function assertProjectCatalog(projects) {
278
278
  }
279
279
  }
280
280
  export function validateProject(project) {
281
- if (project.schemaVersion !== 6) {
282
- throw new Error("Project must use schemaVersion 6.");
281
+ if (project.schemaVersion !== 1) {
282
+ throw new Error("Project must use schemaVersion 1.");
283
283
  }
284
284
  if (!Array.isArray(project.resourceRefs))
285
285
  throw new Error("Project resourceRefs must be an array.");