@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
@@ -10,10 +10,21 @@ import { resolveTaskRecordReference } from "../task/taskRecordReference.js";
10
10
  const PROVIDERS = new Set(["github", "gitlab"]);
11
11
  const EXTERNAL_KINDS = new Set(["pull-request", "merge-request"]);
12
12
  const STATES = new Set(["open", "merged", "closed"]);
13
+ const INHERITED_METADATA_FIELDS = [
14
+ "externalUrl",
15
+ "title",
16
+ "sourceBranch",
17
+ "targetBranch"
18
+ ];
19
+ const INHERITED_EVIDENCE_FIELDS = [
20
+ "remoteCommit",
21
+ "evidence",
22
+ "mergedAt"
23
+ ];
13
24
  export function runPublicationCommand(args, store, options) {
14
25
  const [command, ...rest] = args;
15
26
  switch (command) {
16
- case "add": return addPublication(rest, store, options);
27
+ case "upsert": return upsertPublication(rest, store, options);
17
28
  case "list": return listPublications(rest, store);
18
29
  case "show": return showPublication(rest, store);
19
30
  default:
@@ -22,11 +33,12 @@ export function runPublicationCommand(args, store, options) {
22
33
  : `Unknown command: task publication ${command}`);
23
34
  }
24
35
  }
