@volter/supercode-orchestrator 0.5.24 → 0.5.25
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 +4 -1
- package/board/dispatch.mjs +14 -3
- package/board/store.mjs +10 -3
- package/package.json +1 -1
package/board/cli.mjs
CHANGED
|
@@ -1402,7 +1402,10 @@ async function serve(root, args = {}) {
|
|
|
1402
1402
|
renameSync(temporary, heartbeat);
|
|
1403
1403
|
} catch { /* the next round writes it */ }
|
|
1404
1404
|
}
|
|
1405
|
-
|
|
1405
|
+
// A whole tick that failed is counted as board '*'; a round in which every board succeeded clears it too, so its
|
|
1406
|
+
// recovery is said and a later failure starts a count of its own.
|
|
1407
|
+
const settled = reports.every((r) => !r.error) && failing.has('*') ? [...reports, { board: '*' }] : reports;
|
|
1408
|
+
for (const r of settled) {
|
|
1406
1409
|
const run = failing.get(r.board) ?? { count: 0, since: Date.now(), told: 0 };
|
|
1407
1410
|
if (!r.error) {
|
|
1408
1411
|
if (run.count >= FAILED_ROUNDS_ALERT) await alertManagers(root, r.board === '*' ? 'default' : r.board, `The dispatcher's rounds on board ${r.board} succeed again after ${run.count} failed rounds since ${new Date(run.since).toISOString()}.`, `dispatcher-recovered-${r.board}-${run.since}`);
|
package/board/dispatch.mjs
CHANGED
|
@@ -1074,11 +1074,22 @@ export async function mergeReviewed(board, e) {
|
|
|
1074
1074
|
return true;
|
|
1075
1075
|
}
|
|
1076
1076
|
const entry = await loadWorkflow(board.root);
|
|
1077
|
-
|
|
1077
|
+
// A fault other than the card's refusal keeps the effect for the next round, and the round goes on past it.
|
|
1078
|
+
try { tx(board, (db) => {
|
|
1078
1079
|
event(db, e.id, 'reviewed_merged', { merges });
|
|
1079
|
-
|
|
1080
|
+
// A card that moved after its pass cannot take the event: that is said to the manager once, never thrown, so the
|
|
1081
|
+
// kept effect does not stop every round after it (as merge_refused, above).
|
|
1082
|
+
let r;
|
|
1083
|
+
try { r = fire(db, board, entry, e.id, 'reviewed_merged', { roles: ['platform'], payload: { merges, summary: merges.length ? `Merged at the reviewed heads: ${merges.map((m) => `${m.pr} (${m.reviewed.slice(0, 8)} → ${String(m.merge).slice(0, 8)})`).join(', ')}` : 'The pass named no PR to merge.' } }); }
|
|
1084
|
+
catch (error) {
|
|
1085
|
+
// only the card's refusal is said and consumed; anything else (a write contention, a fault) is thrown and retried
|
|
1086
|
+
if (!error.noTransition && !error.refusal && !/does not apply|there is for/.test(error.message)) throw error;
|
|
1087
|
+
const to = [].concat(entry.params.managers ?? []).find(Boolean);
|
|
1088
|
+
if (to) event(db, e.id, 'board_message', { to, text: `${e.id}: its reviewed PRs are merged (${merges.map((m) => m.pr).join(', ') || 'none'}), but the card could not take reviewed_merged: ${String(error.message).slice(0, 300)}` }, null);
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1080
1091
|
for (const eff of r.effects) db.prepare('INSERT INTO supercode_effects (effect) VALUES (?)').run(JSON.stringify(eff));
|
|
1081
|
-
});
|
|
1092
|
+
}); } catch (error) { process.stderr.write(`board: ${e.id}: reviewed_merged kept for the next round: ${String(error.message).slice(0, 300)}\n`); return false; }
|
|
1082
1093
|
return true;
|
|
1083
1094
|
}
|
|
1084
1095
|
|
package/board/store.mjs
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
// This module opens a board in its backing and gives its callers one primitive, a write transaction; the transitions
|
|
10
10
|
// are in `engine.mjs`. Hermes's own tools may write a `kanban.db` at the same time: every write there takes SQLite's
|
|
11
11
|
// native transaction boundary and retries an optimistic conflict.
|
|
12
|
+
import { noteStep } from '@volter/supercode-harness-sdk/slow-log';
|
|
12
13
|
import { randomBytes } from 'node:crypto';
|
|
13
14
|
import { existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';
|
|
14
15
|
import { hostname } from 'node:os';
|
|
@@ -200,9 +201,15 @@ export function openBoard(root, slug = DEFAULT_BOARD, { create = false, backing
|
|
|
200
201
|
* A read inside the transaction is the state it writes over.
|
|
201
202
|
*/
|
|
202
203
|
export const tx = (board, fn) => {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
204
|
+
// Each transaction is a step named by the function that asked for it, so a round's slow-log line says which of its
|
|
205
|
+
// steps make its transactions (about a hundred a round, at 150 ms each, on 2026-10-02).
|
|
206
|
+
const caller = /at (?:async )?([\w.$<>]+) /.exec(String(new Error().stack).split('\n')[2] ?? '')?.[1] ?? 'unknown';
|
|
207
|
+
const started = performance.now();
|
|
208
|
+
try {
|
|
209
|
+
const result = board.tx(db => {const result=fn(db);recordCardPublications(db,actor);return result;});
|
|
210
|
+
signalBoardWrite(board.root, actor);
|
|
211
|
+
return result;
|
|
212
|
+
} finally { noteStep(`board tx from ${caller}`, performance.now() - started); }
|
|
206
213
|
};
|
|
207
214
|
// SQLite's own contention (BUSY, LOCKED, and their extended codes) and nothing else: a refusal whose words happen to
|
|
208
215
|
// say "blocked" or "busy" reaches its caller at once instead of being retried forever.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-orchestrator",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.25",
|
|
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": {
|