@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
@@ -1,25 +1,4 @@
1
- import { RUNTIME_OBSERVATION_TASK_EVENT } from "../runtime/runtimeObservation.js";
2
1
  import { SYSTEM_LEADER_ROLE } from "../role/systemRoles.js";
3
- /**
4
- * Runtime Task-event types that are emitted with a Role but no Turn in their
5
- * payload. Each one is still only exempted when its `roleName` resolves to the
6
- * Leader — the `runtime.` prefix is never exempted, and a type absent from this
7
- * set keeps disqualifying the Draft.
8
- *
9
- * Turn lifecycle events are deliberately not listed: they use one generic type
10
- * per transition and name the Turn in their payload, so the guard resolves that
11
- * Turn's own purpose instead of trusting the type. Listing invented
12
- * `turn.planning-*` types would have exempted nothing while silently
13
- * disqualifying every Draft that ever held a planning conversation.
14
- */
15
- export const PLANNING_ROLE_EVENT_TYPES = new Set([
16
- "runtime.session-stop-requested",
17
- "runtime.session-termination",
18
- // Written by the liveness/host-exit paths, which admit a Draft planning Turn.
19
- // Its payload carries the Role and embeds the Turn only inside the serialized
20
- // observation, so it is resolved as Role-scoped rather than by that blob.
21
- "runtime.process-exit-observed"
22
- ]);
23
2
  export function assertDraftTaskExecutionFree(store, task) {
24
3
  if (task.status !== "draft") {
25
4
  throw new Error(`Task is not a Draft: ${task.id}/${task.status}.`);
@@ -168,35 +147,3 @@ function firstDraftExecutionFact(store, task) {
168
147
  // of delivery authority. The domain records above own delivery facts.
169
148
  return undefined;
170
149
  }
171
- /**
172
- * Whether one runtime/Turn Task event belongs to this Draft's own planning
173
- * conversation.
174
- *
175
- * The binding the event carries decides this, never its type prefix:
176
- *
177
- * - it names a Turn → that Turn must exist on this Task and be `planning`;
178
- * - it names no Turn → its exact type must be Role-scoped by construction and
179
- * its `roleName` must be the Leader, the only Role a Draft may run.
180
- *
181
- * Everything else stays disqualifying, so an unrecognized type, a foreign Role
182
- * or a Turn reference that cannot be resolved fails closed.
183
- */
184
- function isDraftPlanningEvent(store, task, event) {
185
- // Several emitters write an absent Turn as an empty string, so only a
186
- // non-empty id counts as a reference worth resolving.
187
- const runId = event.payload["runId"];
188
- if (typeof runId === "string" && runId !== "") {
189
- return store.getRun(task.id, runId)?.purpose === "planning";
190
- }
191
- // A canonical runtime observation is the event normal delivery writes for
192
- // session.started/conversation.observed/turn.accepted and every terminal. It
193
- // names its Turn in the same payload field as everything else and was
194
- // resolved above; only an unfenced host-level observation reaches here, where
195
- // the Role still has to prove it. Admitting this one type by its own binding
196
- // is not the same as admitting the `runtime.` prefix.
197
- if (event.type !== RUNTIME_OBSERVATION_TASK_EVENT
198
- && !PLANNING_ROLE_EVENT_TYPES.has(event.type)) {
199
- return false;
200
- }
201
- return event.payload["roleName"] === SYSTEM_LEADER_ROLE;
202
- }
@@ -1,11 +1,11 @@
1
1
  import { createHash } from "node:crypto";
2
+ import { actionableExecutionLaneRecoveries } from "../execution/executionHealth.js";
2
3
  import { governingWorkItemDeliveries, workItemDeliverySettled } from "../integration/deliveryObligation.js";
3
4
  import { isCompletedTaskReviewEvidenceFromRuns } from "../review/reviewAcceptance.js";
4
- import { actionableExecutionLaneRecoveries } from "../execution/executionHealth.js";
5
5
  import { sameTaskFinalReviewContract } from "../review/taskFinalReviewContract.js";
6
6
  import { resolveRecordedTaskFinalReviewContract } from "../review/taskFinalReviewContractResolution.js";
7
- import { draftWorkItemDependencyIssue } from "./draftPlan.js";
8
7
  import { currentWorkItemCandidate, currentWorkItemExecutionGroup, governingWorkItemCandidate } from "../workItem/workItem.js";
8
+ import { draftWorkItemDependencyIssue } from "./draftPlan.js";
9
9
  const OPEN_WORK_ITEM_STATUSES = new Set(["open"]);
10
10
  export function projectNextAction(facts) {
11
11
  const { task } = facts;
@@ -150,7 +150,7 @@ export function projectNextAction(facts) {
150
150
  }
151
151
  return buildAction(facts, {
152
152
  kind: "integrate-work-item",
153
- reason: `Integration ${checkingIntegration.id} awaits check-result consumption (${job?.status ?? "read current Job"}); an empty integration queue does not finalize this direct attempt.`,
153
+ reason: `Integration ${checkingIntegration.id} awaits check-result consumption (${job?.status ?? "read current Job"}); its exact Job must be consumed to finalize the attempt.`,
154
154
  refs,
155
155
  preconditions: [{ fact: "The Integration retains its exact check Job", satisfied: true, ref: refs[0] }],
156
156
  recommendedCommand: `yui task integration continue ${task.id}/${checkingIntegration.id}`
@@ -378,7 +378,7 @@ export function projectNextAction(facts) {
378
378
  }
379
379
  if (deliveryWorkItems.length === 0
380
380
  && !taskFinalReviewRequired(facts)
381
- && !facts.reviewRounds.some((round) => ((round.scope ?? "work-item") === "task"
381
+ && !facts.reviewRounds.some((round) => (round.scope === "task"
382
382
  && (round.status === "pending" || round.status === "running")))) {
383
383
  const reviewAlternative = facts.reviewConfig === null
384
384
  ? []
@@ -675,7 +675,6 @@ export function durableStateFingerprint(facts) {
675
675
  ...facts.workItems.map((item) => `work:${item.id}:${item.status}:${item.revision}:${item.updatedAt}`),
676
676
  ...facts.changeSets.map((changeSet) => `change-set:${changeSet.id}:${changeSet.headCommit}`),
677
677
  ...facts.integrations.map((attempt) => `integration:${attempt.id}:${attempt.status}:${attempt.updatedAt}`),
678
- ...facts.integrationQueueEntries.map((entry) => `integration-queue:${entry.id}:${entry.status}:${entry.updatedAt}`),
679
678
  ...facts.reviewRounds.map((round) => `review:${round.id}:${round.status}:${round.endedAt ?? ""}`)
680
679
  ];
681
680
  return createHash("sha256").update(parts.join("\n")).digest("hex");
@@ -819,22 +818,17 @@ function taskFinalReviewRole(facts) {
819
818
  function ref(kind, id) {
820
819
  return { kind, id };
821
820
  }
822
- function latestFailedReviewFor(rounds, workItemId) {
823
- return [...rounds]
824
- .reverse()
825
- .find((round) => round.workItemId === workItemId && round.status === "failed");
826
- }
827
821
  function latestTaskFinalReview(rounds, contract) {
828
822
  return [...rounds]
829
823
  .reverse()
830
- .find((round) => ((round.scope ?? "work-item") === "task"
824
+ .find((round) => (round.scope === "task"
831
825
  && (contract === undefined || sameTaskFinalReviewContract(round.taskFinalReviewContract, contract))));
832
826
  }
833
827
  function hasValidFinalReview(facts) {
834
828
  const contract = taskFinalReviewContract(facts);
835
829
  const final = [...facts.reviewRounds]
836
830
  .reverse()
837
- .find((round) => ((round.scope ?? "work-item") === "task"
831
+ .find((round) => (round.scope === "task"
838
832
  && (contract === undefined || sameTaskFinalReviewContract(round.taskFinalReviewContract, contract))));
839
833
  if (final === undefined || !isCompletedTaskReviewEvidenceFromRuns(final, facts.reviewOutcomeEvidence?.runs ?? []))
840
834
  return false;
@@ -853,7 +847,7 @@ function detectProtocolInconsistency(facts) {
853
847
  : [ref("candidate", `${item.id}/${candidate.id}`)];
854
848
  });
855
849
  const reviewRefs = facts.reviewRounds
856
- .filter((round) => ((round.scope ?? "work-item") === "task"
850
+ .filter((round) => (round.scope === "task"
857
851
  && round.taskFinalReviewContract !== undefined))
858
852
  .map((round) => ref("review-round", round.id));
859
853
  return {
@@ -0,0 +1,56 @@
1
+ import { contextContentDigest } from "../context/contextSnapshot.js";
2
+ import { publicationExternalKey } from "./publicationReference.js";
3
+ export const PUBLICATION_ADOPTED_EVENT = "publication.candidate-adopted";
4
+ /**
5
+ * An explicit semantic decision over fixed Git endpoints, not an ancestry
6
+ * heuristic or a replacement completion head. Publication metadata/verification
7
+ * successors may retain it only while every intervening local candidate agrees.
8
+ */
9
+ export function publicationAdoption(publication, completion, acceptedCommit, events, publications, integrations) {
10
+ const unavailable = { event: null, reason: null };
11
+ if (completion === undefined || acceptedCommit === null)
12
+ return unavailable;
13
+ const lineage = new Set();
14
+ let current = publication;
15
+ while (current !== undefined && !lineage.has(current.id)
16
+ && current.taskId === publication.taskId
17
+ && current.projectId === publication.projectId
18
+ && current.externalKind === publication.externalKind
19
+ && publicationExternalKey(current) === publicationExternalKey(publication)
20
+ && current.localCommit === publication.localCommit) {
21
+ lineage.add(current.id);
22
+ current = publications.find(p => p.id === current.supersedes);
23
+ }
24
+ const event = events.slice().reverse().find(event => {
25
+ const p = event.payload;
26
+ return event.type === PUBLICATION_ADOPTED_EVENT
27
+ && event.taskId === publication.taskId
28
+ && p.completionEventId === completion.id
29
+ && p.acceptedCommit === acceptedCommit
30
+ && p.projectId === publication.projectId
31
+ && p.localCommit === publication.localCommit
32
+ && lineage.has(p.publicationId ?? "");
33
+ });
34
+ if (event === undefined)
35
+ return unavailable;
36
+ const p = event.payload;
37
+ if (!p.acceptance?.trim()
38
+ || !["user", "operator", "leader"].includes(p.by ?? "")
39
+ || !/^[0-9a-f]{64}$/u.test(p.diffDigest ?? "")
40
+ || !/^[0-9a-f]{40}$/u.test(p.acceptedTree ?? "")
41
+ || !/^[0-9a-f]{40}$/u.test(p.candidateTree ?? "")) {
42
+ return { event: null, reason: `Adoption ${event.id} has incomplete fixed evidence.` };
43
+ }
44
+ if (p.integrationId === undefined)
45
+ return { event, reason: null };
46
+ const integration = integrations.find(i => i.id === p.integrationId);
47
+ const valid = integration !== undefined && integration.status === "committed"
48
+ && integration.taskId === publication.taskId
49
+ && integration.projectId === publication.projectId
50
+ && integration.afterCommit === publication.localCommit
51
+ && contextContentDigest(integration) === p.integrationDigest;
52
+ return valid ? { event, reason: null } : {
53
+ event: null,
54
+ reason: `Adoption ${event.id} references Integration ${p.integrationId}, whose committed candidate evidence is missing or changed (${integration?.status ?? "missing"}).`
55
+ };
56
+ }
@@ -35,6 +35,9 @@ export function createPublicationReference(id, taskId, input, now) {
35
35
  ...(input.localCommit === undefined ? {} : {
36
36
  localCommit: requireCommit(input.localCommit, "Publication local commit")
37
37
  }),
38
+ ...(input.headCommit === undefined ? {} : {
39
+ headCommit: requireCommit(input.headCommit, "Publication remote head")
40
+ }),
38
41
  ...(input.remoteCommit === undefined ? {} : {
39
42
  remoteCommit: requireCommit(input.remoteCommit, "Publication remote commit")
40
43
  }),
@@ -82,6 +85,13 @@ export function validatePublicationReference(reference) {
82
85
  if (reference.localCommit !== undefined) {
83
86
  requireCommit(reference.localCommit, "Publication local commit");
84
87
  }
88
+ if (reference.headCommit !== undefined) {
89
+ requireCommit(reference.headCommit, "Publication remote head");
90
+ if (reference.verification === "verified"
91
+ && reference.headCommit !== reference.localCommit) {
92
+ throw new Error("Verified publication remote head must match its local commit.");
93
+ }
94
+ }
85
95
  if (reference.remoteCommit !== undefined) {
86
96
  requireCommit(reference.remoteCommit, "Publication remote commit");
87
97
  }
@@ -1,3 +1,4 @@
1
+ import { publicationAdoption } from "./publicationAdoption.js";
1
2
  /**
2
3
  * One read-only delivery projection over existing Task, workspace, completion,
3
4
  * and Publication facts. It never writes a merged flag or queries a provider.
@@ -19,13 +20,19 @@ export function projectTaskRemoteDelivery(facts) {
19
20
  .filter((reference) => reference.projectId === binding.projectId);
20
21
  const matching = expectedLocalCommit === null
21
22
  ? []
22
- : projectPublications.filter((reference) => reference.localCommit === expectedLocalCommit);
23
+ : projectPublications.filter((reference) => reference.localCommit === expectedLocalCommit
24
+ || publicationAdoption(reference, expected.event, expectedLocalCommit, facts.events, facts.publications, facts.integrations ?? []).event !== null);
23
25
  const publication = bestPublication(matching.length > 0 ? matching : projectPublications);
24
- const coverage = projectCoverage(codeDelivery, expectedLocalCommit, publication);
26
+ const adoptionEvidence = publication === null ? null
27
+ : publicationAdoption(publication, expected.event, expectedLocalCommit, facts.events, facts.publications, facts.integrations ?? []);
28
+ const adoption = adoptionEvidence?.event ?? null;
29
+ const covers = expectedLocalCommit !== null && publication !== null
30
+ && (publication.localCommit === expectedLocalCommit || adoption !== null);
31
+ const headMatches = publication?.headCommit === undefined
32
+ || publication.headCommit === publication.localCommit;
33
+ const coverage = projectCoverage(codeDelivery, expectedLocalCommit, publication, covers, headMatches);
25
34
  const merged = codeDelivery === "none"
26
- || (publication !== null
27
- && expectedLocalCommit !== null
28
- && publication.localCommit === expectedLocalCommit
35
+ || (covers && headMatches
29
36
  && publication.state === "merged");
30
37
  const verified = codeDelivery === "none"
31
38
  || (merged && publication?.verification === "verified");
@@ -34,6 +41,8 @@ export function projectTaskRemoteDelivery(facts) {
34
41
  directory: binding.directory,
35
42
  baseCommit,
36
43
  expectedLocalCommit,
44
+ deliveryLocalCommit: covers ? publication.localCommit ?? null : null,
45
+ adoption,
37
46
  codeDelivery,
38
47
  publication,
39
48
  state: publication?.state ?? null,
@@ -42,7 +51,8 @@ export function projectTaskRemoteDelivery(facts) {
42
51
  coverage,
43
52
  merged,
44
53
  verified,
45
- reason: projectCoverageReason(codeDelivery, expectedLocalCommit, publication, coverage)
54
+ reason: projectCoverageReason(expectedLocalCommit, publication, coverage, adoption) + (coverage === "stale" && adoptionEvidence?.reason
55
+ ? ` ${adoptionEvidence.reason}` : "")
46
56
  };
47
57
  });
48
58
  const codeProjects = projects.filter((project) => project.codeDelivery !== "none");
@@ -55,8 +65,9 @@ export function projectTaskRemoteDelivery(facts) {
55
65
  : codeProjects.some((project) => project.coverage === "head-unavailable")
56
66
  ? "unavailable"
57
67
  : allMerged
58
- ? "merged"
59
- : mergedProjectCount > 0 ? "partial" : "pending";
68
+ ? allVerified ? "merged" : "unverified"
69
+ : mergedProjectCount > 0 ? "partial"
70
+ : codeProjects.some(p => p.state === "merged") ? "uncovered" : "pending";
60
71
  return {
61
72
  taskId: facts.task.id,
62
73
  source: expected.source,
@@ -111,7 +122,7 @@ function expectedDelivery(facts) {
111
122
  commits: new Map()
112
123
  };
113
124
  }
114
- function completionEvent(events, completedAt) {
125
+ export function completionEvent(events, completedAt) {
115
126
  const exact = latestEvent(events, "task.completed", (event) => (event.createdAt === completedAt));
116
127
  return exact ?? latestEvent(events, "task.completed", (event) => (event.createdAt <= completedAt));
117
128
  }
@@ -154,7 +165,7 @@ function candidateCommits(candidate) {
154
165
  commit.toLowerCase()
155
166
  ]));
156
167
  }
157
- function commitMap(value) {
168
+ export function commitMap(value) {
158
169
  const result = new Map();
159
170
  for (const entry of value?.split(",") ?? []) {
160
171
  const separator = entry.indexOf("@");
@@ -188,7 +199,7 @@ function publicationRank(reference) {
188
199
  return 2;
189
200
  return 1;
190
201
  }
191
- function projectCoverage(codeDelivery, expectedLocalCommit, publication) {
202
+ function projectCoverage(codeDelivery, expectedLocalCommit, publication, covers, headMatches) {
192
203
  if (codeDelivery === "none")
193
204
  return "not-applicable";
194
205
  if (expectedLocalCommit === null)
@@ -197,7 +208,9 @@ function projectCoverage(codeDelivery, expectedLocalCommit, publication) {
197
208
  return "missing";
198
209
  if (publication.localCommit === undefined)
199
210
  return "missing-local-commit";
200
- if (publication.localCommit !== expectedLocalCommit)
211
+ if (!headMatches)
212
+ return "head-mismatch";
213
+ if (!covers)
201
214
  return "stale";
202
215
  if (publication.state === "open")
203
216
  return "open";
@@ -205,7 +218,7 @@ function projectCoverage(codeDelivery, expectedLocalCommit, publication) {
205
218
  return "closed";
206
219
  return publication.verification === "verified" ? "verified" : "merged";
207
220
  }
208
- function projectCoverageReason(codeDelivery, expectedLocalCommit, publication, coverage) {
221
+ function projectCoverageReason(expectedLocalCommit, publication, coverage, adoption) {
209
222
  switch (coverage) {
210
223
  case "not-applicable":
211
224
  return "Task head equals the managed workspace base; no code delivery is required.";
@@ -216,15 +229,17 @@ function projectCoverageReason(codeDelivery, expectedLocalCommit, publication, c
216
229
  case "missing-local-commit":
217
230
  return `Publication ${publication.id} does not record a local commit.`;
218
231
  case "stale":
219
- return `Publication ${publication.id} covers ${publication.localCommit}, not the expected Task head ${expectedLocalCommit}.`;
232
+ return `Publication ${publication.id} is ${publication.state}, but its local candidate ${publication.localCommit} has no valid adoption covering accepted Task head ${expectedLocalCommit}.`;
233
+ case "head-mismatch":
234
+ return `Publication ${publication.id} is ${publication.state}, but observed remote head ${publication.headCommit} differs from local candidate ${publication.localCommit}; verification is invalid.`;
220
235
  case "open":
221
236
  return `Publication ${publication.id} covers the Task head but is still open.`;
222
237
  case "closed":
223
238
  return `Publication ${publication.id} covers the Task head but is closed without merge.`;
224
239
  case "merged":
225
- return `Publication ${publication.id} reports the exact Task head as merged.`;
240
+ return `Publication ${publication.id} reports a covering candidate as merged, but remote verification is still required.`;
226
241
  case "verified":
227
- return `Publication ${publication.id} verifies the exact Task head as merged.`;
242
+ return `Publication ${publication.id} verifies ${adoption === null ? "the exact Task head" : `candidate ${publication.localCommit} adopted by ${adoption.id}`} as merged.`;
228
243
  }
229
244
  }
230
245
  function compareCreated(left, right) {
@@ -0,0 +1,89 @@
1
+ import { isDeepStrictEqual } from "node:util";
2
+ import { usageError } from "../errors/cliError.js";
3
+ import { projectTaskRemoteDelivery } from "../task/remoteDelivery.js";
4
+ import { PUBLICATION_ADOPTED_EVENT } from "./publicationAdoption.js";
5
+ export function projectTaskRemoteDeliveryFromStore(store, task, currentCandidate = null) {
6
+ return createTaskRemoteDeliveryProof(store, task, currentCandidate).delivery;
7
+ }
8
+ export function createTaskRemoteDeliveryProof(store, task, currentCandidate = null) {
9
+ const publications = store.listPublicationReferences(task.id);
10
+ const delivery = projectTaskRemoteDelivery({
11
+ task,
12
+ events: store.listEventsByType(task.id, [
13
+ "task.completed", "task.archived", PUBLICATION_ADOPTED_EVENT
14
+ ]),
15
+ publications,
16
+ managedWorkspaces: store.listManagedWorkspaces(task.id),
17
+ runs: task.projectBindings.length === 0 ? [] : store.listTaskRunWorkspaceBases(task.id),
18
+ integrations: store.listIntegrationAttempts(task.id),
19
+ currentCandidate
20
+ });
21
+ return {
22
+ schemaVersion: 1,
23
+ task: taskRemoteDeliveryProofTask(task),
24
+ publications,
25
+ delivery
26
+ };
27
+ }
28
+ export function assertTaskRemoteDeliveryProof(store, task, proof) {
29
+ if (proof === undefined || proof.schemaVersion !== 1) {
30
+ throw usageError(`Task remote-delivery preflight proof is required: ${task.id}.`);
31
+ }
32
+ if (!isDeepStrictEqual(proof.task, taskRemoteDeliveryProofTask(task))) {
33
+ throw usageError(`Task changed after remote-delivery preflight: ${task.id}.`);
34
+ }
35
+ if (!isDeepStrictEqual(proof.publications, store.listPublicationReferences(task.id))) {
36
+ throw usageError(`Task Publication evidence changed after remote-delivery preflight: ${task.id}.`);
37
+ }
38
+ // Recheck semantic adoption/Integration facts as well as Publication versions.
39
+ // For provisional cancelled Tasks retain the frozen candidate used at preflight.
40
+ const current = projectTaskRemoteDeliveryFromStore(store, task, {
41
+ projects: proof.delivery.projects.flatMap(p => p.expectedLocalCommit === null
42
+ ? [] : [{ projectId: p.projectId, commit: p.expectedLocalCommit }])
43
+ });
44
+ if (!isDeepStrictEqual(current, proof.delivery)) {
45
+ throw usageError(`Task delivery evidence changed after remote-delivery preflight: ${task.id}.`);
46
+ }
47
+ assertTaskRemoteDeliveryIntegrated(proof.delivery);
48
+ return proof.delivery;
49
+ }
50
+ export function assertTaskRemoteDeliveryIntegrated(delivery) {
51
+ if (!delivery.allMerged) {
52
+ const headUnavailable = delivery.projects.filter((project) => (project.codeDelivery !== "none"
53
+ && project.coverage === "head-unavailable"));
54
+ if (headUnavailable.length > 0 && delivery.source === "task-completed") {
55
+ throw usageError(`Task ${delivery.taskId} has no frozen completion heads for: ${headUnavailable.map(({ projectId }) => projectId).join(", ")}. Historical acceptance cannot be inferred from a merge or archive.`);
56
+ }
57
+ const uncovered = delivery.projects
58
+ .filter((project) => project.codeDelivery !== "none" && !project.merged)
59
+ .map((project) => (`${project.projectId}/${project.coverage}`
60
+ + (project.publication === null ? "" : `/${project.publication.id}`)))
61
+ .join(", ");
62
+ throw usageError(`Task ${delivery.taskId} is not fully merged into remote delivery: ${uncovered || "expected Project heads are unavailable"}. Inspect each Project reason with task remote-delivery. Record confirmed Publication facts; `
63
+ + "for a changed post-completion candidate, review publication diff and explicitly adopt its acceptance.");
64
+ }
65
+ if (delivery.allVerified)
66
+ return;
67
+ const unverified = delivery.projects
68
+ .filter((project) => (project.codeDelivery !== "none"
69
+ && project.merged
70
+ && !project.verified))
71
+ .map((project) => (`${project.projectId}/${project.coverage}`
72
+ + (project.publication === null ? "" : `/${project.publication.id}`)))
73
+ .join(", ");
74
+ throw usageError(`Task ${delivery.taskId} has merged Publication evidence that is not verified: `
75
+ + `${unverified || "verification evidence is unavailable"}. Run task publication verify `
76
+ + "for each Publication only when the provider read is authorized.");
77
+ }
78
+ export function taskRemoteDeliveryProofTask(task) {
79
+ return {
80
+ id: task.id,
81
+ status: task.status,
82
+ projectBindings: task.projectBindings,
83
+ ...(task.workspaceIdentity === undefined
84
+ ? {}
85
+ : { workspaceIdentity: task.workspaceIdentity }),
86
+ ...(task.completedAt === undefined ? {} : { completedAt: task.completedAt }),
87
+ ...(task.retiredAt === undefined ? {} : { retiredAt: task.retiredAt })
88
+ };
89
+ }
@@ -22,7 +22,6 @@ export const TASK_ACTIVATION_EVENT = Object.freeze({
22
22
  adopted: "task.activation-adopted",
23
23
  failed: "task.activation-failed"
24
24
  });
25
- export const TASK_ACTIVATION_ORIGINS = Object.freeze(["explicit", "submit-develop"]);
26
25
  /**
27
26
  * The digest that decides whether a repeated requestId is the same request.
28
27
  *
@@ -64,7 +63,6 @@ export function createTaskActivationRequest(taskId, input, now) {
64
63
  partialResultRefs: []
65
64
  },
66
65
  startMode: input.startMode,
67
- ...(input.origin === undefined ? {} : { origin: input.origin }),
68
66
  ...(input.afterPlanningRun === undefined
69
67
  ? {}
70
68
  : { afterPlanningRun: input.afterPlanningRun }),
@@ -196,26 +194,6 @@ export function admitTaskActivationRequest(task, request, planningRunIsActive) {
196
194
  }
197
195
  return { disposition: "ready", request };
198
196
  }
199
- /**
200
- * Whether the Controller may continue (auto-adopt) a released request without an
201
- * explicit activation boundary call (task-32 §2.4).
202
- *
203
- * A deferred request already holds activation authority and is released by its
204
- * planning Turn ending, so it is adoptable regardless of origin. An immediate
205
- * request is auto-continued only when it carries a recognised provable origin:
206
- * an explicit activation action, or a develop submission the shared transaction
207
- * accepted while the Task was unplanned. A request with no origin — every one
208
- * migrated from before the field existed — is deliberately held back so an
209
- * upgrade cannot turn a historical pending request into a silent activation; it
210
- * still adopts through the explicit `yui task activate` boundary, which does not
211
- * consult origin. This replaces the old actor heuristic that both dropped legal
212
- * Operator/user immediate requests and could not distinguish provenance.
213
- */
214
- export function activationRequestIsControllerAdoptable(request) {
215
- if (request.startMode === "after-planning-turn")
216
- return true;
217
- return request.origin !== undefined;
218
- }
219
197
  /**
220
198
  * The durable terminal outcome for a requestId, read from the activation event
221
199
  * ledger.
@@ -258,9 +236,8 @@ export function validateTaskActivationRequest(request) {
258
236
  if (!["immediate", "after-planning-turn"].includes(request.startMode)) {
259
237
  throw new Error(`Activation start mode is invalid: ${String(request.startMode)}.`);
260
238
  }
261
- if (request.origin !== undefined
262
- && !TASK_ACTIVATION_ORIGINS.includes(request.origin)) {
263
- throw new Error(`Activation origin is invalid: ${String(request.origin)}.`);
239
+ if (Object.hasOwn(request, "origin")) {
240
+ throw new Error("Activation contains retired origin metadata; use the storage upgrade boundary.");
264
241
  }
265
242
  if ((request.startMode === "after-planning-turn")
266
243
  !== (request.afterPlanningRun !== undefined)) {
@@ -108,7 +108,6 @@ export function recordTaskActivationRequestInTransaction(tx, input, now) {
108
108
  actorId: input.actorId,
109
109
  authorityRef: input.authorityRef,
110
110
  startMode,
111
- ...(input.origin === undefined ? {} : { origin: input.origin }),
112
111
  ...(planningRun === undefined ? {} : { afterPlanningRun: planningRun }),
113
112
  environmentPlan: plan
114
113
  }, now);
@@ -118,7 +117,6 @@ export function recordTaskActivationRequestInTransaction(tx, input, now) {
118
117
  startMode: request.startMode,
119
118
  environmentPlan: describeEnvironmentPlan(request.environmentPlan),
120
119
  actor: request.operation.actorId,
121
- ...(request.origin === undefined ? {} : { origin: request.origin }),
122
120
  ...(request.afterPlanningRun === undefined
123
121
  ? {}
124
122
  : { afterPlanningRun: request.afterPlanningRun })
@@ -6,7 +6,6 @@ export const TASK_RECORD_ID_PREFIXES = {
6
6
  reviewRound: "review-round",
7
7
  changeSet: "change-set",
8
8
  integrationAttempt: "integration",
9
- integrationQueue: "integration-queue",
10
9
  durableJob: "job",
11
10
  message: "message",
12
11
  inputRequest: "input",
@@ -131,21 +131,10 @@ export function decideSubmissionRouting(input) {
131
131
  /**
132
132
  * Normalize a possibly-absent submission intent to its effective value.
133
133
  *
134
- * An absent intent is `discuss` (task-32 §2.5: "默认/旧客户端都按 discuss"), so
135
- * every old client and every message saved before the field existed keeps its
136
- * historical Leader-waking behaviour. Body text is never inspected.
137
- *
138
- * A legacy `--wake-policy none` carried the "save only, do not wake" meaning that
139
- * `record` now names, so a caller that supplied only that older signal maps to
140
- * `record` rather than silently becoming a Leader-waking `discuss`. An explicit
141
- * intent always wins over the wake-policy shorthand.
134
+ * The current default is `discuss`. Body text is never inspected.
142
135
  */
143
- export function normalizeSubmissionIntent(intent, wakePolicy) {
144
- if (intent !== undefined)
145
- return intent;
146
- if (wakePolicy === "none")
147
- return "record";
148
- return "discuss";
136
+ export function normalizeSubmissionIntent(intent) {
137
+ return intent ?? "discuss";
149
138
  }
150
139
  /**
151
140
  * Whether a retry aims at the same target the key was first bound to (§2.3). An
@@ -0,0 +1,29 @@
1
+ /** Runs on the existing persistence worker's connection, never the ingress
2
+ * thread. Telemetry keeps its existing idempotent keys and aggregate triggers;
3
+ * it neither bumps Task revisions nor creates durable RPC outbox records.
4
+ */
5
+ export function writeTelemetryBatch(db, entries, runCap) {
6
+ if (!Number.isSafeInteger(runCap) || runCap < 1 || entries.length > 256) {
7
+ throw new Error("Telemetry batch exceeds its bounded write contract.");
8
+ }
9
+ const upsert = db.prepare(`INSERT INTO telemetry (task_id, role_name, turn_id, progress_id, sequence, payload, received_at)
10
+ VALUES (?, ?, ?, ?, ?, ?, ?)
11
+ ON CONFLICT(task_id, role_name, turn_id, progress_id) DO UPDATE SET
12
+ sequence = excluded.sequence, payload = excluded.payload, received_at = excluded.received_at
13
+ WHERE excluded.received_at >= telemetry.received_at`);
14
+ const cap = db.prepare(`DELETE FROM telemetry WHERE task_id = ? AND turn_id = ?
15
+ AND (task_id, role_name, turn_id, progress_id) NOT IN (
16
+ SELECT task_id, role_name, turn_id, progress_id FROM telemetry
17
+ WHERE task_id = ? AND turn_id = ?
18
+ ORDER BY received_at DESC, COALESCE(sequence, -1) DESC, progress_id ASC LIMIT ?
19
+ )`);
20
+ db.transaction(() => {
21
+ const runs = new Map();
22
+ for (const entry of entries) {
23
+ upsert.run(entry.taskId, entry.roleName, entry.runId, entry.progressId, entry.sequence ?? null, JSON.stringify(entry.payload), entry.receivedAt);
24
+ runs.set(`${entry.taskId}\0${entry.runId}`, entry);
25
+ }
26
+ for (const { taskId, runId } of runs.values())
27
+ cap.run(taskId, runId, taskId, runId, runCap);
28
+ })();
29
+ }