@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
|
@@ -784,8 +784,8 @@ const taskChildren = [
|
|
|
784
784
|
{
|
|
785
785
|
name: "retire",
|
|
786
786
|
summary: "Retire an incorrect historical Agent Run without deleting its audit record.",
|
|
787
|
-
usage: "yui task run retire <task>/<run> --reason <text>",
|
|
788
|
-
options: ["--reason"]
|
|
787
|
+
usage: "yui task run retire <task>/<run> --reason <text> [--expected-progress-at <timestamp>] [--agent-id <id>] [--adapter-id <id>] [--native-session-id <id>] [--launch-id <id>]",
|
|
788
|
+
options: ["--reason", "--expected-progress-at", "--progress-at", "--agent-id", "--adapter-id", "--native-session-id", "--launch-id"]
|
|
789
789
|
}
|
|
790
790
|
]
|
|
791
791
|
},
|
package/dist/cli/updatePorts.js
CHANGED
|
@@ -765,9 +765,25 @@ function runControllerCommandOutput(home, environment, spawn, method, cliBinary)
|
|
|
765
765
|
const args = cliBinary === undefined
|
|
766
766
|
? [UPDATE_CLI_PATH, "--json", "controller", method]
|
|
767
767
|
: ["--json", "controller", method];
|
|
768
|
-
const result = spawn(command, args, {
|
|
768
|
+
const result = spawn(command, args, {
|
|
769
|
+
cwd: process.cwd(),
|
|
770
|
+
env: {
|
|
771
|
+
...environment,
|
|
772
|
+
YUI_HOME: home,
|
|
773
|
+
// The restart child participates in the handover owned by this parent
|
|
774
|
+
// update process. Without the owner PID it waits on its parent's live
|
|
775
|
+
// lock as though a foreign update owned the Home, then times out.
|
|
776
|
+
YUI_UPDATE_HANDOVER_OWNER_PID: String(process.pid)
|
|
777
|
+
},
|
|
778
|
+
shell: false
|
|
779
|
+
});
|
|
769
780
|
if (result.error !== undefined || result.status !== 0) {
|
|
770
|
-
|
|
781
|
+
const detail = structuredErrorMessage(result) ?? result.stderr.toString("utf8").trim();
|
|
782
|
+
const error = new Error(`Controller ${method} failed (exit ${result.status ?? "null"})${detail.length === 0 ? "." : `: ${detail}`}`);
|
|
783
|
+
const code = controllerErrorCodeFromResult(result);
|
|
784
|
+
if (code !== undefined)
|
|
785
|
+
Object.assign(error, { code });
|
|
786
|
+
throw error;
|
|
771
787
|
}
|
|
772
788
|
const parsed = JSON.parse(result.stdout.toString("utf8"));
|
|
773
789
|
if (!isRecord(parsed) || parsed.ok !== true) {
|
package/dist/cli.js
CHANGED
|
@@ -807,7 +807,7 @@ export async function main() {
|
|
|
807
807
|
if (resolved[1] === "integration") {
|
|
808
808
|
const result = await runTaskIntegrationCommand(resolved.slice(2), store, home, {
|
|
809
809
|
environment: process.env,
|
|
810
|
-
jobPort: createControllerIntegrationJobPort(home, { environment: process.env
|
|
810
|
+
jobPort: createControllerIntegrationJobPort(home, { environment: process.env })
|
|
811
811
|
});
|
|
812
812
|
emit(result.output, false, result.data);
|
|
813
813
|
return;
|
|
@@ -896,10 +896,7 @@ export async function main() {
|
|
|
896
896
|
}
|
|
897
897
|
const reference = cliWorkItemReference(workItemId, process.env);
|
|
898
898
|
const qualified = `${reference.taskId}/${reference.localId}`;
|
|
899
|
-
|
|
900
|
-
if (actor === "operator") {
|
|
901
|
-
throw usageError("Only the Task Leader may clean a WorkItem from a managed Session.");
|
|
902
|
-
}
|
|
899
|
+
taskActor(process.env, reference.taskId);
|
|
903
900
|
if (disposition === "--runtime-only") {
|
|
904
901
|
let runtimeCleanup;
|
|
905
902
|
try {
|
|
@@ -1117,7 +1114,7 @@ export async function main() {
|
|
|
1117
1114
|
// Keep completion offline by default. An explicit refresh is the only
|
|
1118
1115
|
// path that may fetch and reconcile a moved remote baseline.
|
|
1119
1116
|
if (refreshRemote) {
|
|
1120
|
-
await reconcileTaskRemoteBaselines(resolved[2], store, home, { environment: process.env, jobPort: createControllerIntegrationJobPort(home, { environment: process.env
|
|
1117
|
+
await reconcileTaskRemoteBaselines(resolved[2], store, home, { environment: process.env, jobPort: createControllerIntegrationJobPort(home, { environment: process.env }) });
|
|
1121
1118
|
}
|
|
1122
1119
|
}
|
|
1123
1120
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { usageError } from "../errors/cliError.js";
|
|
2
2
|
import { ensureFileTaskController } from "../controller/clientRuntime.js";
|
|
3
3
|
import { acknowledgeDurableJob, cancelDurableJob, getDurableJob, startDurableJob } from "../controller/jobClient.js";
|
|
4
|
-
import { resolveJobCaller,
|
|
4
|
+
import { resolveJobCaller, taskLocalActor } from "./taskActor.js";
|
|
5
5
|
/**
|
|
6
6
|
* The textual `--owner` forms accepted by `job start`. The public help text in
|
|
7
7
|
* the command catalog must document exactly these forms.
|
|
@@ -34,10 +34,7 @@ export async function runDurableJobCommand(args, options) {
|
|
|
34
34
|
async function startJob(args, options) {
|
|
35
35
|
const parsed = parseStartArgs(args);
|
|
36
36
|
await ensureFileTaskController(options.home, { environment: options.environment });
|
|
37
|
-
|
|
38
|
-
// Controller rejects a Reviewer, constrains a Worker to its own Work Item,
|
|
39
|
-
// and requires a verified Leader assertion for user scope.
|
|
40
|
-
const caller = resolveJobCaller(options.environment, parsed.taskId, options.store);
|
|
37
|
+
const caller = resolveJobCaller(options.environment, parsed.taskId);
|
|
41
38
|
const params = {
|
|
42
39
|
taskId: parsed.taskId,
|
|
43
40
|
owner: parsed.owner,
|
|
@@ -89,7 +86,7 @@ async function cancelJob(args, options) {
|
|
|
89
86
|
const ref = parseRefArgs(args, "cancel");
|
|
90
87
|
await ensureFileTaskController(options.home, { environment: options.environment });
|
|
91
88
|
// rr8/rr12: Bind the cancel request to the caller's managed identity.
|
|
92
|
-
const caller = resolveJobCaller(options.environment, ref.taskId
|
|
89
|
+
const caller = resolveJobCaller(options.environment, ref.taskId);
|
|
93
90
|
const result = await cancelDurableJob(options.home, ref.taskId, ref.jobId, caller);
|
|
94
91
|
if (options.json === true)
|
|
95
92
|
return `${JSON.stringify(result, null, 2)}\n`;
|
|
@@ -99,20 +96,11 @@ async function cancelJob(args, options) {
|
|
|
99
96
|
}
|
|
100
97
|
async function acknowledgeJob(args, options) {
|
|
101
98
|
const ref = parseRefArgs(args, "acknowledge");
|
|
102
|
-
// rr5/f5(a): only the Task Leader may acknowledge an
|
|
103
|
-
// unknown-needs-attention job. A non-Leader (Worker, Operator, or plain
|
|
104
|
-
// user) is rejected at the CLI boundary.
|
|
105
|
-
const actor = taskActor(options.environment, ref.taskId);
|
|
106
|
-
if (actor !== "leader") {
|
|
107
|
-
throw usageError("Only the Task Leader may acknowledge a DurableJob: "
|
|
108
|
-
+ `${ref.taskId}.`);
|
|
109
|
-
}
|
|
110
99
|
if (options.store === undefined) {
|
|
111
|
-
throw usageError("job acknowledge requires a Task store to resolve the
|
|
100
|
+
throw usageError("job acknowledge requires a Task store to resolve the caller.");
|
|
112
101
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const caller = resolveJobCaller(options.environment, ref.taskId, options.store);
|
|
102
|
+
taskLocalActor(options.store, options.environment, ref.taskId, options.home);
|
|
103
|
+
const caller = resolveJobCaller(options.environment, ref.taskId);
|
|
116
104
|
await ensureFileTaskController(options.home, { environment: options.environment });
|
|
117
105
|
const result = await acknowledgeDurableJob(options.home, ref.taskId, ref.jobId, caller);
|
|
118
106
|
if (options.json === true)
|
|
@@ -156,7 +156,7 @@ export function renderExecutionAudit(report, width = defaultTableWidth()) {
|
|
|
156
156
|
`Delta-rechecks: ${reviews.deltaRechecks.total} total · `
|
|
157
157
|
+ `${reviews.deltaRechecks.equivalentAndAccepted} accepted · `
|
|
158
158
|
+ `${reviews.deltaRechecks.finding} finding · `
|
|
159
|
-
+ `${reviews.deltaRechecks.requiresFullReview} requiring
|
|
159
|
+
+ `${reviews.deltaRechecks.requiresFullReview} requiring Task Agent decision`
|
|
160
160
|
]));
|
|
161
161
|
}
|
|
162
162
|
else {
|
|
@@ -30,6 +30,24 @@ export function taskActor(environment, taskId) {
|
|
|
30
30
|
}
|
|
31
31
|
return "user";
|
|
32
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Resolve authority for a recoverable Task-local mutation. A managed Leader
|
|
35
|
+
* does not gain that authority from long-lived process environment alone: the
|
|
36
|
+
* command must carry the exact current Turn assertion and it must still match
|
|
37
|
+
* the durable active Run, receipt, Role binding, native Session, and Home.
|
|
38
|
+
* Plain-user and global-Operator behavior remains unchanged.
|
|
39
|
+
*/
|
|
40
|
+
export function taskLocalActor(store, environment, taskId, yuiHome) {
|
|
41
|
+
const actor = taskActor(environment, taskId);
|
|
42
|
+
if (actor !== "leader")
|
|
43
|
+
return actor;
|
|
44
|
+
const assertion = leaderActionAssertion(environment ?? {});
|
|
45
|
+
if (assertion === undefined || assertion === "invalid"
|
|
46
|
+
|| taskLeaderActionRunId(store, taskId, environment, yuiHome) === undefined) {
|
|
47
|
+
throw usageError(`Task-local Leader authority requires the exact current-Turn assertion: ${taskId}.`);
|
|
48
|
+
}
|
|
49
|
+
return actor;
|
|
50
|
+
}
|
|
33
51
|
export function projectActor(environment) {
|
|
34
52
|
const env = environment ?? {};
|
|
35
53
|
if (env.YUI_SESSION_SCOPE === "task")
|
|
@@ -49,29 +67,26 @@ export function projectActor(environment) {
|
|
|
49
67
|
}
|
|
50
68
|
/**
|
|
51
69
|
* rr8: Resolve the caller identity for a `job.start`/`job.cancel` request from
|
|
52
|
-
* the managed Session environment. The Controller
|
|
53
|
-
*
|
|
54
|
-
* its own Work Item's jobs, and a Leader or plain user retains full access.
|
|
70
|
+
* the managed Session environment. The Controller verifies the Agent Session
|
|
71
|
+
* and its scope; Role does not narrow Task control authority.
|
|
55
72
|
*
|
|
56
73
|
* rr12: The identity is now Controller-verified rather than self-reported:
|
|
57
74
|
* - A managed Task Session (`YUI_SESSION_SCOPE=task`) returns `scope: "task"`
|
|
58
|
-
* with its Role and Run.
|
|
59
|
-
*
|
|
60
|
-
* a possibly-stale `YUI_RUN_ID`), which the Controller verifies through the
|
|
61
|
-
* same active-Run + in-flight + Session check as `job.acknowledge`.
|
|
75
|
+
* with its Role and Run. For a Leader, the explicit current-Turn Run id is
|
|
76
|
+
* preferred over a possibly stale `YUI_RUN_ID`.
|
|
62
77
|
*
|
|
63
78
|
* rr13: A managed Task Session also carries `callerKey` — the
|
|
64
79
|
* `YUI_JOB_CALLER_KEY` injected at its native Session launch. The Controller
|
|
65
80
|
* hashes it and compares against the durable `jobCallerKeyHashes` map, so a
|
|
66
81
|
* client that reads durable state cannot replay the caller. A `user`-scope
|
|
67
|
-
* caller is rejected outright for job.start/job.cancel (fail-closed);
|
|
68
|
-
*
|
|
82
|
+
* caller is rejected outright for job.start/job.cancel (fail-closed); a
|
|
83
|
+
* managed global Agent carries its own durable Session identity.
|
|
69
84
|
*
|
|
70
85
|
* A managed Task Session may only start jobs for its own Task. An incomplete
|
|
71
86
|
* managed identity (role/agent/run/session vars without a scope) is rejected
|
|
72
87
|
* rather than silently downgraded to user authority.
|
|
73
88
|
*/
|
|
74
|
-
export function resolveJobCaller(environment, taskId
|
|
89
|
+
export function resolveJobCaller(environment, taskId) {
|
|
75
90
|
const env = environment ?? {};
|
|
76
91
|
if (env.YUI_SESSION_SCOPE === "task") {
|
|
77
92
|
if (env.YUI_TASK_ID !== taskId) {
|
|
@@ -83,9 +98,7 @@ export function resolveJobCaller(environment, taskId, store) {
|
|
|
83
98
|
// Controller boundary.
|
|
84
99
|
const callerKey = env[JOB_CALLER_KEY_ENV];
|
|
85
100
|
if (role === LEADER_ROLE) {
|
|
86
|
-
// Prefer the explicit current-
|
|
87
|
-
// stale YUI_RUN_ID/launch. The Controller verifies it against the
|
|
88
|
-
// active in-flight Leader Run.
|
|
101
|
+
// Prefer the explicit current-Turn Run over a possibly stale YUI_RUN_ID.
|
|
89
102
|
const assertion = leaderActionAssertion(env);
|
|
90
103
|
if (assertion !== undefined && assertion !== "invalid") {
|
|
91
104
|
return {
|
|
@@ -93,7 +106,6 @@ export function resolveJobCaller(environment, taskId, store) {
|
|
|
93
106
|
taskId,
|
|
94
107
|
role,
|
|
95
108
|
runId: assertion.runId,
|
|
96
|
-
receiptId: assertion.receiptId,
|
|
97
109
|
...(callerKey === undefined ? {} : { callerKey })
|
|
98
110
|
};
|
|
99
111
|
}
|
|
@@ -107,7 +119,14 @@ export function resolveJobCaller(environment, taskId, store) {
|
|
|
107
119
|
};
|
|
108
120
|
}
|
|
109
121
|
if (env.YUI_SESSION_SCOPE === "global") {
|
|
110
|
-
return
|
|
122
|
+
return {
|
|
123
|
+
scope: "global",
|
|
124
|
+
role: env.YUI_ROLE,
|
|
125
|
+
agentId: env.YUI_AGENT_ID,
|
|
126
|
+
adapterId: env.YUI_ADAPTER_ID,
|
|
127
|
+
launchId: env.YUI_LAUNCH_ID,
|
|
128
|
+
nativeSessionId: env.YUI_NATIVE_SESSION_ID
|
|
129
|
+
};
|
|
111
130
|
}
|
|
112
131
|
if (env.YUI_ROLE !== undefined
|
|
113
132
|
|| env.YUI_AGENT_ID !== undefined
|
|
@@ -115,52 +134,7 @@ export function resolveJobCaller(environment, taskId, store) {
|
|
|
115
134
|
|| env.YUI_NATIVE_SESSION_ID !== undefined) {
|
|
116
135
|
throw usageError("Managed Agent identity is incomplete; refusing to infer user authority.");
|
|
117
136
|
}
|
|
118
|
-
return
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* rr12: Build a `scope: "user"` caller. When a store is available, attach the
|
|
122
|
-
* active in-flight Leader assertion so the Controller can verify the request
|
|
123
|
-
* acts under real Leader authority. Without a store, return the bare
|
|
124
|
-
* `{scope: "user"}` which the Controller rejects (fail-closed).
|
|
125
|
-
*/
|
|
126
|
-
function userCaller(store, taskId) {
|
|
127
|
-
if (store === undefined)
|
|
128
|
-
return { scope: "user" };
|
|
129
|
-
const runId = activeLeaderRunId(store, taskId);
|
|
130
|
-
if (runId === undefined) {
|
|
131
|
-
throw usageError("job.start/job.cancel user scope requires an active in-flight Task Leader: "
|
|
132
|
-
+ `${taskId}.`);
|
|
133
|
-
}
|
|
134
|
-
return {
|
|
135
|
-
scope: "user",
|
|
136
|
-
leaderAssertion: {
|
|
137
|
-
runId,
|
|
138
|
-
receiptId: formatAgentRunReceiptId(taskId, runId)
|
|
139
|
-
}
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* rr12: Resolve the current in-flight Task Leader Run for a non-managed
|
|
144
|
-
* (operator/bare-shell) caller. Unlike `taskLeaderActionRunId`, this does not
|
|
145
|
-
* require managed Leader environment variables — it verifies the durable
|
|
146
|
-
* state directly: an active Leader Run whose Role Session is currently
|
|
147
|
-
* in-flight with the matching receipt. Returns undefined when no Leader is
|
|
148
|
-
* active or in flight.
|
|
149
|
-
*/
|
|
150
|
-
function activeLeaderRunId(store, taskId) {
|
|
151
|
-
const run = store.getActiveAgentRun(taskId, LEADER_ROLE);
|
|
152
|
-
if (run === null || run.status !== "active" || run.roleName !== LEADER_ROLE) {
|
|
153
|
-
return undefined;
|
|
154
|
-
}
|
|
155
|
-
const sessions = store.getTaskRoleSessionSet(taskId, LEADER_ROLE);
|
|
156
|
-
const expectedReceipt = agentRunDeliveryReceiptId(run);
|
|
157
|
-
if (sessions === null
|
|
158
|
-
|| sessions.inFlight === null
|
|
159
|
-
|| sessions.inFlight.runId !== run.id
|
|
160
|
-
|| sessions.inFlight.receiptId !== expectedReceipt) {
|
|
161
|
-
return undefined;
|
|
162
|
-
}
|
|
163
|
-
return run.id;
|
|
137
|
+
return { scope: "user" };
|
|
164
138
|
}
|
|
165
139
|
/**
|
|
166
140
|
* Resolve an exact current Task Leader Run for event attribution.
|