@volter/supercode-orchestrator 0.5.131 → 0.5.133

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/ci.mjs CHANGED
@@ -333,22 +333,27 @@ async function knownRepositories() {
333
333
 
334
334
  /** Whether the arc's open PRs (`[{ url, head }]`) call for its review: some head no verdict judged that is new work.
335
335
  * Before any verdict every unjudged head is. After a verdict, a PR it judged with a new head is, and so is a head
336
- * committed after that verdict (a new PR carrying the fixes, or any PR after a verdict that named none). A PR that
337
- * joins with a head committed before the verdict is not: after changes are requested the next run is the
338
- * implementer's until it pushes. */
336
+ * committed after that verdict (a new PR carrying the fixes, or any PR after a verdict that named none). After changes
337
+ * are requested, a PR that joins with a head committed before the verdict is not: the next run is the implementer's
338
+ * until it pushes. After a pass nothing is owed by the implementer, so a PR that verdict's request did not name is due
339
+ * at whatever head it has: one that was a draft or stacked on another PR's branch when the request was made, and
340
+ * became the arc's once that PR merged (t_bbbd2086's #1355, committed 2 h before the pass that named only #1353). */
339
341
  export async function reviewDue(board, id, prs) {
340
342
  const unjudged = prs.filter((p) => !judgedHead(board, id, p.head));
341
343
  if (!unjudged.length) return false;
342
344
  const voided = board.db.prepare("SELECT r.payload FROM task_events v JOIN task_events r ON r.id=json_extract(v.payload,'$.review_request') AND r.task_id=v.task_id WHERE v.task_id=? AND v.kind='review_voided' ORDER BY v.id DESC LIMIT 1").get(id);
343
345
  if (candidatePullRequests(voided && JSON.parse(voided.payload)?.ci).some(j => unjudged.some(p => p.url === j.pr.url && p.head === j.commit))) return true;
344
346
  const last = board.db.prepare(`SELECT r.payload, (SELECT MIN(v.created_at) FROM task_events v WHERE v.task_id = r.task_id
345
- AND v.kind IN ('changes_requested', 'review_pass') AND v.id > r.id) AS verdict_at
347
+ AND v.kind IN ('changes_requested', 'review_pass') AND v.id > r.id) AS verdict_at,
348
+ (SELECT v.kind FROM task_events v WHERE v.task_id = r.task_id
349
+ AND v.kind IN ('changes_requested', 'review_pass') AND v.id > r.id ORDER BY v.id LIMIT 1) AS verdict
346
350
  FROM task_events r WHERE r.task_id = ? AND r.kind = 'review_requested'
347
351
  AND EXISTS (SELECT 1 FROM task_events v WHERE v.task_id = r.task_id AND v.kind IN ('changes_requested', 'review_pass') AND v.id > r.id)
348
352
  ORDER BY r.id DESC LIMIT 1`).get(id);
349
353
  if (!last) return true;
350
354
  const judged = candidatePullRequests(JSON.parse(last.payload)?.ci);
351
355
  if (judged.some((j) => prs.some((p) => p.url === j.pr.url && p.head !== j.commit))) return true;
356
+ if (last.verdict === 'review_pass' && unjudged.some((p) => !judged.some((j) => j.pr.url === p.url))) return true;
352
357
  for (const p of unjudged) if (((await commitTime(p)) ?? 0) > Number(last.verdict_at)) return true;
353
358
  return false;
354
359
  }
@@ -642,7 +642,7 @@ async function stopSession(board, session) {
642
642
  return;
643
643
  }
644
644
  if (session.pane) {
645
- const r = await runtimeDoor(normalMachine(session.machine??localMachine())===localMachine()?null:session.machine,'harness.v1.panes.close',{pane:session.pane},{env:commandEnv(),timeout:60_000,as:boardAddress(board.label)});
645
+ const r = await runtimeDoor(normalMachine(session.machine??localMachine())===localMachine()?null:session.machine,'harness.v1.panes.close',{pane:session.pane,...(session.launch_key?{launch:session.launch_key}:{})},{env:commandEnv(),timeout:60_000,as:boardAddress(board.label)});
646
646
  // a door that timed out or could not be reached says nothing about the pane: the close waits for the next round
647
647
  const transient = doorTransient(r);
648
648
  if (r.code !== 0 || json(r.stdout.trim(), null)?.closed !== session.pane) throw new Error(`${transient ? 'close deferred: ' : ''}closing pane ${session.pane}: ${(r.stderr || r.stdout || `exit ${r.code}`).trim().slice(-500)}`);
@@ -1141,12 +1141,20 @@ export async function closeRequested(board, report, { taskId = null, caller = nu
1141
1141
  }
1142
1142
  // A final refusal stops further close attempts. Only an authorized observation of this exact pane may establish
1143
1143
  // absence: panes.list can be filtered or carry missing context keys. Unknown/invisible keeps the original failure.
1144
- const finalFailure = closeRefused(session);
1144
+ // A refusal's reason can end (the machine's daemon was replaced by one that reads the launch's starter): the same
1145
+ // reading says whether this board's close of the pane would be admitted now (`yours`), and a close it would admit
1146
+ // is tried again, unless an unended run works in the pane. A machine that does not say stays refused.
1147
+ let finalFailure = closeRefused(session);
1145
1148
  if (finalFailure) {
1146
1149
  if (!session.pane) continue;
1147
- const r = await doorOn(session.machine ?? localMachine(), 'harness.v1.panes.presence', { pane: session.pane }, { timeout: 10_000, as: boardAddress(board.label) });
1150
+ const r = await doorOn(session.machine ?? localMachine(), 'harness.v1.panes.presence', { pane: session.pane, ...(session.launch_key ? { launch: session.launch_key } : {}) }, { timeout: 10_000, as: boardAddress(board.label) });
1148
1151
  const observed = r.code === 0 ? json(r.stdout.trim(), null) : null;
1149
- if (observed?.pane !== session.pane || observed.present !== false) continue;
1152
+ if (observed?.pane !== session.pane) continue;
1153
+ if (observed.present === true && observed.yours === true) {
1154
+ if (view(board, (db) => db.prepare(`SELECT metadata FROM task_runs r WHERE r.ended_at IS NULL OR EXISTS (SELECT 1 FROM tasks t WHERE t.current_run_id = r.id)`).all())
1155
+ .some((r) => { const s = json(r.metadata, {})?.supercode; return s?.pane === session.pane && normalMachine(s.machine) === normalMachine(session.machine); })) continue;
1156
+ finalFailure = false;
1157
+ } else if (observed.present !== false) continue;
1150
1158
  }
1151
1159
  const active = view(board, (db) => db.prepare(`SELECT r.id, r.metadata FROM task_runs r WHERE r.ended_at IS NULL OR EXISTS (SELECT 1 FROM tasks t WHERE t.current_run_id = r.id)`).all());
1152
1160
  // Persist retries even for historical ends that did not have a close_session action. The close is claimed in the
@@ -1226,7 +1234,7 @@ export async function closeRequested(board, report, { taskId = null, caller = nu
1226
1234
  setRunSession(db, row.id, { ...current, close_attempted_at: at, close_attempts: Number(current.close_attempts ?? 0) + 1, close_error: error, ...(final ? { close_failed_at: at } : {}) });
1227
1235
  // Retain every attempt's latest fact, but avoid an unbounded thread of identical failures.
1228
1236
  if (current.close_error !== error) event(db, row.task_id, 'session_close_failed', { session_id: session.session_id, pane: session.pane, error, retry: !final }, row.id);
1229
- if (final) event(db, row.task_id, 'run_incident', { observation: 'close_failed', run: row.id, reason: `the board could not close ${addressOf(session)} and will not try again: ${error}`, session: session.session_id, machine: session.machine, pane: session.pane ?? null, evidence: { error } }, row.id);
1237
+ if (final && current.close_error !== error) event(db, row.task_id, 'run_incident', { observation: 'close_failed', run: row.id, reason: `the board could not close ${addressOf(session)} and will not try again: ${error}`, session: session.session_id, machine: session.machine, pane: session.pane ?? null, evidence: { error } }, row.id);
1230
1238
  } else if (!current.close_confirmed_at) {
1231
1239
  // a close another closer already confirmed (a claim that expired under it) is said once, by that closer
1232
1240
  const { close_error: _error, ...rest } = current;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/supercode-orchestrator",
3
- "version": "0.5.131",
3
+ "version": "0.5.133",
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": {