@zq-silk/yui 0.15.7 → 0.15.9

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 (308) hide show
  1. package/ARCHITECTURE.md +194 -399
  2. package/ARCHITECTURE.zh-CN.md +151 -0
  3. package/README.md +306 -1131
  4. package/dist/agent/adapterCatalog.js +15 -2
  5. package/dist/agent/agent.js +23 -3
  6. package/dist/agent/argumentPolicy.js +7 -1
  7. package/dist/agent/connectionPlan.js +62 -0
  8. package/dist/agent/executionComponents.js +158 -0
  9. package/dist/agent/launchEnvironment.js +31 -3
  10. package/dist/agent/managedRuntimeEnvironment.js +3 -5
  11. package/dist/{turn/turn.js → agentRun/agentRun.js} +166 -109
  12. package/dist/{turn/turnIdentity.js → agentRun/runIdentity.js} +4 -4
  13. package/dist/artifacts/artifactCapability.js +74 -0
  14. package/dist/artifacts/artifactCommitLock.js +249 -0
  15. package/dist/artifacts/artifactPaths.js +151 -0
  16. package/dist/artifacts/gitArtifactRef.js +146 -0
  17. package/dist/artifacts/managedGit.js +332 -0
  18. package/dist/artifacts/taskArtifactRepository.js +277 -0
  19. package/dist/brief/taskBrief.js +12 -0
  20. package/dist/cli/agentConfigurationPicker.js +13 -0
  21. package/dist/cli/commandCatalog.js +165 -74
  22. package/dist/cli/interactionCandidates.js +5 -5
  23. package/dist/cli/interactionPolicy.js +38 -8
  24. package/dist/cli/invocationRouter.js +1 -1
  25. package/dist/cli/managedDiagnostics.js +28 -0
  26. package/dist/cli/operatorWizard.js +1 -7
  27. package/dist/cli/roleOptionOrder.js +27 -0
  28. package/dist/cli/roleWizard.js +50 -14
  29. package/dist/cli/updateOrchestrator.js +1 -1
  30. package/dist/cli/updatePorts.js +3 -4
  31. package/dist/cli.js +245 -95
  32. package/dist/commands/agentCommands.js +72 -14
  33. package/dist/commands/capabilityCommands.js +9 -6
  34. package/dist/commands/configCommands.js +20 -20
  35. package/dist/commands/deliveryGuardPreflight.js +2 -2
  36. package/dist/commands/executionAuditCommands.js +24 -24
  37. package/dist/commands/globalRoleCommands.js +1 -1
  38. package/dist/commands/grantCommands.js +4 -4
  39. package/dist/commands/operatorCommands.js +34 -9
  40. package/dist/commands/projectCommands.js +4 -4
  41. package/dist/commands/resourcesCommands.js +2 -2
  42. package/dist/commands/roleConfiguration.js +25 -5
  43. package/dist/commands/roleRuntimeGuard.js +4 -5
  44. package/dist/commands/sessionCommands.js +3 -7
  45. package/dist/commands/taskActivationCommands.js +281 -0
  46. package/dist/commands/taskActor.js +28 -49
  47. package/dist/commands/taskCommands.js +1461 -720
  48. package/dist/commands/taskContextCommand.js +39 -583
  49. package/dist/commands/taskExecutionCommands.js +32 -32
  50. package/dist/commands/taskInputCommands.js +40 -104
  51. package/dist/commands/taskIntegrationCommands.js +3 -2
  52. package/dist/commands/taskIntegrationQueueCommands.js +1 -1
  53. package/dist/commands/taskNextActionCommand.js +8 -8
  54. package/dist/commands/taskOverviewCommand.js +33 -45
  55. package/dist/commands/taskRemoteDeliveryCommand.js +2 -2
  56. package/dist/commands/taskRoleRuntimeStatus.js +133 -102
  57. package/dist/commands/telemetryCommands.js +36 -38
  58. package/dist/config/configCatalog.js +4 -4
  59. package/dist/config/yuiConfig.js +8 -8
  60. package/dist/context/contextSnapshot.js +10 -10
  61. package/dist/context/dispatchContext.js +11 -11
  62. package/dist/context/roleSessionContext.js +6 -3
  63. package/dist/context/{turnContextPack.js → runContextPack.js} +158 -81
  64. package/dist/context/{turnInputContract.js → runInputContract.js} +73 -60
  65. package/dist/context/sessionBootstrapManifest.js +21 -2
  66. package/dist/context/sourceRunContext.js +30 -0
  67. package/dist/context/taskContext.js +481 -0
  68. package/dist/context/wakeNotification.js +27 -27
  69. package/dist/controller/agentRuntimeObserver.js +21 -24
  70. package/dist/controller/capabilityBridge.js +17 -6
  71. package/dist/controller/clientRuntime.js +65 -92
  72. package/dist/controller/controller.js +120 -188
  73. package/dist/controller/fileSchedulerStoreAdapter.js +787 -887
  74. package/dist/controller/jobControl.js +54 -85
  75. package/dist/controller/resourceInventory.js +8 -27
  76. package/dist/controller/resourceInventoryLinux.js +12 -13
  77. package/dist/controller/runtime.js +529 -479
  78. package/dist/controller/runtimeEventInbox.js +55 -25
  79. package/dist/controller/runtimeEventProcessor.js +22 -31
  80. package/dist/controller/{runtimeHookTurnFence.js → runtimeHookRunFence.js} +91 -115
  81. package/dist/controller/runtimeLaunchCoordinator.js +80 -426
  82. package/dist/controller/runtimeObservationHook.js +14 -18
  83. package/dist/controller/sessionNotify.js +16 -24
  84. package/dist/controller/sessionOwnerReconciliation.js +168 -50
  85. package/dist/controller/structuredProviderObservation.js +138 -99
  86. package/dist/coordination/workMailbox.js +3 -3
  87. package/dist/coordination/workMailboxQueue.js +36 -33
  88. package/dist/core/boundedRpc.js +8 -1
  89. package/dist/core/controllerClient.js +20 -1
  90. package/dist/core/controllerServer.js +4 -4
  91. package/dist/doctor/doctor.js +13 -2
  92. package/dist/domain/agentResultTransport.js +9 -9
  93. package/dist/execution/codexThreadNaming.js +2 -8
  94. package/dist/execution/executionHealth.js +51 -63
  95. package/dist/execution/reviewMainRun.js +137 -0
  96. package/dist/execution/workItemExecution.js +28 -29
  97. package/dist/execution/workItemExecutionProjection.js +99 -107
  98. package/dist/execution/workItemMainRun.js +141 -0
  99. package/dist/executor/agentAdapter.js +227 -20
  100. package/dist/executor/agentConfigurationCatalog.js +126 -4
  101. package/dist/executor/agentConfigurationProbe.js +162 -4
  102. package/dist/executor/agentExecutor.js +79 -78
  103. package/dist/executor/effectiveLaunch.js +105 -18
  104. package/dist/executor/executorRegistry.js +29 -44
  105. package/dist/executor/fileRoleLaunchPlanner.js +229 -154
  106. package/dist/executor/workspacePreflightClassification.js +16 -16
  107. package/dist/grant/capabilityGrant.js +6 -3
  108. package/dist/input/inputRequest.js +12 -10
  109. package/dist/integration/gitIntegrationService.js +4 -11
  110. package/dist/integration/integrationQueueService.js +4 -4
  111. package/dist/interaction/operatorPresentation.js +1 -1
  112. package/dist/kernel/builtinCapabilities.js +255 -12
  113. package/dist/kernel/capabilityRegistry.js +64 -18
  114. package/dist/kernel/instanceHost.js +12 -1
  115. package/dist/kernel/kernelPorts.js +2 -2
  116. package/dist/lifecycle/canonicalLifecycleEvent.js +44 -49
  117. package/dist/lifecycle/exactRunTerminalization.js +449 -0
  118. package/dist/message/message.js +118 -6
  119. package/dist/message/messageContinuation.js +204 -0
  120. package/dist/observability/executionAudit.js +70 -72
  121. package/dist/observability/faultClassification.js +2 -2
  122. package/dist/observability/orchestrationMetrics.js +8 -8
  123. package/dist/operator/operatorSessionHistory.js +1 -7
  124. package/dist/output/agentConfigurationPresentation.js +8 -3
  125. package/dist/output/agentRunConfigurationPresentation.js +128 -0
  126. package/dist/output/rolePresentation.js +54 -3
  127. package/dist/plugins/pluginChild.js +104 -0
  128. package/dist/plugins/pluginIntent.js +26 -0
  129. package/dist/plugins/pluginInterpreter.js +43 -0
  130. package/dist/plugins/pluginPackage.js +101 -0
  131. package/dist/plugins/pluginProcess.js +112 -0
  132. package/dist/plugins/pluginService.js +388 -0
  133. package/dist/profile/agentProfile.js +1 -1
  134. package/dist/repository/gitWorkspace.js +26 -4
  135. package/dist/repository/project.js +19 -4
  136. package/dist/repository/taskBaseFreshness.js +13 -13
  137. package/dist/repository/taskWorkspaceCoordinator.js +20 -27
  138. package/dist/repository/taskWorkspacePreparer.js +344 -83
  139. package/dist/resources/autoResourceGc.js +3 -3
  140. package/dist/resources/liveReferences.js +3 -3
  141. package/dist/resources/projectResource.js +75 -0
  142. package/dist/resources/projectResourceService.js +343 -0
  143. package/dist/resources/resourceDiscovery.js +6 -6
  144. package/dist/resources/resourceGc.js +1 -1
  145. package/dist/resources/resourceRegistrar.js +1 -1
  146. package/dist/resources/resourceTypes.js +1 -1
  147. package/dist/review/deltaRecheck.js +3 -3
  148. package/dist/review/reviewAcceptance.js +16 -16
  149. package/dist/review/reviewDecision.js +7 -7
  150. package/dist/review/reviewRound.js +21 -20
  151. package/dist/review/reviewerAvailability.js +2 -2
  152. package/dist/role/role.js +51 -7
  153. package/dist/role/taskRoleUpdate.js +30 -0
  154. package/dist/runtime/acpProtocol.js +425 -0
  155. package/dist/runtime/acpSession.js +731 -0
  156. package/dist/runtime/acpSessionConfiguration.js +260 -0
  157. package/dist/runtime/agentDriver.js +30 -11
  158. package/dist/runtime/agentEndpoint.js +278 -0
  159. package/dist/runtime/agentEndpointIdentity.js +86 -0
  160. package/dist/runtime/agentEndpointOwnership.js +239 -0
  161. package/dist/runtime/agentError.js +2 -10
  162. package/dist/runtime/agentHost.js +565 -314
  163. package/dist/runtime/agentRunConfiguration.js +258 -0
  164. package/dist/runtime/builtinAgentDrivers.js +134 -18
  165. package/dist/runtime/builtinAgentErrorMappers.js +55 -3
  166. package/dist/runtime/builtinTranscriptUsage.js +1 -1
  167. package/dist/runtime/claude-process-owner +0 -0
  168. package/dist/runtime/codexAppServerRuntime.js +38 -30
  169. package/dist/runtime/codexInteractiveHost.js +41 -6
  170. package/dist/runtime/continuationManager.js +2 -6
  171. package/dist/runtime/executionEnvironment.js +30 -0
  172. package/dist/runtime/firstProgressAdvisory.js +11 -11
  173. package/dist/runtime/index.js +4 -3
  174. package/dist/runtime/jsonLineChannel.js +109 -0
  175. package/dist/runtime/launchBroker.js +91 -16
  176. package/dist/runtime/launchDiagnostics.js +2 -2
  177. package/dist/runtime/lifecycleReservation.js +10 -18
  178. package/dist/runtime/managedCaller.js +61 -17
  179. package/dist/runtime/nativeSessionControl.js +102 -0
  180. package/dist/runtime/ports.js +6 -21
  181. package/dist/runtime/processExitObservation.js +8 -7
  182. package/dist/runtime/promptEnvelope.js +17 -6
  183. package/dist/runtime/providerContinuation.js +3 -9
  184. package/dist/runtime/providerContinuationReconciliationService.js +4 -13
  185. package/dist/runtime/providerControl.js +2 -7
  186. package/dist/runtime/providerRuntimeIdentity.js +110 -222
  187. package/dist/runtime/providerRuntimeReconciler.js +5 -9
  188. package/dist/runtime/runtimeBinding.js +0 -1
  189. package/dist/runtime/runtimeContinuationProjection.js +4 -7
  190. package/dist/runtime/runtimeDeadlines.js +9 -0
  191. package/dist/runtime/runtimeHealthPolicy.js +1 -1
  192. package/dist/runtime/runtimeObservation.js +29 -65
  193. package/dist/runtime/runtimeProjection.js +43 -51
  194. package/dist/runtime/runtimeSessionCandidate.js +1 -3
  195. package/dist/runtime/sessionLaunchRequest.js +3 -7
  196. package/dist/runtime/sessionOwnerIdentity.js +7 -54
  197. package/dist/runtime/sessionOwnerRegistry.js +22 -17
  198. package/dist/runtime/sessionReconciliation.js +4 -8
  199. package/dist/runtime/sessionTerminationGuard.js +70 -259
  200. package/dist/runtime/sessionTokenMetrics.js +5 -16
  201. package/dist/runtime/structuredProviderHost.js +237 -117
  202. package/dist/runtime/taskRuntimeIsolation.js +39 -122
  203. package/dist/runtime/tmuxAdapters.js +39 -86
  204. package/dist/scheduler/activeRoleRunDelivery.js +354 -0
  205. package/dist/scheduler/leaderWakeupProcessor.js +75 -266
  206. package/dist/scheduler/operatorInputNotificationProcessor.js +1 -1
  207. package/dist/scheduler/ports.js +80 -9
  208. package/dist/scheduler/{roleTurnLiveness.js → roleRunLiveness.js} +26 -30
  209. package/dist/scheduler/{roleTurnStall.js → roleRunStall.js} +128 -139
  210. package/dist/scheduler/taskExecutionProjection.js +120 -124
  211. package/dist/scheduler/taskObservabilityProjection.js +29 -29
  212. package/dist/scheduler/taskWake.js +11 -4
  213. package/dist/scheduler/wakeReason.js +9 -1
  214. package/dist/setup/setupCommand.js +3 -7
  215. package/dist/storage/migrations/agentRunContract.js +159 -0
  216. package/dist/storage/migrations/artifactsToGit.js +338 -0
  217. package/dist/storage/migrations/removeRuntimeGeneration.js +207 -0
  218. package/dist/storage/migrations/submitIntent.js +126 -0
  219. package/dist/storage/sqliteSchema.js +467 -7
  220. package/dist/storage/sqliteStore.js +355 -220
  221. package/dist/storage/storageVersions.js +1 -1
  222. package/dist/storage/storeRpc.js +10 -5
  223. package/dist/storage/taskStore.js +13 -11
  224. package/dist/storage/upgrade/upgradeOrchestrator.js +5 -7
  225. package/dist/surface/surfaceContributions.js +102 -0
  226. package/dist/task/completionReadiness.js +32 -6
  227. package/dist/task/deliveryGuard.js +16 -16
  228. package/dist/task/draftPlan.js +72 -12
  229. package/dist/task/nextAction.js +144 -128
  230. package/dist/task/remoteDelivery.js +6 -6
  231. package/dist/task/task.js +184 -18
  232. package/dist/task/taskActivation.js +327 -0
  233. package/dist/task/taskActivationService.js +408 -0
  234. package/dist/task/taskRecordReference.js +5 -4
  235. package/dist/task/taskRecordRetirement.js +1 -1
  236. package/dist/task/taskSubmission.js +236 -0
  237. package/dist/telemetry/sqliteTelemetryStore.js +55 -68
  238. package/dist/telemetry/telemetryConfig.js +14 -14
  239. package/dist/telemetry/telemetryWiring.js +2 -2
  240. package/dist/web/assets/assetManifest.js +2 -0
  241. package/dist/web/assets/client/app.js +121 -20
  242. package/dist/web/assets/client/components.js +87 -54
  243. package/dist/web/assets/client/i18n.js +83 -41
  244. package/dist/web/assets/client/markdown.js +1 -1
  245. package/dist/web/assets/client/taskSurface.js +442 -0
  246. package/dist/web/assets/client/view.js +49 -44
  247. package/dist/web/assets/shell.js +1 -1
  248. package/dist/web/assets/styles/cards.js +22 -4
  249. package/dist/web/controllerWeb.js +60 -0
  250. package/dist/web/webMutation.js +28 -0
  251. package/dist/web/webServer.js +133 -8
  252. package/dist/web/webSnapshot.js +81 -74
  253. package/dist/web/webTaskSurface.js +64 -0
  254. package/dist/workItem/dependencyGate.js +1 -1
  255. package/dist/workItem/workItem.js +84 -48
  256. package/dist/workspace/workItemChangeSetManager.js +16 -9
  257. package/docs/agent-result-consumption.md +96 -0
  258. package/docs/agent-result-consumption.zh-CN.md +81 -0
  259. package/docs/agent-runtime-drivers.md +93 -0
  260. package/docs/agent-runtime-drivers.zh-CN.md +77 -0
  261. package/docs/architecture/README.md +50 -0
  262. package/docs/architecture/README.zh-CN.md +43 -0
  263. package/docs/architecture/capabilities-and-resources.md +118 -0
  264. package/docs/architecture/capabilities-and-resources.zh-CN.md +83 -0
  265. package/docs/managed-turn-and-session-runtime.md +224 -0
  266. package/docs/managed-turn-and-session-runtime.zh-CN.md +180 -0
  267. package/docs/observability/README.md +83 -0
  268. package/docs/observability/README.zh-CN.md +71 -0
  269. package/docs/plugin-sdk.md +393 -0
  270. package/docs/plugin-sdk.zh-CN.md +293 -0
  271. package/docs/provider-runtime.md +165 -0
  272. package/docs/provider-runtime.zh-CN.md +132 -0
  273. package/docs/release-workflow.md +305 -0
  274. package/docs/release-workflow.zh-CN.md +237 -0
  275. package/docs/roles-and-configuration.md +115 -0
  276. package/docs/roles-and-configuration.zh-CN.md +96 -0
  277. package/docs/sqlite-control-plane-design.md +78 -0
  278. package/docs/sqlite-control-plane-design.zh-CN.md +62 -0
  279. package/docs/task-dag-semantics.md +80 -0
  280. package/docs/task-dag-semantics.zh-CN.md +59 -0
  281. package/docs/task-delivery.md +105 -0
  282. package/docs/task-delivery.zh-CN.md +82 -0
  283. package/docs/task-local-identity.md +8 -6
  284. package/docs/task-local-identity.zh-CN.md +58 -0
  285. package/docs/testing/verification-levels.md +88 -0
  286. package/docs/testing/verification-levels.zh-CN.md +69 -0
  287. package/i18n/README.zh-CN.md +270 -722
  288. package/package.json +3 -2
  289. package/skills/yui-leader/SKILL.md +88 -304
  290. package/skills/yui-leader/references/execution.md +303 -0
  291. package/skills/yui-leader/references/integration.md +39 -0
  292. package/skills/yui-leader/references/planning.md +109 -0
  293. package/skills/yui-leader/references/replicated-execution.md +42 -0
  294. package/skills/yui-leader/references/task-plugins.md +37 -0
  295. package/skills/yui-operator/SKILL.md +46 -62
  296. package/skills/yui-reviewer/SKILL.md +35 -36
  297. package/skills/yui-runtime/SKILL.md +88 -24
  298. package/skills/yui-runtime/references/publication.md +22 -0
  299. package/skills/yui-runtime/references/recovery.md +64 -0
  300. package/skills/yui-worker/SKILL.md +37 -39
  301. package/dist/cli/roleOptionCatalog.js +0 -68
  302. package/dist/context/sourceTurnContext.js +0 -30
  303. package/dist/execution/reviewMainTurn.js +0 -161
  304. package/dist/execution/workItemMainTurn.js +0 -164
  305. package/dist/lifecycle/exactTurnTerminalization.js +0 -407
  306. package/dist/runtime/preallocatedNativeSession.js +0 -13
  307. package/dist/runtime/runtimeStopReceipt.js +0 -42
  308. package/dist/scheduler/activeRoleTurnDelivery.js +0 -315