25
- function addPublication(args, store, options) {
26
- const usage = "Task publication add usage: yui task publication add <task> --project <project> --provider <github|gitlab> --repository <owner/name> --kind <pull-request|merge-request> --id <external-id> [--url <url>] [--title <text>] [--source-branch <branch>] [--target-branch <branch>] [--local-commit <sha>] [--remote-commit <sha>] [--state <open|merged|closed>] [--reported|--verified] [--evidence <text>] [--supersede <publication-id>] [--merged-at <iso-timestamp>].";
27
- const parsed = parseAddArgs(args, usage);
36
+ function upsertPublication(args, store, options) {
37
+ const usage = "Task publication upsert usage: yui task publication upsert <task> --project <project> --provider <github|gitlab> --repository <owner/name> --kind <pull-request|merge-request> --id <external-id> [--url <url>] [--title <text>] [--source-branch <branch>] [--target-branch <branch>] [--local-commit <sha>] [--remote-commit <sha>] [--state <open|merged|closed>] [--reported|--verified] [--evidence <text>] [--merged-at <iso-timestamp>].";
38
+ const parsed = parseUpsertArgs(args, usage);
39
+ exactPositionals(parsed.positionals, 1, usage);
28
40
  const task = requireTask(store, parsed.positionals[0]);
29
- const projectReference = requiredOption(parsed.options, "--project");
41
+ const projectReference = requiredOption(parsed.options, "--project", usage);
30
42
  const project = resolveProject(store.listProjects(), projectReference);
31
43
  if (project === null)
32
44
  throw usageError(`Project not found: ${projectReference}.`, usage);
@@ -34,98 +46,80 @@ function addPublication(args, store, options) {
34
46
  throw usageError(`Project is not bound to Task ${task.id}: ${projectReference}.`, usage);
35
47
  }
36
48
  const actor = taskActor(options.environment, task.id);
37
- const state = enumOption(parsed.options, "--state", STATES, usage) ?? "open";
49
+ const state = enumOption(parsed.options, "--state", STATES, usage);
38
50
  const verification = parseVerification(parsed, usage);
39
51
  const input = {
40
52
  projectId: project.id,
41
- provider: enumOption(parsed.options, "--provider", PROVIDERS, usage),
42
- repository: requiredOption(parsed.options, "--repository"),
43
- externalKind: enumOption(parsed.options, "--kind", EXTERNAL_KINDS, usage),
44
- externalId: requiredOption(parsed.options, "--id"),
45
- ...(parsed.options.has("--url") ? { externalUrl: requiredOption(parsed.options, "--url") } : {}),
46
- ...(parsed.options.has("--title") ? { title: requiredOption(parsed.options, "--title") } : {}),
53
+ provider: requiredEnumOption(parsed.options, "--provider", PROVIDERS, usage),
54
+ repository: requiredOption(parsed.options, "--repository", usage),
55
+ externalKind: requiredEnumOption(parsed.options, "--kind", EXTERNAL_KINDS, usage),
56
+ externalId: requiredOption(parsed.options, "--id", usage),
57
+ ...(parsed.options.has("--url")
58
+ ? { externalUrl: requiredOption(parsed.options, "--url", usage) }
59
+ : {}),
60
+ ...(parsed.options.has("--title")
61
+ ? { title: requiredOption(parsed.options, "--title", usage) }
62
+ : {}),
47
63
  ...(parsed.options.has("--source-branch")
48
- ? { sourceBranch: requiredOption(parsed.options, "--source-branch") }
64
+ ? { sourceBranch: requiredOption(parsed.options, "--source-branch", usage) }
49
65
  : {}),
50
66
  ...(parsed.options.has("--target-branch")
51
- ? { targetBranch: requiredOption(parsed.options, "--target-branch") }
67
+ ? { targetBranch: requiredOption(parsed.options, "--target-branch", usage) }
52
68
  : {}),
53
69
  ...(parsed.options.has("--local-commit")
54
- ? { localCommit: requiredOption(parsed.options, "--local-commit") }
70
+ ? { localCommit: requiredOption(parsed.options, "--local-commit", usage) }
55
71
  : {}),
56
72
  ...(parsed.options.has("--remote-commit")
57
- ? { remoteCommit: requiredOption(parsed.options, "--remote-commit") }
73
+ ? { remoteCommit: requiredOption(parsed.options, "--remote-commit", usage) }
58
74
  : {}),
59
- state,
60
- verification,
75
+ ...(state === undefined ? {} : { state }),
76
+ ...(verification === undefined ? {} : { verification }),
61
77
  ...(parsed.options.has("--evidence")
62
- ? { evidence: requiredOption(parsed.options, "--evidence") }
63
- : {}),
64
- ...(parsed.options.has("--supersede")
65
- ? { supersedes: requiredOption(parsed.options, "--supersede") }
78
+ ? { evidence: requiredOption(parsed.options, "--evidence", usage) }
66
79
  : {}),
67
80
  ...(parsed.options.has("--merged-at")
68
- ? { mergedAt: requiredOption(parsed.options, "--merged-at") }
69
- : {}),
70
- recordedBy: actor,
71
- source: "manual"
81
+ ? { mergedAt: requiredOption(parsed.options, "--merged-at", usage) }
82
+ : {})
72
83
  };
73
- const externalKey = `${input.provider}/${input.repository}/${input.externalId}`;
74
84
  const now = clock(options);
75
- const result = store.transaction((tx) => {
76
- const existing = tx.findPublicationReferenceByExternalKey(externalKey);
77
- if (existing !== null) {
78
- if (existing.taskId !== task.id) {
79
- throw dataError(`Publication external identity already belongs to Task ${existing.taskId}: ${externalKey}.`);
80
- }
81
- const semantic = createPublicationReference(existing.id, task.id, input.supersedes === existing.id
82
- ? {
83
- projectId: input.projectId,
84
- provider: input.provider,
85
- repository: input.repository,
86
- externalKind: input.externalKind,
87
- externalId: input.externalId,
88
- ...(input.externalUrl === undefined ? {} : { externalUrl: input.externalUrl }),
89
- ...(input.title === undefined ? {} : { title: input.title }),
90
- ...(input.sourceBranch === undefined ? {} : { sourceBranch: input.sourceBranch }),
91
- ...(input.targetBranch === undefined ? {} : { targetBranch: input.targetBranch }),
92
- ...(input.localCommit === undefined ? {} : { localCommit: input.localCommit }),
93
- ...(input.remoteCommit === undefined ? {} : { remoteCommit: input.remoteCommit }),
94
- state: input.state,
95
- verification: input.verification,
96
- ...(input.evidence === undefined ? {} : { evidence: input.evidence }),
97
- ...(input.mergedAt === undefined ? {} : { mergedAt: input.mergedAt }),
98
- recordedBy: input.recordedBy,
99
- source: input.source
100
- }
101
- : input, new Date(existing.createdAt));
102
- if (isDeepStrictEqual(semantic, existing)) {
103
- return { reference: existing, idempotent: true };
104
- }
105
- if (input.supersedes === undefined || input.supersedes !== existing.id) {
106
- throw usageError(`Publication ${externalKey} conflicts with current record ${existing.id}; `
107
- + "use --supersede with that id to append corrected evidence.", usage);
108
- }
109
- }
110
- else if (input.supersedes !== undefined) {
111
- throw usageError(`Publication supersede target has a different external identity: ${input.supersedes}.`, usage);
112
- }
113
- const reference = createPublicationReference(tx.nextPublicationReferenceId(task.id), task.id, input, now);
114
- tx.savePublicationReference(task.id, reference);
115
- recordTaskEvent(tx, task.id, "publication.recorded", {
116
- publicationId: reference.id,
117
- provider: reference.provider,
118
- repository: reference.repository,
119
- externalKind: reference.externalKind,
120
- externalId: reference.externalId,
121
- state: reference.state,
122
- verification: reference.verification
123
- }, now);
124
- return { reference, idempotent: false };
125
- });
85
+ const result = store.transaction((tx) => upsertTaskPublication(tx, task, input, actor, now));
126
86
  return output(result.idempotent
127
- ? `Publication reference already recorded: ${result.reference.id}.\n`
128
- : `Recorded publication ${result.reference.id} (${result.reference.provider}/${result.reference.repository}/${result.reference.externalKind}/${result.reference.externalId}) for ${result.reference.taskId}.\n`, result.reference);
87
+ ? `Publication reference already current: ${result.reference.id}.\n`
88
+ : `Upserted publication ${result.reference.id} (${result.reference.provider}/${result.reference.repository}/${result.reference.externalKind}/${result.reference.externalId}) for ${result.reference.taskId}.\n`, result.reference);
89
+ }
90
+ /**
91
+ * One immutable Publication upsert against the caller's current transaction.
92
+ * External verification uses the same lineage and idempotency path as manual
93
+ * evidence instead of maintaining a second mutation protocol.
94
+ */
95
+ export function upsertTaskPublication(store, task, input, actor, now) {
96
+ const externalKey = `${input.provider}/${input.repository}/${input.externalId}`;
97
+ const existing = store.findPublicationReferenceByExternalKey(externalKey);
98
+ if (existing !== null && existing.taskId !== task.id) {
99
+ throw dataError(`Publication external identity already belongs to Task ${existing.taskId}: ${externalKey}.`);
100
+ }
101
+ const effective = existing === null
102
+ ? input
103
+ : inheritPublicationInput(existing, input);
104
+ if (existing !== null) {
105
+ const semantic = createPublicationReference(existing.id, task.id, {
106
+ ...effective,
107
+ recordedBy: existing.recordedBy,
108
+ source: existing.source
109
+ }, new Date(existing.createdAt));
110
+ if (isDeepStrictEqual(publicationSemantics(semantic), publicationSemantics(existing))) {
111
+ return { reference: existing, idempotent: true };
112
+ }
113
+ }
114
+ const reference = createPublicationReference(store.nextPublicationReferenceId(task.id), task.id, {
115
+ ...effective,
116
+ ...(existing === null ? {} : { supersedes: existing.id }),
117
+ recordedBy: actor,
118
+ source: "manual"
119
+ }, now);
120
+ store.savePublicationReference(task.id, reference);
121
+ recordPublicationEvent(store, reference, now);
122
+ return { reference, idempotent: false };
129
123
  }
