@volter/supercode-orchestrator 0.4.82 → 0.4.84

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
@@ -9,7 +9,6 @@ import { spawnSync } from 'node:child_process';
9
9
  import { basename, join, resolve } from 'node:path';
10
10
  import { tmpdir } from 'node:os';
11
11
  import { read } from '../orchestration.mjs';
12
- import { holdDispatcherLock } from '../hermes-locks.mjs';
13
12
  import { backingPaths, layOut, backingOf, boardExists, boardPath, DEFAULT_BOARD, event, json, listBoards, logsDir, now, openBoard, setActor, tx } from './store.mjs';
14
13
  import { openZtrackBoard } from './ztrack.mjs';
15
14
  import * as engine from './engine.mjs';
@@ -1139,11 +1138,10 @@ export function summary(r) {
1139
1138
 
1140
1139
  /**
1141
1140
  * The dispatcher alone, for a home whose board is dispatched without the rest of the orchestrator (the lanes are
1142
- * re-read every tick, so a profile's change takes effect without a restart). It holds the board's one-dispatcher
1143
- * lock, as the daemon and Hermes's gateways do, so one dispatcher serves a board.
1141
+ * re-read every tick, so a profile's change takes effect without a restart). Card claims
1142
+ * are version-checked by the backing; the dispatcher takes no file lock.
1144
1143
  */
1145
1144
  async function serve(root, args = {}) {
1146
- const lock = await holdDispatcherLock(root);
1147
1145
  // Hermes's daemon flags: --interval seconds, --max starts per tick, --failure-limit, --pidfile, --verbose
1148
1146
  const override = Number(args.interval) > 0 ? { interval: Number(args.interval) } : {};
1149
1147
  // --max and --failure-limit are params of every tick, read by the workflow as its own
@@ -1205,7 +1203,6 @@ async function serve(root, args = {}) {
1205
1203
  if (moved) { log(`orchestrator ${moved} is installed (this serve runs ${running}); stopping, for its keeper to start the new one`); break; }
1206
1204
  }
1207
1205
  } finally {
1208
- lock.release();
1209
1206
  if (args.pidfile) { try { unlinkSync(args.pidfile); } catch { /* gone */ } }
1210
1207
  }
1211
1208
  log(stopped ? 'stopped (a signal)' : 'stopped');
@@ -22,7 +22,6 @@ import { basename, dirname, join } from 'node:path';
22
22
  import { randomUUID } from 'node:crypto';
23
23
  import { fileURLToPath } from 'node:url';
24
24
  import { boardPath, claimer, event, json, listBoards, logsDir, now, openBoard, setActor, tx } from './store.mjs';
25
- import { holdBoardTickLock } from '../hermes-locks.mjs';
26
25
  import { doors, fire, loadWorkflow, resumeOf, runSpec, transitionsFor, scopeOf, statusOf, templateValue, workflowFor } from './workflow.mjs';
27
26
  import { evaluate, holds, render } from './expr.mjs';
28
27
  import {
@@ -1206,7 +1205,7 @@ export async function plan({ root, lanes, settings, slugs = null, params = {} })
1206
1205
  /**
1207
1206
  * One tick over every board of the home at `root`. `lanes` from `lanesOf`, `settings` from `boardSettings`.
1208
1207
  * `only` limits starting to one card (a create that answers whether its card started). Each board is ticked under its
1209
- * per-tick lock (Hermes's `.dispatch.lock`), skipped when another dispatcher holds it. Answers one report per board.
1208
+ * version-checked card claims; no dispatcher file lock. Answers one report per board.
1210
1209
  */
1211
1210
  export async function tick({ root, lanes, settings, only = null, slugs = null, dispatch = true, params = {} }) {
1212
1211
  // a verb's flags (dispatch --max, --failure-limit) are params of this tick, read by the workflow as its own
@@ -1230,8 +1229,6 @@ export async function tick({ root, lanes, settings, only = null, slugs = null, d
1230
1229
  const path = boardPath(root, slug);
1231
1230
  const marker = `${path}.corrupt-seen`;
1232
1231
  if (existsSync(marker) && readFileSync(marker, 'utf8').trim() === fingerprint(path)) { report.error = 'skipped: the board file was corrupt and has not changed since (`supercode workflow repair`)'; out.push(report); continue; }
1233
- let lock = null;
1234
- try { lock = await holdBoardTickLock(path); } catch (error) { report.skipped_locked = error.message; out.push(report); continue; }
1235
1232
  let board;
1236
1233
  try {
1237
1234
  board = openBoard(root, slug);
@@ -1268,7 +1265,6 @@ export async function tick({ root, lanes, settings, only = null, slugs = null, d
1268
1265
  } finally {
1269
1266
  await Promise.all(report.answers);
1270
1267
  board?.close();
1271
- lock.release();
1272
1268
  }
1273
1269
  out.push(report);
1274
1270
  }
package/board/files.mjs CHANGED
@@ -5,23 +5,20 @@
5
5
  // counters. A card's history is a file a person or a diff reads, and nothing but supercode's code touches it.
6
6
  //
7
7
  // The engine speaks SQL to every backing, so this one answers it from an in-memory SQLite database loaded from the
8
- // directory: a write transaction takes the directory's lock (`_board.lock`), reloads what another process wrote,
9
- // runs, and writes back the files of the cards it touched (temporary triggers name them), `_board.json` last. A read
8
+ // directory: a write transaction reads the directory's content hash, reloads what another process wrote,
9
+ // runs, retries if that hash changed, and writes the cards it touched (temporary triggers name them), `_board.json` last. A read
10
10
  // outside a transaction reloads first when `_board.json` changed, as a SQLite reader sees the last commit.
11
- import { closeSync, existsSync, mkdirSync, openSync, readdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync, writeSync } from 'node:fs';
12
- import { hostname } from 'node:os';
11
+ import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
12
+ import { createHash } from 'node:crypto';
13
13
  import { join } from 'node:path';
14
14
  import { DatabaseSync } from 'node:sqlite';
15
15
 
16
16
  const STATE = '_board.json';
17
- const LOCK = '_board.lock';
18
- const LOCK_WAIT_MS = 10_000; // as the sqlite backing's busy_timeout
19
17
 
20
18
  /** Whether `dir` holds a files board. */
21
19
  export const isFilesBoard = (dir) => existsSync(join(dir, STATE));
22
20
 
23
21
  const sleep = (ms) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
24
- const alive = (pid) => { try { process.kill(pid, 0); return true; } catch (error) { return error.code === 'EPERM'; } };
25
22
  const quote = (name) => `"${String(name).replaceAll('"', '""')}"`;
26
23
 
27
24
  function writeAtomic(file, text) {
@@ -66,23 +63,28 @@ class FilesBoard {
66
63
  exec: (sql) => self.write(sql, () => self.mem.exec(sql)),
67
64
  close: () => self.close(),
68
65
  };
69
- this.locked(() => this.load());
66
+ this.load();
70
67
  }
71
68
 
72
69
  // ---------------------------------------------------------------- snapshot
73
70
 
74
71
  stampOf() {
75
- try { const s = statSync(join(this.dir, STATE)); return `${s.ino}:${s.mtimeMs}:${s.size}`; } catch { return 'none'; }
72
+ const hash = createHash('sha256');
73
+ for (const name of readdirSync(this.dir).filter(name => name.endsWith('.json')).sort()) {
74
+ try { hash.update(name).update('\0').update(readFileSync(join(this.dir, name))).update('\0'); }
75
+ catch (error) { if (error.code !== 'ENOENT') throw error; return this.stampOf(); }
76
+ }
77
+ return hash.digest('hex');
76
78
  }
77
79
 
78
- /** (Re)load the directory into a fresh in-memory database; the caller holds the lock. */
80
+ /** (Re)load the directory into a fresh in-memory database; writes compare the content hash. */
79
81
  load() {
80
82
  const stamp = this.stampOf();
81
83
  if (this.mem && stamp === this.stamp) return;
82
84
  this.mem?.close();
83
85
  this.statements.clear();
84
86
  const mem = new DatabaseSync(':memory:');
85
- this.stateText = stamp === 'none' ? null : readFileSync(join(this.dir, STATE), 'utf8');
87
+ this.stateText = existsSync(join(this.dir, STATE)) ? readFileSync(join(this.dir, STATE), 'utf8') : null;
86
88
  const state = this.stateText === null ? null : JSON.parse(this.stateText);
87
89
  for (const sql of state?.schema ?? []) mem.exec(sql);
88
90
  this.init(mem, { fresh: !state?.schema?.length });
@@ -143,7 +145,7 @@ class FilesBoard {
143
145
  return this.mem.prepare("SELECT sql FROM sqlite_master WHERE sql IS NOT NULL AND name NOT LIKE 'sqlite_%' ORDER BY CASE type WHEN 'table' THEN 0 ELSE 1 END, rowid").all().map((r) => r.sql);
144
146
  }
145
147
 
146
- /** Write the files of the cards the transaction touched, then `_board.json`; the caller holds the lock. */
148
+ /** Write the files of the cards the transaction touched, then `_board.json`; writes compare the content hash. */
147
149
  persist(schemaBefore) {
148
150
  const schema = this.schema();
149
151
  const reshaped = schema.join('\n') !== schemaBefore.join('\n');
@@ -157,6 +159,7 @@ class FilesBoard {
157
159
  }
158
160
  const state = { format: 'supercode-board-files', format_version: 1, schema, tables: boardTables, sequence: this.mem.prepare('SELECT name, seq FROM sqlite_sequence ORDER BY name').all() };
159
161
  const stateText = `${JSON.stringify(state, null, 2)}\n`;
162
+ const writes = [];
160
163
  let wrote = false;
161
164
  for (const id of touched) {
162
165
  const tables = {};
@@ -166,49 +169,28 @@ class FilesBoard {
166
169
  }
167
170
  const file = join(this.dir, `${String(id).replace(/[^A-Za-z0-9_.-]/g, '_')}.json`);
168
171
  if (!Object.keys(tables).length) {
169
- if (this.texts.has(id)) { try { unlinkSync(file); } catch { /* already gone */ } this.texts.delete(id); wrote = true; }
172
+ if (this.texts.has(id)) { writes.push({ file, text: null }); wrote = true; }
170
173
  continue;
171
174
  }
172
175
  const text = `${JSON.stringify({ id, tables }, null, 2)}\n`;
173
176
  if (this.texts.get(id) === text) continue;
174
- writeAtomic(file, text);
175
- this.texts.set(id, text);
177
+ writes.push({ file, text });
176
178
  wrote = true;
177
179
  }
178
180
  if (wrote || stateText !== this.stateText) {
179
- writeAtomic(join(this.dir, STATE), stateText);
180
- this.stateText = stateText;
181
+ writes.push({ file: join(this.dir, STATE), text: stateText });
181
182
  }
182
- this.mem.exec('DELETE FROM _touched');
183
- this.stamp = this.stampOf();
184
- }
185
-
186
- // ---------------------------------------------------------------- lock and transactions
187
-
188
- locked(fn) {
189
- const file = join(this.dir, LOCK);
190
- const holder = `${hostname()}:${process.pid}`;
191
- const until = Date.now() + LOCK_WAIT_MS;
192
- for (;;) {
193
- try {
194
- const fd = openSync(file, 'wx');
195
- writeSync(fd, holder);
196
- closeSync(fd);
197
- break;
198
- } catch (error) {
199
- if (error.code !== 'EEXIST') throw error;
200
- let held = '';
201
- try { held = readFileSync(file, 'utf8'); } catch { continue; }
202
- const [host, pid] = held.split(':');
203
- // a holder on this host that is gone left its lock behind
204
- if (host === hostname() && Number(pid) && !alive(Number(pid))) { try { unlinkSync(file); } catch { /* taken by another */ } continue; }
205
- if (Date.now() > until) throw new Error(`the board ${this.dir} is locked by ${held || 'another writer'}`);
206
- sleep(20);
207
- }
183
+ if (this.stampOf() !== this.stamp) return false;
184
+ for (const { file, text } of writes) {
185
+ if (text === null) { try { unlinkSync(file); } catch (error) { if (error.code !== 'ENOENT') throw error; } }
186
+ else writeAtomic(file, text);
208
187
  }
209
- try { return fn(); } finally { try { unlinkSync(file); } catch { /* gone */ } }
188
+ this.stamp = null; // next read reloads the committed file hashes and card texts
189
+ return true;
210
190
  }
211
191
 
192
+ // ---------------------------------------------------------------- transactions
193
+
212
194
  statement(sql) {
213
195
  let s = this.statements.get(sql);
214
196
  if (!s) { s = this.mem.prepare(sql); this.statements.set(sql, s); }
@@ -216,7 +198,7 @@ class FilesBoard {
216
198
  }
217
199
 
218
200
  read(fn) {
219
- if (!this.inTx && this.stampOf() !== this.stamp) this.locked(() => this.load());
201
+ if (!this.inTx && this.stampOf() !== this.stamp) this.load();
220
202
  return fn();
221
203
  }
222
204
 
@@ -228,7 +210,8 @@ class FilesBoard {
228
210
 
229
211
  tx(fn, { force = false } = {}) {
230
212
  if (this.inTx) return fn(this.db);
231
- return this.locked(() => {
213
+ for (;;) {
214
+ this.stamp = null;
232
215
  this.load();
233
216
  const before = this.schema();
234
217
  this.mem.exec('BEGIN');
@@ -245,13 +228,13 @@ class FilesBoard {
245
228
  }
246
229
  try {
247
230
  if (force) this.stateText = null;
248
- this.persist(before);
231
+ if (!this.persist(before)) { sleep(10); continue; }
249
232
  } catch (error) {
250
233
  this.stamp = null; // what memory holds may not be on disk: the next reader reloads from the files
251
234
  throw error;
252
235
  }
253
236
  return out;
254
- });
237
+ }
255
238
  }
256
239
 
257
240
  close() {
package/board/ztrack.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
  // ADR 0001. All markdown interpretation belongs to ztrack's public codec and kanban preset.
3
3
  import { createRequire } from 'node:module';
4
4
  import { createHash, randomBytes } from 'node:crypto';
5
- import { closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from 'node:fs';
5
+ import { existsSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from 'node:fs';
6
6
  import { dirname, resolve } from 'node:path';
7
7
  import { spawnSync } from 'node:child_process';
8
8
  import { DatabaseSync } from 'node:sqlite';
@@ -34,7 +34,6 @@ class ZtrackBoard {
34
34
  constructor({ document, seed, marker, init, root, slug, compatPath }) {
35
35
  this.path = resolve(dirname(marker), document);
36
36
  this.archive = `${this.path}.archive.md`;
37
- this.lockPath = `${marker}.lock`;
38
37
  this.seed = seed;
39
38
  this.root = root; this.slug = slug; this.init = init; this.compatPath = compatPath;
40
39
  this.codec = require('@volter/ztrack/board-document');
@@ -51,8 +50,8 @@ class ZtrackBoard {
51
50
  };
52
51
  this.tx(() => {});
53
52
  }
54
- get(key, fallback = null) { return json(this.runtime.prepare('SELECT value FROM state WHERE key = ?').get(key)?.value, fallback); }
55
- put(key, value) { this.runtime.prepare('INSERT INTO state VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value').run(key, JSON.stringify(value)); }
53
+ get(key, fallback = null) { return this.computing && this.pendingValues.has(key) ? this.pendingValues.get(key) : json(this.runtime.prepare('SELECT value FROM state WHERE key = ?').get(key)?.value, fallback); }
54
+ put(key, value) { if (this.computing) { this.pendingValues.set(key, value); return; } this.runtime.prepare('INSERT INTO state VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value').run(key, JSON.stringify(value)); }
56
55
  stamp() {
57
56
  const fileStamp = (path) => { try { const s = statSync(path); return `${s.ino}:${s.size}:${s.mtimeMs}`; } catch { return 'missing'; } };
58
57
  return [fileStamp(this.path), fileStamp(this.archive), fileStamp(this.compatPath), this.get('version', '')].join('|');
@@ -72,47 +71,59 @@ class ZtrackBoard {
72
71
  if (current === file.before) atomic(file.path, file.text);
73
72
  }
74
73
  this.put('snapshot', pending.snapshot);
74
+ if (pending.intent) this.put(`applied:${pending.intent}`, true);
75
+ this.put('version', randomBytes(8).toString('hex'));
75
76
  this.runtime.prepare("DELETE FROM state WHERE key = 'pending'").run();
76
77
  }
77
78
  tx(fn) {
78
79
  if (this.active) return fn(this.db);
79
- let lock;
80
- for (let n = 0; ; n++) {
81
- try { lock = openSync(this.lockPath, 'wx', 0o600); writeFileSync(lock, String(process.pid)); break; }
82
- catch (error) {
83
- if (error.code !== 'EEXIST' || n >= 100) throw error;
84
- const pid = Number(read(this.lockPath));
85
- if (pid) { try { process.kill(pid, 0); } catch (e) { if (e.code === 'ESRCH') { unlinkSync(this.lockPath); continue; } } }
86
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 100);
87
- }
88
- }
89
- try { return this.transaction(fn); } finally { closeSync(lock); unlinkSync(this.lockPath); }
80
+ return this.transaction(fn);
90
81
  }
82
+
91
83
  transaction(fn) {
92
- for (let attempt = 0; attempt < 8; attempt++) {
93
- this.runtime.exec('BEGIN IMMEDIATE'); this.active = true;
84
+ for (;;) {
85
+ try { this.runtime.exec('BEGIN IMMEDIATE'); }
86
+ catch (error) { if (!/busy|locked/i.test(error.message)) throw error; continue; }
87
+ this.active = true;
94
88
  let committed = false;
95
89
  try {
96
90
  this.recover();
91
+ const version = this.get('version');
92
+ this.runtime.exec('COMMIT');
93
+ // Parsing, rules and rendering run against a snapshot, without a runtime writer transaction.
94
+ this.pendingValues = new Map(); this.computing = true;
97
95
  this.load();
98
96
  if (this.seed) this.seedIntoMemory();
99
97
  else this.lift();
100
98
  const result = fn(this.db);
101
99
  const documents = this.documents();
102
100
  const files = [{ path: this.path, before: hash(this.text), text: documents.open }, { path: this.archive, before: hash(this.archiveText), text: documents.archived }];
103
- if (files.some((f) => hash(read(f.path)) !== f.before)) { this.runtime.exec('ROLLBACK'); continue; }
104
101
  const snapshot = this.snapshot();
102
+ this.computing = false;
103
+ this.runtime.exec('BEGIN IMMEDIATE');
104
+ this.recover();
105
+ if (this.get('version') !== version || files.some((f) => hash(read(f.path)) !== f.before)) { this.runtime.exec('ROLLBACK'); continue; }
106
+ for (const [key, value] of this.pendingValues) this.put(key, value);
105
107
  // A durable intent precedes filesystem writes; recovery completes exactly this version.
106
- this.put('pending', { files, snapshot });
108
+ const intent = randomBytes(16).toString('hex');
109
+ this.put('pending', { intent, files, snapshot });
107
110
  this.runtime.exec('COMMIT'); committed = true;
111
+ // A concurrent writer may recover this intent and advance the runtime. Re-read
112
+ // its identity before touching documents, retrying from any newer version.
113
+ this.runtime.exec('BEGIN IMMEDIATE');
114
+ if (this.get('pending')?.intent !== intent) {
115
+ const applied = this.get(`applied:${intent}`, false);
116
+ this.runtime.exec('ROLLBACK');
117
+ if (applied) { this.loadedStamp = null; this.seed = null; return result; }
118
+ continue;
119
+ }
108
120
  // Detect an editor racing the journal commit before replacing either document.
109
121
  if (files.some((f) => hash(read(f.path)) !== f.before)) {
110
- this.runtime.exec('BEGIN IMMEDIATE'); this.runtime.prepare("DELETE FROM state WHERE key = 'pending'").run(); this.runtime.exec('COMMIT');
122
+ this.runtime.prepare("DELETE FROM state WHERE key = 'pending'").run(); this.runtime.exec('COMMIT');
111
123
  continue;
112
124
  }
113
125
  for (const f of files) if (hash(f.text) !== f.before) atomic(f.path, f.text);
114
- this.runtime.exec('BEGIN IMMEDIATE');
115
- this.put('snapshot', snapshot); this.runtime.prepare("DELETE FROM state WHERE key = 'pending'").run();
126
+ this.put('snapshot', snapshot); this.put(`applied:${intent}`, true); this.runtime.prepare("DELETE FROM state WHERE key = 'pending'").run();
116
127
  this.export();
117
128
  this.put('version', randomBytes(8).toString('hex'));
118
129
  this.runtime.exec('COMMIT');
@@ -121,11 +132,11 @@ class ZtrackBoard {
121
132
  return result;
122
133
  } catch (error) {
123
134
  try { this.runtime.exec('ROLLBACK'); } catch { /* intent already durable */ }
135
+ if (/busy|locked|Hermes wrote/i.test(error.message)) continue;
124
136
  if (committed) error.message += ' (the runtime journal retains any unfinished document write)';
125
137
  throw error;
126
- } finally { this.active = false; }
138
+ } finally { this.active = false; this.computing = false; this.pendingValues = null; }
127
139
  }
128
- throw new Error('board document kept changing during eight version-checked retries; no edit was overwritten');
129
140
  }
130
141
  seedIntoMemory() {
131
142
  const source = this.seed;
package/board.mjs CHANGED
@@ -2,14 +2,12 @@
2
2
  // docs/plans/hermes-compat.md row 14). The board and its dispatcher are supercode's own (`board/`): Hermes Kanban's
3
3
  // format and lifecycle, each profile's lane decided by its `worker` in the IR. Where a Hermes gateway would run
4
4
  // its dispatcher, the daemon runs this one the same way: when the board's workflow says so (its `dispatch.serve`),
5
- // holding the board's one-dispatcher lock (`kanban/.dispatcher.lock`, the
6
- // lock Hermes's gateways take, so the two never dispatch one board together), one tick every `dispatch.tick` seconds
5
+ // taking no file locks, one tick every `dispatch.tick` seconds
7
6
  // of the workflow (at least 1), the first after five seconds. Each tick runs as
8
7
  // `workflow dispatch` in a child process: a tick waits on supercode (the fleet, launches, mail), and the daemon's
9
8
  // own loop never does.
10
9
  import { spawn } from 'node:child_process';
11
10
  import { fileURLToPath } from 'node:url';
12
- import { holdDispatcherLock } from './hermes-locks.mjs';
13
11
  import { summary } from './board/cli.mjs';
14
12
  import { notifyChats } from './board/dispatch.mjs';
15
13
  import { workflowEntry } from './board/workflow.mjs';
@@ -38,9 +36,6 @@ export async function startBoard({ root, orchestration = null, log = () => {}, a
38
36
  let entry;
39
37
  try { entry = workflowEntry(orchestration); } catch (error) { log('warn', `board: not dispatched: ${error.message}`); return null; }
40
38
  if (!holds(entry.workflow.dispatch.serve ?? null, { params: entry.params })) { log('info', 'board: not dispatched (the board workflow\'s dispatch.serve)'); return null; }
41
- let lock;
42
- try { lock = await holdDispatcherLock(root); }
43
- catch (error) { log('info', `board: not dispatched: ${error.message}`); return null; }
44
39
  const seconds = Math.max(Number(evaluate(entry.workflow.dispatch.tick, { params: entry.params })) || 60, 1);
45
40
  let running = false;
46
41
  let stopped = false;
@@ -78,6 +73,6 @@ export async function startBoard({ root, orchestration = null, log = () => {}, a
78
73
  log('info', `board: dispatching every ${seconds}s, each card as its profile's worker says`);
79
74
  return {
80
75
  tick,
81
- stop() { stopped = true; clearTimeout(first); clearInterval(timer); lock.release(); },
76
+ stop() { stopped = true; clearTimeout(first); clearInterval(timer); },
82
77
  };
83
78
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/supercode-orchestrator",
3
- "version": "0.4.82",
3
+ "version": "0.4.84",
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": {