@zq-silk/yui 0.14.1 → 0.14.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/ARCHITECTURE.md +64 -35
  2. package/README.md +254 -124
  3. package/dist/agent/managedRuntimeEnvironment.js +5 -8
  4. package/dist/cli/commandCatalog.js +74 -109
  5. package/dist/cli/helpRenderer.js +1 -2
  6. package/dist/cli/interactionCandidates.js +10 -0
  7. package/dist/cli/interactionPolicy.js +76 -37
  8. package/dist/cli/roleWizard.js +5 -6
  9. package/dist/cli/updateOrchestrator.js +10 -8
  10. package/dist/cli/updatePorts.js +17 -13
  11. package/dist/cli/upgradeCommand.js +2 -5
  12. package/dist/cli.js +268 -231
  13. package/dist/commands/agentCommands.js +13 -0
  14. package/dist/commands/configCommands.js +7 -19
  15. package/dist/commands/configOverview.js +3 -2
  16. package/dist/commands/durableJobCommands.js +1 -1
  17. package/dist/commands/executionAuditCommands.js +4 -9
  18. package/dist/commands/globalRoleCommands.js +17 -0
  19. package/dist/commands/profileCommands.js +200 -30
  20. package/dist/commands/sessionCommands.js +1 -1
  21. package/dist/commands/taskActor.js +29 -127
  22. package/dist/commands/taskChangeSetCommands.js +1 -3
  23. package/dist/commands/taskCommands.js +1771 -2107
  24. package/dist/commands/taskCompletionGate.js +27 -103
  25. package/dist/commands/taskContextCommand.js +47 -80
  26. package/dist/commands/taskExecutionCommands.js +9 -27
  27. package/dist/commands/taskInputCommands.js +10 -10
  28. package/dist/commands/taskIntegrationCommands.js +69 -61
  29. package/dist/commands/taskIntegrationQueueCommands.js +1 -1
  30. package/dist/commands/taskNextActionCommand.js +8 -29
  31. package/dist/commands/taskOverviewCommand.js +25 -3
  32. package/dist/commands/taskPublicationCommands.js +151 -85
  33. package/dist/commands/taskPublicationVerifyCommand.js +181 -0
  34. package/dist/commands/taskRemoteDeliveryCommand.js +134 -0
  35. package/dist/commands/taskRoleRuntimeStatus.js +5 -6
  36. package/dist/commands/taskUpstreamCommands.js +79 -95
  37. package/dist/config/configCatalog.js +1 -1
  38. package/dist/context/contextSnapshot.js +17 -2
  39. package/dist/context/sourceTurnContext.js +30 -0
  40. package/dist/context/turnContextPack.js +107 -48
  41. package/dist/context/turnInputContract.js +11 -4
  42. package/dist/context/wakeNotification.js +84 -22
  43. package/dist/controller/agentRuntimeObserver.js +9 -12
  44. package/dist/controller/clientRuntime.js +13 -13
  45. package/dist/controller/controller.js +11 -11
  46. package/dist/controller/fileSchedulerStoreAdapter.js +262 -219
  47. package/dist/controller/jobControl.js +11 -10
  48. package/dist/controller/resourceInventory.js +6 -6
  49. package/dist/controller/resourceInventoryLinux.js +2 -2
  50. package/dist/controller/runtime.js +18 -61
  51. package/dist/controller/runtimeEventInbox.js +57 -34
  52. package/dist/controller/runtimeEventProcessor.js +36 -96
  53. package/dist/controller/runtimeHookTurnFence.js +33 -57
  54. package/dist/controller/runtimeLaunchCoordinator.js +44 -40
  55. package/dist/controller/runtimeObservationHook.js +18 -12
  56. package/dist/controller/sessionNotify.js +43 -28
  57. package/dist/controller/sessionOwnerReconciliation.js +21 -21
  58. package/dist/controller/structuredProviderObservation.js +13 -16
  59. package/dist/coordination/workMailboxQueue.js +132 -0
  60. package/dist/core/controllerServer.js +1 -0
  61. package/dist/domain/agentResultTransport.js +60 -0
  62. package/dist/execution/executionHealth.js +111 -118
  63. package/dist/execution/reviewMainTurn.js +161 -0
  64. package/dist/execution/workItemExecution.js +454 -0
  65. package/dist/execution/workItemExecutionProjection.js +309 -0
  66. package/dist/execution/workItemMainTurn.js +164 -0
  67. package/dist/executor/agentExecutor.js +19 -19
  68. package/dist/executor/executorRegistry.js +7 -28
  69. package/dist/executor/fileRoleLaunchPlanner.js +16 -82
  70. package/dist/external/githubPublicationVerifier.js +114 -0
  71. package/dist/external/gitlabPublicationVerifier.js +158 -0
  72. package/dist/external/pinnedCommandRunner.js +73 -0
  73. package/dist/integration/changeSetManifest.js +0 -3
  74. package/dist/integration/deliveryObligation.js +43 -6
  75. package/dist/integration/gitIntegrationService.js +284 -101
  76. package/dist/integration/integrationAttempt.js +64 -14
  77. package/dist/integration/integrationQueueEntry.js +5 -35
  78. package/dist/integration/integrationQueueService.js +65 -194
  79. package/dist/lifecycle/canonicalLifecycleEvent.js +16 -15
  80. package/dist/lifecycle/exactTurnTerminalization.js +100 -97
  81. package/dist/message/message.js +16 -0
  82. package/dist/observability/executionAudit.js +4 -25
  83. package/dist/observability/faultClassification.js +58 -70
  84. package/dist/observability/orchestrationMetrics.js +8 -50
  85. package/dist/profile/agentProfile.js +60 -15
  86. package/dist/profile/agentProfileRuntime.js +100 -0
  87. package/dist/release/releaseWorkflowPorts.js +5 -68
  88. package/dist/repository/executionLaneGitSnapshot.js +80 -0
  89. package/dist/repository/gitWorkspace.js +9 -67
  90. package/dist/repository/taskBaseFreshness.js +3 -1
  91. package/dist/repository/taskWorkspaceCoordinator.js +32 -4
  92. package/dist/repository/taskWorkspacePreparer.js +252 -411
  93. package/dist/resources/liveReferences.js +6 -55
  94. package/dist/resources/resourceDiscovery.js +1 -30
  95. package/dist/resources/resourceRegistrar.js +0 -23
  96. package/dist/review/deltaRecheck.js +8 -65
  97. package/dist/review/reviewAcceptance.js +22 -19
  98. package/dist/review/reviewConfig.js +0 -10
  99. package/dist/review/reviewDecision.js +2 -2
  100. package/dist/review/reviewRound.js +147 -273
  101. package/dist/review/reviewerAvailability.js +13 -23
  102. package/dist/role/role.js +3 -0
  103. package/dist/runtime/agentDriverObservation.js +1 -1
  104. package/dist/runtime/agentHost.js +51 -51
  105. package/dist/runtime/builtinAgentDrivers.js +9 -2
  106. package/dist/runtime/codexAppServerRuntime.js +23 -5
  107. package/dist/runtime/exactControlPlane.js +2 -267
  108. package/dist/runtime/firstProgressAdvisory.js +1 -1
  109. package/dist/runtime/index.js +1 -1
  110. package/dist/runtime/launchBroker.js +11 -11
  111. package/dist/runtime/lifecycleReservation.js +2 -2
  112. package/dist/runtime/managedCaller.js +111 -0
  113. package/dist/runtime/ports.js +16 -3
  114. package/dist/runtime/preallocatedNativeSession.js +2 -2
  115. package/dist/runtime/processExitObservation.js +3 -2
  116. package/dist/runtime/providerContinuationReconciliationService.js +1 -1
  117. package/dist/runtime/runtimeBinding.js +1 -1
  118. package/dist/runtime/runtimeObservation.js +33 -16
  119. package/dist/runtime/runtimeSessionCandidate.js +2 -2
  120. package/dist/runtime/runtimeStopReceipt.js +11 -11
  121. package/dist/runtime/sessionLaunchRequest.js +2 -2
  122. package/dist/runtime/sessionOwnerIdentity.js +9 -9
  123. package/dist/runtime/sessionOwnerRegistry.js +13 -13
  124. package/dist/runtime/sessionReconciliation.js +4 -4
  125. package/dist/runtime/sessionTerminationGuard.js +9 -9
  126. package/dist/runtime/sessionTokenMetrics.js +5 -7
  127. package/dist/runtime/structuredProviderHost.js +5 -5
  128. package/dist/runtime/taskRuntimeIsolation.js +24 -24
  129. package/dist/runtime/tmuxAdapters.js +32 -30
  130. package/dist/scheduler/activeRoleTurnDelivery.js +37 -15
  131. package/dist/scheduler/leaderWakeupProcessor.js +78 -55
  132. package/dist/scheduler/roleTurnLiveness.js +6 -6
  133. package/dist/scheduler/roleTurnStall.js +8 -9
  134. package/dist/scheduler/taskExecutionProjection.js +19 -41
  135. package/dist/scheduler/taskObservabilityProjection.js +75 -122
  136. package/dist/storage/sqliteSchema.js +1 -21
  137. package/dist/storage/sqliteStore.js +62 -47
  138. package/dist/storage/storageSchema.js +1 -8
  139. package/dist/storage/storageVersions.js +7 -4
  140. package/dist/storage/taskStore.js +8 -8
  141. package/dist/storage/upgrade/upgradeOrchestrator.js +112 -83
  142. package/dist/task/completionReadiness.js +17 -98
  143. package/dist/task/deliveryGuard.js +9 -11
  144. package/dist/task/draftPlan.js +142 -0
  145. package/dist/task/nextAction.js +179 -477
  146. package/dist/task/publicationVerification.js +1 -0
  147. package/dist/task/remoteDelivery.js +233 -0
  148. package/dist/task/task.js +108 -4
  149. package/dist/task/taskRecordReference.js +0 -1
  150. package/dist/turn/turn.js +109 -16
  151. package/dist/verification/verificationGateService.js +0 -26
  152. package/dist/web/assets/client/components.js +99 -121
  153. package/dist/web/assets/client/i18n.js +136 -58
  154. package/dist/web/assets/client/view.js +44 -0
  155. package/dist/web/assets/styles/cards.js +3 -15
  156. package/dist/web/assets/styles/widgets.js +4 -0
  157. package/dist/web/webSnapshot.js +29 -14
  158. package/dist/workItem/dependencyGate.js +34 -0
  159. package/dist/workItem/workItem.js +99 -266
  160. package/dist/workspace/workItemChangeSetManager.js +15 -59
  161. package/docs/task-local-identity.md +1 -1
  162. package/i18n/README.zh-CN.md +164 -58
  163. package/package.json +1 -1
  164. package/skills/yui-leader/SKILL.md +272 -782
  165. package/skills/yui-operator/SKILL.md +190 -365
  166. package/skills/yui-reviewer/SKILL.md +40 -33
  167. package/skills/yui-runtime/SKILL.md +22 -0
  168. package/skills/yui-worker/SKILL.md +19 -6
  169. package/dist/execution/candidateConvergence.js +0 -623
  170. package/dist/execution/executionGroup.js +0 -854
  171. package/dist/execution/resourceBroker.js +0 -426
  172. package/dist/executor/launchPlan.js +0 -16
  173. package/dist/review/reviewFinding.js +0 -240
  174. package/dist/review/reviewFindingLedger.js +0 -550
  175. package/dist/review/reviewOutcomeClassifier.js +0 -255
  176. package/dist/scheduler/resourceQueueProjection.js +0 -71
  177. package/dist/task/repairWave.js +0 -150
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,233 @@
1
+ /**
2
+ * One read-only delivery projection over existing Task, workspace, completion,
3
+ * and Publication facts. It never writes a merged flag or queries a provider.
4
+ */
5
+ export function projectTaskRemoteDelivery(facts) {
6
+ const expected = expectedDelivery(facts);
7
+ const baseCommits = projectBaseCommits(facts, expected.event);
8
+ const archiveDisposition = latestEvent(facts.events, "task.archived", () => true)?.payload.workspaceDisposition;
9
+ const currentPublications = currentPublicationReferences(facts.publications);
10
+ const projects = facts.task.projectBindings.map((binding) => {
11
+ const expectedLocalCommit = expected.commits.get(binding.projectId) ?? null;
12
+ const baseCommit = baseCommits.get(binding.projectId) ?? null;
13
+ const codeDelivery = expectedLocalCommit === null
14
+ ? "unknown"
15
+ : baseCommit === null
16
+ ? "unknown"
17
+ : expectedLocalCommit === baseCommit ? "none" : "code";
18
+ const projectPublications = currentPublications
19
+ .filter((reference) => reference.projectId === binding.projectId);
20
+ const matching = expectedLocalCommit === null
21
+ ? []
22
+ : projectPublications.filter((reference) => reference.localCommit === expectedLocalCommit);
23
+ const publication = bestPublication(matching.length > 0 ? matching : projectPublications);
24
+ const coverage = projectCoverage(codeDelivery, expectedLocalCommit, publication);
25
+ const merged = codeDelivery === "none"
26
+ || (publication !== null
27
+ && expectedLocalCommit !== null
28
+ && publication.localCommit === expectedLocalCommit
29
+ && publication.state === "merged");
30
+ const verified = codeDelivery === "none"
31
+ || (merged && publication?.verification === "verified");
32
+ return {
33
+ projectId: binding.projectId,
34
+ directory: binding.directory,
35
+ baseCommit,
36
+ expectedLocalCommit,
37
+ codeDelivery,
38
+ publication,
39
+ state: publication?.state ?? null,
40
+ verification: publication?.verification ?? null,
41
+ remoteCommit: publication?.remoteCommit ?? null,
42
+ coverage,
43
+ merged,
44
+ verified,
45
+ reason: projectCoverageReason(codeDelivery, expectedLocalCommit, publication, coverage)
46
+ };
47
+ });
48
+ const codeProjects = projects.filter((project) => project.codeDelivery !== "none");
49
+ const mergedProjectCount = codeProjects.filter((project) => project.merged).length;
50
+ const verifiedProjectCount = codeProjects.filter((project) => project.verified).length;
51
+ const allMerged = codeProjects.every((project) => project.merged);
52
+ const allVerified = codeProjects.every((project) => project.verified);
53
+ const status = codeProjects.length === 0
54
+ ? "none"
55
+ : codeProjects.some((project) => project.coverage === "head-unavailable")
56
+ ? "unavailable"
57
+ : allMerged
58
+ ? "merged"
59
+ : mergedProjectCount > 0 ? "partial" : "pending";
60
+ return {
61
+ taskId: facts.task.id,
62
+ source: expected.source,
63
+ provisional: expected.provisional,
64
+ archiveDisposition: archiveDisposition === "integrated"
65
+ || archiveDisposition === "abandoned"
66
+ ? archiveDisposition
67
+ : null,
68
+ status,
69
+ allMerged,
70
+ allVerified,
71
+ totalProjectCount: projects.length,
72
+ codeProjectCount: codeProjects.length,
73
+ mergedProjectCount,
74
+ verifiedProjectCount,
75
+ integratedCoverageSatisfied: allMerged && allVerified,
76
+ projects
77
+ };
78
+ }
79
+ function expectedDelivery(facts) {
80
+ const { task } = facts;
81
+ if ((task.status === "completed" || task.status === "archived")
82
+ && task.completedAt !== undefined) {
83
+ const event = completionEvent(facts.events, task.completedAt);
84
+ return {
85
+ source: "task-completed",
86
+ provisional: false,
87
+ commits: commitMap(event?.payload.projectHeads),
88
+ ...(event === undefined ? {} : { event })
89
+ };
90
+ }
91
+ if (task.status === "archived") {
92
+ const event = latestEvent(facts.events, "task.archived", (candidate) => (candidate.payload.workspaceDisposition === "integrated"
93
+ && candidate.payload.projectHeads !== undefined));
94
+ return {
95
+ source: event === undefined ? "unavailable" : "task-archive",
96
+ provisional: false,
97
+ commits: commitMap(event?.payload.projectHeads),
98
+ ...(event === undefined ? {} : { event })
99
+ };
100
+ }
101
+ if (task.status === "active" || task.status === "retired") {
102
+ return {
103
+ source: "current-task-main",
104
+ provisional: true,
105
+ commits: candidateCommits(facts.currentCandidate)
106
+ };
107
+ }
108
+ return {
109
+ source: "unavailable",
110
+ provisional: false,
111
+ commits: new Map()
112
+ };
113
+ }
114
+ function completionEvent(events, completedAt) {
115
+ const exact = latestEvent(events, "task.completed", (event) => (event.createdAt === completedAt));
116
+ return exact ?? latestEvent(events, "task.completed", (event) => (event.createdAt <= completedAt));
117
+ }
118
+ function latestEvent(events, type, predicate) {
119
+ return events
120
+ .filter((event) => event.type === type && predicate(event))
121
+ .slice()
122
+ .sort(compareCreated)
123
+ .at(-1);
124
+ }
125
+ function projectBaseCommits(facts, event) {
126
+ const result = commitMap(event?.payload.projectBases);
127
+ const archived = latestEvent(facts.events, "task.archived", (candidate) => (candidate.payload.projectBases !== undefined));
128
+ for (const [projectId, commit] of commitMap(archived?.payload.projectBases)) {
129
+ if (!result.has(projectId))
130
+ result.set(projectId, commit);
131
+ }
132
+ const current = facts.managedWorkspaces.find((workspace) => (workspace.owner.type === "task"
133
+ && workspace.owner.taskId === facts.task.id));
134
+ for (const entry of current?.entries ?? []) {
135
+ if (!result.has(entry.projectId))
136
+ result.set(entry.projectId, entry.baseCommit);
137
+ }
138
+ const taskTurns = facts.turns
139
+ .filter((turn) => (turn.workspace?.owner.type === "task"
140
+ && turn.workspace.owner.taskId === facts.task.id))
141
+ .slice()
142
+ .sort(compareCreated);
143
+ for (const turn of taskTurns) {
144
+ for (const entry of turn.workspace?.entries ?? []) {
145
+ if (!result.has(entry.projectId))
146
+ result.set(entry.projectId, entry.baseCommit);
147
+ }
148
+ }
149
+ return result;
150
+ }
151
+ function candidateCommits(candidate) {
152
+ return new Map((candidate?.projects ?? []).map(({ projectId, commit }) => [
153
+ projectId,
154
+ commit.toLowerCase()
155
+ ]));
156
+ }
157
+ function commitMap(value) {
158
+ const result = new Map();
159
+ for (const entry of value?.split(",") ?? []) {
160
+ const separator = entry.indexOf("@");
161
+ if (separator <= 0)
162
+ continue;
163
+ const projectId = entry.slice(0, separator).trim();
164
+ const commit = entry.slice(separator + 1).trim().toLowerCase();
165
+ if (projectId.length === 0 || !/^[0-9a-f]{40}$/u.test(commit))
166
+ continue;
167
+ result.set(projectId, commit);
168
+ }
169
+ return result;
170
+ }
171
+ function currentPublicationReferences(publications) {
172
+ const superseded = new Set(publications.flatMap((reference) => (reference.supersedes === undefined ? [] : [reference.supersedes])));
173
+ return publications.filter((reference) => !superseded.has(reference.id));
174
+ }
175
+ function bestPublication(publications) {
176
+ return publications
177
+ .slice()
178
+ .sort((left, right) => (publicationRank(left) - publicationRank(right)
179
+ || compareCreated(left, right)))
180
+ .at(-1) ?? null;
181
+ }
182
+ function publicationRank(reference) {
183
+ if (reference.state === "merged" && reference.verification === "verified")
184
+ return 4;
185
+ if (reference.state === "merged")
186
+ return 3;
187
+ if (reference.state === "open")
188
+ return 2;
189
+ return 1;
190
+ }
191
+ function projectCoverage(codeDelivery, expectedLocalCommit, publication) {
192
+ if (codeDelivery === "none")
193
+ return "not-applicable";
194
+ if (expectedLocalCommit === null)
195
+ return "head-unavailable";
196
+ if (publication === null)
197
+ return "missing";
198
+ if (publication.localCommit === undefined)
199
+ return "missing-local-commit";
200
+ if (publication.localCommit !== expectedLocalCommit)
201
+ return "stale";
202
+ if (publication.state === "open")
203
+ return "open";
204
+ if (publication.state === "closed")
205
+ return "closed";
206
+ return publication.verification === "verified" ? "verified" : "merged";
207
+ }
208
+ function projectCoverageReason(codeDelivery, expectedLocalCommit, publication, coverage) {
209
+ switch (coverage) {
210
+ case "not-applicable":
211
+ return "Task head equals the managed workspace base; no code delivery is required.";
212
+ case "head-unavailable":
213
+ return "The exact Task delivery head is unavailable, so merge coverage is fail-closed.";
214
+ case "missing":
215
+ return "No current Publication records this Project delivery.";
216
+ case "missing-local-commit":
217
+ return `Publication ${publication.id} does not record a local commit.`;
218
+ case "stale":
219
+ return `Publication ${publication.id} covers ${publication.localCommit}, not the expected Task head ${expectedLocalCommit}.`;
220
+ case "open":
221
+ return `Publication ${publication.id} covers the Task head but is still open.`;
222
+ case "closed":
223
+ return `Publication ${publication.id} covers the Task head but is closed without merge.`;
224
+ case "merged":
225
+ return `Publication ${publication.id} reports the exact Task head as merged.`;
226
+ case "verified":
227
+ return `Publication ${publication.id} verifies the exact Task head as merged.`;
228
+ }
229
+ }
230
+ function compareCreated(left, right) {
231
+ return left.createdAt.localeCompare(right.createdAt)
232
+ || left.id.localeCompare(right.id, undefined, { numeric: true });
233
+ }
package/dist/task/task.js CHANGED
@@ -2,7 +2,7 @@ import { validateTaskWorkspaceIdentity } from "../repository/taskWorkspaceIdenti
2
2
  export function createTask(id, title, now, metadata = {}) {
3
3
  const timestamp = now.toISOString();
4
4
  return {
5
- schemaVersion: 6,
5
+ schemaVersion: 7,
6
6
  id: requireSafeIdentity(id, "Task id"),
7
7
  title: requireText(title, "Task title"),
8
8
  ...cloneMetadata(metadata),
@@ -49,6 +49,89 @@ export function activateTask(task, now) {
49
49
  return task;
50
50
  return { ...task, status: "active", updatedAt: now.toISOString() };
51
51
  }
52
+ /**
53
+ * Freeze the exact remote inputs adopted by activation. Every Project must be
54
+ * present exactly once; Draft metadata remains declarative until this call.
55
+ */
56
+ export function bindTaskProjectCommits(task, commits, now) {
57
+ validateTask(task);
58
+ if (task.status !== "draft") {
59
+ throw new Error(`Task Project commits can only be bound during Draft activation: ${task.id}.`);
60
+ }
61
+ const byProject = new Map(commits.map(({ projectId, commit }) => [
62
+ requireSafeIdentity(projectId, "Project id"),
63
+ requireCommit(commit, "Task Project commit")
64
+ ]));
65
+ if (byProject.size !== commits.length
66
+ || byProject.size !== task.projectBindings.length
67
+ || task.projectBindings.some(({ projectId }) => !byProject.has(projectId))) {
68
+ throw new Error(`Task Project commit scope does not match its bindings: ${task.id}.`);
69
+ }
70
+ return validateTask({
71
+ ...task,
72
+ projectBindings: task.projectBindings.map((binding) => {
73
+ const commit = byProject.get(binding.projectId);
74
+ return { ...binding, baseCommit: commit, currentCommit: commit };
75
+ }),
76
+ updatedAt: now.toISOString()
77
+ });
78
+ }
79
+ /** Compare-and-swap one authoritative Task-main Project commit. */
80
+ export function advanceTaskProjectCommit(task, projectId, expectedCommit, nextCommit, now) {
81
+ validateTask(task);
82
+ const expected = requireCommit(expectedCommit, "Expected Task Project commit");
83
+ const next = requireCommit(nextCommit, "Next Task Project commit");
84
+ let found = false;
85
+ const projectBindings = task.projectBindings.map((binding) => {
86
+ if (binding.projectId !== projectId)
87
+ return binding;
88
+ found = true;
89
+ if (binding.currentCommit !== expected) {
90
+ throw new Error(`Task Project current commit moved: ${task.id}/${projectId}; `
91
+ + `expected ${expected}, found ${String(binding.currentCommit)}.`);
92
+ }
93
+ return { ...binding, currentCommit: next };
94
+ });
95
+ if (!found)
96
+ throw new Error(`Task Project binding not found: ${task.id}/${projectId}.`);
97
+ return validateTask({
98
+ ...task,
99
+ projectBindings,
100
+ updatedAt: now.toISOString()
101
+ });
102
+ }
103
+ /**
104
+ * Refresh Task commit facts from its authoritative main clones. An active
105
+ * Task may persist a newly added Project as pending intent before its first
106
+ * workspace prepare; that first successful prepare adopts the clone's HEAD as
107
+ * both the Project baseline and current result.
108
+ */
109
+ export function synchronizeTaskProjectCommits(task, commits, now) {
110
+ validateTask(task);
111
+ const byProject = new Map(commits.map(({ projectId, commit }) => [
112
+ requireSafeIdentity(projectId, "Project id"),
113
+ requireCommit(commit, "Task Project current commit")
114
+ ]));
115
+ if (byProject.size !== commits.length
116
+ || byProject.size !== task.projectBindings.length
117
+ || task.projectBindings.some(({ projectId }) => !byProject.has(projectId))) {
118
+ throw new Error(`Task Project commit scope does not match its bindings: ${task.id}.`);
119
+ }
120
+ const projectBindings = task.projectBindings.map((binding) => {
121
+ const commit = byProject.get(binding.projectId);
122
+ if (binding.baseCommit === undefined) {
123
+ return { ...binding, baseCommit: commit, currentCommit: commit };
124
+ }
125
+ return { ...binding, currentCommit: commit };
126
+ });
127
+ if (projectBindings.every((binding, index) => (binding.currentCommit === task.projectBindings[index]?.currentCommit)))
128
+ return task;
129
+ return validateTask({
130
+ ...task,
131
+ projectBindings,
132
+ updatedAt: now.toISOString()
133
+ });
134
+ }
52
135
  /** Explicitly retires a stale aggregate while retaining all historical facts. */
53
136
  export function retireTask(task, input, now) {
54
137
  validateTask(task);
@@ -200,8 +283,8 @@ export function startTaskExecution(task, now) {
200
283
  });
201
284
  }
202
285
  export function validateTask(task) {
203
- if (task.schemaVersion !== 6)
204
- throw new Error("Task must use schemaVersion 6.");
286
+ if (task.schemaVersion !== 7)
287
+ throw new Error("Task must use schemaVersion 7.");
205
288
  requireSafeIdentity(task.id, "Task id");
206
289
  requireText(task.title, "Task title");
207
290
  if (task.type !== undefined)
@@ -338,6 +421,15 @@ function normalizeProjectBindings(bindings) {
338
421
  const projectId = requireSafeIdentity(binding.projectId, "Project id");
339
422
  const directory = requireSafeIdentity(binding.directory, "Project directory");
340
423
  const baseRef = requireText(binding.baseRef, "Task base ref");
424
+ const baseCommit = binding.baseCommit === undefined
425
+ ? undefined
426
+ : requireCommit(binding.baseCommit, "Task Project base commit");
427
+ const currentCommit = binding.currentCommit === undefined
428
+ ? undefined
429
+ : requireCommit(binding.currentCommit, "Task Project current commit");
430
+ if ((baseCommit === undefined) !== (currentCommit === undefined)) {
431
+ throw new Error(`Task Project commit metadata must include both baseCommit and currentCommit: ${projectId}.`);
432
+ }
341
433
  if (projectIds.has(projectId)) {
342
434
  throw new Error(`Task Project is duplicated: ${projectId}.`);
343
435
  }
@@ -346,9 +438,21 @@ function normalizeProjectBindings(bindings) {
346
438
  }
347
439
  projectIds.add(projectId);
348
440
  directories.add(directory);
349
- return { projectId, directory, baseRef };
441
+ return {
442
+ projectId,
443
+ directory,
444
+ baseRef,
445
+ ...(baseCommit === undefined ? {} : { baseCommit, currentCommit })
446
+ };
350
447
  });
351
448
  }