130
124
  function listPublications(args, store) {
131
125
  const usage = "Task publication list usage: yui task publication list <task>.";
@@ -207,7 +201,11 @@ function parseVerification(parsed, usage) {
207
201
  if (parsed.flags.has("--reported") && parsed.flags.has("--verified")) {
208
202
  throw usageError("--reported and --verified are mutually exclusive.", usage);
209
203
  }
210
- return parsed.flags.has("--verified") ? "verified" : "reported";
204
+ if (parsed.flags.has("--verified"))
205
+ return "verified";
206
+ if (parsed.flags.has("--reported"))
207
+ return "reported";
208
+ return undefined;
211
209
  }
212
210
  function enumOption(options, name, allowed, usage) {
213
211
  if (!options.has(name))
@@ -218,6 +216,59 @@ function enumOption(options, name, allowed, usage) {
218
216
  }
219
217
  return value;
220
218
  }
219
+ function requiredEnumOption(options, name, allowed, usage) {
220
+ const value = enumOption(options, name, allowed, usage);
221
+ if (value === undefined) {
222
+ throw usageError(`${name} is required.`, usage);
223
+ }
224
+ return value;
225
+ }
226
+ function inheritPublicationInput(existing, input) {
227
+ const localCommitChanged = input.localCommit !== undefined
228
+ && normalizeCommitForComparison(input.localCommit) !== existing.localCommit;
229
+ const stateChanged = input.state !== undefined && input.state !== existing.state;
230
+ const remoteCommitChanged = input.remoteCommit !== undefined
231
+ && normalizeCommitForComparison(input.remoteCommit) !== existing.remoteCommit;
232
+ const evidenceChanged = input.evidence !== undefined
233
+ && input.evidence.trim() !== existing.evidence;
234
+ const mergedAtChanged = input.mergedAt !== undefined
235
+ && input.mergedAt !== existing.mergedAt;
236
+ const evidenceContextChanged = localCommitChanged
237
+ || stateChanged
238
+ || remoteCommitChanged
239
+ || evidenceChanged
240
+ || mergedAtChanged;
241
+ const inherited = {
242
+ projectId: input.projectId,
243
+ provider: input.provider,
244
+ repository: input.repository,
245
+ externalKind: input.externalKind,
246
+ externalId: input.externalId,
247
+ state: input.state ?? (localCommitChanged ? "open" : existing.state),
248
+ verification: input.verification ?? (evidenceContextChanged ? "reported" : existing.verification),
249
+ ...(input.localCommit === undefined
250
+ ? (existing.localCommit === undefined ? {} : { localCommit: existing.localCommit })
251
+ : { localCommit: input.localCommit })
252
+ };
253
+ for (const field of INHERITED_METADATA_FIELDS) {
254
+ const value = input[field] ?? existing[field];
255
+ if (value !== undefined)
256
+ Object.assign(inherited, { [field]: value });
257
+ }
258
+ for (const field of INHERITED_EVIDENCE_FIELDS) {
259
+ const value = input[field] ?? (evidenceContextChanged ? undefined : existing[field]);
260
+ if (value !== undefined)
261
+ Object.assign(inherited, { [field]: value });
262
+ }
263
+ return inherited;
264
+ }
265
+ function normalizeCommitForComparison(value) {
266
+ return value.trim().toLowerCase();
267
+ }
268
+ function publicationSemantics(reference) {
269
+ const { schemaVersion: _schemaVersion, id: _id, taskId: _taskId, supersedes: _supersedes, recordedBy: _recordedBy, source: _source, createdAt: _createdAt, ...semantics } = reference;
270
+ return semantics;
271
+ }
221
272
  function requireTask(store, taskId) {
222
273
  const id = requiredText(taskId, "Task id");
223
274
  const task = store.getTask(id);
@@ -225,11 +276,26 @@ function requireTask(store, taskId) {
225
276
  throw taskNotFound(id);
226
277
  return task;
227
278
  }
279
+ function recordPublicationEvent(store, reference, now) {
280
+ recordTaskEvent(store, reference.taskId, "publication.recorded", {
281
+ publicationId: reference.id,
282
+ provider: reference.provider,
283
+ repository: reference.repository,
284
+ externalKind: reference.externalKind,
285
+ externalId: reference.externalId,
286
+ state: reference.state,
287
+ verification: reference.verification
288
+ }, now);
289
+ }
228
290
  function recordTaskEvent(store, taskId, type, payload, now) {
229
291
  store.saveEvent(taskId, createTaskEvent(store.nextEventId(taskId), taskId, type, payload, now));
230
292
  }
231
- function requiredOption(options, name) {
232
- return requiredText(options.get(name), name);
293
+ function requiredOption(options, name, usage) {
294
+ const value = options.get(name)?.trim();
295
+ if (value === undefined || value.length === 0) {
296
+ throw usageError(`${name} is required.`, usage);
297
+ }
298
+ return value;
233
299
  }
234
300
  function requiredText(value, label) {
235
301
  const normalized = value?.trim();
@@ -251,11 +317,11 @@ function exactPositionals(values, count, usage) {
251
317
  throw usageError(usage);
252
318
  }
253
319
  }
254
- function parseAddArgs(args, usage) {
320
+ function parseUpsertArgs(args, usage) {
255
321
  const valueOptions = new Set([
256
322
  "--project", "--provider", "--repository", "--kind", "--id", "--url",
257
323
  "--title", "--source-branch", "--target-branch", "--local-commit",
258
- "--remote-commit", "--state", "--evidence", "--supersede", "--merged-at"
324
+ "--remote-commit", "--state", "--evidence", "--merged-at"
259
325
  ]);
260
326
  const flags = new Set(["--reported", "--verified"]);
261
327
  const positionals = [];
@@ -0,0 +1,181 @@
1
+ import { isDeepStrictEqual } from "node:util";
2
+ import { taskActor } from "./taskActor.js";
3
+ import { upsertTaskPublication } from "./taskPublicationCommands.js";
4
+ import { projectTaskRemoteDeliveryFromStore } from "./taskRemoteDeliveryCommand.js";
5
+ import { dataError, runtimeError, taskNotFound, usageError, CliError } from "../errors/cliError.js";
6
+ import { publicationExternalKey } from "../task/publicationReference.js";
7
+ import { resolveTaskRecordReference } from "../task/taskRecordReference.js";
8
+ export async function runTaskPublicationVerifyCommand(args, store, options) {
9
+ const reference = parsePublicationVerificationReference(args);
10
+ const task = store.getTask(reference.taskId);
11
+ if (task === null)
12
+ throw taskNotFound(reference.taskId);
13
+ if (task.status === "archived") {
14
+ throw usageError(`Archived Task Publication cannot be verified: ${task.id}.`);
15
+ }
16
+ taskActor(options.environment, task.id);
17
+ const initialCandidate = await options.candidateForTask?.(reference.taskId) ?? null;
18
+ const request = preparePublicationVerification(store, reference.taskId, reference.localId, initialCandidate, options.environment);
19
+ const verifier = options.verifiers[request.publication.provider];
20
+ if (verifier === undefined) {
21
+ throw usageError(`Publication verification is not supported for provider `
22
+ + `${request.publication.provider}.`);
23
+ }
24
+ let observation;
25
+ try {
26
+ observation = await verifier.inspect({
27
+ provider: request.publication.provider,
28
+ repository: request.publication.repository,
29
+ externalKind: request.publication.externalKind,
30
+ externalId: request.publication.externalId,
31
+ ...(request.publication.externalUrl === undefined
32
+ ? {}
33
+ : { externalUrl: request.publication.externalUrl }),
34
+ expectedLocalCommit: request.expectedLocalCommit
35
+ });
36
+ }
37
+ catch (error) {
38
+ if (error instanceof CliError)
39
+ throw error;
40
+ throw runtimeError(`Publication verification failed for ${request.publication.provider}/`
41
+ + `${request.publication.repository}/${request.publication.externalId}: `
42
+ + `${error instanceof Error ? error.message : String(error)}`);
43
+ }
44
+ assertVerificationObservation(request, observation);
45
+ const finalCandidate = await options.candidateForTask?.(reference.taskId) ?? null;
46
+ const result = commitPublicationVerification(store, request, observation, finalCandidate, options.now?.() ?? new Date());
47
+ return {
48
+ kind: "output",
49
+ output: result.idempotent
50
+ ? `Publication ${result.reference.id} is already verified for `
51
+ + `${result.reference.repository}#${result.reference.externalId}.\n`
52
+ : `Verified publication ${result.reference.id} for `
53
+ + `${result.reference.repository}#${result.reference.externalId}.\n`,
54
+ data: { publication: result.reference, observation }
55
+ };
56
+ }
57
+ function preparePublicationVerification(store, taskId, publicationId, currentCandidate, environment) {
58
+ return store.transaction((reader) => {
59
+ const task = reader.getTask(taskId);
60
+ if (task === null)
61
+ throw taskNotFound(taskId);
62
+ if (task.status === "archived") {
63
+ throw usageError(`Archived Task Publication cannot be verified: ${task.id}.`);
64
+ }
65
+ const actor = taskActor(environment, task.id);
66
+ const publication = reader.getPublicationReference(task.id, publicationId);
67
+ if (publication === null) {
68
+ throw dataError(`Publication reference not found: ${task.id}/${publicationId}.`);
69
+ }
70
+ const current = reader.findPublicationReferenceByExternalKey(publicationExternalKey(publication));
71
+ if (current === null || current.taskId !== task.id || current.id !== publication.id) {
72
+ throw usageError(`Publication ${publication.id} is not the current unsuperseded record `
73
+ + `for ${publicationExternalKey(publication)}.`);
74
+ }
75
+ const delivery = projectTaskRemoteDeliveryFromStore(reader, task, currentCandidate);
76
+ const project = delivery.projects.find(({ projectId }) => (projectId === publication.projectId));
77
+ if (project === undefined) {
78
+ throw usageError(`Publication ${publication.id} Project is not bound to Task ${task.id}: `
79
+ + `${publication.projectId}.`);
80
+ }
81
+ if (project.expectedLocalCommit === null) {
82
+ throw usageError(`Task delivery head is unavailable for Publication ${publication.id}.`);
83
+ }
84
+ if (publication.localCommit === undefined) {
85
+ throw usageError(`Publication ${publication.id} must record a local commit before verification.`);
86
+ }
87
+ if (publication.localCommit !== project.expectedLocalCommit) {
88
+ throw usageError(`Publication ${publication.id} local commit ${publication.localCommit} `
89
+ + `does not match Task delivery head ${project.expectedLocalCommit}.`);
90
+ }
91
+ return {
92
+ task: taskVerificationInvariant(task),
93
+ actor,
94
+ publication,
95
+ expectedLocalCommit: project.expectedLocalCommit
96
+ };
97
+ });
98
+ }
99
+ function assertVerificationObservation(request, observation) {
100
+ const publication = request.publication;
101
+ if (observation.provider !== publication.provider
102
+ || observation.repository !== publication.repository
103
+ || observation.externalKind !== publication.externalKind
104
+ || observation.externalId !== publication.externalId) {
105
+ throw usageError(`Provider returned mismatched Publication identity for ${publication.id}.`);
106
+ }
107
+ if (observation.state !== "merged") {
108
+ throw usageError(`Remote ${publication.externalKind} ${publication.repository}#`
109
+ + `${publication.externalId} is ${observation.state}, not merged.`);
110
+ }
111
+ if (observation.headCommit !== request.expectedLocalCommit) {
112
+ throw usageError(`Remote ${publication.externalKind} head ${observation.headCommit} `
113
+ + `does not match Task delivery head ${request.expectedLocalCommit}.`);
114
+ }
115
+ if (observation.remoteCommit === undefined) {
116
+ throw usageError(`Merged remote ${publication.externalKind} did not expose a remote commit.`);
117
+ }
118
+ if (observation.evidence.trim().length === 0) {
119
+ throw usageError("Provider verification evidence is required.");
120
+ }
121
+ }
122
+ function commitPublicationVerification(store, request, observation, currentCandidate, now) {
123
+ return store.transaction((tx) => {
124
+ const task = tx.getTask(request.task.id);
125
+ if (task === null)
126
+ throw taskNotFound(request.task.id);
127
+ if (!isDeepStrictEqual(taskVerificationInvariant(task), request.task)) {
128
+ throw usageError(`Task changed during Publication verification: ${request.task.id}.`);
129
+ }
130
+ const current = tx.findPublicationReferenceByExternalKey(publicationExternalKey(request.publication));
131
+ if (current === null
132
+ || current.taskId !== task.id
133
+ || !isDeepStrictEqual(current, request.publication)) {
134
+ throw usageError(`Publication evidence changed during verification: `
135
+ + `${task.id}/${request.publication.id}.`);
136
+ }
137
+ const delivery = projectTaskRemoteDeliveryFromStore(tx, task, currentCandidate);
138
+ const expectedLocalCommit = delivery.projects.find(({ projectId }) => (projectId === request.publication.projectId))?.expectedLocalCommit;
139
+ if (expectedLocalCommit !== request.expectedLocalCommit) {
140
+ throw usageError(`Task delivery head changed during Publication verification: `
141
+ + `${request.publication.projectId}@${expectedLocalCommit ?? "unavailable"}.`);
142
+ }
143
+ return upsertTaskPublication(tx, task, {
144
+ projectId: request.publication.projectId,
145
+ provider: request.publication.provider,
146
+ repository: request.publication.repository,
147
+ externalKind: request.publication.externalKind,
148
+ externalId: request.publication.externalId,
149
+ ...(observation.externalUrl === undefined
150
+ ? {}
151
+ : { externalUrl: observation.externalUrl }),
152
+ localCommit: request.expectedLocalCommit,
153
+ remoteCommit: observation.remoteCommit,
154
+ state: "merged",
155
+ verification: "verified",
156
+ evidence: observation.evidence,
157
+ ...(observation.mergedAt === undefined
158
+ ? {}
159
+ : { mergedAt: observation.mergedAt })
160
+ }, request.actor, now);
161
+ });
162
+ }
163
+ function parsePublicationVerificationReference(args) {
164
+ const usage = "Task publication verify usage: "
165
+ + "yui task publication verify (<task>/<publication-id> | <task> <publication-id>).";
166
+ if (args.length !== 1 && args.length !== 2)
167
+ throw usageError(usage);
168
+ return resolveTaskRecordReference(args.length === 1 ? args[0] : `${args[0]}/${args[1]}`, { kind: "publicationReference", label: "Publication reference" });
169
+ }
170
+ function taskVerificationInvariant(task) {
171
+ return {
172
+ id: task.id,
173
+ status: task.status,
174
+ projectBindings: task.projectBindings,
175
+ ...(task.workspaceIdentity === undefined
176
+ ? {}
177
+ : { workspaceIdentity: task.workspaceIdentity }),
178
+ ...(task.completedAt === undefined ? {} : { completedAt: task.completedAt }),
179
+ ...(task.retiredAt === undefined ? {} : { retiredAt: task.retiredAt })
180
+ };
181
+ }
@@ -0,0 +1,134 @@
1
+ import { isDeepStrictEqual } from "node:util";
2
+ import { taskNotFound, usageError } from "../errors/cliError.js";
3
+ import { projectTaskRemoteDelivery } from "../task/remoteDelivery.js";
4
+ export function runTaskRemoteDeliveryCommand(args, store, currentCandidate = null) {
5
+ const usage = "Task remote-delivery usage: yui task remote-delivery <task> [--json].";
6
+ const asJson = args.includes("--json");
7
+ const positionals = args.filter((arg) => arg !== "--json");
8
+ if (positionals.length !== 1 || positionals[0]?.trim().length === 0) {
9
+ throw usageError(usage);
10
+ }
11
+ const taskId = positionals[0].trim();
12
+ const data = store.transaction((reader) => {
13
+ const task = reader.getTask(taskId);
14
+ if (task === null)
15
+ throw taskNotFound(taskId);
16
+ return projectTaskRemoteDeliveryFromStore(reader, task, currentCandidate);
17
+ });
18
+ return {
19
+ kind: "output",
20
+ output: asJson
21
+ ? `${JSON.stringify(data, null, 2)}\n`
22
+ : renderTaskRemoteDelivery(data),
23
+ data
24
+ };
25
+ }
26
+ export function projectTaskRemoteDeliveryFromStore(store, task, currentCandidate = null) {
27
+ return projectTaskRemoteDelivery({
28
+ task,
29
+ events: store.listEvents(task.id),
30
+ publications: store.listPublicationReferences(task.id),
31
+ managedWorkspaces: store.listManagedWorkspaces(task.id),
32
+ turns: store.listTurns(task.id),
33
+ currentCandidate
34
+ });
35
+ }
36
+ export function createTaskRemoteDeliveryProof(store, task, currentCandidate = null) {
37
+ const publications = store.listPublicationReferences(task.id);
38
+ const delivery = projectTaskRemoteDelivery({
39
+ task,
40
+ events: store.listEvents(task.id),
41
+ publications,
42
+ managedWorkspaces: store.listManagedWorkspaces(task.id),
43
+ turns: store.listTurns(task.id),
44
+ currentCandidate
45
+ });
46
+ return {
47
+ schemaVersion: 1,
48
+ task: taskRemoteDeliveryProofTask(task),
49
+ publications,
50
+ delivery
51
+ };
52
+ }
53
+ export function assertTaskRemoteDeliveryProof(store, task, proof, options = {}) {
54
+ if (proof === undefined || proof.schemaVersion !== 1) {
55
+ throw usageError(`Task remote-delivery preflight proof is required: ${task.id}.`);
56
+ }
57
+ if (!isDeepStrictEqual(proof.task, taskRemoteDeliveryProofTask(task))) {
58
+ throw usageError(`Task changed after remote-delivery preflight: ${task.id}.`);
59
+ }
60
+ if (!isDeepStrictEqual(proof.publications, store.listPublicationReferences(task.id))) {
61
+ throw usageError(`Task Publication evidence changed after remote-delivery preflight: ${task.id}.`);
62
+ }
63
+ assertTaskRemoteDeliveryIntegrated(proof.delivery, options);
64
+ return proof.delivery;
65
+ }
66
+ export function assertTaskRemoteDeliveryIntegrated(delivery, options = {}) {
67
+ if (!delivery.allMerged) {
68
+ const headUnavailable = delivery.projects.filter((project) => (project.codeDelivery !== "none"
69
+ && project.coverage === "head-unavailable"));
70
+ if (headUnavailable.length > 0 && delivery.source === "task-completed") {
71
+ throw usageError(`Task ${delivery.taskId} has no frozen completion heads for: ${headUnavailable.map(({ projectId }) => projectId).join(", ")}. Reopen and complete it again to record exact heads before archiving; `
72
+ + "--force cannot override missing head evidence.");
73
+ }
74
+ const uncovered = delivery.projects
75
+ .filter((project) => project.codeDelivery !== "none" && !project.merged)
76
+ .map((project) => (`${project.projectId}/${project.coverage}`
77
+ + (project.publication === null ? "" : `/${project.publication.id}`)))
78
+ .join(", ");
79
+ throw usageError(`Task ${delivery.taskId} is not fully merged into remote delivery: ${uncovered || "expected Project heads are unavailable"}. Record confirmed current-head evidence with task publication upsert, `
80
+ + "or use task archive --abandon for an intentional non-merge.");
81
+ }
82
+ if (delivery.allVerified || options.forceUnverified === true)
83
+ return;
84
+ const unverified = delivery.projects
85
+ .filter((project) => (project.codeDelivery !== "none"
86
+ && project.merged
87
+ && !project.verified))
88
+ .map((project) => (`${project.projectId}/${project.coverage}`
89
+ + (project.publication === null ? "" : `/${project.publication.id}`)))
90
+ .join(", ");
91
+ throw usageError(`Task ${delivery.taskId} has merged Publication evidence that is not verified: `
92
+ + `${unverified || "verification evidence is unavailable"}. Run task publication verify `
93
+ + "for each Publication, or repeat task archive --integrated --force to explicitly "
94
+ + "override verification. --force never overrides missing, stale, open, or closed merge evidence.");
95
+ }
96
+ export function renderTaskRemoteDelivery(delivery, indent = "") {
97
+ const source = `${delivery.source}${delivery.provisional ? " (provisional)" : ""}`;
98
+ const lines = [
99
+ `${indent}Remote delivery: ${delivery.status}`,
100
+ `${indent}Expected heads: ${source}`,
101
+ `${indent}All merged: ${delivery.allMerged ? "yes" : "no"}`,
102
+ `${indent}All verified: ${delivery.allVerified ? "yes" : "no"}`,
103
+ `${indent}Code Projects: ${delivery.mergedProjectCount}/${delivery.codeProjectCount} merged; ${delivery.verifiedProjectCount}/${delivery.codeProjectCount} verified`,
104
+ `${indent}Archive --integrated coverage: ${delivery.integratedCoverageSatisfied ? "satisfied" : "blocked"}`,
105
+ ...(delivery.archiveDisposition === null
106
+ ? []
107
+ : [`${indent}Archive disposition: ${delivery.archiveDisposition}`]),
108
+ `${indent}Projects:${delivery.projects.length === 0 ? " none" : ""}`,
109
+ ...delivery.projects.map((project) => (`${indent}- ${project.directory} (${project.projectId}): `
110
+ + `expected=${shortCommit(project.expectedLocalCommit)}; `
111
+ + `base=${shortCommit(project.baseCommit)}; `
112
+ + `publication=${project.publication?.id ?? "none"}; `
113
+ + `state=${project.state ?? "none"}; `
114
+ + `verification=${project.verification ?? "none"}; `
115
+ + `remote=${shortCommit(project.remoteCommit)}; `
116
+ + `coverage=${project.coverage}`))
117
+ ];
118
+ return `${lines.join("\n")}\n`;
119
+ }
120
+ function shortCommit(commit) {
121
+ return commit === null ? "unknown" : commit.slice(0, 12);
122
+ }
123
+ function taskRemoteDeliveryProofTask(task) {
124
+ return {
125
+ id: task.id,
126
+ status: task.status,
127
+ projectBindings: task.projectBindings,
128
+ ...(task.workspaceIdentity === undefined
129
+ ? {}
130
+ : { workspaceIdentity: task.workspaceIdentity }),
131
+ ...(task.completedAt === undefined ? {} : { completedAt: task.completedAt }),
132
+ ...(task.retiredAt === undefined ? {} : { retiredAt: task.retiredAt })
133
+ };
134
+ }