@zq-silk/yui 0.12.4 → 0.13.1

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.
@@ -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, { cwd: process.cwd(), env: { ...environment, YUI_HOME: home }, shell: false });
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
- throw new Error(`Controller ${method} failed (exit ${result.status ?? "null"}).`);
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, store })
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
- const actor = taskActor(process.env, reference.taskId);
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, store }) });
1117
+ await reconcileTaskRemoteBaselines(resolved[2], store, home, { environment: process.env, jobPort: createControllerIntegrationJobPort(home, { environment: process.env }) });
1121
1118
  }
1122
1119
  }
1123
1120
  }
@@ -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
- // rr8/rr12: Bind the declared owner to the caller's managed identity. The
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, options.store);
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
99
  if (options.store === undefined) {
106
- throw usageError("job acknowledge requires a Task store to resolve the Leader assertion.");
100
+ throw usageError("job acknowledge requires a Task store to resolve the caller.");
107
101
  }
108
- const actor = taskLocalActor(options.store, options.environment, ref.taskId, options.home);
109
- if (actor !== "leader") {
110
- throw usageError("Only the Task Leader may acknowledge a DurableJob: "
111
- + `${ref.taskId}.`);
112
- }
113
- // Carry the full managed task caller, including its ephemeral launch key.
114
- // A durable leaderAssertion by itself is intentionally not sufficient.
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 Leader decision`
159
+ + `${reviews.deltaRechecks.requiresFullReview} requiring Task Agent decision`
160
160
  ]));
161
161
  }
162
162
  else {
@@ -67,29 +67,26 @@ export function projectActor(environment) {
67
67
  }
68
68
  /**
69
69
  * rr8: Resolve the caller identity for a `job.start`/`job.cancel` request from
70
- * the managed Session environment. The Controller binds the declared job owner
71
- * to this identity a Reviewer is rejected outright, a Worker can only touch
72
- * 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.
73
72
  *
74
73
  * rr12: The identity is now Controller-verified rather than self-reported:
75
74
  * - A managed Task Session (`YUI_SESSION_SCOPE=task`) returns `scope: "task"`
76
- * with its Role and Run. A Leader additionally carries the current in-flight
77
- * Turn receipt (preferring the explicit `YUI_LEADER_ACTION_*` assertion over
78
- * a possibly-stale `YUI_RUN_ID`), which the Controller verifies through the
79
- * 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`.
80
77
  *
81
78
  * rr13: A managed Task Session also carries `callerKey` — the
82
79
  * `YUI_JOB_CALLER_KEY` injected at its native Session launch. The Controller
83
80
  * hashes it and compares against the durable `jobCallerKeyHashes` map, so a
84
81
  * client that reads durable state cannot replay the caller. A `user`-scope
85
- * caller is rejected outright for job.start/job.cancel (fail-closed); the
86
- * human operator acts through the Leader Session.
82
+ * caller is rejected outright for job.start/job.cancel (fail-closed); a
83
+ * managed global Agent carries its own durable Session identity.
87
84
  *
88
85
  * A managed Task Session may only start jobs for its own Task. An incomplete
89
86
  * managed identity (role/agent/run/session vars without a scope) is rejected
90
87
  * rather than silently downgraded to user authority.
91
88
  */
92
- export function resolveJobCaller(environment, taskId, store) {
89
+ export function resolveJobCaller(environment, taskId) {
93
90
  const env = environment ?? {};
94
91
  if (env.YUI_SESSION_SCOPE === "task") {
95
92
  if (env.YUI_TASK_ID !== taskId) {
@@ -101,9 +98,7 @@ export function resolveJobCaller(environment, taskId, store) {
101
98
  // Controller boundary.
102
99
  const callerKey = env[JOB_CALLER_KEY_ENV];
103
100
  if (role === LEADER_ROLE) {
104
- // Prefer the explicit current-turn Leader assertion over a possibly
105
- // stale YUI_RUN_ID/launch. The Controller verifies it against the
106
- // active in-flight Leader Run.
101
+ // Prefer the explicit current-Turn Run over a possibly stale YUI_RUN_ID.
107
102
  const assertion = leaderActionAssertion(env);
108
103
  if (assertion !== undefined && assertion !== "invalid") {
109
104
  return {
@@ -111,7 +106,6 @@ export function resolveJobCaller(environment, taskId, store) {
111
106
  taskId,
112
107
  role,
113
108
  runId: assertion.runId,
114
- receiptId: assertion.receiptId,
115
109
  ...(callerKey === undefined ? {} : { callerKey })
116
110
  };
117
111
  }
@@ -125,7 +119,14 @@ export function resolveJobCaller(environment, taskId, store) {
125
119
  };
126
120
  }
127
121
  if (env.YUI_SESSION_SCOPE === "global") {
128
- return userCaller(store, taskId);
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
+ };
129
130
  }
130
131
  if (env.YUI_ROLE !== undefined
131
132
  || env.YUI_AGENT_ID !== undefined
@@ -133,52 +134,7 @@ export function resolveJobCaller(environment, taskId, store) {
133
134
  || env.YUI_NATIVE_SESSION_ID !== undefined) {
134
135
  throw usageError("Managed Agent identity is incomplete; refusing to infer user authority.");
135
136
  }
136
- return userCaller(store, taskId);
137
- }
138
- /**
139
- * rr12: Build a `scope: "user"` caller. When a store is available, attach the
140
- * active in-flight Leader assertion so the Controller can verify the request
141
- * acts under real Leader authority. Without a store, return the bare
142
- * `{scope: "user"}` which the Controller rejects (fail-closed).
143
- */
144
- function userCaller(store, taskId) {
145
- if (store === undefined)
146
- return { scope: "user" };
147
- const runId = activeLeaderRunId(store, taskId);
148
- if (runId === undefined) {
149
- throw usageError("job.start/job.cancel user scope requires an active in-flight Task Leader: "
150
- + `${taskId}.`);
151
- }
152
- return {
153
- scope: "user",
154
- leaderAssertion: {
155
- runId,
156
- receiptId: formatAgentRunReceiptId(taskId, runId)
157
- }
158
- };
159
- }
160
- /**
161
- * rr12: Resolve the current in-flight Task Leader Run for a non-managed
162
- * (operator/bare-shell) caller. Unlike `taskLeaderActionRunId`, this does not
163
- * require managed Leader environment variables — it verifies the durable
164
- * state directly: an active Leader Run whose Role Session is currently
165
- * in-flight with the matching receipt. Returns undefined when no Leader is
166
- * active or in flight.
167
- */
168
- function activeLeaderRunId(store, taskId) {
169
- const run = store.getActiveAgentRun(taskId, LEADER_ROLE);
170
- if (run === null || run.status !== "active" || run.roleName !== LEADER_ROLE) {
171
- return undefined;
172
- }
173
- const sessions = store.getTaskRoleSessionSet(taskId, LEADER_ROLE);
174
- const expectedReceipt = agentRunDeliveryReceiptId(run);
175
- if (sessions === null
176
- || sessions.inFlight === null
177
- || sessions.inFlight.runId !== run.id
178
- || sessions.inFlight.receiptId !== expectedReceipt) {
179
- return undefined;
180
- }
181
- return run.id;
137
+ return { scope: "user" };
182
138
  }
183
139
  /**
184
140
  * Resolve an exact current Task Leader Run for event attribution.