449
+ function requireCommit(value, label) {
450
+ const normalized = requireText(value, label).toLowerCase();
451
+ if (!/^[a-f0-9]{40}(?:[a-f0-9]{24})?$/u.test(normalized)) {
452
+ throw new Error(`${label} is invalid.`);
453
+ }
454
+ return normalized;
455
+ }
352
456
  export function taskProjectBinding(task, projectId) {
353
457
  return task.projectBindings.find((binding) => binding.projectId === projectId);
354
458
  }
@@ -4,7 +4,6 @@ export const TASK_RECORD_ID_PREFIXES = {
4
4
  contextSnapshot: "context-snapshot",
5
5
  turn: "turn",
6
6
  reviewRound: "review-round",
7
- reviewFinding: "review-finding",
8
7
  changeSet: "change-set",
9
8
  integrationAttempt: "integration",
10
9
  integrationQueue: "integration-queue",
package/dist/turn/turn.js CHANGED
@@ -2,6 +2,8 @@ import { validateEffectiveLaunchSnapshot } from "../executor/effectiveLaunch.js"
2
2
  import { validateTaskRecordReference } from "../task/taskRecordReference.js";
3
3
  import { validateManagedWorkspace } from "../worktree/managedWorkspace.js";
4
4
  import { createTurnInput, createTurnInputEnvelope, validateTurnInput } from "../context/turnInputContract.js";
5
+ import { boundedTurnFailureDiagnostic, MAX_TURN_FAILURE_DIAGNOSTIC_BYTES, MAX_TURN_RESULT_OUTPUT_BYTES } from "../domain/agentResultTransport.js";
6
+ export { boundedTurnFailureDiagnostic, MAX_TURN_FAILURE_DIAGNOSTIC_BYTES, MAX_TURN_RESULT_OUTPUT_BYTES, transportAgentResult } from "../domain/agentResultTransport.js";
5
7
  export function createTurn(id, taskId, roleName, mode, input, now, context) {
6
8
  if (mode !== "new" && mode !== "resume") {
7
9
  throw new Error(`Turn dispatch mode is invalid: ${mode}.`);
@@ -9,7 +11,7 @@ export function createTurn(id, taskId, roleName, mode, input, now, context) {
9
11
  const timestamp = now.toISOString();
10
12
  const normalizedInput = validateTurnInput(input);
11
13
  return {
12
- schemaVersion: 1,
14
+ schemaVersion: 5,
13
15
  id: requireSafeIdentity(id, "Turn id"),
14
16
  taskId: requireSafeIdentity(taskId, "Task id"),
15
17
  roleName: requireSafeIdentity(roleName, "Role name"),
@@ -28,6 +30,11 @@ export function createTurn(id, taskId, roleName, mode, input, now, context) {
28
30
  ...(context.executionLaneId === undefined
29
31
  ? {}
30
32
  : { executionLaneId: requireSafeIdentity(context.executionLaneId, "ExecutionLane id") }),
33
+ ...(context.sourceExecutionGroupId === undefined
34
+ ? {}
35
+ : {
36
+ sourceExecutionGroupId: requireSafeIdentity(context.sourceExecutionGroupId, "Source ExecutionGroup id")
37
+ }),
31
38
  ...(context.workspace === undefined
32
39
  ? {}
33
40
  : { workspace: validateManagedWorkspace(context.workspace) }),
@@ -91,6 +98,7 @@ export function validateTurn(run) {
91
98
  "reviewRoundId",
92
99
  "executionGroupId",
93
100
  "executionLaneId",
101
+ "sourceExecutionGroupId",
94
102
  "workspace",
95
103
  "effective",
96
104
  "status",
@@ -98,8 +106,8 @@ export function validateTurn(run) {
98
106
  "createdAt",
99
107
  "updatedAt"
100
108
  ], "Turn");
101
- if (run.schemaVersion !== 1)
102
- throw new Error("Turn must use schemaVersion 1.");
109
+ if (run.schemaVersion !== 5)
110
+ throw new Error("Turn must use schemaVersion 5.");
103
111
  validateTaskRecordReference({ taskId: run.taskId, localId: run.id }, "turn");
104
112
  requireSafeIdentity(run.roleName, "Role name");
105
113
  if (run.mode !== "new" && run.mode !== "resume") {
@@ -133,6 +141,16 @@ export function validateTurn(run) {
133
141
  requireSafeIdentity(run.executionGroupId, "ExecutionGroup id");
134
142
  requireSafeIdentity(run.executionLaneId, "ExecutionLane id");
135
143
  }
144
+ if (run.sourceExecutionGroupId !== undefined) {
145
+ requireSafeIdentity(run.sourceExecutionGroupId, "Source ExecutionGroup id");
146
+ if ((run.purpose === "execution" && run.workItemId === undefined)
147
+ || (run.purpose === "review" && run.reviewRoundId === undefined)) {
148
+ throw new Error("A source ExecutionGroup requires a main execution or review Turn.");
149
+ }
150
+ if (run.executionGroupId !== undefined || run.executionLaneId !== undefined) {
151
+ throw new Error("A main Turn cannot also be an Execution Lane Turn.");
152
+ }
153
+ }
136
154
  if (run.workspace !== undefined) {
137
155
  validateManagedWorkspace(run.workspace);
138
156
  if (run.workspace.owner.taskId !== run.taskId) {
@@ -233,20 +251,31 @@ export function validateTurn(run) {
233
251
  if (!isTurnFailureReason(result.failureReason)) {
234
252
  throw new Error(`Failed Turn reason is invalid: ${String(result.failureReason)}.`);
235
253
  }
254
+ if (result.diagnostic === undefined) {
255
+ throw new Error("A failed Turn requires a Core diagnostic.");
256
+ }
257
+ if (result.systemEvidence !== undefined) {
258
+ throw new Error("A failed Turn cannot carry successful system evidence.");
259
+ }
236
260
  }
237
- else if (result.failureReason !== undefined) {
238
- throw new Error("A completed Turn cannot carry a failure reason.");
261
+ else {
262
+ if (result.output === undefined) {
263
+ throw new Error("A completed Turn requires an Agent result.");
264
+ }
265
+ if (result.failureReason !== undefined || result.diagnostic !== undefined) {
266
+ throw new Error("A completed Turn cannot carry failure metadata.");
267
+ }
239
268
  }
240
269
  }
241
270
  return run;
242
271
  }
243
- export function completeTurn(run, output, now, provider) {
244
- return finishTurn(run, "completed", output, now, undefined, provider);
272
+ export function completeTurn(run, output, now, provider, systemEvidence) {
273
+ return finishTurn(run, "completed", output, now, undefined, provider, systemEvidence);
245
274
  }
246
- export function failTurn(run, reason, output, now, provider) {
247
- return finishTurn(run, "failed", output, now, reason, provider);
275
+ export function failTurn(run, reason, diagnostic, now, provider, output) {
276
+ return finishTurn(run, "failed", output, now, reason, provider, undefined, boundedTurnFailureDiagnostic(diagnostic));
248
277
  }
249
- function finishTurn(run, status, output, now, failureReason, provider) {
278
+ function finishTurn(run, status, output, now, failureReason, provider, systemEvidence, diagnostic) {
250
279
  if (run.status !== "active") {
251
280
  throw new Error(`Turn is already terminal: ${run.id}.`);
252
281
  }
@@ -255,10 +284,16 @@ function finishTurn(run, status, output, now, failureReason, provider) {
255
284
  ...run,
256
285
  status,
257
286
  result: {
258
- schemaVersion: 1,
259
- output: requireResultText(output, "Turn result output"),
287
+ schemaVersion: 2,
288
+ ...(output === undefined ? {} : { output: requireResultText(output, "Turn result output") }),
289
+ ...(diagnostic === undefined
290
+ ? {}
291
+ : { diagnostic: requireDiagnosticText(diagnostic, "Turn result diagnostic") }),
260
292
  completedAt: timestamp,
261
293
  ...(provider === undefined ? {} : { provider: validateTurnProviderResult(provider) }),
294
+ ...(systemEvidence === undefined
295
+ ? {}
296
+ : { systemEvidence: validateTurnSystemEvidence(systemEvidence) }),
262
297
  ...(failureReason === undefined ? {} : { failureReason })
263
298
  },
264
299
  updatedAt: timestamp,
@@ -266,15 +301,54 @@ function finishTurn(run, status, output, now, failureReason, provider) {
266
301
  return validateTurn(terminal);
267
302
  }
268
303
  function validateTurnResult(result) {
269
- if (result === undefined || result.schemaVersion !== 1) {
270
- throw new Error("A terminal Turn requires TurnResult schemaVersion 1.");
304
+ if (result === undefined || result.schemaVersion !== 2) {
305
+ throw new Error("A terminal Turn requires TurnResult schemaVersion 2.");
306
+ }
307
+ rejectUnknownFields(result, [
308
+ "schemaVersion",
309
+ "output",
310
+ "diagnostic",
311
+ "completedAt",
312
+ "provider",
313
+ "systemEvidence",
314
+ "failureReason"
315
+ ], "Turn result");
316
+ if (result.output !== undefined)
317
+ requireResultText(result.output, "Turn result output");
318
+ if (result.diagnostic !== undefined) {
319
+ requireDiagnosticText(result.diagnostic, "Turn result diagnostic");
271
320
  }
272
- requireResultText(result.output, "Turn result output");
273
321
  requireTimestamp(result.completedAt, "Turn result completedAt");
274
322
  if (result.provider !== undefined)
275
323
  validateTurnProviderResult(result.provider);
324
+ if (result.systemEvidence !== undefined)
325
+ validateTurnSystemEvidence(result.systemEvidence);
276
326
  return result;
277
327
  }
328
+ function validateTurnSystemEvidence(evidence) {
329
+ rejectUnknownFields(evidence, [
330
+ "workspaceSnapshot"
331
+ ], "Turn system evidence");
332
+ if (evidence.workspaceSnapshot !== undefined) {
333
+ const snapshot = evidence.workspaceSnapshot;
334
+ if (snapshot.schemaVersion !== 1 || !Array.isArray(snapshot.projects)) {
335
+ throw new Error("Turn workspace snapshot is invalid.");
336
+ }
337
+ const projectIds = new Set();
338
+ for (const project of snapshot.projects) {
339
+ requireSafeIdentity(project.projectId, "Turn workspace snapshot Project id");
340
+ if (projectIds.has(project.projectId)) {
341
+ throw new Error("Turn workspace snapshot Project ids must be unique.");
342
+ }
343
+ projectIds.add(project.projectId);
344
+ if (!/^[0-9a-f]{40}$/u.test(project.headCommit)) {
345
+ throw new Error("Turn workspace snapshot commit is invalid.");
346
+ }
347
+ requireText(project.branch, "Turn workspace snapshot branch");
348
+ }
349
+ }
350
+ return evidence;
351
+ }
278
352
  function validateTurnProviderResult(provider) {
279
353
  requireText(provider.providerNamespace, "Provider namespace");
280
354
  requireText(provider.accountScope, "Provider account scope");
@@ -306,7 +380,10 @@ function turnEnvelopeContext(run) {
306
380
  ...(run.executionGroupId === undefined ? {} : {
307
381
  executionGroupId: run.executionGroupId,
308
382
  executionLaneId: run.executionLaneId
309
- })
383
+ }),
384
+ ...(run.sourceExecutionGroupId === undefined
385
+ ? {}
386
+ : { sourceExecutionGroupId: run.sourceExecutionGroupId })
310
387
  }
311
388
  };
312
389
  }
@@ -332,6 +409,19 @@ function requireResultText(value, label) {
332
409
  throw new Error(`${label} is invalid.`);
333
410
  if (value.trim().length === 0)
334
411
  throw new Error(`${label} is required.`);
412
+ if (Buffer.byteLength(value, "utf8") > MAX_TURN_RESULT_OUTPUT_BYTES) {
413
+ throw new Error(`${label} exceeds ${MAX_TURN_RESULT_OUTPUT_BYTES} bytes.`);
414
+ }
415
+ return value;
416
+ }
417
+ function requireDiagnosticText(value, label) {
418
+ if (typeof value !== "string" || value.includes("\0"))
419
+ throw new Error(`${label} is invalid.`);
420
+ if (value.trim().length === 0)
421
+ throw new Error(`${label} is required.`);
422
+ if (Buffer.byteLength(value, "utf8") > MAX_TURN_FAILURE_DIAGNOSTIC_BYTES) {
423
+ throw new Error(`${label} exceeds ${MAX_TURN_FAILURE_DIAGNOSTIC_BYTES} bytes.`);
424
+ }
335
425
  return value;
336
426
  }
337
427
  function requireTimestamp(value, label) {
@@ -345,6 +435,9 @@ function isTurnFailureReason(value) {
345
435
  "runtime-failed",
346
436
  "delivery-unknown",
347
437
  "missing-result",
438
+ "workspace-unavailable",
439
+ "workspace-dirty",
440
+ "workspace-branch-mismatch",
348
441
  "cancelled"
349
442
  ].includes(String(value));
350
443
  }
@@ -331,32 +331,6 @@ export async function verifyGateArtifactForReview(store, projectId, key, expecte
331
331
  }
332
332
  return { ok: true, artifact };
333
333
  }
334
- /**
335
- * Whether a `gate-artifact:` evidence reference covers the requested check
336
- * commands for an exact candidate commit. Used by the integration queue's
337
- * evidence fence: a passed gate step covers only its own command, and the
338
- * artifact must bind the exact candidate tree.
339
- */
340
- export async function gateArtifactCoversCheckCommands(store, projectId, ref, checkCommands, candidateCommit) {
341
- const key = parseGateArtifactRef(ref);
342
- if (key === undefined)
343
- return false;
344
- const artifact = loadGateArtifact(store, projectId, key);
345
- if (artifact === null || !isReusableGateArtifact(artifact))
346
- return false;
347
- if (artifact.commit !== candidateCommit)
348
- return false;
349
- const logs = store.getGateArtifactLogs(artifact.key);
350
- const verification = verifyGateArtifactLogs(artifact, logs);
351
- if (!verification.ok)
352
- return false;
353
- if (checkCommands.length === 0)
354
- return true;
355
- const covered = new Set(artifact.steps
356
- .filter((step) => step.outcome === "passed")
357
- .map((step) => step.command));
358
- return checkCommands.every((command) => covered.has(command));
359
- }
360
334
  /**
361
335
  * Enforce mode (rollout step 4): reject ad-hoc full-suite shell checks that
362
336
  * duplicate the plan's L2 steps for a configured Project. Explicit targeted