@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.
- package/ARCHITECTURE.md +64 -35
- package/README.md +258 -125
- package/dist/agent/argumentPolicy.js +2 -1
- package/dist/agent/managedRuntimeEnvironment.js +5 -8
- package/dist/cli/commandCatalog.js +74 -109
- package/dist/cli/helpRenderer.js +1 -2
- package/dist/cli/interactionCandidates.js +10 -0
- package/dist/cli/interactionPolicy.js +76 -37
- package/dist/cli/roleWizard.js +5 -6
- package/dist/cli/updateOrchestrator.js +10 -8
- package/dist/cli/updatePorts.js +17 -13
- package/dist/cli/upgradeCommand.js +2 -5
- package/dist/cli.js +268 -231
- package/dist/commands/agentCommands.js +13 -0
- package/dist/commands/configCommands.js +7 -19
- package/dist/commands/configOverview.js +3 -2
- package/dist/commands/durableJobCommands.js +1 -1
- package/dist/commands/executionAuditCommands.js +4 -9
- package/dist/commands/globalRoleCommands.js +17 -0
- package/dist/commands/profileCommands.js +200 -30
- package/dist/commands/sessionCommands.js +1 -1
- package/dist/commands/taskActor.js +29 -127
- package/dist/commands/taskChangeSetCommands.js +1 -3
- package/dist/commands/taskCommands.js +1771 -2107
- package/dist/commands/taskCompletionGate.js +27 -103
- package/dist/commands/taskContextCommand.js +47 -80
- package/dist/commands/taskExecutionCommands.js +9 -27
- package/dist/commands/taskInputCommands.js +10 -10
- package/dist/commands/taskIntegrationCommands.js +69 -61
- package/dist/commands/taskIntegrationQueueCommands.js +1 -1
- package/dist/commands/taskNextActionCommand.js +8 -29
- package/dist/commands/taskOverviewCommand.js +25 -3
- package/dist/commands/taskPublicationCommands.js +151 -85
- package/dist/commands/taskPublicationVerifyCommand.js +181 -0
- package/dist/commands/taskRemoteDeliveryCommand.js +134 -0
- package/dist/commands/taskRoleRuntimeStatus.js +5 -6
- package/dist/commands/taskUpstreamCommands.js +79 -95
- package/dist/config/configCatalog.js +1 -1
- package/dist/context/contextSnapshot.js +17 -2
- package/dist/context/sessionBootstrapManifest.js +18 -1
- package/dist/context/sourceTurnContext.js +30 -0
- package/dist/context/turnContextPack.js +107 -48
- package/dist/context/turnInputContract.js +11 -4
- package/dist/context/wakeNotification.js +84 -22
- package/dist/controller/agentRuntimeObserver.js +9 -12
- package/dist/controller/clientRuntime.js +13 -13
- package/dist/controller/controller.js +11 -11
- package/dist/controller/fileSchedulerStoreAdapter.js +262 -219
- package/dist/controller/jobControl.js +11 -10
- package/dist/controller/resourceInventory.js +6 -6
- package/dist/controller/resourceInventoryLinux.js +2 -2
- package/dist/controller/runtime.js +18 -61
- package/dist/controller/runtimeEventInbox.js +57 -34
- package/dist/controller/runtimeEventProcessor.js +36 -96
- package/dist/controller/runtimeHookTurnFence.js +33 -57
- package/dist/controller/runtimeLaunchCoordinator.js +44 -40
- package/dist/controller/runtimeObservationHook.js +18 -12
- package/dist/controller/sessionNotify.js +43 -28
- package/dist/controller/sessionOwnerReconciliation.js +21 -21
- package/dist/controller/structuredProviderObservation.js +13 -16
- package/dist/coordination/workMailboxQueue.js +132 -0
- package/dist/core/controllerServer.js +1 -0
- package/dist/domain/agentResultTransport.js +60 -0
- package/dist/execution/executionHealth.js +111 -118
- package/dist/execution/reviewMainTurn.js +161 -0
- package/dist/execution/workItemExecution.js +454 -0
- package/dist/execution/workItemExecutionProjection.js +309 -0
- package/dist/execution/workItemMainTurn.js +164 -0
- package/dist/executor/agentExecutor.js +19 -19
- package/dist/executor/executorRegistry.js +7 -28
- package/dist/executor/fileRoleLaunchPlanner.js +51 -83
- package/dist/external/githubPublicationVerifier.js +114 -0
- package/dist/external/gitlabPublicationVerifier.js +158 -0
- package/dist/external/pinnedCommandRunner.js +73 -0
- package/dist/integration/changeSetManifest.js +0 -3
- package/dist/integration/deliveryObligation.js +43 -6
- package/dist/integration/gitIntegrationService.js +284 -101
- package/dist/integration/integrationAttempt.js +64 -14
- package/dist/integration/integrationQueueEntry.js +5 -35
- package/dist/integration/integrationQueueService.js +65 -194
- package/dist/lifecycle/canonicalLifecycleEvent.js +16 -15
- package/dist/lifecycle/exactTurnTerminalization.js +100 -97
- package/dist/message/message.js +16 -0
- package/dist/observability/executionAudit.js +4 -25
- package/dist/observability/faultClassification.js +58 -70
- package/dist/observability/orchestrationMetrics.js +8 -50
- package/dist/profile/agentProfile.js +60 -15
- package/dist/profile/agentProfileRuntime.js +100 -0
- package/dist/release/releaseWorkflowPorts.js +5 -68
- package/dist/repository/executionLaneGitSnapshot.js +80 -0
- package/dist/repository/gitWorkspace.js +9 -67
- package/dist/repository/taskBaseFreshness.js +3 -1
- package/dist/repository/taskWorkspaceCoordinator.js +32 -4
- package/dist/repository/taskWorkspacePreparer.js +252 -411
- package/dist/resources/liveReferences.js +6 -55
- package/dist/resources/resourceDiscovery.js +1 -30
- package/dist/resources/resourceRegistrar.js +0 -23
- package/dist/review/deltaRecheck.js +8 -65
- package/dist/review/reviewAcceptance.js +22 -19
- package/dist/review/reviewConfig.js +0 -10
- package/dist/review/reviewDecision.js +2 -2
- package/dist/review/reviewRound.js +147 -273
- package/dist/review/reviewerAvailability.js +13 -23
- package/dist/role/role.js +3 -0
- package/dist/runtime/agentDriverObservation.js +1 -1
- package/dist/runtime/agentHost.js +51 -51
- package/dist/runtime/builtinAgentDrivers.js +9 -2
- package/dist/runtime/codexAppServerRuntime.js +23 -5
- package/dist/runtime/exactControlPlane.js +2 -267
- package/dist/runtime/firstProgressAdvisory.js +1 -1
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/launchBroker.js +11 -11
- package/dist/runtime/lifecycleReservation.js +2 -2
- package/dist/runtime/managedCaller.js +111 -0
- package/dist/runtime/ports.js +16 -3
- package/dist/runtime/preallocatedNativeSession.js +2 -2
- package/dist/runtime/processExitObservation.js +3 -2
- package/dist/runtime/providerContinuationReconciliationService.js +1 -1
- package/dist/runtime/runtimeBinding.js +1 -1
- package/dist/runtime/runtimeObservation.js +33 -16
- package/dist/runtime/runtimeSessionCandidate.js +2 -2
- package/dist/runtime/runtimeStopReceipt.js +11 -11
- package/dist/runtime/sessionLaunchRequest.js +2 -2
- package/dist/runtime/sessionOwnerIdentity.js +9 -9
- package/dist/runtime/sessionOwnerRegistry.js +13 -13
- package/dist/runtime/sessionReconciliation.js +4 -4
- package/dist/runtime/sessionTerminationGuard.js +9 -9
- package/dist/runtime/sessionTokenMetrics.js +5 -7
- package/dist/runtime/structuredProviderHost.js +5 -5
- package/dist/runtime/taskRuntimeIsolation.js +24 -24
- package/dist/runtime/tmuxAdapters.js +32 -30
- package/dist/scheduler/activeRoleTurnDelivery.js +37 -15
- package/dist/scheduler/leaderWakeupProcessor.js +78 -55
- package/dist/scheduler/roleTurnLiveness.js +6 -6
- package/dist/scheduler/roleTurnStall.js +8 -9
- package/dist/scheduler/taskExecutionProjection.js +19 -41
- package/dist/scheduler/taskObservabilityProjection.js +75 -122
- package/dist/storage/sqliteSchema.js +1 -21
- package/dist/storage/sqliteStore.js +62 -47
- package/dist/storage/storageSchema.js +1 -8
- package/dist/storage/storageVersions.js +7 -4
- package/dist/storage/taskStore.js +8 -8
- package/dist/storage/upgrade/upgradeOrchestrator.js +112 -83
- package/dist/task/completionReadiness.js +17 -98
- package/dist/task/deliveryGuard.js +9 -11
- package/dist/task/draftPlan.js +142 -0
- package/dist/task/nextAction.js +179 -477
- package/dist/task/publicationVerification.js +1 -0
- package/dist/task/remoteDelivery.js +233 -0
- package/dist/task/task.js +108 -4
- package/dist/task/taskRecordReference.js +0 -1
- package/dist/turn/turn.js +109 -16
- package/dist/verification/verificationGateService.js +0 -26
- package/dist/web/assets/client/components.js +99 -121
- package/dist/web/assets/client/i18n.js +136 -58
- package/dist/web/assets/client/view.js +44 -0
- package/dist/web/assets/styles/cards.js +3 -15
- package/dist/web/assets/styles/widgets.js +4 -0
- package/dist/web/webSnapshot.js +29 -14
- package/dist/workItem/dependencyGate.js +34 -0
- package/dist/workItem/workItem.js +99 -266
- package/dist/workspace/workItemChangeSetManager.js +15 -59
- package/docs/task-local-identity.md +1 -1
- package/i18n/README.zh-CN.md +165 -59
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +272 -782
- package/skills/yui-operator/SKILL.md +190 -365
- package/skills/yui-reviewer/SKILL.md +40 -33
- package/skills/yui-runtime/SKILL.md +27 -6
- package/skills/yui-worker/SKILL.md +19 -6
- package/dist/execution/candidateConvergence.js +0 -623
- package/dist/execution/executionGroup.js +0 -854
- package/dist/execution/resourceBroker.js +0 -426
- package/dist/executor/launchPlan.js +0 -16
- package/dist/review/reviewFinding.js +0 -240
- package/dist/review/reviewFindingLedger.js +0 -550
- package/dist/review/reviewOutcomeClassifier.js +0 -255
- package/dist/scheduler/resourceQueueProjection.js +0 -71
- 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 "
|
|
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
|
|
26
|
-
const usage = "Task publication
|
|
27
|
-
const parsed =
|
|
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)
|
|
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:
|
|
42
|
-
repository: requiredOption(parsed.options, "--repository"),
|
|
43
|
-
externalKind:
|
|
44
|
-
externalId: requiredOption(parsed.options, "--id"),
|
|
45
|
-
...(parsed.options.has("--url")
|
|
46
|
-
|
|
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
|
|
128
|
-
: `
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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", "--
|
|
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
|
+
}
|