@zq-silk/yui 0.15.11 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (280) hide show
  1. package/ARCHITECTURE.md +11 -6
  2. package/ARCHITECTURE.zh-CN.md +8 -4
  3. package/README.md +13 -5
  4. package/dist/agentRun/agentRun.js +3 -0
  5. package/dist/artifacts/artifactCommitLock.js +16 -55
  6. package/dist/artifacts/taskArtifactRepository.js +3 -3
  7. package/dist/cli/agentConfigurationPicker.js +1 -1
  8. package/dist/cli/commandCatalog.js +92 -48
  9. package/dist/cli/completion.js +18 -18
  10. package/dist/cli/completionWizard.js +1 -1
  11. package/dist/cli/dynamicCompletion.js +1 -1
  12. package/dist/cli/interactionPolicy.js +11 -3
  13. package/dist/cli/invocationAuthority.js +64 -0
  14. package/dist/cli/managedDiagnostics.js +2 -2
  15. package/dist/cli/parseRepeatable.js +35 -0
  16. package/dist/cli/updatePorts.js +26 -5
  17. package/dist/cli.js +1303 -1235
  18. package/dist/commands/agentCommands.js +4 -4
  19. package/dist/commands/capabilityCommands.js +1 -1
  20. package/dist/commands/configCommands.js +6 -57
  21. package/dist/commands/configOverview.js +2 -2
  22. package/dist/commands/durableJobCommands.js +12 -6
  23. package/dist/commands/executionAuditCommands.js +10 -0
  24. package/dist/commands/globalRoleCommands.js +78 -29
  25. package/dist/commands/grantCommands.js +0 -3
  26. package/dist/commands/jobCommands.js +1 -4
  27. package/dist/commands/operatorCommands.js +3 -3
  28. package/dist/commands/projectCommands.js +45 -25
  29. package/dist/commands/resourcesCommands.js +10 -40
  30. package/dist/commands/roleConfiguration.js +2 -6
  31. package/dist/commands/taskActivationCommands.js +4 -8
  32. package/dist/commands/taskAgentCapabilities.js +9 -0
  33. package/dist/commands/taskCommandSupport.js +155 -0
  34. package/dist/commands/taskCommandTypes.js +1 -0
  35. package/dist/commands/taskCommands.js +355 -732
  36. package/dist/commands/taskCompletionGate.js +1 -1
  37. package/dist/commands/taskExecutionCommands.js +1 -1
  38. package/dist/commands/taskFactCommands.js +325 -0
  39. package/dist/commands/taskInputCommands.js +12 -42
  40. package/dist/commands/taskIntegrationCommands.js +20 -58
  41. package/dist/commands/taskNextActionCommand.js +7 -6
  42. package/dist/commands/taskPublicationAdoptCommand.js +127 -0
  43. package/dist/commands/taskPublicationCommands.js +12 -3
  44. package/dist/commands/taskPublicationVerifyCommand.js +24 -40
  45. package/dist/commands/taskRemoteDeliveryCommand.js +5 -86
  46. package/dist/commands/taskUpstreamCommands.js +19 -8
  47. package/dist/commands/workflowCommands.js +6 -1
  48. package/dist/completion/completionInstaller.js +26 -26
  49. package/dist/completion/completionState.js +26 -26
  50. package/dist/completion/fileCompletionManager.js +6 -11
  51. package/dist/config/configCatalog.js +0 -2
  52. package/dist/config/timeZone.js +14 -0
  53. package/dist/config/yuiConfig.js +0 -29
  54. package/dist/context/runContextPack.js +5 -3
  55. package/dist/context/taskCatalog.js +185 -0
  56. package/dist/context/taskContext.js +152 -151
  57. package/dist/context/wakeRunReferences.js +11 -0
  58. package/dist/controller/agentCapabilities.js +58 -0
  59. package/dist/controller/agentRuntimeObserver.js +6 -11
  60. package/dist/controller/clientRuntime.js +22 -14
  61. package/dist/controller/controller.js +20 -24
  62. package/dist/controller/fileSchedulerStoreAdapter.js +191 -225
  63. package/dist/controller/globalInputDelivery.js +13 -0
  64. package/dist/controller/jobClient.js +4 -4
  65. package/dist/controller/jobControl.js +60 -25
  66. package/dist/controller/jobSupervisor.js +4 -4
  67. package/dist/controller/providerRetryAdmission.js +100 -0
  68. package/dist/controller/providerRetryDelivery.js +218 -0
  69. package/dist/controller/runtime.js +85 -43
  70. package/dist/controller/runtimeEventProcessor.js +0 -5
  71. package/dist/controller/sessionOwnerReconciliation.js +5 -0
  72. package/dist/controller/taskAgentError.js +35 -0
  73. package/dist/coordination/workMailboxQueue.js +2 -24
  74. package/dist/core/controllerClient.js +2 -2
  75. package/dist/core/fileLockOwner.js +74 -0
  76. package/dist/decision/decision.js +17 -0
  77. package/dist/doctor/doctor.js +18 -9
  78. package/dist/event/taskEvent.js +12 -0
  79. package/dist/execution/workItemExecutionProjection.js +2 -2
  80. package/dist/executor/agentCapabilityConfig.js +43 -0
  81. package/dist/executor/agentConfigurationCatalog.js +49 -11
  82. package/dist/executor/agentExecutor.js +5 -8
  83. package/dist/executor/fileRoleLaunchPlanner.js +11 -53
  84. package/dist/executor/taskAgentCapabilities.js +64 -0
  85. package/dist/integration/deliveryObligation.js +1 -69
  86. package/dist/integration/gitIntegrationService.js +79 -90
  87. package/dist/integration/integrationAttempt.js +3 -14
  88. package/dist/integration/integrationSourceApplication.js +4 -12
  89. package/dist/integration/manifestTags.js +2 -2
  90. package/dist/job/durableJob.js +4 -25
  91. package/dist/job/jobAssignmentScope.js +22 -0
  92. package/dist/job/jobOperation.js +16 -0
  93. package/dist/job/jobRunner.js +2 -1
  94. package/dist/job/stepDirectory.js +15 -0
  95. package/dist/kernel/builtinCapabilities.js +15 -11
  96. package/dist/kernel/kernelPorts.js +1 -17
  97. package/dist/lifecycle/exactRunTerminalization.js +5 -10
  98. package/dist/message/globalProviderRetry.js +15 -0
  99. package/dist/message/inputControlResolution.js +3 -4
  100. package/dist/message/message.js +17 -20
  101. package/dist/message/messageContinuation.js +1 -1
  102. package/dist/milestone/milestone.js +11 -0
  103. package/dist/observability/executionAudit.js +19 -0
  104. package/dist/observability/orchestrationMetrics.js +24 -1
  105. package/dist/observability/runtimeIdentity.js +1 -17
  106. package/dist/output/agentConfigurationPresentation.js +2 -0
  107. package/dist/output/timePresentation.js +1 -14
  108. package/dist/plugins/pluginService.js +12 -4
  109. package/dist/release/releaseWorkflowPorts.js +45 -96
  110. package/dist/release/runtimeRelease.js +9 -15
  111. package/dist/repository/gitWorkspace.js +371 -105
  112. package/dist/repository/projectMaintenanceLock.js +98 -82
  113. package/dist/repository/taskBaseFreshness.js +1 -1
  114. package/dist/repository/taskWorkspaceCoordinator.js +82 -144
  115. package/dist/repository/taskWorkspacePreparer.js +113 -47
  116. package/dist/repository/workspaceCleanupInspection.js +187 -0
  117. package/dist/resources/autoResourceGc.js +4 -77
  118. package/dist/resources/liveReferences.js +7 -54
  119. package/dist/resources/resourceDiscovery.js +5 -118
  120. package/dist/resources/resourceGc.js +233 -181
  121. package/dist/resources/resourceRegistrar.js +5 -4
  122. package/dist/resources/resourceRegistry.js +3 -37
  123. package/dist/resources/resourceRegistryStore.js +13 -36
  124. package/dist/resources/sqliteResourceRegistry.js +43 -26
  125. package/dist/review/deltaRecheck.js +1 -1
  126. package/dist/review/reviewAcceptance.js +1 -1
  127. package/dist/review/reviewDecision.js +1 -1
  128. package/dist/review/reviewRound.js +8 -7
  129. package/dist/review/taskFinalReviewContractResolution.js +1 -1
  130. package/dist/runtime/acpProtocol.js +4 -51
  131. package/dist/runtime/acpSession.js +10 -75
  132. package/dist/runtime/acpSessionConfiguration.js +1 -8
  133. package/dist/runtime/agentEndpoint.js +3 -2
  134. package/dist/runtime/agentError.js +5 -3
  135. package/dist/runtime/agentFailureContext.js +43 -0
  136. package/dist/runtime/agentHost.js +42 -25
  137. package/dist/runtime/builtinAgentErrorMappers.js +91 -0
  138. package/dist/runtime/codexAppServerRuntime.js +34 -3
  139. package/dist/runtime/codexInteractiveHost.js +2 -2
  140. package/dist/runtime/index.js +0 -1
  141. package/dist/runtime/managedCaller.js +21 -1
  142. package/dist/runtime/providerControl.js +5 -1
  143. package/dist/runtime/providerErrors.js +38 -0
  144. package/dist/runtime/providerRetry.js +198 -0
  145. package/dist/runtime/providerRuntimeIdentity.js +28 -2
  146. package/dist/runtime/runtimeObservation.js +0 -5
  147. package/dist/runtime/runtimeSessionCandidate.js +2 -3
  148. package/dist/runtime/sessionOwnerIdentity.js +1 -1
  149. package/dist/runtime/sessionTokenMetrics.js +15 -5
  150. package/dist/runtime/structuredProviderHost.js +10 -43
  151. package/dist/runtime/taskUsageMetrics.js +275 -0
  152. package/dist/runtime/tmuxAdapters.js +6 -8
  153. package/dist/scheduler/activeRoleRunDelivery.js +19 -7
  154. package/dist/scheduler/leaderWakeupProcessor.js +24 -6
  155. package/dist/scheduler/operatorEvent.js +12 -20
  156. package/dist/scheduler/operatorInputNotificationProcessor.js +1 -1
  157. package/dist/scheduler/ports.js +5 -8
  158. package/dist/scheduler/roleRunLiveness.js +4 -4
  159. package/dist/scheduler/roleRunStall.js +16 -23
  160. package/dist/scheduler/taskExecutionProjection.js +49 -36
  161. package/dist/scheduler/taskObservabilityProjection.js +6 -45
  162. package/dist/scheduler/taskWake.js +5 -10
  163. package/dist/scheduler/wakeReason.js +2 -0
  164. package/dist/scheduler/wakeupQueue.js +2 -4
  165. package/dist/setup/setupCommand.js +1 -1
  166. package/dist/storage/contextRecords.js +106 -0
  167. package/dist/storage/currentTaskStore.js +1 -1
  168. package/dist/storage/homeLayout.js +13 -17
  169. package/dist/storage/migrations/agentFailureContext.js +22 -0
  170. package/dist/storage/migrations/currentInputContract.js +86 -0
  171. package/dist/storage/migrations/currentRuntimeContract.js +228 -0
  172. package/dist/storage/migrations/historicalVerificationPlan.js +35 -0
  173. package/dist/storage/migrations/integrationContinuation.js +5 -4
  174. package/dist/storage/migrations/narrowAgentFailureContext.js +65 -0
  175. package/dist/storage/migrations/notificationOnlyWakes.js +74 -0
  176. package/dist/storage/migrations/verificationPlanV1.js +162 -0
  177. package/dist/storage/migrations/verificationPolicy.js +74 -0
  178. package/dist/storage/migrations/workItemHistory.js +46 -0
  179. package/dist/storage/persistenceWorker.js +12 -4
  180. package/dist/storage/recordValidation.js +87 -0
  181. package/dist/storage/sqliteSchema.js +173 -1
  182. package/dist/storage/sqliteStore.js +172 -161
  183. package/dist/storage/storageSchema.js +2 -14
  184. package/dist/storage/storageVersions.js +1 -1
  185. package/dist/storage/storeRpc.js +16 -7
  186. package/dist/storage/taskCatalog.js +123 -0
  187. package/dist/storage/taskStore.js +5 -7
  188. package/dist/storage/upgrade/upgradeOrchestrator.js +25 -37
  189. package/dist/task/archiveDiagnostics.js +1 -0
  190. package/dist/task/archivePreflight.js +124 -0
  191. package/dist/task/completionReadiness.js +5 -24
  192. package/dist/task/draftPlan.js +0 -53
  193. package/dist/task/nextAction.js +7 -13
  194. package/dist/task/publicationAdoption.js +56 -0
  195. package/dist/task/publicationReference.js +10 -0
  196. package/dist/task/remoteDelivery.js +31 -16
  197. package/dist/task/remoteDeliveryService.js +89 -0
  198. package/dist/task/taskActivation.js +2 -25
  199. package/dist/task/taskActivationService.js +0 -2
  200. package/dist/task/taskRecordReference.js +0 -1
  201. package/dist/task/taskSubmission.js +3 -14
  202. package/dist/telemetry/sqliteTelemetryBatch.js +29 -0
  203. package/dist/telemetry/sqliteTelemetryStore.js +49 -49
  204. package/dist/telemetry/telemetryWiring.js +4 -3
  205. package/dist/tmux/tmuxManager.js +29 -20
  206. package/dist/verification/gateArtifact.js +15 -24
  207. package/dist/verification/gateArtifactStore.js +14 -7
  208. package/dist/verification/verificationGateService.js +29 -88
  209. package/dist/verification/verificationPlan.js +27 -72
  210. package/dist/web/assets/client/app.js +92 -17
  211. package/dist/web/assets/client/components.js +55 -13
  212. package/dist/web/assets/client/i18n.js +72 -4
  213. package/dist/web/assets/client/taskSurface.js +6 -5
  214. package/dist/web/assets/client/view.js +35 -7
  215. package/dist/web/assets/shell.js +6 -0
  216. package/dist/web/assets/styles/layout.js +7 -0
  217. package/dist/web/webServer.js +12 -3
  218. package/dist/web/webSnapshot.js +14 -85
  219. package/dist/web/webTaskSurface.js +17 -46
  220. package/dist/workItem/workItem.js +1 -12
  221. package/dist/workspace/cleanupInspection.js +63 -0
  222. package/dist/workspace/workItemChangeSetManager.js +110 -50
  223. package/docs/agent-result-consumption.md +4 -0
  224. package/docs/agent-result-consumption.zh-CN.md +3 -0
  225. package/docs/agent-runtime-drivers.md +7 -0
  226. package/docs/agent-runtime-drivers.zh-CN.md +5 -0
  227. package/docs/architecture/README.md +2 -0
  228. package/docs/architecture/README.zh-CN.md +3 -1
  229. package/docs/architecture/capabilities-and-resources.md +42 -5
  230. package/docs/architecture/capabilities-and-resources.zh-CN.md +33 -3
  231. package/docs/managed-turn-and-session-runtime.md +121 -1
  232. package/docs/managed-turn-and-session-runtime.zh-CN.md +96 -2
  233. package/docs/observability/README.md +62 -0
  234. package/docs/observability/README.zh-CN.md +47 -0
  235. package/docs/plugin-sdk.md +4 -2
  236. package/docs/project-refresh.md +77 -0
  237. package/docs/project-refresh.zh-CN.md +59 -0
  238. package/docs/provider-retry.md +70 -0
  239. package/docs/provider-runtime.md +3 -1
  240. package/docs/provider-runtime.zh-CN.md +4 -2
  241. package/docs/release-workflow.md +190 -7
  242. package/docs/release-workflow.zh-CN.md +139 -5
  243. package/docs/roles-and-configuration.md +29 -0
  244. package/docs/roles-and-configuration.zh-CN.md +21 -0
  245. package/docs/sqlite-control-plane-design.md +16 -3
  246. package/docs/sqlite-control-plane-design.zh-CN.md +13 -2
  247. package/docs/task-dag-semantics.md +3 -3
  248. package/docs/task-dag-semantics.zh-CN.md +2 -2
  249. package/docs/task-delivery.md +223 -17
  250. package/docs/task-delivery.zh-CN.md +171 -14
  251. package/docs/task-discovery.md +101 -0
  252. package/docs/task-discovery.zh-CN.md +85 -0
  253. package/docs/testing/verification-levels.md +161 -9
  254. package/docs/testing/verification-levels.zh-CN.md +116 -9
  255. package/i18n/README.zh-CN.md +13 -7
  256. package/package.json +1 -1
  257. package/skills/yui-leader/SKILL.md +5 -0
  258. package/skills/yui-leader/references/execution.md +3 -2
  259. package/skills/yui-leader/references/integration.md +9 -5
  260. package/skills/yui-leader/references/planning.md +9 -3
  261. package/skills/yui-operator/SKILL.md +24 -8
  262. package/skills/yui-reviewer/SKILL.md +4 -0
  263. package/skills/yui-runtime/SKILL.md +17 -2
  264. package/skills/yui-runtime/references/publication.md +26 -4
  265. package/skills/yui-runtime/references/recovery.md +71 -0
  266. package/dist/agent/agentRegistry.js +0 -10
  267. package/dist/agentRun/runIdentity.js +0 -22
  268. package/dist/commands/deliveryGuardPreflight.js +0 -30
  269. package/dist/commands/taskIntegrationQueueCommands.js +0 -226
  270. package/dist/commands/taskOverviewCommand.js +0 -376
  271. package/dist/completion/completionWizard.js +0 -125
  272. package/dist/context/dispatchContext.js +0 -110
  273. package/dist/context/wakeNotification.js +0 -144
  274. package/dist/integration/integrationCheckEvidenceReuse.js +0 -53
  275. package/dist/integration/integrationQueueEntry.js +0 -191
  276. package/dist/integration/integrationQueueService.js +0 -810
  277. package/dist/release/fakeReleasePorts.js +0 -55
  278. package/dist/runtime/sessionOwnerRegistry.js +0 -137
  279. package/dist/task/deliveryGuard.js +0 -226
  280. /package/dist/{commands/taskActor.js → task/taskAuthority.js} +0 -0
