@zq-silk/yui 0.12.3 → 0.13.0
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/dist/cli/commandCatalog.js +2 -2
- package/dist/cli/updatePorts.js +18 -2
- package/dist/cli.js +3 -6
- package/dist/commands/durableJobCommands.js +6 -18
- package/dist/commands/executionAuditCommands.js +1 -1
- package/dist/commands/taskActor.js +35 -61
- package/dist/commands/taskCommands.js +260 -200
- package/dist/commands/taskIntegrationCommands.js +33 -24
- package/dist/commands/taskIntegrationQueueCommands.js +16 -18
- package/dist/commands/taskPublicationCommands.js +0 -4
- package/dist/commands/taskWorkspaceCommands.js +1 -1
- package/dist/context/runContextPack.js +15 -0
- package/dist/controller/fileSchedulerStoreAdapter.js +17 -1
- package/dist/controller/jobClient.js +5 -8
- package/dist/controller/jobControl.js +59 -105
- package/dist/executor/workspacePreflightClassification.js +6 -5
- package/dist/integration/gitIntegrationService.js +3 -3
- package/dist/integration/integrationAttempt.js +13 -9
- package/dist/integration/integrationQueueService.js +7 -7
- package/dist/lifecycle/exactRunTerminalization.js +146 -6
- package/dist/milestone/milestone.js +9 -3
- package/dist/review/deltaRecheck.js +1 -1
- package/dist/review/reviewFinding.js +3 -3
- package/dist/review/reviewRound.js +11 -8
- package/dist/run/rejectedYieldAttempt.js +221 -0
- package/dist/runtime/tmuxAdapters.js +12 -3
- package/dist/scheduler/activeRoleRunDelivery.js +4 -0
- package/dist/storage/migration/productionRegistry.js +9 -0
- package/dist/storage/taskStore.js +9 -8
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +4 -0
- package/skills/yui-operator/SKILL.md +19 -13
|
@@ -6,7 +6,7 @@ import { defaultTableWidth, renderTable } from "../output/table.js";
|
|
|
6
6
|
import { createIntegrationAttempt, recordResolutionDecision, supersedeIntegration, updateIntegrationAttempt } from "../integration/integrationAttempt.js";
|
|
7
7
|
import { GitIntegrationService } from "../integration/gitIntegrationService.js";
|
|
8
8
|
import { runTaskIntegrationQueueCommand } from "./taskIntegrationQueueCommands.js";
|
|
9
|
-
import {
|
|
9
|
+
import { taskLocalActor } from "./taskActor.js";
|
|
10
10
|
import { resolveTaskRecordReference } from "../task/taskRecordReference.js";
|
|
11
11
|
import { runDeliveryGuardPreflight, withGuardWarnings } from "./deliveryGuardPreflight.js";
|
|
12
12
|
export async function runTaskIntegrationCommand(args, store, home, options = {}) {
|
|
@@ -18,12 +18,13 @@ export async function runTaskIntegrationCommand(args, store, home, options = {})
|
|
|
18
18
|
return continueIntegration(rest, store, home, now, options);
|
|
19
19
|
}
|
|
20
20
|
if (command === "resolve") {
|
|
21
|
-
return resolveDecision(rest, store, now, options.environment);
|
|
21
|
+
return resolveDecision(rest, store, now, options.environment, home);
|
|
22
22
|
}
|
|
23
23
|
if (command === "abort")
|
|
24
|
-
return abortIntegration(rest, store, now(), options);
|
|
25
|
-
if (command === "supersede")
|
|
26
|
-
return supersedeIntegrationCommand(rest, store, now(), options.environment);
|
|
24
|
+
return abortIntegration(rest, store, now(), options, home);
|
|
25
|
+
if (command === "supersede") {
|
|
26
|
+
return supersedeIntegrationCommand(rest, store, now(), options.environment, home);
|
|
27
|
+
}
|
|
27
28
|
if (command === "cleanup") {
|
|
28
29
|
return cleanupIntegration(rest, store, home, options.environment);
|
|
29
30
|
}
|
|
@@ -43,6 +44,7 @@ async function cleanupIntegration(args, store, home, environment) {
|
|
|
43
44
|
throw usageError("Task Integration cleanup usage: yui task integration cleanup <task>/<integration>.");
|
|
44
45
|
}
|
|
45
46
|
const integration = requireIntegration(store, args[0], environment);
|
|
47
|
+
taskLocalActor(store, environment, integration.taskId, home);
|
|
46
48
|
if (integration.status !== "committed"
|
|
47
49
|
&& integration.status !== "superseded"
|
|
48
50
|
&& integration.status !== "failed") {
|
|
@@ -83,6 +85,7 @@ async function start(args, store, home, now, options) {
|
|
|
83
85
|
if (task.status !== "active") {
|
|
84
86
|
throw usageError(`Task is not active: ${task.id}/${task.status}.`);
|
|
85
87
|
}
|
|
88
|
+
taskLocalActor(store, options.environment, task.id, home);
|
|
86
89
|
const changeSetIds = parsed.many.get("--change-set") ?? [];
|
|
87
90
|
if (changeSetIds.length === 0)
|
|
88
91
|
throw usageError("--change-set is required.", usage);
|
|
@@ -135,6 +138,7 @@ async function start(args, store, home, now, options) {
|
|
|
135
138
|
// record creation are atomic, so a concurrent Leader cannot sneak a
|
|
136
139
|
// duplicate Integration between the guard and the write.
|
|
137
140
|
const integration = store.transaction((tx) => {
|
|
141
|
+
taskLocalActor(tx, options.environment, task.id, home);
|
|
138
142
|
const guard = runDeliveryGuardPreflight(tx, task.id, {
|
|
139
143
|
kind: "integration-start",
|
|
140
144
|
projectId: project.id,
|
|
@@ -165,6 +169,7 @@ async function continueIntegration(args, store, home, now, options) {
|
|
|
165
169
|
throw usageError(usage);
|
|
166
170
|
const integration = requireIntegration(store, parsed.positionals[0], options.environment);
|
|
167
171
|
requireActiveIntegrationTask(store, integration);
|
|
172
|
+
taskLocalActor(store, options.environment, integration.taskId, home);
|
|
168
173
|
if (integration.status !== "validating"
|
|
169
174
|
&& integration.status !== "running"
|
|
170
175
|
&& (integration.status !== "blocked"
|
|
@@ -178,45 +183,47 @@ async function runIntegration(store, home, integration, now, options) {
|
|
|
178
183
|
const output = result.status === "committed"
|
|
179
184
|
? `Integrated ${result.attempt.changeSetIds.join(", ")} into ${result.attempt.targetRef} with CAS (${result.attempt.id})\n`
|
|
180
185
|
: result.status === "blocked"
|
|
181
|
-
? `Integration ${result.attempt.id} requires a
|
|
186
|
+
? `Integration ${result.attempt.id} requires a Task Agent resolution decision in ${result.workspace.path}\n`
|
|
182
187
|
: result.status === "checks-running"
|
|
183
188
|
? `Integration ${result.attempt.id} checks are running as DurableJob ${result.job.id}; run 'yui task integration continue ${result.attempt.taskId}/${result.attempt.id}' when the job finishes\n`
|
|
184
189
|
: `Integration ${result.attempt.id} failed; target ref was not advanced\n`;
|
|
185
190
|
return { output, data: result };
|
|
186
191
|
}
|
|
187
|
-
function resolveDecision(args, store, now, environment) {
|
|
192
|
+
function resolveDecision(args, store, now, environment, home) {
|
|
188
193
|
const usage = "Task Integration resolve usage: yui task integration resolve <task>/<integration> --option <manual-resolution|reject> --rationale <text>.";
|
|
189
194
|
const parsed = parseRepeatable(args, new Set(), new Set(["--option", "--rationale"]), usage);
|
|
190
195
|
if (parsed.positionals.length !== 1)
|
|
191
196
|
throw usageError(usage);
|
|
192
|
-
const integration = requireIntegration(store, parsed.positionals[0], environment);
|
|
193
|
-
const task = requireActiveIntegrationTask(store, integration);
|
|
194
|
-
if (taskActor(environment, task.id) !== "leader") {
|
|
195
|
-
throw usageError("Only the Task Leader can resolve an Integration conflict.");
|
|
196
|
-
}
|
|
197
197
|
const selectedOption = parsed.one.get("--option");
|
|
198
198
|
const rationale = parsed.one.get("--rationale");
|
|
199
199
|
if (selectedOption === undefined || rationale === undefined)
|
|
200
200
|
throw usageError(usage);
|
|
201
|
-
const resolved =
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
201
|
+
const resolved = store.transaction((tx) => {
|
|
202
|
+
const integration = requireIntegration(tx, parsed.positionals[0], environment);
|
|
203
|
+
const task = requireActiveIntegrationTask(tx, integration);
|
|
204
|
+
const actor = taskLocalActor(tx, environment, task.id, home);
|
|
205
|
+
const updated = recordResolutionDecision(integration, {
|
|
206
|
+
action: selectedOption,
|
|
207
|
+
rationale
|
|
208
|
+
}, actor, now());
|
|
209
|
+
tx.saveIntegrationAttempt(updated.taskId, updated);
|
|
210
|
+
return updated;
|
|
211
|
+
});
|
|
206
212
|
return {
|
|
207
213
|
output: selectedOption === "manual-resolution"
|
|
208
|
-
? `
|
|
209
|
-
: `
|
|
214
|
+
? `Selected manual resolution for ${resolved.id}; resolve its candidate worktree, then run integration continue\n`
|
|
215
|
+
: `Rejected Integration ${resolved.id}\n`,
|
|
210
216
|
data: { integration: resolved }
|
|
211
217
|
};
|
|
212
218
|
}
|
|
213
|
-
async function abortIntegration(args, store, now, options) {
|
|
219
|
+
async function abortIntegration(args, store, now, options, home) {
|
|
214
220
|
const usage = "Task Integration abort usage: yui task integration abort <task>/<integration> --reason <text>.";
|
|
215
221
|
const parsed = parseRepeatable(args, new Set(), new Set(["--reason"]), usage);
|
|
216
222
|
if (parsed.positionals.length !== 1)
|
|
217
223
|
throw usageError(usage);
|
|
218
224
|
const integration = requireIntegration(store, parsed.positionals[0], options.environment);
|
|
219
225
|
requireActiveIntegrationTask(store, integration);
|
|
226
|
+
taskLocalActor(store, options.environment, integration.taskId, home);
|
|
220
227
|
if (integration.status !== "running" && integration.status !== "blocked") {
|
|
221
228
|
throw usageError(`Integration cannot be aborted from ${integration.status}: ${integration.id}.`);
|
|
222
229
|
}
|
|
@@ -238,6 +245,7 @@ async function abortIntegration(args, store, now, options) {
|
|
|
238
245
|
if (current.status !== "running" && current.status !== "blocked") {
|
|
239
246
|
throw usageError(`Integration cannot be aborted from ${current.status}: ${current.id}.`);
|
|
240
247
|
}
|
|
248
|
+
taskLocalActor(tx, options.environment, current.taskId, home);
|
|
241
249
|
const aborted = updateIntegrationAttempt(current, {
|
|
242
250
|
status: "failed",
|
|
243
251
|
checks: [
|
|
@@ -252,7 +260,7 @@ async function abortIntegration(args, store, now, options) {
|
|
|
252
260
|
};
|
|
253
261
|
});
|
|
254
262
|
}
|
|
255
|
-
function supersedeIntegrationCommand(args, store, now, environment) {
|
|
263
|
+
function supersedeIntegrationCommand(args, store, now, environment, home) {
|
|
256
264
|
const usage = "Task Integration supersede usage: yui task integration supersede <task>/<integration> --reason <text>.";
|
|
257
265
|
const parsed = parseRepeatable(args, new Set(), new Set(["--reason"]), usage);
|
|
258
266
|
if (parsed.positionals.length !== 1)
|
|
@@ -265,9 +273,9 @@ function supersedeIntegrationCommand(args, store, now, environment) {
|
|
|
265
273
|
const reason = parsed.one.get("--reason");
|
|
266
274
|
if (reason === undefined)
|
|
267
275
|
throw usageError(usage);
|
|
268
|
-
//
|
|
269
|
-
//
|
|
270
|
-
|
|
276
|
+
// Superseding a committed Integration rewrites delivery-baseline evidence
|
|
277
|
+
// and audit history, so it remains an explicit Task-control decision.
|
|
278
|
+
taskLocalActor(store, environment, integration.taskId, home);
|
|
271
279
|
// A queue-backed committed Attempt cannot be superseded: the queue entry
|
|
272
280
|
// would remain in its current status while its Attempt becomes "superseded",
|
|
273
281
|
// leaving contradictory terminal records that never converge. This covers
|
|
@@ -287,6 +295,7 @@ function supersedeIntegrationCommand(args, store, now, environment) {
|
|
|
287
295
|
if (current.status !== "committed") {
|
|
288
296
|
throw usageError(`Integration cannot be superseded from ${current.status}: ${current.id}.`);
|
|
289
297
|
}
|
|
298
|
+
taskLocalActor(tx, environment, current.taskId, home);
|
|
290
299
|
const superseded = supersedeIntegration(current, reason, now);
|
|
291
300
|
tx.saveIntegrationAttempt(superseded.taskId, superseded);
|
|
292
301
|
return {
|
|
@@ -3,7 +3,7 @@ import { defaultTableWidth, renderTable } from "../output/table.js";
|
|
|
3
3
|
import { resolveProject } from "../repository/project.js";
|
|
4
4
|
import { NodeGitWorkspace } from "../repository/gitWorkspace.js";
|
|
5
5
|
import { enqueueIntegrationQueueEntry, processIntegrationQueue, reconcileIntegrationQueueEntry, requeueIntegrationQueueEntry, supersedeIntegrationQueueEntry } from "../integration/integrationQueueService.js";
|
|
6
|
-
import {
|
|
6
|
+
import { taskLocalActor } from "./taskActor.js";
|
|
7
7
|
import { parseRepeatable } from "./taskIntegrationCommands.js";
|
|
8
8
|
import { resolveTaskRecordReference } from "../task/taskRecordReference.js";
|
|
9
9
|
/**
|
|
@@ -22,11 +22,11 @@ export async function runTaskIntegrationQueueCommand(args, store, home, options
|
|
|
22
22
|
if (command === "process")
|
|
23
23
|
return process(rest, store, home, options);
|
|
24
24
|
if (command === "supersede")
|
|
25
|
-
return supersede(rest, store, options);
|
|
25
|
+
return supersede(rest, store, options, home);
|
|
26
26
|
if (command === "requeue")
|
|
27
|
-
return requeue(rest, store, options);
|
|
27
|
+
return requeue(rest, store, options, home);
|
|
28
28
|
if (command === "reconcile")
|
|
29
|
-
return reconcile(rest, store, options);
|
|
29
|
+
return reconcile(rest, store, options, home);
|
|
30
30
|
throw usageError(command === undefined
|
|
31
31
|
? "Task Integration queue command is required."
|
|
32
32
|
: `Unknown command: task integration queue ${command}`);
|
|
@@ -37,7 +37,7 @@ async function enqueue(args, store, options, home) {
|
|
|
37
37
|
if (parsed.positionals.length !== 1)
|
|
38
38
|
throw usageError(usage);
|
|
39
39
|
const task = requireActiveTask(store, parsed.positionals[0]);
|
|
40
|
-
|
|
40
|
+
requireTaskControlActor(store, options.environment, task.id, home);
|
|
41
41
|
const projectRef = parsed.one.get("--project");
|
|
42
42
|
const changeSetId = parsed.one.get("--change-set");
|
|
43
43
|
if (projectRef === undefined || changeSetId === undefined)
|
|
@@ -120,7 +120,7 @@ async function process(args, store, home, options) {
|
|
|
120
120
|
if (parsed.positionals.length !== 1)
|
|
121
121
|
throw usageError(usage);
|
|
122
122
|
const task = requireActiveTask(store, parsed.positionals[0]);
|
|
123
|
-
|
|
123
|
+
requireTaskControlActor(store, options.environment, task.id, home);
|
|
124
124
|
const limitValue = parsed.one.get("--limit");
|
|
125
125
|
const limit = limitValue === undefined ? undefined : Number.parseInt(limitValue, 10);
|
|
126
126
|
if (limitValue !== undefined && (!Number.isSafeInteger(limit) || limit < 1)) {
|
|
@@ -148,40 +148,40 @@ async function process(args, store, home, options) {
|
|
|
148
148
|
: `${lines.join("\n")}\n`;
|
|
149
149
|
return { output, data: { processed } };
|
|
150
150
|
}
|
|
151
|
-
function supersede(args, store, options) {
|
|
151
|
+
function supersede(args, store, options, home) {
|
|
152
152
|
const usage = "Task Integration queue supersede usage: yui task integration queue supersede <task>/<entry> --reason <text>.";
|
|
153
153
|
const parsed = parseRepeatable(args, new Set(), new Set(["--reason"]), usage);
|
|
154
154
|
if (parsed.positionals.length !== 1)
|
|
155
155
|
throw usageError(usage);
|
|
156
156
|
const entry = requireQueueEntry(store, parsed.positionals[0], options.environment);
|
|
157
|
-
|
|
157
|
+
const actor = requireTaskControlActor(store, options.environment, entry.taskId, home);
|
|
158
158
|
const reason = parsed.one.get("--reason");
|
|
159
159
|
if (reason === undefined)
|
|
160
160
|
throw usageError(usage);
|
|
161
|
-
const superseded = supersedeIntegrationQueueEntry(store, entry.taskId, entry.id, reason, options.now ?? (() => new Date()));
|
|
161
|
+
const superseded = supersedeIntegrationQueueEntry(store, entry.taskId, entry.id, reason, actor, options.now ?? (() => new Date()));
|
|
162
162
|
return {
|
|
163
163
|
output: `Superseded ${superseded.id}: ${reason}\n`,
|
|
164
164
|
data: { entry: superseded }
|
|
165
165
|
};
|
|
166
166
|
}
|
|
167
|
-
function requeue(args, store, options) {
|
|
167
|
+
function requeue(args, store, options, home) {
|
|
168
168
|
const usage = "Task Integration queue requeue usage: yui task integration queue requeue <task>/<entry>.";
|
|
169
169
|
if (args.length !== 1)
|
|
170
170
|
throw usageError(usage);
|
|
171
171
|
const entry = requireQueueEntry(store, args[0], options.environment);
|
|
172
|
-
|
|
173
|
-
const waiting = requeueIntegrationQueueEntry(store, entry.taskId, entry.id, options.now ?? (() => new Date()));
|
|
172
|
+
const actor = requireTaskControlActor(store, options.environment, entry.taskId, home);
|
|
173
|
+
const waiting = requeueIntegrationQueueEntry(store, entry.taskId, entry.id, actor, options.now ?? (() => new Date()));
|
|
174
174
|
return {
|
|
175
175
|
output: `Requeued ${waiting.id}; it will be processed again on the next queue run\n`,
|
|
176
176
|
data: { entry: waiting }
|
|
177
177
|
};
|
|
178
178
|
}
|
|
179
|
-
async function reconcile(args, store, options) {
|
|
179
|
+
async function reconcile(args, store, options, home) {
|
|
180
180
|
const usage = "Task Integration queue reconcile usage: yui task integration queue reconcile <task>/<entry>.";
|
|
181
181
|
if (args.length !== 1)
|
|
182
182
|
throw usageError(usage);
|
|
183
183
|
const entry = requireQueueEntry(store, args[0], options.environment);
|
|
184
|
-
|
|
184
|
+
requireTaskControlActor(store, options.environment, entry.taskId, home);
|
|
185
185
|
const committed = await reconcileIntegrationQueueEntry(store, entry.taskId, entry.id, new NodeGitWorkspace(), options.now ?? (() => new Date()));
|
|
186
186
|
return {
|
|
187
187
|
output: `Reconciled ${committed.id} as committed -> ${committed.targetAfter ?? "-"}\n`,
|
|
@@ -221,8 +221,6 @@ function requireQueueEntry(store, value, environment) {
|
|
|
221
221
|
}
|
|
222
222
|
return entry;
|
|
223
223
|
}
|
|
224
|
-
function
|
|
225
|
-
|
|
226
|
-
throw usageError("Only the Task Leader can change the integration queue.");
|
|
227
|
-
}
|
|
224
|
+
function requireTaskControlActor(store, environment, taskId, home) {
|
|
225
|
+
return taskLocalActor(store, environment, taskId, home);
|
|
228
226
|
}
|
|
@@ -34,10 +34,6 @@ function addPublication(args, store, options) {
|
|
|
34
34
|
throw usageError(`Project is not bound to Task ${task.id}: ${projectReference}.`, usage);
|
|
35
35
|
}
|
|
36
36
|
const actor = taskActor(options.environment, task.id);
|
|
37
|
-
if (actor === "leader") {
|
|
38
|
-
throw usageError("A managed Task Leader cannot record external publication evidence; "
|
|
39
|
-
+ "use a user or global Operator session.", usage);
|
|
40
|
-
}
|
|
41
37
|
const state = enumOption(parsed.options, "--state", STATES, usage) ?? "open";
|
|
42
38
|
const verification = parseVerification(parsed, usage);
|
|
43
39
|
const input = {
|
|
@@ -135,7 +135,7 @@ function replaceTaskCommand(args, store, options) {
|
|
|
135
135
|
}
|
|
136
136
|
const now = options.now?.() ?? new Date();
|
|
137
137
|
const milestone = createMilestone(store.nextMilestoneId(replacement.id), replacement.id, `Replaces ${old.id}`, `Replacement Task for ${old.id} (${old.status}); the original Task, its refs, `
|
|
138
|
-
+ "and its evidence are preserved and were not rewritten.", now);
|
|
138
|
+
+ "and its evidence are preserved and were not rewritten.", "user", now);
|
|
139
139
|
store.saveMilestone(replacement.id, milestone);
|
|
140
140
|
return {
|
|
141
141
|
output: `${created.output}\nRecorded replacement of ${old.id} on ${replacement.id} `
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { rejectedYieldAttemptFromTaskEvent, RUN_YIELD_REJECTED_EVENT } from "../run/rejectedYieldAttempt.js";
|
|
1
2
|
import { operationalTaskRecords } from "../task/taskRecordRetirement.js";
|
|
2
3
|
import { TASK_COMPLETION_PUBLISHED_TREE_AUTHORIZED_EVENT } from "../task/publicationReference.js";
|
|
3
4
|
import { RUN_BOOTSTRAP_MAX_DELTAS } from "./runContextContract.js";
|
|
@@ -426,6 +427,20 @@ function collectAuthorizedContext(store, run) {
|
|
|
426
427
|
for (const event of publishedTreeAuthorizations.reverse().slice(-16)) {
|
|
427
428
|
result.push(materialize("L4", "task-event", event.id, event));
|
|
428
429
|
}
|
|
430
|
+
for (const event of events.filter(({ type }) => (type === RUN_YIELD_REJECTED_EVENT)).slice(-16)) {
|
|
431
|
+
result.push(materialize("L4", "task-event", event.id, event));
|
|
432
|
+
const attempt = rejectedYieldAttemptFromTaskEvent(event);
|
|
433
|
+
if (attempt === null)
|
|
434
|
+
continue;
|
|
435
|
+
const rejectedRun = store.getAgentRun(task.id, attempt.runId);
|
|
436
|
+
if (rejectedRun !== null) {
|
|
437
|
+
result.push(materialize("L4", "agent-run", rejectedRun.id, rejectedRun));
|
|
438
|
+
}
|
|
439
|
+
const rejectedRound = store.getReviewRound(task.id, attempt.reviewRoundId);
|
|
440
|
+
if (rejectedRound !== null) {
|
|
441
|
+
result.push(materialize("L3", "review-round", rejectedRound.id, rejectedRound));
|
|
442
|
+
}
|
|
443
|
+
}
|
|
429
444
|
for (const request of store.listOpenInputRequests([task.id])) {
|
|
430
445
|
result.push(materialize("L4", "input-request", request.id, request));
|
|
431
446
|
}
|
|
@@ -1714,8 +1714,24 @@ export class FileSchedulerStoreAdapter {
|
|
|
1714
1714
|
}
|
|
1715
1715
|
}
|
|
1716
1716
|
}
|
|
1717
|
-
|
|
1717
|
+
const blocksAutomaticLeaderRecovery = role.name === "leader"
|
|
1718
|
+
&& input.leaderRecovery === "blocked";
|
|
1719
|
+
store.saveRole(task.id, updateRoleStatus(role, blocksAutomaticLeaderRecovery ? "failed" : "exited", input.now));
|
|
1718
1720
|
stopTaskSessionIfPresent(store, task.id, role.name, currentRun.effective.agentId, input.now);
|
|
1721
|
+
if (blocksAutomaticLeaderRecovery) {
|
|
1722
|
+
const failure = recordLeaderFailure(task.id, input.session?.nativeSessionId ?? "(unregistered)", input.summary, input.now, store.getLeaderFailure(task.id));
|
|
1723
|
+
store.saveLeaderFailure(failure);
|
|
1724
|
+
recordLeaderAttentionRequired(store, {
|
|
1725
|
+
taskId: task.id,
|
|
1726
|
+
reason: "leader-recovery-failed",
|
|
1727
|
+
payload: {
|
|
1728
|
+
message: failure.message,
|
|
1729
|
+
runId: currentRun.id
|
|
1730
|
+
},
|
|
1731
|
+
now: input.now
|
|
1732
|
+
});
|
|
1733
|
+
return "failed";
|
|
1734
|
+
}
|
|
1719
1735
|
queueLeaderWakeup(store, task.id, wakeReason(role.name === "leader" ? "leader-run-failed" : "role-run-failed"), input.now);
|
|
1720
1736
|
return "failed";
|
|
1721
1737
|
});
|
|
@@ -68,16 +68,13 @@ function parseJobResult(value) {
|
|
|
68
68
|
* list/show paths do not use this port and stay lean.
|
|
69
69
|
*/
|
|
70
70
|
export function createControllerIntegrationJobPort(home, clientOptions = {}) {
|
|
71
|
-
const { store, ...controllerOptions } = clientOptions;
|
|
72
71
|
return {
|
|
73
72
|
async startCheckJob(input) {
|
|
74
73
|
const owner = {
|
|
75
74
|
kind: "integration-attempt",
|
|
76
75
|
integrationAttemptId: input.integrationId
|
|
77
76
|
};
|
|
78
|
-
|
|
79
|
-
// identity. A user-scope caller carries a verified Leader assertion.
|
|
80
|
-
const caller = resolveJobCaller(controllerOptions.environment, input.taskId, store);
|
|
77
|
+
const caller = resolveJobCaller(clientOptions.environment, input.taskId);
|
|
81
78
|
const { job } = await startDurableJob(home, {
|
|
82
79
|
taskId: input.taskId,
|
|
83
80
|
owner,
|
|
@@ -87,16 +84,16 @@ export function createControllerIntegrationJobPort(home, clientOptions = {}) {
|
|
|
87
84
|
env: input.env,
|
|
88
85
|
steps: input.steps,
|
|
89
86
|
caller
|
|
90
|
-
},
|
|
87
|
+
}, clientOptions);
|
|
91
88
|
return job;
|
|
92
89
|
},
|
|
93
90
|
async getJob(taskId, jobId) {
|
|
94
|
-
return getDurableJob(home, taskId, jobId,
|
|
91
|
+
return getDurableJob(home, taskId, jobId, clientOptions);
|
|
95
92
|
},
|
|
96
93
|
async cancelJob(taskId, jobId) {
|
|
97
94
|
// rr8/rr12: Bind the cancel request to the caller's managed identity.
|
|
98
|
-
const caller = resolveJobCaller(
|
|
99
|
-
await cancelDurableJob(home, taskId, jobId, caller,
|
|
95
|
+
const caller = resolveJobCaller(clientOptions.environment, taskId);
|
|
96
|
+
await cancelDurableJob(home, taskId, jobId, caller, clientOptions);
|
|
100
97
|
}
|
|
101
98
|
};
|
|
102
99
|
}
|