@zq-silk/yui 0.14.0 → 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 (179) hide show
  1. package/ARCHITECTURE.md +64 -35
  2. package/README.md +258 -125
  3. package/dist/agent/argumentPolicy.js +2 -1
  4. package/dist/agent/managedRuntimeEnvironment.js +5 -8
  5. package/dist/cli/commandCatalog.js +74 -109
  6. package/dist/cli/helpRenderer.js +1 -2
  7. package/dist/cli/interactionCandidates.js +10 -0
  8. package/dist/cli/interactionPolicy.js +76 -37
  9. package/dist/cli/roleWizard.js +5 -6
  10. package/dist/cli/updateOrchestrator.js +10 -8
  11. package/dist/cli/updatePorts.js +17 -13
  12. package/dist/cli/upgradeCommand.js +2 -5
  13. package/dist/cli.js +268 -231
  14. package/dist/commands/agentCommands.js +13 -0
  15. package/dist/commands/configCommands.js +7 -19
  16. package/dist/commands/configOverview.js +3 -2
  17. package/dist/commands/durableJobCommands.js +1 -1
  18. package/dist/commands/executionAuditCommands.js +4 -9
  19. package/dist/commands/globalRoleCommands.js +17 -0
  20. package/dist/commands/profileCommands.js +200 -30
  21. package/dist/commands/sessionCommands.js +1 -1
  22. package/dist/commands/taskActor.js +29 -127
  23. package/dist/commands/taskChangeSetCommands.js +1 -3
  24. package/dist/commands/taskCommands.js +1771 -2107
  25. package/dist/commands/taskCompletionGate.js +27 -103
  26. package/dist/commands/taskContextCommand.js +47 -80
  27. package/dist/commands/taskExecutionCommands.js +9 -27
  28. package/dist/commands/taskInputCommands.js +10 -10
  29. package/dist/commands/taskIntegrationCommands.js +69 -61
  30. package/dist/commands/taskIntegrationQueueCommands.js +1 -1
  31. package/dist/commands/taskNextActionCommand.js +8 -29
  32. package/dist/commands/taskOverviewCommand.js +25 -3
  33. package/dist/commands/taskPublicationCommands.js +151 -85
  34. package/dist/commands/taskPublicationVerifyCommand.js +181 -0
  35. package/dist/commands/taskRemoteDeliveryCommand.js +134 -0
  36. package/dist/commands/taskRoleRuntimeStatus.js +5 -6
  37. package/dist/commands/taskUpstreamCommands.js +79 -95
  38. package/dist/config/configCatalog.js +1 -1
  39. package/dist/context/contextSnapshot.js +17 -2
  40. package/dist/context/sessionBootstrapManifest.js +18 -1
  41. package/dist/context/sourceTurnContext.js +30 -0
  42. package/dist/context/turnContextPack.js +107 -48
  43. package/dist/context/turnInputContract.js +11 -4
  44. package/dist/context/wakeNotification.js +84 -22
  45. package/dist/controller/agentRuntimeObserver.js +9 -12
  46. package/dist/controller/clientRuntime.js +13 -13
  47. package/dist/controller/controller.js +11 -11
  48. package/dist/controller/fileSchedulerStoreAdapter.js +262 -219
  49. package/dist/controller/jobControl.js +11 -10
  50. package/dist/controller/resourceInventory.js +6 -6
  51. package/dist/controller/resourceInventoryLinux.js +2 -2
  52. package/dist/controller/runtime.js +18 -61
  53. package/dist/controller/runtimeEventInbox.js +57 -34
  54. package/dist/controller/runtimeEventProcessor.js +36 -96
  55. package/dist/controller/runtimeHookTurnFence.js +33 -57
  56. package/dist/controller/runtimeLaunchCoordinator.js +44 -40
  57. package/dist/controller/runtimeObservationHook.js +18 -12
  58. package/dist/controller/sessionNotify.js +43 -28
  59. package/dist/controller/sessionOwnerReconciliation.js +21 -21
  60. package/dist/controller/structuredProviderObservation.js +13 -16
  61. package/dist/coordination/workMailboxQueue.js +132 -0
  62. package/dist/core/controllerServer.js +1 -0
  63. package/dist/domain/agentResultTransport.js +60 -0
  64. package/dist/execution/executionHealth.js +111 -118
  65. package/dist/execution/reviewMainTurn.js +161 -0
  66. package/dist/execution/workItemExecution.js +454 -0
  67. package/dist/execution/workItemExecutionProjection.js +309 -0
  68. package/dist/execution/workItemMainTurn.js +164 -0
  69. package/dist/executor/agentExecutor.js +19 -19
  70. package/dist/executor/executorRegistry.js +7 -28
  71. package/dist/executor/fileRoleLaunchPlanner.js +51 -83
  72. package/dist/external/githubPublicationVerifier.js +114 -0
  73. package/dist/external/gitlabPublicationVerifier.js +158 -0
  74. package/dist/external/pinnedCommandRunner.js +73 -0
  75. package/dist/integration/changeSetManifest.js +0 -3
  76. package/dist/integration/deliveryObligation.js +43 -6
  77. package/dist/integration/gitIntegrationService.js +284 -101
  78. package/dist/integration/integrationAttempt.js +64 -14
  79. package/dist/integration/integrationQueueEntry.js +5 -35
  80. package/dist/integration/integrationQueueService.js +65 -194
  81. package/dist/lifecycle/canonicalLifecycleEvent.js +16 -15
  82. package/dist/lifecycle/exactTurnTerminalization.js +100 -97
  83. package/dist/message/message.js +16 -0
  84. package/dist/observability/executionAudit.js +4 -25
  85. package/dist/observability/faultClassification.js +58 -70
  86. package/dist/observability/orchestrationMetrics.js +8 -50
  87. package/dist/profile/agentProfile.js +60 -15
  88. package/dist/profile/agentProfileRuntime.js +100 -0
  89. package/dist/release/releaseWorkflowPorts.js +5 -68
  90. package/dist/repository/executionLaneGitSnapshot.js +80 -0
  91. package/dist/repository/gitWorkspace.js +9 -67
  92. package/dist/repository/taskBaseFreshness.js +3 -1
  93. package/dist/repository/taskWorkspaceCoordinator.js +32 -4
  94. package/dist/repository/taskWorkspacePreparer.js +252 -411
  95. package/dist/resources/liveReferences.js +6 -55
  96. package/dist/resources/resourceDiscovery.js +1 -30
  97. package/dist/resources/resourceRegistrar.js +0 -23
  98. package/dist/review/deltaRecheck.js +8 -65
  99. package/dist/review/reviewAcceptance.js +22 -19
  100. package/dist/review/reviewConfig.js +0 -10
  101. package/dist/review/reviewDecision.js +2 -2
  102. package/dist/review/reviewRound.js +147 -273
  103. package/dist/review/reviewerAvailability.js +13 -23
  104. package/dist/role/role.js +3 -0
  105. package/dist/runtime/agentDriverObservation.js +1 -1
  106. package/dist/runtime/agentHost.js +51 -51
  107. package/dist/runtime/builtinAgentDrivers.js +9 -2
  108. package/dist/runtime/codexAppServerRuntime.js +23 -5
  109. package/dist/runtime/exactControlPlane.js +2 -267
  110. package/dist/runtime/firstProgressAdvisory.js +1 -1
  111. package/dist/runtime/index.js +1 -1
  112. package/dist/runtime/launchBroker.js +11 -11
  113. package/dist/runtime/lifecycleReservation.js +2 -2
  114. package/dist/runtime/managedCaller.js +111 -0
  115. package/dist/runtime/ports.js +16 -3
  116. package/dist/runtime/preallocatedNativeSession.js +2 -2
  117. package/dist/runtime/processExitObservation.js +3 -2
  118. package/dist/runtime/providerContinuationReconciliationService.js +1 -1
  119. package/dist/runtime/runtimeBinding.js +1 -1
  120. package/dist/runtime/runtimeObservation.js +33 -16
  121. package/dist/runtime/runtimeSessionCandidate.js +2 -2
  122. package/dist/runtime/runtimeStopReceipt.js +11 -11
  123. package/dist/runtime/sessionLaunchRequest.js +2 -2
  124. package/dist/runtime/sessionOwnerIdentity.js +9 -9
  125. package/dist/runtime/sessionOwnerRegistry.js +13 -13
  126. package/dist/runtime/sessionReconciliation.js +4 -4
  127. package/dist/runtime/sessionTerminationGuard.js +9 -9
  128. package/dist/runtime/sessionTokenMetrics.js +5 -7
  129. package/dist/runtime/structuredProviderHost.js +5 -5
  130. package/dist/runtime/taskRuntimeIsolation.js +24 -24
  131. package/dist/runtime/tmuxAdapters.js +32 -30
  132. package/dist/scheduler/activeRoleTurnDelivery.js +37 -15
  133. package/dist/scheduler/leaderWakeupProcessor.js +78 -55
  134. package/dist/scheduler/roleTurnLiveness.js +6 -6
  135. package/dist/scheduler/roleTurnStall.js +8 -9
  136. package/dist/scheduler/taskExecutionProjection.js +19 -41
  137. package/dist/scheduler/taskObservabilityProjection.js +75 -122
  138. package/dist/storage/sqliteSchema.js +1 -21
  139. package/dist/storage/sqliteStore.js +62 -47
  140. package/dist/storage/storageSchema.js +1 -8
  141. package/dist/storage/storageVersions.js +7 -4
  142. package/dist/storage/taskStore.js +8 -8
  143. package/dist/storage/upgrade/upgradeOrchestrator.js +112 -83
  144. package/dist/task/completionReadiness.js +17 -98
  145. package/dist/task/deliveryGuard.js +9 -11
  146. package/dist/task/draftPlan.js +142 -0
  147. package/dist/task/nextAction.js +179 -477
  148. package/dist/task/publicationVerification.js +1 -0
  149. package/dist/task/remoteDelivery.js +233 -0
  150. package/dist/task/task.js +108 -4
  151. package/dist/task/taskRecordReference.js +0 -1
  152. package/dist/turn/turn.js +109 -16
  153. package/dist/verification/verificationGateService.js +0 -26
  154. package/dist/web/assets/client/components.js +99 -121
  155. package/dist/web/assets/client/i18n.js +136 -58
  156. package/dist/web/assets/client/view.js +44 -0
  157. package/dist/web/assets/styles/cards.js +3 -15
  158. package/dist/web/assets/styles/widgets.js +4 -0
  159. package/dist/web/webSnapshot.js +29 -14
  160. package/dist/workItem/dependencyGate.js +34 -0
  161. package/dist/workItem/workItem.js +99 -266
  162. package/dist/workspace/workItemChangeSetManager.js +15 -59
  163. package/docs/task-local-identity.md +1 -1
  164. package/i18n/README.zh-CN.md +165 -59
  165. package/package.json +1 -1
  166. package/skills/yui-leader/SKILL.md +272 -782
  167. package/skills/yui-operator/SKILL.md +190 -365
  168. package/skills/yui-reviewer/SKILL.md +40 -33
  169. package/skills/yui-runtime/SKILL.md +27 -6
  170. package/skills/yui-worker/SKILL.md +19 -6
  171. package/dist/execution/candidateConvergence.js +0 -623
  172. package/dist/execution/executionGroup.js +0 -854
  173. package/dist/execution/resourceBroker.js +0 -426
  174. package/dist/executor/launchPlan.js +0 -16
  175. package/dist/review/reviewFinding.js +0 -240
  176. package/dist/review/reviewFindingLedger.js +0 -550
  177. package/dist/review/reviewOutcomeClassifier.js +0 -255
  178. package/dist/scheduler/resourceQueueProjection.js +0 -71
  179. package/dist/task/repairWave.js +0 -150
