@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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One read-only delivery projection over existing Task, workspace, completion,
|
|
3
|
+
* and Publication facts. It never writes a merged flag or queries a provider.
|
|
4
|
+
*/
|
|
5
|
+
export function projectTaskRemoteDelivery(facts) {
|
|
6
|
+
const expected = expectedDelivery(facts);
|
|
7
|
+
const baseCommits = projectBaseCommits(facts, expected.event);
|
|
8
|
+
const archiveDisposition = latestEvent(facts.events, "task.archived", () => true)?.payload.workspaceDisposition;
|
|
9
|
+
const currentPublications = currentPublicationReferences(facts.publications);
|
|
10
|
+
const projects = facts.task.projectBindings.map((binding) => {
|
|
11
|
+
const expectedLocalCommit = expected.commits.get(binding.projectId) ?? null;
|
|
12
|
+
const baseCommit = baseCommits.get(binding.projectId) ?? null;
|
|
13
|
+
const codeDelivery = expectedLocalCommit === null
|
|
14
|
+
? "unknown"
|
|
15
|
+
: baseCommit === null
|
|
16
|
+
? "unknown"
|
|
17
|
+
: expectedLocalCommit === baseCommit ? "none" : "code";
|
|
18
|
+
const projectPublications = currentPublications
|
|
19
|
+
.filter((reference) => reference.projectId === binding.projectId);
|
|
20
|
+
const matching = expectedLocalCommit === null
|
|
21
|
+
? []
|
|
22
|
+
: projectPublications.filter((reference) => reference.localCommit === expectedLocalCommit);
|
|
23
|
+
const publication = bestPublication(matching.length > 0 ? matching : projectPublications);
|
|
24
|
+
const coverage = projectCoverage(codeDelivery, expectedLocalCommit, publication);
|
|
25
|
+
const merged = codeDelivery === "none"
|
|
26
|
+
|| (publication !== null
|
|
27
|
+
&& expectedLocalCommit !== null
|
|
28
|
+
&& publication.localCommit === expectedLocalCommit
|
|
29
|
+
&& publication.state === "merged");
|
|
30
|
+
const verified = codeDelivery === "none"
|
|
31
|
+
|| (merged && publication?.verification === "verified");
|
|
32
|
+
return {
|
|
33
|
+
projectId: binding.projectId,
|
|
34
|
+
directory: binding.directory,
|
|
35
|
+
baseCommit,
|
|
36
|
+
expectedLocalCommit,
|
|
37
|
+
codeDelivery,
|
|
38
|
+
publication,
|
|
39
|
+
state: publication?.state ?? null,
|
|
40
|
+
verification: publication?.verification ?? null,
|
|
41
|
+
remoteCommit: publication?.remoteCommit ?? null,
|
|
42
|
+
coverage,
|
|
43
|
+
merged,
|
|
44
|
+
verified,
|
|
45
|
+
reason: projectCoverageReason(codeDelivery, expectedLocalCommit, publication, coverage)
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
const codeProjects = projects.filter((project) => project.codeDelivery !== "none");
|
|
49
|
+
const mergedProjectCount = codeProjects.filter((project) => project.merged).length;
|
|
50
|
+
const verifiedProjectCount = codeProjects.filter((project) => project.verified).length;
|
|
51
|
+
const allMerged = codeProjects.every((project) => project.merged);
|
|
52
|
+
const allVerified = codeProjects.every((project) => project.verified);
|
|
53
|
+
const status = codeProjects.length === 0
|
|
54
|
+
? "none"
|
|
55
|
+
: codeProjects.some((project) => project.coverage === "head-unavailable")
|
|
56
|
+
? "unavailable"
|
|
57
|
+
: allMerged
|
|
58
|
+
? "merged"
|
|
59
|
+
: mergedProjectCount > 0 ? "partial" : "pending";
|
|
60
|
+
return {
|
|
61
|
+
taskId: facts.task.id,
|
|
62
|
+
source: expected.source,
|
|
63
|
+
provisional: expected.provisional,
|
|
64
|
+
archiveDisposition: archiveDisposition === "integrated"
|
|
65
|
+
|| archiveDisposition === "abandoned"
|
|
66
|
+
? archiveDisposition
|
|
67
|
+
: null,
|
|
68
|
+
status,
|
|
69
|
+
allMerged,
|
|
70
|
+
allVerified,
|
|
71
|
+
totalProjectCount: projects.length,
|
|
72
|
+
codeProjectCount: codeProjects.length,
|
|
73
|
+
mergedProjectCount,
|
|
74
|
+
verifiedProjectCount,
|
|
75
|
+
integratedCoverageSatisfied: allMerged && allVerified,
|
|
76
|
+
projects
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function expectedDelivery(facts) {
|
|
80
|
+
const { task } = facts;
|
|
81
|
+
if ((task.status === "completed" || task.status === "archived")
|
|
82
|
+
&& task.completedAt !== undefined) {
|
|
83
|
+
const event = completionEvent(facts.events, task.completedAt);
|
|
84
|
+
return {
|
|
85
|
+
source: "task-completed",
|
|
86
|
+
provisional: false,
|
|
87
|
+
commits: commitMap(event?.payload.projectHeads),
|
|
88
|
+
...(event === undefined ? {} : { event })
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
if (task.status === "archived") {
|
|
92
|
+
const event = latestEvent(facts.events, "task.archived", (candidate) => (candidate.payload.workspaceDisposition === "integrated"
|
|
93
|
+
&& candidate.payload.projectHeads !== undefined));
|
|
94
|
+
return {
|
|
95
|
+
source: event === undefined ? "unavailable" : "task-archive",
|
|
96
|
+
provisional: false,
|
|
97
|
+
commits: commitMap(event?.payload.projectHeads),
|
|
98
|
+
...(event === undefined ? {} : { event })
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (task.status === "active" || task.status === "retired") {
|
|
102
|
+
return {
|
|
103
|
+
source: "current-task-main",
|
|
104
|
+
provisional: true,
|
|
105
|
+
commits: candidateCommits(facts.currentCandidate)
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
source: "unavailable",
|
|
110
|
+
provisional: false,
|
|
111
|
+
commits: new Map()
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function completionEvent(events, completedAt) {
|
|
115
|
+
const exact = latestEvent(events, "task.completed", (event) => (event.createdAt === completedAt));
|
|
116
|
+
return exact ?? latestEvent(events, "task.completed", (event) => (event.createdAt <= completedAt));
|
|
117
|
+
}
|
|
118
|
+
function latestEvent(events, type, predicate) {
|
|
119
|
+
return events
|
|
120
|
+
.filter((event) => event.type === type && predicate(event))
|
|
121
|
+
.slice()
|
|
122
|
+
.sort(compareCreated)
|
|
123
|
+
.at(-1);
|
|
124
|
+
}
|
|
125
|
+
function projectBaseCommits(facts, event) {
|
|
126
|
+
const result = commitMap(event?.payload.projectBases);
|
|
127
|
+
const archived = latestEvent(facts.events, "task.archived", (candidate) => (candidate.payload.projectBases !== undefined));
|
|
128
|
+
for (const [projectId, commit] of commitMap(archived?.payload.projectBases)) {
|
|
129
|
+
if (!result.has(projectId))
|
|
130
|
+
result.set(projectId, commit);
|
|
131
|
+
}
|
|
132
|
+
const current = facts.managedWorkspaces.find((workspace) => (workspace.owner.type === "task"
|
|
133
|
+
&& workspace.owner.taskId === facts.task.id));
|
|
134
|
+
for (const entry of current?.entries ?? []) {
|
|
135
|
+
if (!result.has(entry.projectId))
|
|
136
|
+
result.set(entry.projectId, entry.baseCommit);
|
|
137
|
+
}
|
|
138
|
+
const taskTurns = facts.turns
|
|
139
|
+
.filter((turn) => (turn.workspace?.owner.type === "task"
|
|
140
|
+
&& turn.workspace.owner.taskId === facts.task.id))
|
|
141
|
+
.slice()
|
|
142
|
+
.sort(compareCreated);
|
|
143
|
+
for (const turn of taskTurns) {
|
|
144
|
+
for (const entry of turn.workspace?.entries ?? []) {
|
|
145
|
+
if (!result.has(entry.projectId))
|
|
146
|
+
result.set(entry.projectId, entry.baseCommit);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return result;
|
|
150
|
+
}
|
|
151
|
+
function candidateCommits(candidate) {
|
|
152
|
+
return new Map((candidate?.projects ?? []).map(({ projectId, commit }) => [
|
|
153
|
+
projectId,
|
|
154
|
+
commit.toLowerCase()
|
|
155
|
+
]));
|
|
156
|
+
}
|
|
157
|
+
function commitMap(value) {
|
|
158
|
+
const result = new Map();
|
|
159
|
+
for (const entry of value?.split(",") ?? []) {
|
|
160
|
+
const separator = entry.indexOf("@");
|
|
161
|
+
if (separator <= 0)
|
|
162
|
+
continue;
|
|
163
|
+
const projectId = entry.slice(0, separator).trim();
|
|
164
|
+
const commit = entry.slice(separator + 1).trim().toLowerCase();
|
|
165
|
+
if (projectId.length === 0 || !/^[0-9a-f]{40}$/u.test(commit))
|
|
166
|
+
continue;
|
|
167
|
+
result.set(projectId, commit);
|
|
168
|
+
}
|
|
169
|
+
return result;
|
|
170
|
+
}
|
|
171
|
+
function currentPublicationReferences(publications) {
|
|
172
|
+
const superseded = new Set(publications.flatMap((reference) => (reference.supersedes === undefined ? [] : [reference.supersedes])));
|
|
173
|
+
return publications.filter((reference) => !superseded.has(reference.id));
|
|
174
|
+
}
|
|
175
|
+
function bestPublication(publications) {
|
|
176
|
+
return publications
|
|
177
|
+
.slice()
|
|
178
|
+
.sort((left, right) => (publicationRank(left) - publicationRank(right)
|
|
179
|
+
|| compareCreated(left, right)))
|
|
180
|
+
.at(-1) ?? null;
|
|
181
|
+
}
|
|
182
|
+
function publicationRank(reference) {
|
|
183
|
+
if (reference.state === "merged" && reference.verification === "verified")
|
|
184
|
+
return 4;
|
|
185
|
+
if (reference.state === "merged")
|
|
186
|
+
return 3;
|
|
187
|
+
if (reference.state === "open")
|
|
188
|
+
return 2;
|
|
189
|
+
return 1;
|
|
190
|
+
}
|
|
191
|
+
function projectCoverage(codeDelivery, expectedLocalCommit, publication) {
|
|
192
|
+
if (codeDelivery === "none")
|
|
193
|
+
return "not-applicable";
|
|
194
|
+
if (expectedLocalCommit === null)
|
|
195
|
+
return "head-unavailable";
|
|
196
|
+
if (publication === null)
|
|
197
|
+
return "missing";
|
|
198
|
+
if (publication.localCommit === undefined)
|
|
199
|
+
return "missing-local-commit";
|
|
200
|
+
if (publication.localCommit !== expectedLocalCommit)
|
|
201
|
+
return "stale";
|
|
202
|
+
if (publication.state === "open")
|
|
203
|
+
return "open";
|
|
204
|
+
if (publication.state === "closed")
|
|
205
|
+
return "closed";
|
|
206
|
+
return publication.verification === "verified" ? "verified" : "merged";
|
|
207
|
+
}
|
|
208
|
+
function projectCoverageReason(codeDelivery, expectedLocalCommit, publication, coverage) {
|
|
209
|
+
switch (coverage) {
|
|
210
|
+
case "not-applicable":
|
|
211
|
+
return "Task head equals the managed workspace base; no code delivery is required.";
|
|
212
|
+
case "head-unavailable":
|
|
213
|
+
return "The exact Task delivery head is unavailable, so merge coverage is fail-closed.";
|
|
214
|
+
case "missing":
|
|
215
|
+
return "No current Publication records this Project delivery.";
|
|
216
|
+
case "missing-local-commit":
|
|
217
|
+
return `Publication ${publication.id} does not record a local commit.`;
|
|
218
|
+
case "stale":
|
|
219
|
+
return `Publication ${publication.id} covers ${publication.localCommit}, not the expected Task head ${expectedLocalCommit}.`;
|
|
220
|
+
case "open":
|
|
221
|
+
return `Publication ${publication.id} covers the Task head but is still open.`;
|
|
222
|
+
case "closed":
|
|
223
|
+
return `Publication ${publication.id} covers the Task head but is closed without merge.`;
|
|
224
|
+
case "merged":
|
|
225
|
+
return `Publication ${publication.id} reports the exact Task head as merged.`;
|
|
226
|
+
case "verified":
|
|
227
|
+
return `Publication ${publication.id} verifies the exact Task head as merged.`;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
function compareCreated(left, right) {
|
|
231
|
+
return left.createdAt.localeCompare(right.createdAt)
|
|
232
|
+
|| left.id.localeCompare(right.id, undefined, { numeric: true });
|
|
233
|
+
}
|
package/dist/task/task.js
CHANGED
|
@@ -2,7 +2,7 @@ import { validateTaskWorkspaceIdentity } from "../repository/taskWorkspaceIdenti
|
|
|
2
2
|
export function createTask(id, title, now, metadata = {}) {
|
|
3
3
|
const timestamp = now.toISOString();
|
|
4
4
|
return {
|
|
5
|
-
schemaVersion:
|
|
5
|
+
schemaVersion: 7,
|
|
6
6
|
id: requireSafeIdentity(id, "Task id"),
|
|
7
7
|
title: requireText(title, "Task title"),
|
|
8
8
|
...cloneMetadata(metadata),
|
|
@@ -49,6 +49,89 @@ export function activateTask(task, now) {
|
|
|
49
49
|
return task;
|
|
50
50
|
return { ...task, status: "active", updatedAt: now.toISOString() };
|
|
51
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Freeze the exact remote inputs adopted by activation. Every Project must be
|
|
54
|
+
* present exactly once; Draft metadata remains declarative until this call.
|
|
55
|
+
*/
|
|
56
|
+
export function bindTaskProjectCommits(task, commits, now) {
|
|
57
|
+
validateTask(task);
|
|
58
|
+
if (task.status !== "draft") {
|
|
59
|
+
throw new Error(`Task Project commits can only be bound during Draft activation: ${task.id}.`);
|
|
60
|
+
}
|
|
61
|
+
const byProject = new Map(commits.map(({ projectId, commit }) => [
|
|
62
|
+
requireSafeIdentity(projectId, "Project id"),
|
|
63
|
+
requireCommit(commit, "Task Project commit")
|
|
64
|
+
]));
|
|
65
|
+
if (byProject.size !== commits.length
|
|
66
|
+
|| byProject.size !== task.projectBindings.length
|
|
67
|
+
|| task.projectBindings.some(({ projectId }) => !byProject.has(projectId))) {
|
|
68
|
+
throw new Error(`Task Project commit scope does not match its bindings: ${task.id}.`);
|
|
69
|
+
}
|
|
70
|
+
return validateTask({
|
|
71
|
+
...task,
|
|
72
|
+
projectBindings: task.projectBindings.map((binding) => {
|
|
73
|
+
const commit = byProject.get(binding.projectId);
|
|
74
|
+
return { ...binding, baseCommit: commit, currentCommit: commit };
|
|
75
|
+
}),
|
|
76
|
+
updatedAt: now.toISOString()
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/** Compare-and-swap one authoritative Task-main Project commit. */
|
|
80
|
+
export function advanceTaskProjectCommit(task, projectId, expectedCommit, nextCommit, now) {
|
|
81
|
+
validateTask(task);
|
|
82
|
+
const expected = requireCommit(expectedCommit, "Expected Task Project commit");
|
|
83
|
+
const next = requireCommit(nextCommit, "Next Task Project commit");
|
|
84
|
+
let found = false;
|
|
85
|
+
const projectBindings = task.projectBindings.map((binding) => {
|
|
86
|
+
if (binding.projectId !== projectId)
|
|
87
|
+
return binding;
|
|
88
|
+
found = true;
|
|
89
|
+
if (binding.currentCommit !== expected) {
|
|
90
|
+
throw new Error(`Task Project current commit moved: ${task.id}/${projectId}; `
|
|
91
|
+
+ `expected ${expected}, found ${String(binding.currentCommit)}.`);
|
|
92
|
+
}
|
|
93
|
+
return { ...binding, currentCommit: next };
|
|
94
|
+
});
|
|
95
|
+
if (!found)
|
|
96
|
+
throw new Error(`Task Project binding not found: ${task.id}/${projectId}.`);
|
|
97
|
+
return validateTask({
|
|
98
|
+
...task,
|
|
99
|
+
projectBindings,
|
|
100
|
+
updatedAt: now.toISOString()
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Refresh Task commit facts from its authoritative main clones. An active
|
|
105
|
+
* Task may persist a newly added Project as pending intent before its first
|
|
106
|
+
* workspace prepare; that first successful prepare adopts the clone's HEAD as
|
|
107
|
+
* both the Project baseline and current result.
|
|
108
|
+
*/
|
|
109
|
+
export function synchronizeTaskProjectCommits(task, commits, now) {
|
|
110
|
+
validateTask(task);
|
|
111
|
+
const byProject = new Map(commits.map(({ projectId, commit }) => [
|
|
112
|
+
requireSafeIdentity(projectId, "Project id"),
|
|
113
|
+
requireCommit(commit, "Task Project current commit")
|
|
114
|
+
]));
|
|
115
|
+
if (byProject.size !== commits.length
|
|
116
|
+
|| byProject.size !== task.projectBindings.length
|
|
117
|
+
|| task.projectBindings.some(({ projectId }) => !byProject.has(projectId))) {
|
|
118
|
+
throw new Error(`Task Project commit scope does not match its bindings: ${task.id}.`);
|
|
119
|
+
}
|
|
120
|
+
const projectBindings = task.projectBindings.map((binding) => {
|
|
121
|
+
const commit = byProject.get(binding.projectId);
|
|
122
|
+
if (binding.baseCommit === undefined) {
|
|
123
|
+
return { ...binding, baseCommit: commit, currentCommit: commit };
|
|
124
|
+
}
|
|
125
|
+
return { ...binding, currentCommit: commit };
|
|
126
|
+
});
|
|
127
|
+
if (projectBindings.every((binding, index) => (binding.currentCommit === task.projectBindings[index]?.currentCommit)))
|
|
128
|
+
return task;
|
|
129
|
+
return validateTask({
|
|
130
|
+
...task,
|
|
131
|
+
projectBindings,
|
|
132
|
+
updatedAt: now.toISOString()
|
|
133
|
+
});
|
|
134
|
+
}
|
|
52
135
|
/** Explicitly retires a stale aggregate while retaining all historical facts. */
|
|
53
136
|
export function retireTask(task, input, now) {
|
|
54
137
|
validateTask(task);
|
|
@@ -200,8 +283,8 @@ export function startTaskExecution(task, now) {
|
|
|
200
283
|
});
|
|
201
284
|
}
|
|
202
285
|
export function validateTask(task) {
|
|
203
|
-
if (task.schemaVersion !==
|
|
204
|
-
throw new Error("Task must use schemaVersion
|
|
286
|
+
if (task.schemaVersion !== 7)
|
|
287
|
+
throw new Error("Task must use schemaVersion 7.");
|
|
205
288
|
requireSafeIdentity(task.id, "Task id");
|
|
206
289
|
requireText(task.title, "Task title");
|
|
207
290
|
if (task.type !== undefined)
|
|
@@ -338,6 +421,15 @@ function normalizeProjectBindings(bindings) {
|
|
|
338
421
|
const projectId = requireSafeIdentity(binding.projectId, "Project id");
|
|
339
422
|
const directory = requireSafeIdentity(binding.directory, "Project directory");
|
|
340
423
|
const baseRef = requireText(binding.baseRef, "Task base ref");
|
|
424
|
+
const baseCommit = binding.baseCommit === undefined
|
|
425
|
+
? undefined
|
|
426
|
+
: requireCommit(binding.baseCommit, "Task Project base commit");
|
|
427
|
+
const currentCommit = binding.currentCommit === undefined
|
|
428
|
+
? undefined
|
|
429
|
+
: requireCommit(binding.currentCommit, "Task Project current commit");
|
|
430
|
+
if ((baseCommit === undefined) !== (currentCommit === undefined)) {
|
|
431
|
+
throw new Error(`Task Project commit metadata must include both baseCommit and currentCommit: ${projectId}.`);
|
|
432
|
+
}
|
|
341
433
|
if (projectIds.has(projectId)) {
|
|
342
434
|
throw new Error(`Task Project is duplicated: ${projectId}.`);
|
|
343
435
|
}
|
|
@@ -346,9 +438,21 @@ function normalizeProjectBindings(bindings) {
|
|
|
346
438
|
}
|
|
347
439
|
projectIds.add(projectId);
|
|
348
440
|
directories.add(directory);
|
|
349
|
-
return {
|
|
441
|
+
return {
|
|
442
|
+
projectId,
|
|
443
|
+
directory,
|
|
444
|
+
baseRef,
|
|
445
|
+
...(baseCommit === undefined ? {} : { baseCommit, currentCommit })
|
|
446
|
+
};
|
|
350
447
|
});
|
|
351
448
|
}
|
|
449
|
+
function requireCommit(value, label) {
|
|
450
|
+
const normalized = requireText(value, label).toLowerCase();
|
|
451
|
+
if (!/^[a-f0-9]{40}(?:[a-f0-9]{24})?$/u.test(normalized)) {
|
|
452
|
+
throw new Error(`${label} is invalid.`);
|
|
453
|
+
}
|
|
454
|
+
return normalized;
|
|
455
|
+
}
|
|
352
456
|
export function taskProjectBinding(task, projectId) {
|
|
353
457
|
return task.projectBindings.find((binding) => binding.projectId === projectId);
|
|
354
458
|
}
|
package/dist/turn/turn.js
CHANGED
|
@@ -2,6 +2,8 @@ import { validateEffectiveLaunchSnapshot } from "../executor/effectiveLaunch.js"
|
|
|
2
2
|
import { validateTaskRecordReference } from "../task/taskRecordReference.js";
|
|
3
3
|
import { validateManagedWorkspace } from "../worktree/managedWorkspace.js";
|
|
4
4
|
import { createTurnInput, createTurnInputEnvelope, validateTurnInput } from "../context/turnInputContract.js";
|
|
5
|
+
import { boundedTurnFailureDiagnostic, MAX_TURN_FAILURE_DIAGNOSTIC_BYTES, MAX_TURN_RESULT_OUTPUT_BYTES } from "../domain/agentResultTransport.js";
|
|
6
|
+
export { boundedTurnFailureDiagnostic, MAX_TURN_FAILURE_DIAGNOSTIC_BYTES, MAX_TURN_RESULT_OUTPUT_BYTES, transportAgentResult } from "../domain/agentResultTransport.js";
|
|
5
7
|
export function createTurn(id, taskId, roleName, mode, input, now, context) {
|
|
6
8
|
if (mode !== "new" && mode !== "resume") {
|
|
7
9
|
throw new Error(`Turn dispatch mode is invalid: ${mode}.`);
|
|
@@ -9,7 +11,7 @@ export function createTurn(id, taskId, roleName, mode, input, now, context) {
|
|
|
9
11
|
const timestamp = now.toISOString();
|
|
10
12
|
const normalizedInput = validateTurnInput(input);
|
|
11
13
|
return {
|
|
12
|
-
schemaVersion:
|
|
14
|
+
schemaVersion: 5,
|
|
13
15
|
id: requireSafeIdentity(id, "Turn id"),
|
|
14
16
|
taskId: requireSafeIdentity(taskId, "Task id"),
|
|
15
17
|
roleName: requireSafeIdentity(roleName, "Role name"),
|
|
@@ -28,6 +30,11 @@ export function createTurn(id, taskId, roleName, mode, input, now, context) {
|
|
|
28
30
|
...(context.executionLaneId === undefined
|
|
29
31
|
? {}
|
|
30
32
|
: { executionLaneId: requireSafeIdentity(context.executionLaneId, "ExecutionLane id") }),
|
|
33
|
+
...(context.sourceExecutionGroupId === undefined
|
|
34
|
+
? {}
|
|
35
|
+
: {
|
|
36
|
+
sourceExecutionGroupId: requireSafeIdentity(context.sourceExecutionGroupId, "Source ExecutionGroup id")
|
|
37
|
+
}),
|
|
31
38
|
...(context.workspace === undefined
|
|
32
39
|
? {}
|
|
33
40
|
: { workspace: validateManagedWorkspace(context.workspace) }),
|
|
@@ -91,6 +98,7 @@ export function validateTurn(run) {
|
|
|
91
98
|
"reviewRoundId",
|
|
92
99
|
"executionGroupId",
|
|
93
100
|
"executionLaneId",
|
|
101
|
+
"sourceExecutionGroupId",
|
|
94
102
|
"workspace",
|
|
95
103
|
"effective",
|
|
96
104
|
"status",
|
|
@@ -98,8 +106,8 @@ export function validateTurn(run) {
|
|
|
98
106
|
"createdAt",
|
|
99
107
|
"updatedAt"
|
|
100
108
|
], "Turn");
|
|
101
|
-
if (run.schemaVersion !==
|
|
102
|
-
throw new Error("Turn must use schemaVersion
|
|
109
|
+
if (run.schemaVersion !== 5)
|
|
110
|
+
throw new Error("Turn must use schemaVersion 5.");
|
|
103
111
|
validateTaskRecordReference({ taskId: run.taskId, localId: run.id }, "turn");
|
|
104
112
|
requireSafeIdentity(run.roleName, "Role name");
|
|
105
113
|
if (run.mode !== "new" && run.mode !== "resume") {
|
|
@@ -133,6 +141,16 @@ export function validateTurn(run) {
|
|
|
133
141
|
requireSafeIdentity(run.executionGroupId, "ExecutionGroup id");
|
|
134
142
|
requireSafeIdentity(run.executionLaneId, "ExecutionLane id");
|
|
135
143
|
}
|
|
144
|
+
if (run.sourceExecutionGroupId !== undefined) {
|
|
145
|
+
requireSafeIdentity(run.sourceExecutionGroupId, "Source ExecutionGroup id");
|
|
146
|
+
if ((run.purpose === "execution" && run.workItemId === undefined)
|
|
147
|
+
|| (run.purpose === "review" && run.reviewRoundId === undefined)) {
|
|
148
|
+
throw new Error("A source ExecutionGroup requires a main execution or review Turn.");
|
|
149
|
+
}
|
|
150
|
+
if (run.executionGroupId !== undefined || run.executionLaneId !== undefined) {
|
|
151
|
+
throw new Error("A main Turn cannot also be an Execution Lane Turn.");
|
|
152
|
+
}
|
|
153
|
+
}
|
|
136
154
|
if (run.workspace !== undefined) {
|
|
137
155
|
validateManagedWorkspace(run.workspace);
|
|
138
156
|
if (run.workspace.owner.taskId !== run.taskId) {
|
|
@@ -233,20 +251,31 @@ export function validateTurn(run) {
|
|
|
233
251
|
if (!isTurnFailureReason(result.failureReason)) {
|
|
234
252
|
throw new Error(`Failed Turn reason is invalid: ${String(result.failureReason)}.`);
|
|
235
253
|
}
|
|
254
|
+
if (result.diagnostic === undefined) {
|
|
255
|
+
throw new Error("A failed Turn requires a Core diagnostic.");
|
|
256
|
+
}
|
|
257
|
+
if (result.systemEvidence !== undefined) {
|
|
258
|
+
throw new Error("A failed Turn cannot carry successful system evidence.");
|
|
259
|
+
}
|
|
236
260
|
}
|
|
237
|
-
else
|
|
238
|
-
|
|
261
|
+
else {
|
|
262
|
+
if (result.output === undefined) {
|
|
263
|
+
throw new Error("A completed Turn requires an Agent result.");
|
|
264
|
+
}
|
|
265
|
+
if (result.failureReason !== undefined || result.diagnostic !== undefined) {
|
|
266
|
+
throw new Error("A completed Turn cannot carry failure metadata.");
|
|
267
|
+
}
|
|
239
268
|
}
|
|
240
269
|
}
|
|
241
270
|
return run;
|
|
242
271
|
}
|
|
243
|
-
export function completeTurn(run, output, now, provider) {
|
|
244
|
-
return finishTurn(run, "completed", output, now, undefined, provider);
|
|
272
|
+
export function completeTurn(run, output, now, provider, systemEvidence) {
|
|
273
|
+
return finishTurn(run, "completed", output, now, undefined, provider, systemEvidence);
|
|
245
274
|
}
|
|
246
|
-
export function failTurn(run, reason,
|
|
247
|
-
return finishTurn(run, "failed", output, now, reason, provider);
|
|
275
|
+
export function failTurn(run, reason, diagnostic, now, provider, output) {
|
|
276
|
+
return finishTurn(run, "failed", output, now, reason, provider, undefined, boundedTurnFailureDiagnostic(diagnostic));
|
|
248
277
|
}
|
|
249
|
-
function finishTurn(run, status, output, now, failureReason, provider) {
|
|
278
|
+
function finishTurn(run, status, output, now, failureReason, provider, systemEvidence, diagnostic) {
|
|
250
279
|
if (run.status !== "active") {
|
|
251
280
|
throw new Error(`Turn is already terminal: ${run.id}.`);
|
|
252
281
|
}
|
|
@@ -255,10 +284,16 @@ function finishTurn(run, status, output, now, failureReason, provider) {
|
|
|
255
284
|
...run,
|
|
256
285
|
status,
|
|
257
286
|
result: {
|
|
258
|
-
schemaVersion:
|
|
259
|
-
output: requireResultText(output, "Turn result output"),
|
|
287
|
+
schemaVersion: 2,
|
|
288
|
+
...(output === undefined ? {} : { output: requireResultText(output, "Turn result output") }),
|
|
289
|
+
...(diagnostic === undefined
|
|
290
|
+
? {}
|
|
291
|
+
: { diagnostic: requireDiagnosticText(diagnostic, "Turn result diagnostic") }),
|
|
260
292
|
completedAt: timestamp,
|
|
261
293
|
...(provider === undefined ? {} : { provider: validateTurnProviderResult(provider) }),
|
|
294
|
+
...(systemEvidence === undefined
|
|
295
|
+
? {}
|
|
296
|
+
: { systemEvidence: validateTurnSystemEvidence(systemEvidence) }),
|
|
262
297
|
...(failureReason === undefined ? {} : { failureReason })
|
|
263
298
|
},
|
|
264
299
|
updatedAt: timestamp,
|
|
@@ -266,15 +301,54 @@ function finishTurn(run, status, output, now, failureReason, provider) {
|
|
|
266
301
|
return validateTurn(terminal);
|
|
267
302
|
}
|
|
268
303
|
function validateTurnResult(result) {
|
|
269
|
-
if (result === undefined || result.schemaVersion !==
|
|
270
|
-
throw new Error("A terminal Turn requires TurnResult schemaVersion
|
|
304
|
+
if (result === undefined || result.schemaVersion !== 2) {
|
|
305
|
+
throw new Error("A terminal Turn requires TurnResult schemaVersion 2.");
|
|
306
|
+
}
|
|
307
|
+
rejectUnknownFields(result, [
|
|
308
|
+
"schemaVersion",
|
|
309
|
+
"output",
|
|
310
|
+
"diagnostic",
|
|
311
|
+
"completedAt",
|
|
312
|
+
"provider",
|
|
313
|
+
"systemEvidence",
|
|
314
|
+
"failureReason"
|
|
315
|
+
], "Turn result");
|
|
316
|
+
if (result.output !== undefined)
|
|
317
|
+
requireResultText(result.output, "Turn result output");
|
|
318
|
+
if (result.diagnostic !== undefined) {
|
|
319
|
+
requireDiagnosticText(result.diagnostic, "Turn result diagnostic");
|
|
271
320
|
}
|
|
272
|
-
requireResultText(result.output, "Turn result output");
|
|
273
321
|
requireTimestamp(result.completedAt, "Turn result completedAt");
|
|
274
322
|
if (result.provider !== undefined)
|
|
275
323
|
validateTurnProviderResult(result.provider);
|
|
324
|
+
if (result.systemEvidence !== undefined)
|
|
325
|
+
validateTurnSystemEvidence(result.systemEvidence);
|
|
276
326
|
return result;
|
|
277
327
|
}
|
|
328
|
+
function validateTurnSystemEvidence(evidence) {
|
|
329
|
+
rejectUnknownFields(evidence, [
|
|
330
|
+
"workspaceSnapshot"
|
|
331
|
+
], "Turn system evidence");
|
|
332
|
+
if (evidence.workspaceSnapshot !== undefined) {
|
|
333
|
+
const snapshot = evidence.workspaceSnapshot;
|
|
334
|
+
if (snapshot.schemaVersion !== 1 || !Array.isArray(snapshot.projects)) {
|
|
335
|
+
throw new Error("Turn workspace snapshot is invalid.");
|
|
336
|
+
}
|
|
337
|
+
const projectIds = new Set();
|
|
338
|
+
for (const project of snapshot.projects) {
|
|
339
|
+
requireSafeIdentity(project.projectId, "Turn workspace snapshot Project id");
|
|
340
|
+
if (projectIds.has(project.projectId)) {
|
|
341
|
+
throw new Error("Turn workspace snapshot Project ids must be unique.");
|
|
342
|
+
}
|
|
343
|
+
projectIds.add(project.projectId);
|
|
344
|
+
if (!/^[0-9a-f]{40}$/u.test(project.headCommit)) {
|
|
345
|
+
throw new Error("Turn workspace snapshot commit is invalid.");
|
|
346
|
+
}
|
|
347
|
+
requireText(project.branch, "Turn workspace snapshot branch");
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return evidence;
|
|
351
|
+
}
|
|
278
352
|
function validateTurnProviderResult(provider) {
|
|
279
353
|
requireText(provider.providerNamespace, "Provider namespace");
|
|
280
354
|
requireText(provider.accountScope, "Provider account scope");
|
|
@@ -306,7 +380,10 @@ function turnEnvelopeContext(run) {
|
|
|
306
380
|
...(run.executionGroupId === undefined ? {} : {
|
|
307
381
|
executionGroupId: run.executionGroupId,
|
|
308
382
|
executionLaneId: run.executionLaneId
|
|
309
|
-
})
|
|
383
|
+
}),
|
|
384
|
+
...(run.sourceExecutionGroupId === undefined
|
|
385
|
+
? {}
|
|
386
|
+
: { sourceExecutionGroupId: run.sourceExecutionGroupId })
|
|
310
387
|
}
|
|
311
388
|
};
|
|
312
389
|
}
|
|
@@ -332,6 +409,19 @@ function requireResultText(value, label) {
|
|
|
332
409
|
throw new Error(`${label} is invalid.`);
|
|
333
410
|
if (value.trim().length === 0)
|
|
334
411
|
throw new Error(`${label} is required.`);
|
|
412
|
+
if (Buffer.byteLength(value, "utf8") > MAX_TURN_RESULT_OUTPUT_BYTES) {
|
|
413
|
+
throw new Error(`${label} exceeds ${MAX_TURN_RESULT_OUTPUT_BYTES} bytes.`);
|
|
414
|
+
}
|
|
415
|
+
return value;
|
|
416
|
+
}
|
|
417
|
+
function requireDiagnosticText(value, label) {
|
|
418
|
+
if (typeof value !== "string" || value.includes("\0"))
|
|
419
|
+
throw new Error(`${label} is invalid.`);
|
|
420
|
+
if (value.trim().length === 0)
|
|
421
|
+
throw new Error(`${label} is required.`);
|
|
422
|
+
if (Buffer.byteLength(value, "utf8") > MAX_TURN_FAILURE_DIAGNOSTIC_BYTES) {
|
|
423
|
+
throw new Error(`${label} exceeds ${MAX_TURN_FAILURE_DIAGNOSTIC_BYTES} bytes.`);
|
|
424
|
+
}
|
|
335
425
|
return value;
|
|
336
426
|
}
|
|
337
427
|
function requireTimestamp(value, label) {
|
|
@@ -345,6 +435,9 @@ function isTurnFailureReason(value) {
|
|
|
345
435
|
"runtime-failed",
|
|
346
436
|
"delivery-unknown",
|
|
347
437
|
"missing-result",
|
|
438
|
+
"workspace-unavailable",
|
|
439
|
+
"workspace-dirty",
|
|
440
|
+
"workspace-branch-mismatch",
|
|
348
441
|
"cancelled"
|
|
349
442
|
].includes(String(value));
|
|
350
443
|
}
|
|
@@ -331,32 +331,6 @@ export async function verifyGateArtifactForReview(store, projectId, key, expecte
|
|
|
331
331
|
}
|
|
332
332
|
return { ok: true, artifact };
|
|
333
333
|
}
|
|
334
|
-
/**
|
|
335
|
-
* Whether a `gate-artifact:` evidence reference covers the requested check
|
|
336
|
-
* commands for an exact candidate commit. Used by the integration queue's
|
|
337
|
-
* evidence fence: a passed gate step covers only its own command, and the
|
|
338
|
-
* artifact must bind the exact candidate tree.
|
|
339
|
-
*/
|
|
340
|
-
export async function gateArtifactCoversCheckCommands(store, projectId, ref, checkCommands, candidateCommit) {
|
|
341
|
-
const key = parseGateArtifactRef(ref);
|
|
342
|
-
if (key === undefined)
|
|
343
|
-
return false;
|
|
344
|
-
const artifact = loadGateArtifact(store, projectId, key);
|
|
345
|
-
if (artifact === null || !isReusableGateArtifact(artifact))
|
|
346
|
-
return false;
|
|
347
|
-
if (artifact.commit !== candidateCommit)
|
|
348
|
-
return false;
|
|
349
|
-
const logs = store.getGateArtifactLogs(artifact.key);
|
|
350
|
-
const verification = verifyGateArtifactLogs(artifact, logs);
|
|
351
|
-
if (!verification.ok)
|
|
352
|
-
return false;
|
|
353
|
-
if (checkCommands.length === 0)
|
|
354
|
-
return true;
|
|
355
|
-
const covered = new Set(artifact.steps
|
|
356
|
-
.filter((step) => step.outcome === "passed")
|
|
357
|
-
.map((step) => step.command));
|
|
358
|
-
return checkCommands.every((command) => covered.has(command));
|
|
359
|
-
}
|
|
360
334
|
/**
|
|
361
335
|
* Enforce mode (rollout step 4): reject ad-hoc full-suite shell checks that
|
|
362
336
|
* duplicate the plan's L2 steps for a configured Project. Explicit targeted
|