@@ -16,23 +16,11 @@ export class StorageSchemaError extends Error {
16
16
  /**
17
17
  * Inspect the one authoritative SQLite migration head without changing it.
18
18
  *
19
- * `schema.json` and `state.json` are recognized only to prevent setup from
20
- * overwriting an old Home whose SQLite authority is missing. They are not
21
- * version authorities for current Homes.
19
+ * Extra files never override the SQLite authority. A missing database in a
20
+ * non-empty Home is unverified, not permission to initialize over its evidence.
22
21
  */
23
22
  export function inspectStorageSchema(rootDir) {
24
23
  const databasePath = join(rootDir, CURRENT_DATABASE_FILENAME);
25
- const hasPreBaselineEvidence = existsSync(join(rootDir, "schema.json")) || existsSync(join(rootDir, "state.json"));
26
- if (hasPreBaselineEvidence) {
27
- return {
28
- status: "unsupported",
29
- direction: "older",
30
- currentVersion: 0,
31
- latestVersion: CURRENT_STORAGE_VERSION,
32
- minimumSupportedVersion: MIN_SUPPORTED_STORAGE_VERSION,
33
- databasePath
34
- };
35
- }
36
24
  if (!existsSync(databasePath)) {
37
25
  if (existsSync(rootDir)) {
38
26
  try {
@@ -13,4 +13,4 @@
13
13
  * intermediate Yui releases.
14
14
  */
15
15
  export const MIN_SUPPORTED_STORAGE_VERSION = 1;
16
- export const CURRENT_STORAGE_VERSION = 25;
16
+ export const CURRENT_STORAGE_VERSION = 37;
@@ -8,7 +8,8 @@
8
8
  *
9
9
  * - {@link AsyncTaskStore} ........ the async counterpart to `TaskStore`
10
10
  * (design §6): every method returns a promise; the worker owns the
11
- * `SqliteTaskStore` connection, the main thread never touches the db.
11
+ * worker-side connection. The Controller's separate synchronous scheduler
12
+ * connection is not routed through this interface.
12
13
  * - {@link AsyncTaskStoreClient} .. the client: serializes requests, applies
13
14
  * the storage idempotency/dialect rules on top of the shared bounded RPC
14
15
  * (outbox §5.4, `AbortSignal` cancellation §3.1, restart + replay §3.1).
@@ -16,8 +17,8 @@
16
17
  * `transactionAsync` ships an ordered batch that the worker runs inside one
17
18
  * `BEGIN IMMEDIATE … COMMIT` (§3.2).
18
19
  *
19
- * SQLite is the only product Store. `YUI_STORE_WORKER` controls whether its
20
- * connection lives in a Worker Thread; it does not select another authority.
20
+ * SQLite is the only product Store. `YUI_STORE_WORKER` selects the observer
21
+ * execution boundary; it does not select another authority.
21
22
  */
22
23
  import { BoundedRpcClient, nextRequestId } from "../core/boundedRpc.js";
23
24
  import { StorageCancelledError, StorageConflictError, StorageRecordError } from "./taskStore.js";
@@ -51,6 +52,12 @@ const READ_ONLY_STORE_METHODS = new Set([
51
52
  "getTask",
52
53
  "readNextActionFacts",
53
54
  "readCompletionReadinessFacts",
55
+ "queryContextRecords",
56
+ "latestEventSequence",
57
+ "listEventsByType",
58
+ "listTaskRunWorkspaceBases",
59
+ "contextInputReferences",
60
+ "listActiveRuns",
54
61
  "listActiveTaskIds",
55
62
  "listPlanningDraftTaskIds",
56
63
  "listPendingActivationRequestTaskIds",
@@ -72,6 +79,7 @@ const READ_ONLY_STORE_METHODS = new Set([
72
79
  "getRoleSessionSet",
73
80
  "getTaskRoleSessionSet",
74
81
  "listRoleSessionSets",
82
+ "listProviderRetrySessions",
75
83
  "listRuntimeSessionCandidates",
76
84
  "getRoleSession",
77
85
  "getWorkItem",
@@ -167,12 +175,8 @@ function storageProtocol(home, options) {
167
175
  * RPCs) plus {@link transactionAsync}, {@link invokeObserver}, and {@link close}.
168
176
  */
169
177
  export class AsyncTaskStoreClient {
170
- #home;
171
- #options;
172
178
  #rpc;
173
179
  constructor(home, options = {}) {
174
- this.#home = home;
175
- this.#options = options;
176
180
  this.#rpc = new BoundedRpcClient(storageProtocol(home, options), {
177
181
  maxInFlight: options.maxInFlight,
178
182
  maxQueue: options.maxQueue,
@@ -217,6 +221,11 @@ export class AsyncTaskStoreClient {
217
221
  close() {
218
222
  return this.#rpc.close();
219
223
  }
224
+ /** Best-effort diagnostic batch on this worker, without a Task outbox entry. */
225
+ async flushTelemetry(entries, runCap) {
226
+ const requestId = nextRequestId();
227
+ await this.#rpc.send(requestId, { kind: "telemetry", requestId, entries, runCap });
228
+ }
220
229
  /** Currently in-flight requests (metrics/tests). */
221
230
  get inFlight() {
222
231
  return this.#rpc.inFlight;
@@ -0,0 +1,123 @@
1
+ /** Current facts, not another persisted projection or execution status. */
2
+ export const CATALOG_ATTENTION = [
3
+ "openInputs", "pendingOperations", "unknownOperations", "executionSignals"
4
+ ];
5
+ /**
6
+ * All filtering/counting stays in SQLite. Only page-sized rows and fixed
7
+ * attention samples cross into JS; no Task/Run/Message/Event body is decoded.
8
+ * The existing catalog and record tables remain the only stored authorities.
9
+ */
10
+ export function queryTaskCatalog(db, query) {
11
+ const scope = ["1=1"];
12
+ const params = { limit: query.limit };
13
+ if (query.taskId !== undefined) {
14
+ scope.push("c.task_id = @taskId");
15
+ params.taskId = query.taskId;
16
+ }
17
+ if (!query.all)
18
+ scope.push("c.status <> 'archived'");
19
+ const match = ["1=1"];
20
+ if (query.status !== undefined) {
21
+ match.push("status = @status");
22
+ params.status = query.status;
23
+ }
24
+ if (query.project !== undefined) {
25
+ match.push(`EXISTS (SELECT 1 FROM task_records r, json_each(r.payload, '$.projectBindings') p
26
+ WHERE r.task_id = facts.id AND json_extract(p.value, '$.projectId') = @project)`);
27
+ params.project = query.project;
28
+ }
29
+ if (query.search) {
30
+ match.push(`EXISTS (SELECT 1 FROM task_records r WHERE r.task_id = facts.id AND
31
+ (instr(lower(json_extract(r.payload, '$.title')), lower(@search)) > 0
32
+ OR instr(lower(r.task_id), lower(@search)) > 0
33
+ OR EXISTS (SELECT 1 FROM json_each(r.payload, '$.tags') tag
34
+ WHERE instr(lower(tag.value), lower(@search)) > 0)
35
+ OR EXISTS (SELECT 1 FROM json_each(r.payload, '$.projectBindings') b
36
+ JOIN projects p ON p.id = json_extract(b.value, '$.projectId')
37
+ WHERE instr(lower(json_extract(p.payload, '$.name')), lower(@search)) > 0)))`);
38
+ params.search = query.search;
39
+ }
40
+ if (query.attention !== undefined)
41
+ match.push(`${query.attention} > 0`);
42
+ const position = (name, op) => {
43
+ const value = query[name];
44
+ if (value === undefined)
45
+ return;
46
+ params[`${name}Date`] = value.createdAt;
47
+ params[`${name}Id`] = value.id;
48
+ match.push(`(createdAt, id) ${op} (@${name}Date, @${name}Id)`);
49
+ };
50
+ position("through", "<=");
51
+ // A fixed upper key excludes newly-created Tasks on later pages. Updates
52
+ // to mutable filters are current reads, not a snapshot/session protocol.
53
+ const after = [];
54
+ if (query.after !== undefined) {
55
+ params.afterDate = query.after.createdAt;
56
+ params.afterId = query.after.id;
57
+ after.push("(createdAt, id) > (@afterDate, @afterId)");
58
+ }
59
+ const count = (table, condition) => `(SELECT count(*) FROM ${table} x WHERE x.task_id = c.task_id AND (${condition}))`;
60
+ // Execution signals deliberately preserve raw inspectable conditions:
61
+ // live runs (including unknown admission/identity/stall), unresolved work,
62
+ // integrations, and pending Leader recovery. They are NOT a second
63
+ // classifier for working/blocked/success; precise execution stays in detail.
64
+ // Including all live runs is conservative: an off-page runtime attention
65
+ // cannot disappear just because a lightweight read did not fold its history.
66
+ const cte = `WITH facts AS MATERIALIZED (
67
+ SELECT c.task_id AS id, c.created_at AS createdAt, c.updated_at AS updatedAt, c.status,
68
+ ${count("work_items", "1=1")} AS workItems,
69
+ ${count("turns", "status = 'active'")} AS activeRuns,
70
+ ${count("input_requests", "status = 'open'")} AS openInputs,
71
+ ${count("durable_jobs", "status IN ('queued','running')")} AS pendingOperations,
72
+ ${count("durable_jobs", "status = 'unknown-needs-attention'")} AS unknownOperations,
73
+ CASE WHEN c.status IN ('active','draft') THEN
74
+ ${count("turns", "status = 'active'")} +
75
+ ${count("work_items", "status = 'open' OR (status = 'accepted' AND json_extract(payload, '$.currentExecutionGroupId') IS NOT NULL)")} +
76
+ ${count("integration_attempts", "status IN ('running','validating','blocked','conflicted','failed')")} +
77
+ ${count("review_rounds", "status IN ('pending','running','failed')")} +
78
+ ${count("task_projections", "kind = 'leader-failure' AND payload IS NOT NULL AND payload <> 'null'")} +
79
+ ${count("mailboxes", "role_name = 'leader' AND (json_type(pending) = 'object' OR json_type(processing) = 'object')")}
80
+ ELSE 0 END AS executionSignals
81
+ FROM tasks_catalog c WHERE ${scope.join(" AND ")}
82
+ ), matching AS MATERIALIZED (SELECT * FROM facts WHERE ${match.join(" AND ")})
83
+ SELECT
84
+ (SELECT json_group_array(json_object('status',status,'count',n))
85
+ FROM (SELECT status,count(*) n FROM facts GROUP BY status)) AS counts,
86
+ (SELECT count(*) FROM matching) AS total,
87
+ (SELECT json_object('id',id,'createdAt',createdAt) FROM matching
88
+ ORDER BY createdAt DESC,id DESC LIMIT 1) AS upper,
89
+ ${CATALOG_ATTENTION.map(kind => `
90
+ (SELECT sum(${kind}) FROM facts) AS ${kind}Count,
91
+ (SELECT count(*) FROM facts WHERE ${kind} > 0) AS ${kind}Tasks,
92
+ (SELECT json_group_array(id) FROM (SELECT id FROM facts WHERE ${kind} > 0
93
+ ORDER BY createdAt,id LIMIT 4)) AS ${kind}Ids`).join(",")},
94
+ (SELECT json_group_array(json_object(
95
+ 'id',page.id,'createdAt',page.createdAt,'updatedAt',page.updatedAt,'status',page.status,
96
+ 'title',substr(json_extract(r.payload,'$.title'),1,256),
97
+ 'summary',substr(json_extract(r.brief,'$.leaderSummary'),1,512),
98
+ 'summaryPresent',json(CASE WHEN r.brief IS NULL THEN 'false' ELSE 'true' END),
99
+ 'workItems',page.workItems,'activeRuns',page.activeRuns,
100
+ 'openInputs',page.openInputs,'pendingOperations',page.pendingOperations,
101
+ 'unknownOperations',page.unknownOperations,'executionSignals',page.executionSignals)
102
+ ORDER BY page.createdAt,page.id)
103
+ FROM (SELECT * FROM matching ${after.length ? `WHERE ${after.join(" AND ")}` : ""}
104
+ ORDER BY createdAt,id LIMIT @limit) page
105
+ JOIN task_records r ON r.task_id = page.id) AS rows`;
106
+ const result = db.prepare(cte).get(params);
107
+ const counts = { draft: 0, active: 0, completed: 0, cancelled: 0, archived: 0, total: 0 };
108
+ for (const entry of JSON.parse(result.counts)) {
109
+ counts[entry.status] = entry.count;
110
+ counts.total += entry.count;
111
+ }
112
+ return {
113
+ rows: JSON.parse(result.rows),
114
+ total: result.total,
115
+ through: result.upper === null ? null : JSON.parse(result.upper),
116
+ counts,
117
+ attention: Object.fromEntries(CATALOG_ATTENTION.map(kind => [kind, {
118
+ count: Number(result[`${kind}Count`] ?? 0),
119
+ taskCount: Number(result[`${kind}Tasks`] ?? 0),
120
+ taskIds: JSON.parse(result[`${kind}Ids`])
121
+ }]))
122
+ };
123
+ }
@@ -2,12 +2,12 @@ import { lstatSync, mkdirSync, realpathSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import { basename, dirname, join, resolve } from "node:path";
4
4
  import { isDeepStrictEqual } from "node:util";
5
+ import { resolveTimeZone } from "../config/timeZone.js";
6
+ import { reconciliationIntervalMilliseconds, resolveAgentLaunchInactivityTimeoutSeconds, resolveControllerTaskConcurrency, resolveDeliveryTimeoutSeconds, resolveResourcesGcAutoQuarantine, resolveResourcesGcMode, resolveResourcesQuarantineTtlHours, resolveRuntimeHealth, resolveTelemetryEnabled, resolveTelemetryRunCap, resolveTelemetryTerminalKeep, resolveTmuxBin, resolveTmuxHistoryLimit } from "../config/yuiConfig.js";
5
7
  import { validateCapabilityGrant } from "../grant/capabilityGrant.js";
6
8
  import { validateReleaseWorkflow } from "../release/releaseWorkflow.js";
7
- import { validatePublicationReference } from "../task/publicationReference.js";
8
- import { reconciliationIntervalMilliseconds, resolveAgentLaunchInactivityTimeoutSeconds, resolveControllerTaskConcurrency, resolveDeliveryTimeoutSeconds, resolveLeaderNextActionMode, resolveLeaderSemanticBudgetRuns, resolveResourcesGcAutoQuarantine, resolveResourcesGcMode, resolveResourcesQuarantineTtlHours, resolveRuntimeHealth, resolveTelemetryEnabled, resolveTelemetryRunCap, resolveTelemetryTerminalKeep, resolveTmuxBin, resolveTmuxHistoryLimit } from "../config/yuiConfig.js";
9
- import { resolveTimeZone } from "../output/timePresentation.js";
10
9
  import { validateReviewConfig } from "../review/reviewConfig.js";
10
+ import { validatePublicationReference } from "../task/publicationReference.js";
11
11
  import { validateTaskRecordReference } from "../task/taskRecordReference.js";
12
12
  export const CURRENT_CONFIG_SCHEMA_VERSION = 6;
13
13
  /** Current SQLite payload-family versions owned by this storage boundary. */
@@ -151,12 +151,10 @@ export function validateYuiConfig(config) {
151
151
  "resourcesGcMode",
152
152
  "resourcesGcAutoQuarantine",
153
153
  "review",
154
- "leaderNextActionMode",
155
154
  "runtimeHealth",
156
155
  "controllerTaskConcurrency",
157
156
  "agentLaunchInactivityTimeoutSeconds",
158
157
  "deliveryTimeoutSeconds",
159
- "leaderSemanticBudgetRuns",
160
158
  "resourcesQuarantineTtlHours",
161
159
  "tmuxBin",
162
160
  "tmuxHistoryLimit",
@@ -176,7 +174,6 @@ export function validateYuiConfig(config) {
176
174
  resolveTimeZone(config.timeZone);
177
175
  if (config.review !== undefined)
178
176
  validateReviewConfig(config.review);
179
- resolveLeaderNextActionMode(config.leaderNextActionMode);
180
177
  resolveResourcesGcMode(config.resourcesGcMode);
181
178
  resolveResourcesGcAutoQuarantine(config.resourcesGcAutoQuarantine);
182
179
  resolveResourcesQuarantineTtlHours(config.resourcesQuarantineTtlHours);
@@ -184,7 +181,6 @@ export function validateYuiConfig(config) {
184
181
  resolveControllerTaskConcurrency(config.controllerTaskConcurrency);
185
182
  resolveAgentLaunchInactivityTimeoutSeconds(config.agentLaunchInactivityTimeoutSeconds);
186
183
  resolveDeliveryTimeoutSeconds(config.deliveryTimeoutSeconds);
187
- resolveLeaderSemanticBudgetRuns(config.leaderSemanticBudgetRuns);
188
184
  resolveTmuxBin(config.tmuxBin);
189
185
  resolveTmuxHistoryLimit(config.tmuxHistoryLimit);
190
186
  resolveTelemetryEnabled(config.telemetryEnabled);
@@ -448,6 +444,8 @@ export function storedPublicationReference(value) {
448
444
  fields.push("targetBranch");
449
445
  if (reference.localCommit !== undefined)
450
446
  fields.push("localCommit");
447
+ if (reference.headCommit !== undefined)
448
+ fields.push("headCommit");
451
449
  if (reference.remoteCommit !== undefined)
452
450
  fields.push("remoteCommit");
453
451
  if (reference.evidence !== undefined)
@@ -4,16 +4,14 @@ import Database from "better-sqlite3";
4
4
  import { inspectAgentHostCompatibility, describeAgentHostUpgradeBlockers } from "../../runtime/agentHostCompatibility.js";
5
5
  import { RUNTIME_OBSERVATION_TASK_EVENT, createRuntimeObservation } from "../../runtime/runtimeObservation.js";
6
6
  import { RUNTIME_PROCESS_EXIT_TASK_EVENT, validateRuntimeProcessExitObservation } from "../../runtime/processExitObservation.js";
7
- import { validateAgentProfile } from "../../profile/agentProfile.js";
8
- import { validateRoleSessionSet } from "../../executor/agentExecutor.js";
9
- import { validateReviewRound } from "../../review/reviewRound.js";
10
- import { validateRun } from "../../agentRun/agentRun.js";
11
- import { validateDurableJob } from "../../job/durableJob.js";
12
- import { validateWorkItem } from "../../workItem/workItem.js";
13
7
  import { SqliteTaskStore } from "../sqliteStore.js";
14
8
  import { storageBackupRoot } from "../homeLayout.js";
15
9
  import { preflightUnifyHomeLayout } from "../migrations/unifyHomeLayout.js";
16
10
  import { preflightCollapseWorktreeLayout } from "../migrations/collapseWorktreeLayout.js";
11
+ import { preflightNotificationOnlyWakes } from "../migrations/notificationOnlyWakes.js";
12
+ import { preflightVerificationPolicy } from "../migrations/verificationPolicy.js";
13
+ import { preflightCurrentInputContract } from "../migrations/currentInputContract.js";
14
+ import { preflightCurrentRuntimeContract } from "../migrations/currentRuntimeContract.js";
17
15
  import { migrateSqliteSchema, storageMigrationPlan } from "../sqliteSchema.js";
18
16
  import { CURRENT_DATABASE_FILENAME, inspectStorageSchema } from "../storageSchema.js";
19
17
  import { CURRENT_STORAGE_VERSION, MIN_SUPPORTED_STORAGE_VERSION } from "../storageVersions.js";
@@ -153,7 +151,6 @@ export async function runStorageUpgrade(options) {
153
151
  database.close();
154
152
  }
155
153
  validateCurrentStore(options.home);
156
- rmSync(join(options.home, "schema.json"), { force: true });
157
154
  }
158
155
  catch (error) {
159
156
  const restoration = migrationCommitted
@@ -206,29 +203,8 @@ export async function runStorageUpgrade(options) {
206
203
  function validateCurrentStore(home) {
207
204
  const store = new SqliteTaskStore(home);
208
205
  try {
209
- store.getConfig();
210
- for (const profile of store.listAgentProfiles())
211
- validateAgentProfile(profile);
212
- for (const sessions of store.listGlobalRoleSessionSets()) {
213
- validateRoleSessionSet(sessions);
214
- }
215
- for (const owner of store.listSessionOwners()) {
216
- if (owner.schemaVersion !== 2 || Object.hasOwn(owner, "launchId")) {
217
- throw new Error("Session owner runtime identity is invalid.");
218
- }
219
- }
206
+ store.validateCurrentRecords();
220
207
  for (const taskId of store.listTasks().map(({ id }) => id)) {
221
- for (const job of store.listDurableJobs(taskId))
222
- validateDurableJob(job);
223
- for (const item of store.listWorkItems(taskId))
224
- validateWorkItem(item);
225
- for (const round of store.listReviewRounds(taskId))
226
- validateReviewRound(round);
227
- for (const run of store.listRuns(taskId))
228
- validateRun(run);
229
- for (const sessions of store.listRoleSessionSets(taskId)) {
230
- validateRoleSessionSet(sessions);
231
- }
232
208
  for (const event of store.listEvents(taskId)) {
233
209
  if (event.type === RUNTIME_OBSERVATION_TASK_EVENT) {
234
210
  createRuntimeObservation(JSON.parse(event.payload.observation ?? ""));
@@ -345,16 +321,17 @@ function blocked(classification, stage, message, action) {
345
321
  };
346
322
  }
347
323
  /**
348
- * Run each path-relocating data migration's READ-ONLY preflight against the
324
+ * Run each data migration's registered READ-ONLY preflight against the
349
325
  * current (pre-upgrade) database and, when any finds a blocker, return a
350
326
  * `blocked` result the caller surfaces before touching the Controller or the
351
- * Home. Returns `null` when the plan carries no path-relocating data migration,
327
+ * Home. Returns `null` when the plan carries no registered readiness check,
352
328
  * or when every preflight is clear.
353
329
  *
354
- * Both the 18->19 unify and the 19->20 collapse migrations physically relocate
330
+ * Both the 23->24 unify and the 24->25 collapse migrations physically relocate
355
331
  * managed worktrees and share the same blocker shape (in-flight Job, conflicting
356
332
  * relocation target). When a Home is upgraded across both in one run, their
357
- * blockers are aggregated so the operator sees every readiness problem at once.
333
+ * blockers are aggregated so the operator sees their readiness problems together.
334
+ * Notification retirement separately rejects unsettled execution references.
358
335
  *
359
336
  * The database is opened read-only so the checks cannot mutate the authoritative
360
337
  * store, and the handle is always closed. An unexpected failure to evaluate a
@@ -364,8 +341,11 @@ function blocked(classification, stage, message, action) {
364
341
  function preflightMigrationBlockers(home, plan, classification) {
365
342
  const relocatesUnify = plan.some((step) => step.name === "unify-home-layout");
366
343
  const relocatesCollapse = plan.some((step) => step.name === "collapse-worktree-layout");
367
- // Only meaningful when the plan actually includes a path-relocating migration.
368
- if (!relocatesUnify && !relocatesCollapse)
344
+ const retiresRunWakes = plan.some((step) => step.name === "notification-only-wakes");
345
+ const changesVerification = plan.some(step => step.name === "advisory-and-verification-policy");
346
+ const changesInput = plan.some(step => step.name === "current-input-contract");
347
+ const retiresRuntime = plan.some(step => step.name === "current-verification-and-owner-contract");
348
+ if (!relocatesUnify && !relocatesCollapse && !retiresRunWakes && !changesVerification && !changesInput && !retiresRuntime)
369
349
  return null;
370
350
  let blockers;
371
351
  try {
@@ -379,6 +359,14 @@ function preflightMigrationBlockers(home, plan, classification) {
379
359
  blockers.push(...preflightUnifyHomeLayout(database).blockers);
380
360
  if (relocatesCollapse)
381
361
  blockers.push(...preflightCollapseWorktreeLayout(database).blockers);
362
+ if (retiresRunWakes)
363
+ preflightNotificationOnlyWakes(database);
364
+ if (changesVerification)
365
+ preflightVerificationPolicy(database);
366
+ if (changesInput)
367
+ preflightCurrentInputContract(database);
368
+ if (retiresRuntime)
369
+ preflightCurrentRuntimeContract(database);
382
370
  }
383
371
  finally {
384
372
  database.close();
@@ -386,8 +374,8 @@ function preflightMigrationBlockers(home, plan, classification) {
386
374
  }
387
375
  catch (error) {
388
376
  return {
389
- ...blocked(classification, "in-flight", `The storage migration readiness check could not be completed: ${messageOf(error)}`, "Resolve the reported problem, confirm no Job is queued or running against a managed "
390
- + "workspace, then rerun the upgrade.")
377
+ ...blocked(classification, "in-flight", `The storage migration readiness check could not be completed: ${messageOf(error)}`, "Resolve the named execution or resource boundary, preserving original input and results. "
378
+ + "Confirm the relevant Runs and Jobs are settled, then rerun the upgrade.")
391
379
  };
392
380
  }
393
381
  if (blockers.length === 0)
@@ -122,6 +122,7 @@ export function taskArchiveDiagnostics(store, task) {
122
122
  export function renderArchiveDiagnostics(data) {
123
123
  return [
124
124
  `Archived: ${data.archived}; forced: ${data.forced}; cleanup finished: ${data.cleanupFinished}`,
125
+ `Retained references: ${data.retainedResources.length}; a finished cleanup attempt is not physical resource release evidence.`,
125
126
  ...data.warnings.map(w => `Warning [${w.resource}]: ${w.detail}`),
126
127
  ...data.retainedResources.map(r => `Retained [${r.resource}]: ${r.detail}${r.paths ? ` (${r.paths.join(", ")})` : ""}`)
127
128
  ].join("\n") + "\n";
@@ -0,0 +1,124 @@
1
+ import { readArchiveDelivery } from "../repository/workspaceCleanupInspection.js";
2
+ import { WorkspaceCleanupBlockedError } from "../repository/taskWorkspacePreparer.js";
3
+ import { managedWorkspaceKey } from "../worktree/managedWorkspace.js";
4
+ import { hasRuntimeLifecycleWork, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
5
+ import { cleanupCheckFromError, renderCleanupCheck } from "../workspace/cleanupInspection.js";
6
+ /** Semantic admission facts. Cleanup still has its own resource boundary. */
7
+ export function archiveSettlementChecks(store, task) {
8
+ const checks = [];
9
+ const add = (resource, reason, detail, expected, observed, action) => checks.push({ resource, reason, status: "blocked", detail, expected, observed, sources: [resource], actions: [action] });
10
+ for (const input of store.listInputRequests(task.id).filter(i => i.status === "open")) {
11
+ add(`input:${task.id}/${input.id}`, "unresolved-input", "Open user input is not answered by archive.", "resolved input", input.status, `yui task input show ${task.id}/${input.id}`);
12
+ }
13
+ for (const item of store.listWorkItems(task.id).filter(i => i.status === "open")) {
14
+ add(`work-item:${task.id}/${item.id}`, "owner-unsettled", "WorkItem must be accepted or explicitly retired before archive.", ["accepted", "retired"], item.status, `yui task work show ${task.id}/${item.id}`);
15
+ }
16
+ for (const attempt of store.listIntegrationAttempts(task.id).filter(a => ["running", "blocked", "conflicted", "validating"].includes(a.status))) {
17
+ add(`integration-attempt:${task.id}/${attempt.id}`, "unresolved-integration", "Task has an unresolved Integration Attempt.", "terminal attempt", attempt.status, `yui task integration show ${task.id}/${attempt.id}`);
18
+ }
19
+ for (const job of store.listDurableJobs(task.id).filter(j => ["queued", "running"].includes(j.status) || (j.status === "unknown-needs-attention" && j.acknowledgedAt === undefined))) {
20
+ add(`job:${task.id}/${job.id}`, "active-durable-job", "Task has an active or unacknowledged DurableJob.", "settled job", job.status, `yui job get --task ${task.id} --job ${job.id}`);
21
+ }
22
+ return checks;
23
+ }
24
+ /** Unknown execution is never permission to remove workspaces, even after
25
+ * force admission. These facts are reloaded in the actual force cleanup path.
26
+ */
27
+ export function archiveExecutionChecks(store, taskId) {
28
+ const checks = [];
29
+ const add = (resource, reason, detail, observed, action) => checks.push({ resource, reason, status: "unknown", detail, expected: "settled execution",
30
+ observed, sources: [resource], actions: [action] });
31
+ for (const run of store.listRuns(taskId).filter(r => r.status === "active")) {
32
+ add(`run:${taskId}/${run.id}`, "active-turn", "AgentRun remains active; archive is not stop evidence.", run.status, `yui task run show ${taskId}/${run.id}`);
33
+ }
34
+ for (const job of store.listDurableJobs(taskId).filter(j => ["queued", "running", "unknown-needs-attention"].includes(j.status))) {
35
+ add(`job:${taskId}/${job.id}`, "unresolved-execution", "DurableJob may still hold resources; acknowledgement is not physical exit evidence.", job.status, `yui job get --task ${taskId} --job ${job.id}`);
36
+ }
37
+ for (const role of store.listRoles(taskId)) {
38
+ const resource = `role:${taskId}/${role.name}`;
39
+ const action = `yui task role session inspect ${taskId} ${role.name}`;
40
+ const provider = store.getTaskRoleSessionSet(taskId, role.name)?.providerBinding;
41
+ const mailbox = store.getWorkMailbox({ kind: "role", taskId, roleName: role.name });
42
+ if (provider?.run != null && !["completed", "failed", "cancelled"].includes(provider.run.status)) {
43
+ add(resource, "unresolved-provider-input", "Original provider input outcome is unresolved; do not replay it.", { attemptId: provider.run.attemptId, status: provider.run.status }, action);
44
+ }
45
+ if (mailbox?.processing != null && (provider?.run == null
46
+ || !["completed", "failed", "cancelled"].includes(provider.run.status))) {
47
+ add(resource, "unresolved-mailbox", "Original mailbox claim has no proven terminal; archive does not acknowledge it.", { batchId: mailbox.processing.batchId }, action);
48
+ }
49
+ if (hasRuntimeLifecycleWork(store.getWorkMailbox(runtimeLifecycleTarget({ scope: "task", taskId, roleName: role.name })))) {
50
+ add(resource, "unresolved-runtime-lifecycle", "Role has unsettled runtime lifecycle work.", "pending", action);
51
+ }
52
+ }
53
+ return checks;
54
+ }
55
+ export async function inspectTaskArchive(coordinator, request) {
56
+ const { store, preparer, runtime } = coordinator;
57
+ const task = store.getTask(request.taskId);
58
+ if (task === null)
59
+ throw new Error(`Task not found: ${request.taskId}.`);
60
+ const settlement = archiveSettlementChecks(store, task);
61
+ const delivery = await readArchiveDelivery(store, preparer.git, task, request.force);
62
+ const deliveryChecks = delivery.projects.filter(p => p.codeDelivery !== "none" && (!p.merged || !p.verified))
63
+ .map(p => ({ resource: `delivery:${task.id}/${p.projectId}`, reason: "delivery-coverage", status: "blocked",
64
+ detail: p.reason, expected: { merged: true, verified: true, localCommit: p.expectedLocalCommit },
65
+ observed: { coverage: p.coverage, merged: p.merged, verified: p.verified, localCommit: p.publication?.localCommit ?? null },
66
+ sources: [`task:${task.id}`, ...(p.publication === null ? [] : [`publication:${task.id}/${p.publication.id}`])],
67
+ actions: [`yui task remote-delivery ${task.id}`] }));
68
+ const execution = archiveExecutionChecks(store, task.id);
69
+ const runtimeResource = `runtime:${task.id}`;
70
+ if (runtime.assertTaskPhysicalResourcesReleased === undefined) {
71
+ execution.push(...cleanupCheckFromError(null, runtimeResource, [`task:${task.id}`], [`yui task role status ${task.id}`]));
72
+ }
73
+ else {
74
+ try {
75
+ await runtime.assertTaskPhysicalResourcesReleased(task.id);
76
+ }
77
+ catch (error) {
78
+ execution.push({ resource: runtimeResource, reason: "physical-resources-unreleased", status: "unknown",
79
+ detail: error instanceof WorkspaceCleanupBlockedError ? error.message
80
+ : "Exact physical resource release is not established by the runtime inspection.",
81
+ expected: "proven physical absence",
82
+ observed: error instanceof WorkspaceCleanupBlockedError ? { reason: error.reason, resource: error.resource } : "unavailable",
83
+ sources: [`task:${task.id}`],
84
+ actions: [`yui task role status ${task.id}`, `yui session reconcile --report`] });
85
+ }
86
+ }
87
+ const resources = [];
88
+ for (const workspace of store.listManagedWorkspaces(task.id)) {
89
+ const disposition = workspace.owner.type === "work-item"
90
+ && store.getWorkItem(task.id, workspace.owner.workItemId)?.status === "retired" ? "abandoned" : request.disposition;
91
+ let checks;
92
+ try {
93
+ checks = await preparer.inspectWorkspaceCleanup(workspace, disposition, request.force);
94
+ }
95
+ catch (error) {
96
+ checks = cleanupCheckFromError(error, managedWorkspaceKey(workspace.owner), [managedWorkspaceKey(workspace.owner)], [`yui task show ${task.id}`]);
97
+ }
98
+ resources.push({ resource: managedWorkspaceKey(workspace.owner), owner: workspace.owner,
99
+ status: checks.some(c => c.status === "unknown") ? "unknown" : checks.length > 0 ? "blocked" : "checked",
100
+ checks });
101
+ }
102
+ const eligible = ["completed", "cancelled", "archived"].includes(task.status);
103
+ return { taskId: task.id, observedAt: new Date().toISOString(), disposition: request.disposition, force: request.force,
104
+ readOnly: true, authorizesCleanup: false,
105
+ archive: { status: task.status, eligible, alreadyArchived: task.status === "archived",
106
+ settlement, delivery: deliveryChecks, forceBypassesSettlement: request.force,
107
+ requiresIndependentAuthorization: true },
108
+ cleanup: { execution, resources },
109
+ note: "Current observations only. Execution reloads the checks. Force commits archive first and retains unsafe resources; a finished attempt does not prove resource release." };
110
+ }
111
+ export function renderTaskArchivePreflight(data) {
112
+ return [
113
+ `Archive preflight: ${data.taskId} (${data.disposition}${data.force ? ", force" : ""}); read-only, not authorization`,
114
+ `Task: ${data.archive.status}; terminal eligibility: ${data.archive.eligible}`,
115
+ ...data.archive.settlement.map(c => `Admission${data.force ? " (force preserves)" : ""}: ${renderCleanupCheck(c)}`),
116
+ ...data.archive.delivery.map(c => `Delivery${data.force || data.disposition === "abandoned" ? " (advisory for admission)" : ""}: ${renderCleanupCheck(c)}`),
117
+ ...data.cleanup.execution.map(c => `Cleanup: ${renderCleanupCheck(c)}`),
118
+ ...data.cleanup.resources.flatMap(r => [
119
+ `Workspace [${r.resource}]: ${r.status}`,
120
+ ...r.checks.map(c => ` ${renderCleanupCheck(c)}`)
121
+ ]),
122
+ data.note
123
+ ].join("\n") + "\n";
124
+ }
@@ -17,18 +17,13 @@ export function pendingCompletionMessages(store, taskId) {
17
17
  const ids = new Set(refs.filter(ref => ref.type === "message" && ref.taskId === taskId).map(ref => ref.id));
18
18
  return operationalTaskRecords(store.listMessages(taskId), store.listEvents(taskId), "message")
19
19
  .filter(message => ids.has(message.id)
20
- && (message.kind === "user" || message.kind === "operator") && message.wakePolicy !== "none");
20
+ && (message.kind === "user" || message.kind === "operator"));
21
21
  }
22
22
  const ACTIVE_JOB_STATUSES = new Set([
23
23
  "queued",
24
24
  "running",
25
25
  "unknown-needs-attention"
26
26
  ]);
27
- const UNRESOLVED_INTEGRATION_STATUSES = new Set([
28
- "running",
29
- "blocked",
30
- "validating"
31
- ]);
32
27
  const TERMINAL_REVIEW_STATUSES = new Set(["completed", "failed"]);
33
28
  const TERMINAL_LANE_STATUSES = new Set(["completed", "failed", "skipped"]);
34
29
  export function projectCompletionReadiness(facts) {
@@ -45,7 +40,7 @@ export function projectCompletionReadiness(facts) {
45
40
  }
46
41
  // A pending/running Task-final Review must be resumed or blocked first.
47
42
  for (const round of facts.reviewRounds) {
48
- if ((round.scope ?? "work-item") !== "task")
43
+ if (round.scope !== "task")
49
44
  continue;
50
45
  if (round.status !== "pending" && round.status !== "running")
51
46
  continue;
@@ -103,11 +98,9 @@ export function projectCompletionReadiness(facts) {
103
98
  fix: `yui task integration start ${task.id} --work-item ${delivery.workItemId} --project ${delivery.projectId} --strategy <ff|cherry-pick|merge|manual>`
104
99
  });
105
100
  }
106
- // Current delivery Attempts and any Attempt that may still be writing must
107
- // settle. Historical blocked Attempts remain audit evidence only.
101
+ // Unsettled attempts must be explicitly resolved; terminal history remains
102
+ // evidence and is not a second delivery workflow.
108
103
  for (const integration of facts.integrations) {
109
- if (!UNRESOLVED_INTEGRATION_STATUSES.has(integration.status))
110
- continue;
111
104
  if (!integrationAttemptRequiresSettlement(integration))
112
105
  continue;
113
106
  blockers.push({
@@ -117,18 +110,6 @@ export function projectCompletionReadiness(facts) {
117
110
  fix: `yui task integration continue ${task.id}/${integration.id}`
118
111
  });
119
112
  }
120
- // Legacy queue entries may still launch an Integration and therefore must
121
- // settle even though ChangeSets are no longer delivery authority.
122
- for (const entry of facts.integrationQueueEntries) {
123
- if (entry.status === "committed" || entry.status === "superseded")
124
- continue;
125
- blockers.push({
126
- code: "unsettled-integration-queue-entry",
127
- ref: ref("integration-queue-entry", entry.id),
128
- reason: `Integration queue entry ${entry.id} is ${entry.status}.`,
129
- fix: `settle integration queue entry ${entry.id} (continue or supersede)`
130
- });
131
- }
132
113
  // Terminal child workspaces are cleanup advisories: Task completion is the
133
114
  // semantic delivery boundary, while archive remains the fail-closed resource
134
115
  // reclamation boundary. Missing/non-terminal ownership stays conservative.
@@ -198,7 +179,7 @@ function workspaceCompletionDisposition(facts, taskId, workspace) {
198
179
  }
199
180
  case "integration-attempt": {
200
181
  const integration = facts.integrations.find((entry) => entry.id === owner.integrationAttemptId);
201
- if (integration !== undefined && UNRESOLVED_INTEGRATION_STATUSES.has(integration.status)) {
182
+ if (integration !== undefined && integrationAttemptRequiresSettlement(integration)) {
202
183
  return null;
203
184
  }
204
185
  const value = {