@@ -1,623 +0,0 @@
1
- import { requireIdentity, requireText } from "../domain/validation.js";
2
- export const CANDIDATE_CONVERGENCE_SCHEMA_VERSION = 1;
3
- export const CANDIDATE_ARTIFACT_TYPES = [
4
- "research",
5
- "architecture",
6
- "code"
7
- ];
8
- /** The exact stage-local result contract appended to managed Lane assignments. */
9
- export function candidateConvergenceDirective(stage) {
10
- if (stage.convergence === undefined)
11
- return "";
12
- const parentRefs = stage.parentResults.map(parentResultRef);
13
- const common = [
14
- "Candidate convergence contract v1 is active.",
15
- "Return one JSON object. Keep summary/checks/findings/evidence at the top level and add convergence={schemaVersion:1,stage,artifactType,parentResultRefs,...}.",
16
- `convergence.stage must be ${stage.stage}; parentResultRefs must equal ${JSON.stringify(parentRefs)} in this order.`,
17
- "artifactType is research, architecture, or code and must stay unchanged across the round.",
18
- "Use only frozen parent result references; never replay sibling transcripts. Direct sources, executable checks, and frozen artifacts outrank derived analysis or vote counts."
19
- ];
20
- const specific = {
21
- plan: "Choose artifactType and state the evidence and acceptance plan in the report. Round 1 has no parent results; later rounds must carry the selected Resolve gap results listed above.",
22
- generate: "Add candidate={key,claims:[{id,statement,evidenceRefs}],artifactRefs,assumptions,tradeoffs}. Independent hypotheses may be unsupported here, but every evidenceRef must name top-level evidence.",
23
- compare: "Add comparison={clusters:[{id,resultRefs,rationale}],selectedResultRefs,evidenceBasis:[{resultRef,kind,detail}],contradictions}. Clusters partition every parent exactly once; every selected result needs executable-check, direct-source, or frozen-artifact evidence.",
24
- synthesize: "Add candidate plus synthesis={strategy,sourceResultRefs,unresolvedContradictions}. strategy is claim-evidence-table for research, decision-matrix for architecture, and frozen-code-selection for code. Code selects one frozen tree; do not splice unfrozen edits.",
25
- verify: "Add verification={subjectResultRef,disposition,criteria:[{criterion,status,evidenceRefs}],gaps}. disposition is passed, needs-next-round, or inconclusive. passed requires criteria to uniquely and completely match the frozen WorkItem acceptance (or its objective when acceptance is empty), with every criterion passed with evidence and no gaps; code also requires passed executable checks on the unchanged frozen Git snapshot.",
26
- resolve: "Add resolution={subjectResultRef,disposition,rationale}. disposition=candidate only for a passed Verify parent; otherwise disposition=next-round so the Leader can use Resolve retry within the frozen round budget."
27
- };
28
- return [...common, specific[stage.stage]].join("\n");
29
- }
30
- /**
31
- * Parse one selected Lane's durable JSON report at an exploration boundary.
32
- * Unknown report fields remain preserved in ExecutionLaneResult.report; this
33
- * parser validates only the convergence contract that the control plane can
34
- * prove without pretending to perform semantic comparison itself.
35
- */
36
- export function validateCandidateConvergenceReport(result, stage, parentResults) {
37
- const report = requireText(result.report ?? "", "Candidate convergence report");
38
- let parsed;
39
- try {
40
- parsed = JSON.parse(report);
41
- }
42
- catch {
43
- throw new Error("Candidate convergence report must be a JSON object.");
44
- }
45
- const root = asObject(parsed, "Candidate convergence report");
46
- const envelope = asObject(root.convergence, "Candidate convergence envelope");
47
- if (envelope.schemaVersion !== CANDIDATE_CONVERGENCE_SCHEMA_VERSION) {
48
- throw new Error("Candidate convergence envelope must use schemaVersion 1.");
49
- }
50
- if (envelope.stage !== stage) {
51
- throw new Error(`Candidate convergence stage does not match ${stage}.`);
52
- }
53
- if (!CANDIDATE_ARTIFACT_TYPES.includes(envelope.artifactType)) {
54
- throw new Error("Candidate convergence artifactType is invalid.");
55
- }
56
- const expectedParents = parentResults.map(parentResultRef);
57
- const declaredParents = textArray(envelope.parentResultRefs, "Candidate parent result ref");
58
- if (!sameStrings(declaredParents, expectedParents)) {
59
- throw new Error("Candidate convergence parentResultRefs do not match the frozen stage parents.");
60
- }
61
- const base = {
62
- schemaVersion: CANDIDATE_CONVERGENCE_SCHEMA_VERSION,
63
- stage,
64
- artifactType: envelope.artifactType,
65
- parentResultRefs: declaredParents
66
- };
67
- if (stage === "compare") {
68
- return { ...base, comparison: validateComparison(envelope.comparison, expectedParents) };
69
- }
70
- if (stage === "synthesize") {
71
- return {
72
- ...base,
73
- candidate: validateCandidate(envelope.candidate, envelope.artifactType, result, true),
74
- synthesis: validateSynthesis(envelope.synthesis, envelope.artifactType, expectedParents)
75
- };
76
- }
77
- if (stage === "verify") {
78
- return {
79
- ...base,
80
- verification: validateVerification(envelope.verification, envelope.artifactType, expectedParents, result)
81
- };
82
- }
83
- if (stage === "generate") {
84
- return {
85
- ...base,
86
- candidate: validateCandidate(envelope.candidate, envelope.artifactType, result, false)
87
- };
88
- }
89
- if (stage === "resolve") {
90
- const resolution = validateResolutionPayload(envelope.resolution, expectedParents);
91
- return {
92
- ...base,
93
- resolution
94
- };
95
- }
96
- return base;
97
- }
98
- /**
99
- * Verify runs must not reuse a Role that authored the selected synthesized
100
- * subject. Task Roles own distinct native Sessions, so this direct-parent
101
- * check supplies the minimum provider-neutral independence boundary without
102
- * excluding independent generators or critics from later verification.
103
- */
104
- export function assertIndependentVerificationRoles(workItem, stage, roleNames) {
105
- if (stage.stage !== "verify" || stage.convergence === undefined)
106
- return;
107
- const forbidden = new Set(stage.parentResults.map((parent) => (requireParentLane(workItem, parent).lane.roleName)));
108
- const reused = roleNames.filter((roleName) => forbidden.has(roleName));
109
- if (reused.length > 0) {
110
- throw new Error(`Verify Roles must be independent from selected Synthesize authors in round ${stage.round}: `
111
- + reused.join(", "));
112
- }
113
- }
114
- /**
115
- * Validate the Leader's durable Group decision against the selected stage
116
- * reports. Semantic judgment remains with Agents and Leader; only structural
117
- * evidence, independence and passed-vs-next-round boundaries are enforced.
118
- */
119
- export function validateCandidateConvergenceResolution(workItem, group) {
120
- const stage = group.stage;
121
- const resolution = group.resolution;
122
- if (stage?.convergence === undefined || resolution === undefined)
123
- return;
124
- if (resolution.decision !== "accept"
125
- && !(stage.stage === "resolve" && resolution.decision === "retry")) {
126
- return;
127
- }
128
- const selected = resolution.selectedLaneIds.map((laneId) => {
129
- const lane = group.lanes.find(({ id }) => id === laneId);
130
- if (lane?.result === undefined) {
131
- throw new Error(`Selected convergence Lane result is missing: ${group.id}/${laneId}.`);
132
- }
133
- return {
134
- lane,
135
- envelope: validateCandidateConvergenceReport(lane.result, stage.stage, stage.parentResults)
136
- };
137
- });
138
- const parentTypes = stage.parentResults.map((parent) => {
139
- const source = requireParentLane(workItem, parent);
140
- if (source.group.stage?.convergence === undefined || source.lane.result === undefined) {
141
- throw new Error(`Candidate convergence parent is not structured: ${parentResultRef(parent)}.`);
142
- }
143
- return validateCandidateConvergenceReport(source.lane.result, source.group.stage.stage, source.group.stage.parentResults).artifactType;
144
- });
145
- const artifactTypes = new Set([
146
- ...parentTypes,
147
- ...selected.map(({ envelope }) => envelope.artifactType)
148
- ]);
149
- if (artifactTypes.size > 1) {
150
- throw new Error("Candidate convergence cannot mix artifact types across a stage boundary.");
151
- }
152
- if (stage.stage === "compare") {
153
- for (const { envelope } of selected) {
154
- for (const basis of envelope.comparison.evidenceBasis) {
155
- if (basis.kind === "derived-analysis")
156
- continue;
157
- const evidence = resultForRef(workItem, basis.resultRef);
158
- const supported = basis.kind === "executable-check"
159
- ? (evidence.checks ?? []).some(({ outcome }) => outcome === "passed")
160
- : basis.kind === "direct-source"
161
- ? (evidence.evidence?.length ?? 0) > 0
162
- : evidence.gitSnapshot !== undefined || evidence.evidenceCommit !== undefined;
163
- if (!supported) {
164
- throw new Error(`Candidate comparison ${basis.kind} is not supported by ${basis.resultRef}.`);
165
- }
166
- }
167
- if (envelope.artifactType === "code"
168
- && envelope.comparison.selectedResultRefs.length !== 1) {
169
- throw new Error("Code comparison must select one frozen candidate before synthesis.");
170
- }
171
- }
172
- }
173
- if (stage.stage === "synthesize") {
174
- for (const { lane, envelope } of selected) {
175
- if (envelope.artifactType !== "code")
176
- continue;
177
- const selectedGenerateRefs = envelope.synthesis.sourceResultRefs.flatMap((sourceRef) => {
178
- const comparison = convergenceForRef(workItem, sourceRef).comparison;
179
- if (comparison === undefined) {
180
- throw new Error("Code synthesis source is not a Compare result.");
181
- }
182
- return comparison.selectedResultRefs;
183
- });
184
- const uniqueGenerateRefs = [...new Set(selectedGenerateRefs)];
185
- if (uniqueGenerateRefs.length !== 1) {
186
- throw new Error("Code synthesis must inherit one uniquely selected frozen candidate.");
187
- }
188
- assertSameGitSnapshot(lane.result.gitSnapshot, resultForRef(workItem, uniqueGenerateRefs[0]).gitSnapshot, "Synthesized code Candidate");
189
- }
190
- }
191
- if (stage.stage === "verify") {
192
- for (const { lane, envelope } of selected) {
193
- if (envelope.verification.disposition === "passed") {
194
- assertVerificationCoversAcceptance(workItem, envelope.verification.criteria);
195
- }
196
- if (envelope.artifactType === "code"
197
- && envelope.verification.disposition === "passed") {
198
- assertSameGitSnapshot(lane.result.gitSnapshot, resultForRef(workItem, envelope.verification.subjectResultRef).gitSnapshot, "Passed code verification");
199
- }
200
- }
201
- }
202
- if (stage.stage !== "resolve")
203
- return;
204
- if (resolution.decision === "accept" && selected.length !== 1) {
205
- throw new Error("Candidate convergence Resolve must accept exactly one Lane result.");
206
- }
207
- for (const { lane, envelope } of selected) {
208
- const verification = convergenceForRef(workItem, envelope.resolution.subjectResultRef);
209
- if (verification.verification === undefined) {
210
- throw new Error("Resolve subject is not a Verify result.");
211
- }
212
- if (resolution.decision === "accept") {
213
- if (envelope.resolution.disposition !== "candidate"
214
- || verification.verification.disposition !== "passed") {
215
- throw new Error("Resolve cannot accept a Candidate without passed verification.");
216
- }
217
- if (envelope.artifactType === "code") {
218
- assertSameGitSnapshot(lane.result.gitSnapshot, resultForRef(workItem, envelope.resolution.subjectResultRef).gitSnapshot, "Resolved code Candidate");
219
- }
220
- continue;
221
- }
222
- if (envelope.resolution.disposition !== "next-round"
223
- || verification.verification.disposition === "passed") {
224
- throw new Error("Resolve retry requires a non-passing Verify result and next-round disposition.");
225
- }
226
- }
227
- }
228
- /**
229
- * Acceptance-level sufficiency proof used by T6 early termination. Earlier
230
- * stages can satisfy their local quorum, but only passed Verify evidence (or a
231
- * Resolve Candidate backed by it) may authorize stopping for cost reasons.
232
- */
233
- export function candidateConvergenceEvidenceSufficient(workItem, group, selectedLaneIds) {
234
- const stage = group.stage;
235
- if (stage?.convergence === undefined
236
- || (stage.stage !== "verify" && stage.stage !== "resolve")
237
- || selectedLaneIds.length === 0)
238
- return false;
239
- const usable = group.lanes.filter(({ status }) => status === "completed");
240
- if (selectedLaneIds.some((laneId) => !usable.some(({ id }) => id === laneId)))
241
- return false;
242
- // Cost policy may never hide contradictory evidence that has already
243
- // arrived. Every usable output must independently clear the same acceptance
244
- // boundary before never-started siblings can be skipped.
245
- return usable.length > 0 && usable.every((lane) => {
246
- if (lane.result === undefined)
247
- return false;
248
- try {
249
- const envelope = validateCandidateConvergenceReport(lane.result, stage.stage, stage.parentResults);
250
- if (stage.stage === "verify") {
251
- if (envelope.verification?.disposition !== "passed")
252
- return false;
253
- assertVerificationCoversAcceptance(workItem, envelope.verification.criteria);
254
- if (envelope.artifactType === "code") {
255
- assertSameGitSnapshot(lane.result.gitSnapshot, resultForRef(workItem, envelope.verification.subjectResultRef).gitSnapshot, "Passed code verification");
256
- }
257
- return true;
258
- }
259
- if (envelope.resolution?.disposition !== "candidate")
260
- return false;
261
- const verification = convergenceForRef(workItem, envelope.resolution.subjectResultRef);
262
- if (verification.verification?.disposition !== "passed")
263
- return false;
264
- assertVerificationCoversAcceptance(workItem, verification.verification.criteria);
265
- if (envelope.artifactType === "code") {
266
- assertSameGitSnapshot(lane.result.gitSnapshot, resultForRef(workItem, envelope.resolution.subjectResultRef).gitSnapshot, "Resolved code Candidate");
267
- }
268
- return true;
269
- }
270
- catch {
271
- return false;
272
- }
273
- });
274
- }
275
- /**
276
- * Project only disagreement that structured convergence results make durable.
277
- * Unknown or malformed reports never manufacture an expansion signal.
278
- */
279
- export function candidateConvergenceDisagreement(group) {
280
- const stage = group.stage;
281
- if (stage?.convergence === undefined || stage.stage === "plan")
282
- return "unknown";
283
- const envelopes = usableCandidateConvergenceEnvelopes(group);
284
- if (envelopes === null || envelopes.length === 0)
285
- return "unknown";
286
- if (stage.stage === "generate") {
287
- return new Set(envelopes.map(({ candidate }) => candidate.key)).size > 1
288
- ? "high"
289
- : "low";
290
- }
291
- if (stage.stage === "compare") {
292
- return envelopes.some(({ comparison }) => (comparison.contradictions.length > 0 || comparison.clusters.length > 1)) || new Set(envelopes.map(({ comparison }) => JSON.stringify([...comparison.selectedResultRefs].sort()))).size > 1 ? "high" : "low";
293
- }
294
- if (stage.stage === "synthesize") {
295
- return envelopes.some(({ synthesis }) => synthesis.unresolvedContradictions.length > 0)
296
- || new Set(envelopes.map(({ candidate }) => candidate.key)).size > 1
297
- ? "high"
298
- : "low";
299
- }
300
- if (stage.stage === "verify") {
301
- const signatures = envelopes.map(({ verification }) => JSON.stringify({
302
- disposition: verification.disposition,
303
- criteria: verification.criteria.map(({ criterion, status }) => ({ criterion, status }))
304
- .sort((left, right) => left.criterion.localeCompare(right.criterion)),
305
- gaps: [...verification.gaps].sort()
306
- }));
307
- return new Set(signatures).size > 1 ? "high" : "low";
308
- }
309
- const dispositions = envelopes.map(({ resolution }) => resolution.disposition);
310
- return new Set(dispositions).size > 1 ? "high" : "low";
311
- }
312
- /** True only when every usable structured Lane result satisfies its frozen stage contract. */
313
- export function candidateConvergenceStageResultsValid(group) {
314
- if (group.stage?.convergence === undefined)
315
- return true;
316
- const envelopes = usableCandidateConvergenceEnvelopes(group);
317
- return envelopes !== null && envelopes.length > 0;
318
- }
319
- function usableCandidateConvergenceEnvelopes(group) {
320
- const stage = group.stage;
321
- if (stage?.convergence === undefined)
322
- return [];
323
- const usable = group.lanes.filter(({ status, result }) => (status === "completed" && result !== undefined));
324
- try {
325
- return usable.map(({ result }) => validateCandidateConvergenceReport(result, stage.stage, stage.parentResults));
326
- }
327
- catch {
328
- return null;
329
- }
330
- }
331
- function assertVerificationCoversAcceptance(workItem, criteria) {
332
- const requiredCriteria = workItem.acceptance.length === 0
333
- ? [workItem.objective]
334
- : workItem.acceptance;
335
- const declaredCriteria = criteria.map(({ criterion }) => criterion);
336
- if (new Set(declaredCriteria).size !== declaredCriteria.length
337
- || !sameStringSets(declaredCriteria, requiredCriteria)) {
338
- throw new Error("Passed verification criteria must uniquely and completely match the frozen WorkItem acceptance.");
339
- }
340
- }
341
- function validateComparison(value, expectedParents) {
342
- const comparison = asObject(value, "Candidate comparison");
343
- if (!Array.isArray(comparison.clusters) || comparison.clusters.length === 0) {
344
- throw new Error("Candidate comparison requires explicit clusters.");
345
- }
346
- const clusterIds = new Set();
347
- const clusteredRefs = [];
348
- const clusters = comparison.clusters.map((value, index) => {
349
- const cluster = asObject(value, `Candidate cluster ${index + 1}`);
350
- const id = requireIdentity(requiredText(cluster.id, "Candidate cluster id"), "Candidate cluster id");
351
- if (clusterIds.has(id))
352
- throw new Error(`Candidate cluster is duplicated: ${id}.`);
353
- clusterIds.add(id);
354
- const resultRefs = textArray(cluster.resultRefs, "Candidate cluster result ref");
355
- if (resultRefs.length === 0)
356
- throw new Error(`Candidate cluster is empty: ${id}.`);
357
- clusteredRefs.push(...resultRefs);
358
- return {
359
- id,
360
- resultRefs,
361
- rationale: requiredText(cluster.rationale, "Candidate cluster rationale")
362
- };
363
- });
364
- if (new Set(clusteredRefs).size !== clusteredRefs.length
365
- || !sameStringSets(clusteredRefs, expectedParents)) {
366
- throw new Error("Candidate comparison clusters must partition every frozen parent result exactly once.");
367
- }
368
- const selectedResultRefs = textArray(comparison.selectedResultRefs, "Selected candidate result ref");
369
- if (selectedResultRefs.length === 0
370
- || new Set(selectedResultRefs).size !== selectedResultRefs.length
371
- || selectedResultRefs.some((ref) => !expectedParents.includes(ref))) {
372
- throw new Error("Candidate comparison selectedResultRefs are invalid.");
373
- }
374
- if (!Array.isArray(comparison.evidenceBasis)) {
375
- throw new Error("Candidate comparison evidenceBasis is invalid.");
376
- }
377
- const evidenceBasis = comparison.evidenceBasis.map((value, index) => {
378
- const basis = asObject(value, `Candidate evidence basis ${index + 1}`);
379
- const resultRef = requiredText(basis.resultRef, "Candidate evidence result ref");
380
- if (!expectedParents.includes(resultRef)) {
381
- throw new Error(`Candidate evidence references an unknown result: ${resultRef}.`);
382
- }
383
- if (basis.kind !== "executable-check"
384
- && basis.kind !== "direct-source"
385
- && basis.kind !== "frozen-artifact"
386
- && basis.kind !== "derived-analysis") {
387
- throw new Error("Candidate evidence kind is invalid.");
388
- }
389
- return {
390
- resultRef,
391
- kind: basis.kind,
392
- detail: requiredText(basis.detail, "Candidate evidence detail")
393
- };
394
- });
395
- for (const selected of selectedResultRefs) {
396
- const direct = evidenceBasis.some(({ resultRef, kind }) => (resultRef === selected && kind !== "derived-analysis"));
397
- if (!direct) {
398
- throw new Error(`Selected candidate ${selected} requires direct or executable evidence; `
399
- + "derived analysis or voting alone is insufficient.");
400
- }
401
- }
402
- return {
403
- clusters,
404
- selectedResultRefs,
405
- evidenceBasis,
406
- contradictions: textArray(comparison.contradictions, "Candidate contradiction")
407
- };
408
- }
409
- function validateCandidate(value, artifactType, result, requireEvidence) {
410
- const candidate = asObject(value, "Normalized candidate");
411
- if (!Array.isArray(candidate.claims) || candidate.claims.length === 0) {
412
- throw new Error("Normalized candidate requires material claims.");
413
- }
414
- const evidence = new Set(result.evidence ?? []);
415
- const claimIds = new Set();
416
- const claims = candidate.claims.map((value, index) => {
417
- const claim = asObject(value, `Candidate claim ${index + 1}`);
418
- const id = requireIdentity(requiredText(claim.id, "Candidate claim id"), "Candidate claim id");
419
- if (claimIds.has(id))
420
- throw new Error(`Candidate claim is duplicated: ${id}.`);
421
- claimIds.add(id);
422
- const evidenceRefs = textArray(claim.evidenceRefs, "Candidate claim evidence ref");
423
- if (requireEvidence && evidenceRefs.length === 0) {
424
- throw new Error(`Synthesized candidate claim requires evidence: ${id}.`);
425
- }
426
- if (evidenceRefs.some((ref) => !evidence.has(ref))) {
427
- throw new Error(`Candidate claim references unavailable evidence: ${id}.`);
428
- }
429
- return {
430
- id,
431
- statement: requiredText(claim.statement, "Candidate claim statement"),
432
- evidenceRefs
433
- };
434
- });
435
- const artifactRefs = textArray(candidate.artifactRefs, "Candidate artifact ref");
436
- if (artifactRefs.length === 0)
437
- throw new Error("Normalized candidate requires artifactRefs.");
438
- if (artifactType === "code" && result.gitSnapshot === undefined) {
439
- throw new Error("A code candidate requires a frozen Git snapshot.");
440
- }
441
- return {
442
- key: requiredText(candidate.key, "Candidate key"),
443
- claims,
444
- artifactRefs,
445
- assumptions: optionalTextArray(candidate.assumptions, "Candidate assumption"),
446
- tradeoffs: optionalTextArray(candidate.tradeoffs, "Candidate tradeoff")
447
- };
448
- }
449
- function validateResolutionPayload(value, expectedParents) {
450
- const resolution = asObject(value, "Candidate resolution");
451
- const subjectResultRef = requiredText(resolution.subjectResultRef, "Candidate resolution subject result ref");
452
- if (!expectedParents.includes(subjectResultRef)) {
453
- throw new Error("Candidate resolution subjectResultRef is not a frozen parent result.");
454
- }
455
- if (resolution.disposition !== "candidate" && resolution.disposition !== "next-round") {
456
- throw new Error("Candidate resolution disposition is invalid.");
457
- }
458
- return {
459
- subjectResultRef,
460
- disposition: resolution.disposition,
461
- rationale: requiredText(resolution.rationale, "Candidate resolution rationale")
462
- };
463
- }
464
- function validateSynthesis(value, artifactType, expectedParents) {
465
- const synthesis = asObject(value, "Candidate synthesis");
466
- const expectedStrategy = artifactType === "research"
467
- ? "claim-evidence-table"
468
- : artifactType === "architecture"
469
- ? "decision-matrix"
470
- : "frozen-code-selection";
471
- if (synthesis.strategy !== expectedStrategy) {
472
- throw new Error(`Candidate ${artifactType} synthesis must use ${expectedStrategy}.`);
473
- }
474
- const sourceResultRefs = textArray(synthesis.sourceResultRefs, "Candidate synthesis source result ref");
475
- if (sourceResultRefs.length === 0
476
- || new Set(sourceResultRefs).size !== sourceResultRefs.length
477
- || sourceResultRefs.some((ref) => !expectedParents.includes(ref))) {
478
- throw new Error("Candidate synthesis sourceResultRefs are invalid.");
479
- }
480
- return {
481
- strategy: expectedStrategy,
482
- sourceResultRefs,
483
- unresolvedContradictions: textArray(synthesis.unresolvedContradictions, "Candidate synthesis unresolved contradiction")
484
- };
485
- }
486
- function validateVerification(value, artifactType, expectedParents, result) {
487
- const verification = asObject(value, "Candidate verification");
488
- const subjectResultRef = requiredText(verification.subjectResultRef, "Candidate verification subject result ref");
489
- if (!expectedParents.includes(subjectResultRef)) {
490
- throw new Error("Candidate verification subjectResultRef is not a frozen parent result.");
491
- }
492
- if (verification.disposition !== "passed"
493
- && verification.disposition !== "needs-next-round"
494
- && verification.disposition !== "inconclusive") {
495
- throw new Error("Candidate verification disposition is invalid.");
496
- }
497
- if (!Array.isArray(verification.criteria) || verification.criteria.length === 0) {
498
- throw new Error("Candidate verification requires acceptance criteria.");
499
- }
500
- const availableEvidence = new Set([
501
- ...(result.evidence ?? []),
502
- ...(result.checks ?? [])
503
- .filter(({ outcome }) => outcome === "passed")
504
- .map(({ name }) => name)
505
- ]);
506
- const criteria = verification.criteria.map((value, index) => {
507
- const criterion = asObject(value, `Candidate verification criterion ${index + 1}`);
508
- if (criterion.status !== "passed"
509
- && criterion.status !== "failed"
510
- && criterion.status !== "unverified") {
511
- throw new Error("Candidate verification criterion status is invalid.");
512
- }
513
- const evidenceRefs = textArray(criterion.evidenceRefs, "Candidate verification criterion evidence ref");
514
- if (evidenceRefs.some((ref) => !availableEvidence.has(ref))) {
515
- throw new Error("Candidate verification criterion references unavailable evidence.");
516
- }
517
- return {
518
- criterion: requiredText(criterion.criterion, "Candidate verification criterion"),
519
- status: criterion.status,
520
- evidenceRefs
521
- };
522
- });
523
- const gaps = textArray(verification.gaps, "Candidate verification gap");
524
- if (verification.disposition === "passed") {
525
- if (gaps.length > 0
526
- || criteria.some(({ status, evidenceRefs }) => status !== "passed" || evidenceRefs.length === 0)
527
- || (result.checks ?? []).some(({ outcome }) => outcome !== "passed")
528
- || (result.findings ?? []).some(({ status, severity }) => (status === "open" && (severity === "high" || severity === "critical")))) {
529
- throw new Error("A passed verification requires complete criterion evidence and no gaps or failed checks.");
530
- }
531
- if (artifactType === "code"
532
- && ((result.checks?.length ?? 0) === 0 || result.gitSnapshot === undefined)) {
533
- throw new Error("Passed code verification requires executable checks and a frozen Git snapshot.");
534
- }
535
- }
536
- else if (gaps.length === 0
537
- || criteria.every(({ status }) => status === "passed")) {
538
- throw new Error("A non-passing verification must identify a bounded gap for the next round.");
539
- }
540
- return {
541
- subjectResultRef,
542
- disposition: verification.disposition,
543
- criteria,
544
- gaps
545
- };
546
- }
547
- function parentResultRef(parent) {
548
- return `${parent.executionGroupId}/${parent.executionLaneId}`;
549
- }
550
- function requireParentLane(workItem, parent) {
551
- const group = workItem.executionGroups.find(({ id }) => id === parent.executionGroupId);
552
- const lane = group?.lanes.find(({ id }) => id === parent.executionLaneId);
553
- if (group === undefined || lane === undefined) {
554
- throw new Error(`Candidate convergence parent result is missing: `
555
- + `${parent.executionGroupId}/${parent.executionLaneId}.`);
556
- }
557
- return { group, lane };
558
- }
559
- function splitResultRef(value) {
560
- const separator = value.indexOf("/");
561
- if (separator <= 0 || separator === value.length - 1 || value.indexOf("/", separator + 1) !== -1) {
562
- throw new Error(`Candidate result ref is invalid: ${value}.`);
563
- }
564
- return {
565
- executionGroupId: value.slice(0, separator),
566
- executionLaneId: value.slice(separator + 1)
567
- };
568
- }
569
- function resultForRef(workItem, value) {
570
- const source = requireParentLane(workItem, splitResultRef(value));
571
- if (source.lane.result === undefined) {
572
- throw new Error(`Candidate convergence result is missing: ${value}.`);
573
- }
574
- return source.lane.result;
575
- }
576
- function convergenceForRef(workItem, value) {
577
- const ref = splitResultRef(value);
578
- const source = requireParentLane(workItem, ref);
579
- if (source.group.stage?.convergence === undefined || source.lane.result === undefined) {
580
- throw new Error(`Candidate convergence result is not structured: ${value}.`);
581
- }
582
- return validateCandidateConvergenceReport(source.lane.result, source.group.stage.stage, source.group.stage.parentResults);
583
- }
584
- function assertSameGitSnapshot(actual, expected, label) {
585
- const normalized = (snapshot) => (snapshot === undefined
586
- ? null
587
- : snapshot.projects
588
- .map(({ projectId, headCommit }) => `${projectId}\0${headCommit}`)
589
- .sort());
590
- const actualProjects = normalized(actual);
591
- const expectedProjects = normalized(expected);
592
- if (actual === undefined
593
- || expected === undefined
594
- || JSON.stringify(actualProjects) !== JSON.stringify(expectedProjects)) {
595
- throw new Error(`${label} must preserve the selected frozen Git snapshot.`);
596
- }
597
- }
598
- function asObject(value, label) {
599
- if (value === null || typeof value !== "object" || Array.isArray(value)) {
600
- throw new Error(`${label} must be an object.`);
601
- }
602
- return value;
603
- }
604
- function textArray(value, label) {
605
- if (!Array.isArray(value))
606
- throw new Error(`${label} list is invalid.`);
607
- return value.map((entry) => requiredText(entry, label));
608
- }
609
- function optionalTextArray(value, label) {
610
- return value === undefined ? [] : textArray(value, label);
611
- }
612
- function requiredText(value, label) {
613
- if (typeof value !== "string")
614
- throw new Error(`${label} must be text.`);
615
- return requireText(value, label);
616
- }
617
- function sameStrings(left, right) {
618
- return left.length === right.length && left.every((value, index) => value === right[index]);
619
- }
620
- function sameStringSets(left, right) {
621
- return left.length === right.length
622
- && [...left].sort().every((value, index) => value === [...right].sort()[index]);
623
- }