@volter/supercode-orchestrator 0.4.122 → 0.4.124

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/board/cli.mjs CHANGED
@@ -48,7 +48,7 @@ supercode's own:
48
48
  create … --machine M --subtask-of ARC --notify ADDRESS|--no-notify --no-start
49
49
  With a pane lane and room, a card starts at once and answers "started" with its session; else it answers why it
50
50
  waits, and its creator is told when it starts. A subtask is worked by its arc's session and never starts alone.
51
- move <id> <machine|none> adopt <id> <session> [--assignee P] close <id> serve (= daemon) list --machine M
51
+ move <id> <machine|none> adopt <id> <session> [--assignee P] [--role ROLE] close <id> serve (= daemon) list --machine M
52
52
  notify-subscribe <id> --address sc:<machine>:<harness>:<id> [--role creator|worker|dependency]
53
53
  Any verb … --at sc:<machine>:board:<home>: sent as mail to a board kept on another machine.
54
54
  <event> <id>... [reason] [--<field> value]: any other event the board's workflow declares, its flags the event's fields.
@@ -814,7 +814,7 @@ export async function runWorkflow(argv) {
814
814
  case 'adopt': {
815
815
  need(2, 'a card id and a session');
816
816
  const { settings } = await laneState(root);
817
- const got = withBoard((b) => adoptSession(b, id, rest[1], { workspace: args.workspace ?? null, ttl: settings.ttl, assignee: args.assignee ?? null }));
817
+ const got = withBoard((b) => adoptSession(b, id, rest[1], { workspace: args.workspace ?? null, ttl: settings.ttl, assignee: args.assignee ?? null, role: args.role ?? null }));
818
818
  say(args, `${id} is worked by ${got.address} (pane ${got.pane ?? '?'} in ${got.workspace ?? 'its card\'s directory'})`, { ok: true, ...got });
819
819
  return 0;
820
820
  }
@@ -1159,6 +1159,21 @@ async function serve(root, args = {}) {
1159
1159
  let pausedSaid = false;
1160
1160
  try {
1161
1161
  while (!stopped) {
1162
+ // An installer owns this operational window, not a card pause. Finish the
1163
+ // current tick, acknowledge the request, then leave every run untouched.
1164
+ // A replacement serve sees the same request before its first tick.
1165
+ const installation = join(root, 'installation-window.json');
1166
+ if (existsSync(installation)) {
1167
+ const request = JSON.parse(readFileSync(installation, 'utf8'));
1168
+ const acknowledgment = join(root, 'installation-window.ack.json');
1169
+ const previous = existsSync(acknowledgment) ? JSON.parse(readFileSync(acknowledgment, 'utf8')) : null;
1170
+ if (previous?.token !== request.token || previous?.pid !== process.pid) {
1171
+ writeFileSync(acknowledgment, JSON.stringify({ token: request.token, pid: process.pid, at: Date.now() }));
1172
+ log(`installation window held by ${request.owner}; dispatch waits`);
1173
+ }
1174
+ await new Promise(resolve => setTimeout(resolve, 250));
1175
+ continue;
1176
+ }
1162
1177
  pausedSaid = Boolean(paused(root)) && pausedSaid;
1163
1178
  let interval = 60;
1164
1179
  const began = Date.now();
@@ -1158,7 +1158,7 @@ function resolveLocalWorkspace(board, task) {
1158
1158
  * never as the session describes itself. From then on the board watches it, resumes it when lost, and tells it of
1159
1159
  * its card. `who` is an address, a session id, or a `name@machine` the fleet lists.
1160
1160
  */
1161
- export function adoptSession(board, id, who, { workspace = null, ttl, assignee = null } = {}) {
1161
+ export function adoptSession(board, id, who, { workspace = null, ttl, assignee = null, role = null } = {}) {
1162
1162
  const fleet = observeFleet();
1163
1163
  if (!fleet.ok) throw new Error(`supercode could not read the fleet: ${fleet.error}`);
1164
1164
  const found = fleet.sessions.find((s) => s.address === who || s.address.endsWith(`:${who}`) || s.name === who);
@@ -1195,14 +1195,20 @@ export function adoptSession(board, id, who, { workspace = null, ttl, assignee =
1195
1195
  if (dir && !task.workspace_anchor && !(task.workspace_kind === 'dir' && task.workspace_path)) {
1196
1196
  db.prepare("UPDATE tasks SET workspace_kind = 'dir', workspace_path = ? WHERE id = ?").run(dir.replace(/^dir:/, ''), id);
1197
1197
  }
1198
- // the workflow's adopt event: the card is claimed for its implementer, whose session this now is
1198
+ // Adoption takes the role and slot declared for this card's current stage.
1199
1199
  const entry = workflowFor(board.root);
1200
+ const expected = runSpec(entry, statusOf(db, task), task);
1201
+ if (role && role !== expected?.role) throw new Error(`${id} needs its ${expected?.role ?? 'declared'} role, not ${role}`);
1202
+ if (expected?.role === 'reviewer') {
1203
+ const implementer = lastSession(db, id, 'worker');
1204
+ if (implementer?.session_id === sessionId) throw new Error('a reviewer must be independent of the implementer session');
1205
+ }
1200
1206
  if (!beforeRun) fire(db, board, entry, id, entry.workflow.run ? entry.workflow.dispatch.start : 'adopt', { roles: ['manager'], payload: { lock: claimer() } });
1201
1207
  const runId = getTask(db, id).current_run_id;
1202
1208
  if (!runId) throw new Error(`${id} waits on parents that are not done`);
1203
1209
  const session = { surface: 'pane', session_id: sessionId, harness, machine: host, pane: found.pane ?? null, address: found.address, mode: 'adopt', launched_at: now(), seen_at: now() };
1204
- setRunSession(db, runId, { ...(currentRun(db, id)?.metadata?.supercode ?? {}), ...session, slot: beforeSession?.slot ?? 'worker', close_requested: null, closed_at: null, close_confirmed_at: null, close_error: null, missing_since: null });
1205
- event(db, id, 'session_adopted', { session_id: sessionId, machine: host, pane: session.pane, address: found.address, mode: 'adopt' }, runId);
1210
+ setRunSession(db, runId, { ...(currentRun(db, id)?.metadata?.supercode ?? {}), ...session, slot: currentRun(db, id)?.metadata?.supercode?.slot ?? beforeSession?.slot ?? expected?.session ?? 'worker', close_requested: null, closed_at: null, close_confirmed_at: null, close_error: null, missing_since: null });
1211
+ event(db, id, 'session_adopted', { session_id: sessionId, machine: host, pane: session.pane, address: found.address, mode: 'adopt', role: currentRun(db, id)?.metadata?.supercode?.role }, runId);
1206
1212
  subscribeIn(db, id, found.address, 'worker');
1207
1213
  for (const p of db.prepare('SELECT parent_id FROM task_links WHERE child_id = ?').all(id)) subscribeIn(db, p.parent_id, found.address, 'dependency');
1208
1214
  return { ...session, workspace: dir };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/supercode-orchestrator",
3
- "version": "0.4.122",
3
+ "version": "0.4.124",
4
4
  "type": "module",
5
5
  "description": "The orchestrator runtime over the Volter Harness ontology: one typed operational model whose folder is its serialization, read and written through the harness orchestration doors (docs/ORCHESTRATOR-IR.md)",
6
6
  "exports": {