@@ -98,14 +98,14 @@ function collectTaskStatuses(store) {
98
98
  return statuses;
99
99
  }
100
100
  /**
101
- * Workspace paths claimed by active durable Jobs. An active Turn still
101
+ * Workspace paths claimed by active durable Jobs. An active AgentRun still
102
102
  * holds its workspace even after the managed workspace record is gone, so
103
- * those paths stay protected until the Turn finishes.
103
+ * those paths stay protected until the AgentRun finishes.
104
104
  */
105
105
  function collectActiveWorkspaceOwnerPaths(store) {
106
106
  const paths = [];
107
107
  for (const task of store.listTasks()) {
108
- for (const run of store.listTurns(task.id)) {
108
+ for (const run of store.listRuns(task.id)) {
109
109
  if (run.status !== "active")
110
110
  continue;
111
111
  const workspace = run.workspace;
@@ -142,7 +142,7 @@ export async function scanLiveReferences(input) {
142
142
  }
143
143
  }
144
144
  }
145
- // 6. Active durable workspace owners (e.g. an active Turn launch).
145
+ // 6. Active durable workspace owners (e.g. an active AgentRun launch).
146
146
  const activeOwners = input.ports?.activeWorkspaceOwners !== undefined
147
147
  ? input.ports.activeWorkspaceOwners()
148
148
  : [];
@@ -232,7 +232,7 @@ function readActiveReleaseClaims(home) {
232
232
  }
233
233
  }
234
234
  /**
235
- * Read Session owner records (`runtime/session-owners/<runtimeGenerationId>.json`). A
235
+ * Read Session owner records (`runtime/session-owners/<pid-startIdentity>.json`). A
236
236
  * record whose Provider root is physically alive with a matching start
237
237
  * identity protects its runtime root. A dead PID is stale (reconciliation
238
238
  * removes those records); an identity conflict or unreadable record fails
@@ -301,7 +301,7 @@ function readSessionOwnerClaims(home) {
301
301
  if (typeof record.runtimeRoot === "string" && record.runtimeRoot.length > 0) {
302
302
  claims.push({
303
303
  path: resolve(record.runtimeRoot),
304
- token: `session-owner:${typeof record.runtimeGenerationId === "string" ? record.runtimeGenerationId : entry.name}`
304
+ token: `session-owner:${pid}-${startIdentity}`
305
305
  });
306
306
  }
307
307
  }
@@ -0,0 +1,75 @@
1
+ import { createHash } from "node:crypto";
2
+ import { isAbsolute, resolve } from "node:path";
3
+ import { requireIdentity, requireText, requireTimestamp } from "../domain/validation.js";
4
+ export function validateExecutionEnvironmentSnapshot(value) {
5
+ if (!value || typeof value !== "object")
6
+ throw new Error("Execution environment must be an object.");
7
+ requireIdentity(value.taskId, "Execution environment Task");
8
+ requireIdentity(value.preparationId, "Execution environment preparation");
9
+ if (value.environmentRef !== `${value.taskId}/${value.preparationId}`) {
10
+ throw new Error("Execution environment reference does not match its preparation.");
11
+ }
12
+ if (!["read", "write"].includes(value.access) || value.isolation !== "trusted-local" || !value.directory) {
13
+ throw new Error("Execution environment requires a trusted-local directory and explicit access.");
14
+ }
15
+ requireText(value.directory.path, "Execution environment path");
16
+ if (!isAbsolute(value.directory.path) || resolve(value.directory.path) !== value.directory.path) {
17
+ throw new Error("Execution environment path must be absolute and normalized.");
18
+ }
19
+ requireText(value.directory.device, "Execution environment device");
20
+ requireText(value.directory.inode, "Execution environment inode");
21
+ if (!["preparation", "user"].includes(value.directory.ownership))
22
+ throw new Error("Invalid execution directory owner.");
23
+ return value;
24
+ }
25
+ export function contentDigest(content) {
26
+ return createHash("sha256").update(content, "utf8").digest("hex");
27
+ }
28
+ export function validateLocalResource(resource) {
29
+ if (resource.schemaVersion !== 1 || resource.kind !== "local-directory" || resource.ownership !== "user") {
30
+ throw new Error("Invalid local Resource record.");
31
+ }
32
+ requireIdentity(resource.id, "Resource id");
33
+ requireText(resource.displayName, "Resource name");
34
+ requireText(resource.path, "Resource path");
35
+ requireText(resource.device, "Resource device");
36
+ requireText(resource.inode, "Resource inode");
37
+ requireTimestamp(resource.createdAt, "Resource timestamp");
38
+ return resource;
39
+ }
40
+ export function validateEnvironmentPreparation(record) {
41
+ if (record.schemaVersion !== 1 || !["prepared", "adopted", "released"].includes(record.disposition)) {
42
+ throw new Error("Invalid Environment preparation.");
43
+ }
44
+ requireIdentity(record.id, "Preparation id");
45
+ requireIdentity(record.taskId, "Preparation Task");
46
+ requireText(record.intentDigest, "Preparation intent");
47
+ if (!Array.isArray(record.resourceRefs) || new Set(record.resourceRefs).size !== record.resourceRefs.length) {
48
+ throw new Error("Preparation resources must be unique.");
49
+ }
50
+ record.resourceRefs.forEach((id) => requireIdentity(id, "Preparation resource"));
51
+ if (!["read", "write"].includes(record.access))
52
+ throw new Error("Invalid preparation access.");
53
+ if (!["none", "trusted-local"].includes(record.isolation)
54
+ || (record.directory === undefined) !== (record.isolation === "none")) {
55
+ throw new Error("Environment isolation must describe the actual local preparation.");
56
+ }
57
+ if ((record.directory === undefined) !== (record.environmentRef === undefined)) {
58
+ throw new Error("Environment reference requires an actual directory.");
59
+ }
60
+ if (record.directory) {
61
+ requireText(record.directory.path, "Environment path");
62
+ requireText(record.directory.device, "Environment device");
63
+ requireText(record.directory.inode, "Environment inode");
64
+ if (!["preparation", "user"].includes(record.directory.ownership))
65
+ throw new Error("Invalid directory owner.");
66
+ }
67
+ requireTimestamp(record.createdAt, "Preparation timestamp");
68
+ requireTimestamp(record.updatedAt, "Preparation update");
69
+ if (record.releaseEvidence !== undefined) {
70
+ if (record.disposition !== "released")
71
+ throw new Error("Release evidence requires a released preparation.");
72
+ requireText(record.releaseEvidence, "Release evidence");
73
+ }
74
+ return record;
75
+ }
@@ -0,0 +1,343 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { lstatSync, mkdirSync, mkdtempSync, realpathSync, rmdirSync, statSync } from "node:fs";
3
+ import { join, relative, resolve, isAbsolute } from "node:path";
4
+ import { checkGrant, recordGrantUse } from "../grant/capabilityGrant.js";
5
+ import { requireText } from "../domain/validation.js";
6
+ import { validateProject } from "../repository/project.js";
7
+ import { updateRole } from "../role/role.js";
8
+ import { saveTaskRoleUpdate } from "../role/taskRoleUpdate.js";
9
+ import { assertRoleRuntimeMutationAllowed } from "../commands/roleRuntimeGuard.js";
10
+ import { assertProviderConversationReplaceable, currentProviderConversation } from "../runtime/providerRuntimeIdentity.js";
11
+ import { projectProviderContinuations } from "../runtime/runtimeContinuationProjection.js";
12
+ import { contentDigest, validateExecutionEnvironmentSnapshot } from "./projectResource.js";
13
+ /** Trusted typed owner; the public caller is authenticated by the existing
14
+ * capability boundary. No new Store, worker, scheduling or Git authority. */
15
+ export function createProjectResources(store, now = () => new Date()) {
16
+ const task = (taskId) => {
17
+ const value = store.getTask(taskId);
18
+ if (!value)
19
+ throw new Error(`Task not found: ${taskId}.`);
20
+ return value;
21
+ };
22
+ const intent = (taskId) => {
23
+ const value = task(taskId);
24
+ return contentDigest(JSON.stringify({
25
+ // Git's moving delivery pointers do not change Resource intent.
26
+ projectBindings: value.projectBindings.map(({ projectId, directory, baseRef }) => ({ projectId, directory, baseRef })),
27
+ projects: value.projectBindings.map(({ projectId }) => {
28
+ const project = store.getProject(projectId);
29
+ return { projectId, resources: project?.resourceRefs, providers: project?.defaultCapabilityProviders };
30
+ })
31
+ }));
32
+ };
33
+ const openTask = (taskId) => {
34
+ const value = task(taskId);
35
+ if (value.status !== "draft" && value.status !== "active")
36
+ throw new Error("Task is not open for environment adoption.");
37
+ return value;
38
+ };
39
+ const resource = (resourceId) => {
40
+ const value = store.getLocalResource(resourceId);
41
+ if (!value)
42
+ throw new Error(`Local resource not found: ${resourceId}.`);
43
+ return value;
44
+ };
45
+ const grantFor = (taskId, resourceId, access, reservation, reservedOnly = false) => {
46
+ const target = resource(resourceId);
47
+ const grant = store.listCapabilityGrants(taskId).find((candidate) => {
48
+ // A Project scope is visibility, not an exhaustive Resource grant. A
49
+ // concrete resource bound is mandatory even for a broadly named action.
50
+ if (!candidate.parameterBounds.resourceId?.includes(resourceId))
51
+ return false;
52
+ if (reservedOnly && (reservation === undefined || !candidate.useReservations.includes(reservation)))
53
+ return false;
54
+ if (candidate.scope.homePath !== undefined && resolve(candidate.scope.homePath) !== target.path)
55
+ return false;
56
+ if (candidate.scope.projectIds?.some((id) => !task(taskId).projectBindings.some((binding) => binding.projectId === id)))
57
+ return false;
58
+ return checkGrant(candidate, { action: `resource.local.${access}`, params: { resourceId } }, now(), { skipUsesCheck: reservation !== undefined && candidate.useReservations.includes(reservation) }).allowed;
59
+ });
60
+ if (!grant)
61
+ throw new Error(`Resource grant unavailable: ${resourceId}/${access}.`);
62
+ return grant;
63
+ };
64
+ const preparation = (taskId, id) => {
65
+ const value = store.getEnvironmentPreparation(taskId, id);
66
+ if (!value)
67
+ throw new Error(`Environment preparation not found: ${taskId}/${id}.`);
68
+ return value;
69
+ };
70
+ const assertLocalWriteOwner = (path, access) => {
71
+ if (access !== "write")
72
+ return;
73
+ const protectedPaths = [
74
+ realpathSync(store.rootDirectory()),
75
+ ...store.listProjects().map((project) => project.path),
76
+ ...store.listTasks().flatMap((value) => store.listManagedWorkspaces(value.id)
77
+ .flatMap((workspace) => [workspace.root, ...workspace.entries.map((entry) => entry.path)]))
78
+ ];
79
+ if (protectedPaths.some((protectedPath) => overlaps(path, currentOrAbsentPath(protectedPath)))) {
80
+ throw new Error("Local resource overlaps a Yui Home or managed Git workspace; use its existing owner and Git operations.");
81
+ }
82
+ };
83
+ const resolveExecutionEnvironment = (taskId, id) => {
84
+ openTask(taskId);
85
+ const value = preparation(taskId, id);
86
+ if (value.disposition !== "adopted")
87
+ throw new Error("Execution environment must be adopted and not released.");
88
+ if (value.intentDigest !== intent(taskId))
89
+ throw new Error("Task resource/configuration intent changed; prepare again.");
90
+ if (!value.directory || !value.environmentRef || value.isolation !== "trusted-local") {
91
+ throw new Error("Empty preparation has no native execution directory; select a directory environment or managed workspace.");
92
+ }
93
+ assertDirectory(value.directory);
94
+ if (value.directory.ownership === "user")
95
+ assertLocalWriteOwner(value.directory.path, value.access);
96
+ for (const resourceId of value.resourceRefs) {
97
+ // Adoption already charged the bounded grant. Launch/resume may recheck
98
+ // that reservation, but must neither invent nor consume another use.
99
+ grantFor(taskId, resourceId, value.access, id, true);
100
+ const target = resource(resourceId);
101
+ assertDirectory(target);
102
+ if (target.path !== value.directory.path || target.device !== value.directory.device || target.inode !== value.directory.inode) {
103
+ throw new Error("Execution environment no longer matches its registered Resource.");
104
+ }
105
+ }
106
+ return validateExecutionEnvironmentSnapshot({
107
+ taskId, preparationId: id, environmentRef: value.environmentRef,
108
+ access: value.access, isolation: value.isolation, directory: { ...value.directory }
109
+ });
110
+ };
111
+ return {
112
+ resolveExecutionEnvironment,
113
+ bindEnvironment(taskId, roleName, preparationId, source = { source: "environment.bind" }) {
114
+ return store.transaction((tx) => {
115
+ openTask(taskId);
116
+ const role = tx.getRole(taskId, roleName);
117
+ if (!role)
118
+ throw new Error(`Role not found: ${taskId}/${roleName}.`);
119
+ assertRoleRuntimeMutationAllowed(tx, { scope: "task", taskId, roleName }, "environment binding");
120
+ const executionEnvironment = preparationId === null ? null : resolveExecutionEnvironment(taskId, preparationId);
121
+ const timestamp = now();
122
+ const updated = updateRole(role, { executionEnvironment }, timestamp);
123
+ // Desired configuration only: active native Sessions retain their
124
+ // immutable actual snapshot until explicitly ended.
125
+ saveTaskRoleUpdate(tx, role, updated, timestamp, source);
126
+ return updated;
127
+ });
128
+ },
129
+ gitResult(taskId, changeSetId) {
130
+ const changeSet = store.getChangeSet(taskId, changeSetId);
131
+ if (!changeSet)
132
+ throw new Error("ChangeSet not found in this Task.");
133
+ return {
134
+ kind: "external-version", resourceId: changeSet.projectId,
135
+ version: changeSet.headCommit,
136
+ verification: { taskId, changeSetId: changeSet.id, baseCommit: changeSet.baseCommit },
137
+ // This is a projection of the existing fixed Git result, not another
138
+ // mutable repository/worktree/Integration record.
139
+ changeSet
140
+ };
141
+ },
142
+ registerLocalDirectory(displayName, path) {
143
+ const identity = directoryIdentity(path);
144
+ return store.transaction((tx) => {
145
+ const existing = tx.listLocalResources().find((value) => value.device === identity.device && value.inode === identity.inode);
146
+ if (existing)
147
+ return existing;
148
+ const value = { schemaVersion: 1, id: `resource-${randomUUID()}`,
149
+ kind: "local-directory", displayName: requireText(displayName, "Resource name"),
150
+ ...identity, ownership: "user", createdAt: now().toISOString() };
151
+ tx.saveLocalResource(value);
152
+ return value;
153
+ });
154
+ },
155
+ readLocalResource(taskId, resourceId) {
156
+ grantFor(taskId, resourceId, "read");
157
+ return resource(resourceId);
158
+ },
159
+ projectContext(taskId, projectId) {
160
+ if (!task(taskId).projectBindings.some((binding) => binding.projectId === projectId))
161
+ throw new Error("Project is outside the Task context.");
162
+ const project = store.getProject(projectId);
163
+ if (!project)
164
+ throw new Error("Project not found.");
165
+ return { id: project.id, name: project.name, knowledge: project.knowledge,
166
+ resourceRefs: project.resourceRefs, defaultCapabilityProviders: project.defaultCapabilityProviders };
167
+ },
168
+ configureProject(projectId, resourceRefs, defaultCapabilityProviders) {
169
+ return store.transaction((tx) => {
170
+ const project = tx.getProject(projectId);
171
+ if (!project || project.status !== "active")
172
+ throw new Error("Active Project not found.");
173
+ resourceRefs.forEach(resource);
174
+ const next = validateProject({ ...project, resourceRefs, defaultCapabilityProviders, updatedAt: now().toISOString() });
175
+ tx.saveProject(next);
176
+ return next;
177
+ });
178
+ },
179
+ prepare(taskId, plan) {
180
+ openTask(taskId);
181
+ let directory;
182
+ const id = `preparation-${randomUUID()}`;
183
+ const access = plan.kind === "local" ? plan.access : "write";
184
+ const resourceRefs = plan.kind === "local" ? [plan.resourceId] : [];
185
+ if (plan.kind === "local") {
186
+ grantFor(taskId, plan.resourceId, access);
187
+ const target = resource(plan.resourceId);
188
+ assertDirectory(target);
189
+ assertLocalWriteOwner(target.path, access);
190
+ directory = { path: target.path, device: target.device, inode: target.inode, ownership: "user" };
191
+ }
192
+ else if (plan.kind === "scratch") {
193
+ const parent = join(store.rootDirectory(), "environments");
194
+ mkdirSync(parent, { recursive: true, mode: 0o700 });
195
+ if (realpathSync(parent) !== resolve(parent))
196
+ throw new Error("Environment root must not be a symlink.");
197
+ directory = { ...directoryIdentity(mkdtempSync(join(parent, `${id}-`))), ownership: "preparation" };
198
+ }
199
+ try {
200
+ return store.transaction((tx) => {
201
+ openTask(taskId);
202
+ const timestamp = now().toISOString();
203
+ const value = { schemaVersion: 1, id, taskId,
204
+ disposition: "prepared", intentDigest: intent(taskId), resourceRefs, access,
205
+ isolation: directory ? "trusted-local" : "none",
206
+ ...(directory ? { directory, environmentRef: `${taskId}/${id}` } : {}),
207
+ createdAt: timestamp, updatedAt: timestamp };
208
+ tx.saveEnvironmentPreparation(value);
209
+ return value;
210
+ });
211
+ }
212
+ catch (error) {
213
+ if (directory?.ownership === "preparation") {
214
+ // Only the exact newly-created empty directory is eligible. Never
215
+ // recursively remove a partial/unknown external preparation.
216
+ try {
217
+ assertDirectory(directory);
218
+ rmdirSync(directory.path);
219
+ }
220
+ catch (cleanup) {
221
+ throw new AggregateError([error, cleanup], `Unadopted environment retained: ${directory.path}`);
222
+ }
223
+ }
224
+ throw error;
225
+ }
226
+ },
227
+ adopt(taskId, id) {
228
+ return store.transaction((tx) => {
229
+ openTask(taskId);
230
+ const value = preparation(taskId, id);
231
+ if (value.disposition === "released")
232
+ throw new Error("Environment was released.");
233
+ if (value.intentDigest !== intent(taskId))
234
+ throw new Error("Task resource/configuration intent changed; prepare again.");
235
+ if (value.directory)
236
+ assertDirectory(value.directory);
237
+ if (value.directory?.ownership === "user")
238
+ assertLocalWriteOwner(value.directory.path, value.access);
239
+ const grants = value.resourceRefs.map((resourceId) => grantFor(taskId, resourceId, value.access, id));
240
+ if (value.disposition === "adopted")
241
+ return value;
242
+ if (value.directory) {
243
+ for (const otherTask of tx.listTasks()) {
244
+ const conflict = tx.listEnvironmentPreparations(otherTask.id).some((other) => other.disposition === "adopted" && other.directory
245
+ && (value.access === "write" || other.access === "write")
246
+ && overlaps(other.directory.path, value.directory.path));
247
+ if (conflict)
248
+ throw new Error("Resource conflicts with an adopted environment; inspect its use or choose isolated scratch.");
249
+ }
250
+ }
251
+ grants.forEach((grant) => tx.saveCapabilityGrant(taskId, recordGrantUse(grant, now(), id)));
252
+ const adopted = { ...value, disposition: "adopted", updatedAt: now().toISOString() };
253
+ tx.saveEnvironmentPreparation(adopted);
254
+ return adopted;
255
+ });
256
+ },
257
+ release(taskId, id, evidence) {
258
+ return store.transaction((tx) => {
259
+ const value = preparation(taskId, id);
260
+ if (value.disposition === "released")
261
+ return value;
262
+ if (value.disposition === "adopted" && value.directory) {
263
+ requireText(evidence?.quiescence ?? "", "Actual quiescence evidence");
264
+ const references = (environment) => environment?.taskId === taskId && environment.preparationId === id;
265
+ const sessionSets = tx.listRoleSessionSets(taskId);
266
+ const continuations = projectProviderContinuations(tx.listEvents(taskId));
267
+ for (const set of sessionSets) {
268
+ const users = [...Object.values(set.sessions), ...(set.history ?? [])]
269
+ .filter((session) => references(session.effective.executionEnvironment));
270
+ if (set.providerBinding != null && users.some((session) => session.nativeSessionId === currentProviderConversation(set.providerBinding).conversationId)) {
271
+ // A dead Host cannot prove that the native daemon accepted
272
+ // nothing or that a detached input has finished.
273
+ assertProviderConversationReplaceable(set.providerBinding);
274
+ }
275
+ if (continuations.some((continuation) => continuation.roleName === set.owner.roleName
276
+ && (continuation.execution !== "quiescent"
277
+ || continuation.observation !== "exact" || continuation.identityConflict)
278
+ && (users.some((session) => session.agentId === continuation.identity.accountScope
279
+ && session.nativeSessionId === continuation.identity.conversationId)
280
+ || references(tx.getRun(taskId, continuation.runId)?.effective.executionEnvironment)))) {
281
+ throw new Error("Environment still has a live or unknown native continuation.");
282
+ }
283
+ }
284
+ if (value.directory && (tx.listRuns(taskId).some((run) => run.status === "active"
285
+ && (references(run.effective.executionEnvironment)
286
+ || (run.workspace && overlaps(run.workspace.root, value.directory.path))))
287
+ || sessionSets.some((set) => [...Object.values(set.sessions), ...(set.history ?? [])].some((session) => session.status === "active" && references(session.effective.executionEnvironment)))
288
+ || tx.listDurableJobs(taskId).some((job) => ["queued", "running", "unknown-needs-attention"].includes(job.status)
289
+ && overlaps(job.workspace, value.directory.path))))
290
+ throw new Error("Environment still has a live or unknown execution reference.");
291
+ }
292
+ if (value.directory?.ownership === "preparation") {
293
+ assertDirectory(value.directory);
294
+ const parent = realpathSync(join(store.rootDirectory(), "environments"));
295
+ if (!within(parent, value.directory.path) || !value.directory.path.startsWith(join(parent, `${id}-`))) {
296
+ throw new Error("Preparation directory is outside its owned root.");
297
+ }
298
+ // Content must first be saved or explicitly handled by its owner.
299
+ // Nonempty directories are retained with ENOTEMPTY, never force-deleted.
300
+ rmdirSync(value.directory.path);
301
+ }
302
+ const released = { ...value, disposition: "released", updatedAt: now().toISOString(),
303
+ ...(evidence === undefined ? {} : { releaseEvidence: requireText(evidence.quiescence, "Actual quiescence evidence") }) };
304
+ tx.saveEnvironmentPreparation(released);
305
+ return released;
306
+ });
307
+ }
308
+ };
309
+ }
310
+ function directoryIdentity(path) {
311
+ const canonical = realpathSync(resolve(path));
312
+ const stat = statSync(canonical, { bigint: true });
313
+ if (!stat.isDirectory())
314
+ throw new Error("Resource must be a local directory.");
315
+ return { path: canonical, device: String(stat.dev), inode: String(stat.ino) };
316
+ }
317
+ function assertDirectory(expected) {
318
+ if (lstatSync(expected.path).isSymbolicLink())
319
+ throw new Error("Directory identity changed to a symlink.");
320
+ const current = directoryIdentity(expected.path);
321
+ if (current.path !== expected.path || current.device !== expected.device || current.inode !== expected.inode) {
322
+ throw new Error("Directory identity changed; retained for owner inspection.");
323
+ }
324
+ }
325
+ function within(parent, path) {
326
+ const value = relative(parent, path);
327
+ return value !== "" && value !== ".." && !value.startsWith("../") && !isAbsolute(value);
328
+ }
329
+ function overlaps(left, right) {
330
+ return left === right || within(left, right) || within(right, left);
331
+ }
332
+ function currentOrAbsentPath(path) {
333
+ try {
334
+ return realpathSync(path);
335
+ }
336
+ catch (error) {
337
+ // Historical workspace references may outlive a removed directory. Retain
338
+ // that exact address; never manufacture a replacement or ignore other errors.
339
+ if (error.code === "ENOENT")
340
+ return resolve(path);
341
+ throw error;
342
+ }
343
+ }
@@ -179,8 +179,8 @@ function discoverRuntimeArtifacts(home, input) {
179
179
  for (const launchEntry of safeReaddir(join(taskRoot, ownerEntry.name))) {
180
180
  if (!launchEntry.isDirectory())
181
181
  continue;
182
- const generationRoot = join(taskRoot, ownerEntry.name, launchEntry.name);
183
- const marker = readTaskRuntimeMarker(generationRoot);
182
+ const runtimeRoot = join(taskRoot, ownerEntry.name, launchEntry.name);
183
+ const marker = readTaskRuntimeMarker(runtimeRoot);
184
184
  const owner = marker === undefined
185
185
  ? { home, taskId, basis: "naming-convention" }
186
186
  : {
@@ -193,9 +193,9 @@ function discoverRuntimeArtifacts(home, input) {
193
193
  discovered.push({
194
194
  record: createResourceRecord({
195
195
  kind: "runtime-artifact",
196
- path: resolve(generationRoot),
196
+ path: resolve(runtimeRoot),
197
197
  owner,
198
- ...(sizeOf(generationRoot) === undefined ? {} : { sizeBytes: sizeOf(generationRoot) }),
198
+ ...(sizeOf(runtimeRoot) === undefined ? {} : { sizeBytes: sizeOf(runtimeRoot) }),
199
199
  cleanliness: "n/a",
200
200
  activeRefs: [],
201
201
  disposition: "active"
@@ -352,9 +352,9 @@ function readDeploymentGitMetadata(path) {
352
352
  return { repositoryPath: path };
353
353
  }
354
354
  }
355
- function readTaskRuntimeMarker(generationRoot) {
355
+ function readTaskRuntimeMarker(runtimeRoot) {
356
356
  try {
357
- const marker = JSON.parse(readFileSync(join(generationRoot, ".yui-task-runtime-owner.json"), "utf8"));
357
+ const marker = JSON.parse(readFileSync(join(runtimeRoot, ".yui-task-runtime-owner.json"), "utf8"));
358
358
  const owner = marker.descriptor?.workspace?.owner;
359
359
  if (owner === undefined || owner === null)
360
360
  return {};
@@ -125,7 +125,7 @@ function classifyDiscoveredResources(discovered, registry, scan, input, home) {
125
125
  }
126
126
  /**
127
127
  * Collect active workspace owner path fragments from durable state.
128
- * These protect resources that an active Session, Job, or Turn still
128
+ * These protect resources that an active Session, Job, or AgentRun still
129
129
  * references even when the Task itself is terminal.
130
130
  */
131
131
  function collectActiveWorkspaceOwners(input) {
@@ -68,7 +68,7 @@ export class ResourceRegistrar {
68
68
  #registerTaskRuntimeIsolation(descriptor) {
69
69
  const owner = ownerFromManagedWorkspace(this.#home, descriptor.workspace.owner);
70
70
  this.#save([
71
- descriptor.roots.generation,
71
+ descriptor.roots.runtime,
72
72
  descriptor.roots.data,
73
73
  descriptor.roots.cache,
74
74
  descriptor.roots.temporary
@@ -30,7 +30,7 @@ export function isReleasable(record) {
30
30
  }
31
31
  /** Terminal Task statuses: a terminal owner keeps no active runtime claim. */
32
32
  export function isTerminalTaskStatus(status) {
33
- return status === "completed" || status === "retired" || status === "archived";
33
+ return status === "completed" || status === "cancelled" || status === "archived";
34
34
  }
35
35
  /**
36
36
  * The immutable release namespace owned by the package installer (Issue 02).
@@ -7,7 +7,7 @@ import { validateDeltaRecheckRecord } from "./reviewRound.js";
7
7
  export async function assessDeltaRecheck(input) {
8
8
  const { repositoryPaths, previousRound, candidate, git } = input;
9
9
  if (previousRound.status !== "completed"
10
- || previousRound.reviewerTurnId === undefined
10
+ || previousRound.reviewerRunId === undefined
11
11
  || (previousRound.scope ?? "work-item") !== "task") {
12
12
  return {
13
13
  kind: "ineligible",
@@ -129,8 +129,8 @@ export function buildDeltaRecheckDispatchContext(input) {
129
129
  " semantic change, evidence doubt). This is the safe default.",
130
130
  "Yui verified only the technical delta boundary; a Task-control Agent selected this mode.",
131
131
  `Previous accepted ReviewRound: ${previousRound.id}@${record.previousBaseCommit}`,
132
- `Previous Reviewer Turn: ${previousRound.reviewerTurnId ?? "unavailable"}`,
133
- "Read that exact source Turn's original result from the frozen Context Snapshot.",
132
+ `Previous Reviewer AgentRun: ${previousRound.reviewerRunId ?? "unavailable"}`,
133
+ "Read that exact source AgentRun's original result from the frozen Context Snapshot.",
134
134
  ...(round.taskCandidate?.projects.map((project) => {
135
135
  const previous = previousRound.taskCandidate?.projects
136
136
  .find((entry) => entry.projectId === project.projectId)?.commit;
@@ -1,29 +1,29 @@
1
- export function isCompletedReviewExecutionFromTurns(round, turns) {
2
- if (round.status !== "completed" || round.reviewerTurnId === undefined)
1
+ export function isCompletedReviewExecutionFromRuns(round, runs) {
2
+ if (round.status !== "completed" || round.reviewerRunId === undefined)
3
3
  return false;
4
- const turn = turns.find(({ id }) => id === round.reviewerTurnId);
5
- return turn !== undefined
6
- && turn.status === "completed"
7
- && turn.result !== undefined
8
- && turn.purpose === "review"
9
- && turn.taskId === round.taskId
10
- && turn.reviewRoundId === round.id
11
- && turn.roleName === round.reviewerRoleName
12
- && turn.executionGroupId === undefined
13
- && turn.effective.reviewBaseCommit === round.reviewBaseCommit;
4
+ const run = runs.find(({ id }) => id === round.reviewerRunId);
5
+ return run !== undefined
6
+ && run.status === "completed"
7
+ && run.result !== undefined
8
+ && run.purpose === "review"
9
+ && run.taskId === round.taskId
10
+ && run.reviewRoundId === round.id
11
+ && run.roleName === round.reviewerRoleName
12
+ && run.executionGroupId === undefined
13
+ && run.effective.reviewBaseCommit === round.reviewBaseCommit;
14
14
  }
15
15
  /**
16
- * Whether a Task-final ReviewRound has one exact completed main Reviewer Turn.
16
+ * Whether a Task-final ReviewRound has one exact completed main Reviewer AgentRun.
17
17
  * This is structural evidence only and never means the Leader accepted it.
18
18
  */
19
19
  export function isCompletedTaskReviewEvidence(store, round) {
20
- return isCompletedTaskReviewEvidenceFromTurns(round, store.listTurns(round.taskId));
20
+ return isCompletedTaskReviewEvidenceFromRuns(round, store.listRuns(round.taskId));
21
21
  }
22
- export function isCompletedTaskReviewEvidenceFromTurns(round, turns) {
22
+ export function isCompletedTaskReviewEvidenceFromRuns(round, runs) {
23
23
  if ((round.scope ?? "work-item") !== "task"
24
24
  || round.taskCandidate === undefined
25
25
  || round.taskCandidate.projects.length === 0) {
26
26
  return false;
27
27
  }
28
- return isCompletedReviewExecutionFromTurns(round, turns);
28
+ return isCompletedReviewExecutionFromRuns(round, runs);
29
29
  }
@@ -3,7 +3,7 @@ import { isCompletedTaskReviewEvidence } from "./reviewAcceptance.js";
3
3
  import { projectReviewerAvailability } from "./reviewerAvailability.js";
4
4
  /** Read-only facts for Leader judgment; this projection never blocks an action. */
5
5
  export function projectReviewDecision(input) {
6
- const { store, task, roles, turns, rounds, reviewConfig, currentCandidate } = input;
6
+ const { store, task, roles, runs, rounds, reviewConfig, currentCandidate } = input;
7
7
  const taskRounds = rounds.filter((round) => (round.scope ?? "work-item") === "task");
8
8
  const accepted = [...taskRounds]
9
9
  .filter((round) => isCompletedTaskReviewEvidence(store, round))
@@ -20,15 +20,15 @@ export function projectReviewDecision(input) {
20
20
  const activeReviews = taskRounds
21
21
  .filter((round) => (round.status === "pending" || round.status === "running"))
22
22
  .map((round) => {
23
- const activeTurn = turns.find((turn) => (turn.status === "active" && turn.reviewRoundId === round.id));
24
- const workspaceRoot = round.workspace?.root ?? activeTurn?.workspace?.root;
23
+ const activeRun = runs.find((run) => (run.status === "active" && run.reviewRoundId === round.id));
24
+ const workspaceRoot = round.workspace?.root ?? activeRun?.workspace?.root;
25
25
  return {
26
26
  reviewRoundId: round.id,
27
27
  reviewerRoleName: round.reviewerRoleName,
28
28
  mode: round.deltaRecheck === undefined ? "full" : "delta-recheck",
29
29
  status: round.status,
30
- ...(activeTurn === undefined ? {} : { activeTurnId: activeTurn.id }),
31
- startedAt: activeTurn?.createdAt ?? round.createdAt,
30
+ ...(activeRun === undefined ? {} : { activeRunId: activeRun.id }),
31
+ startedAt: activeRun?.createdAt ?? round.createdAt,
32
32
  frozenCandidate: round.taskCandidate ?? null,
33
33
  candidateRelation: candidateRelation(round.taskCandidate ?? null, currentCandidate),
34
34
  ...(workspaceRoot === undefined ? {} : { workspaceRoot })
@@ -62,9 +62,9 @@ export function projectReviewDecision(input) {
62
62
  reviewerRoleName,
63
63
  status: "busy",
64
64
  phase: availability.phase,
65
- ...(availability.activeTurnId === undefined
65
+ ...(availability.activeRunId === undefined
66
66
  ? {}
67
- : { activeTurnId: availability.activeTurnId }),
67
+ : { activeRunId: availability.activeRunId }),
68
68
  ...(availability.activeReviewRoundId === undefined
69
69
  ? {}
70
70
  : { activeReviewRoundId: availability.activeReviewRoundId }),