@volter/supercode-orchestrator 0.4.92 → 0.4.94

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/rows.mjs CHANGED
@@ -58,7 +58,7 @@ export class RuntimeRows {
58
58
  });
59
59
  }
60
60
  write(changes) {
61
- this.db.exec('CREATE TABLE IF NOT EXISTS runtime_schema(name TEXT PRIMARY KEY,sql TEXT NOT NULL); CREATE TABLE IF NOT EXISTS runtime_rows(name TEXT,key TEXT,value TEXT,revision INTEGER NOT NULL,PRIMARY KEY(name,key)); CREATE INDEX IF NOT EXISTS runtime_rows_revision ON runtime_rows(revision)');
61
+ this.db.exec(`CREATE TABLE IF NOT EXISTS runtime_schema(name TEXT PRIMARY KEY,sql TEXT NOT NULL); CREATE TABLE IF NOT EXISTS runtime_rows(name TEXT,key TEXT,value TEXT,revision INTEGER NOT NULL,PRIMARY KEY(name,key)); CREATE INDEX IF NOT EXISTS runtime_rows_revision ON runtime_rows(revision); CREATE INDEX IF NOT EXISTS runtime_rows_task ON runtime_rows(name,json_extract(value,'$.task_id'),json_extract(value,'$.id')); CREATE INDEX IF NOT EXISTS runtime_rows_id ON runtime_rows(name,json_extract(value,'$.id'))`);
62
62
  const revision=Number(this.db.prepare("SELECT value FROM state WHERE key='rows_revision'").get()?.value??0)+1;
63
63
  const statement=this.db.prepare('INSERT INTO runtime_rows VALUES(?,?,?,?) ON CONFLICT(name,key) DO UPDATE SET value=excluded.value,revision=excluded.revision');
64
64
  for(const {name,key,value,source} of changes) {
@@ -116,7 +116,9 @@ export function refreshCardInputs(board, id, { commits = false } = {}) {
116
116
  if (commits) {
117
117
  const own=task.workspace_kind!=='scratch'?task.workspace_path:null;
118
118
  const parent=!own && task.subtask_of?board.db.prepare('SELECT workspace_path FROM tasks WHERE id=?').get(task.subtask_of)?.workspace_path:null;
119
- observeLastCommit(own??parent??null,id);
119
+ const path=own??parent??null;
120
+ if(path)commitTimes.delete(`${path}:${id}`);
121
+ observeLastCommit(path,id);
120
122
  }
121
123
  }
122
124
 
package/board/ztrack.mjs CHANGED
@@ -36,10 +36,9 @@ class ZtrackBoard {
36
36
  this.archive = `${this.path}.archive.md`;
37
37
  this.seed = seed;
38
38
  this.root = root; this.slug = slug; this.init = init; this.compatPath = compatPath;
39
- this.codec = require('@volter/ztrack/board-document');
40
- this.preset = require('@volter/ztrack/kanban-preset');
41
- this.runtime = new DatabaseSync(`${marker}.runtime.db`);
42
- if (!this.runtime.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='state'").get()) this.runtime.exec('CREATE TABLE state (key TEXT PRIMARY KEY, value TEXT NOT NULL)');
39
+ this.runtimePath = `${marker}.runtime.db`;
40
+ this.runtime = new DatabaseSync(this.runtimePath);
41
+ this.readOnly(()=>{ if (!this.runtime.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='state'").get()) this.runtime.exec('CREATE TABLE IF NOT EXISTS state (key TEXT PRIMARY KEY, value TEXT NOT NULL)'); });
43
42
  this.errors = new Map(); this.preserve = new Map();
44
43
  this.mem = null; this.active = false; this.rows = new RuntimeRows(this.runtime);
45
44
  mkdirSync(dirname(this.path), { recursive: true });
@@ -53,6 +52,8 @@ class ZtrackBoard {
53
52
  if (seed) this.tx(() => {});
54
53
  else this.readOnly(()=>{ this.load(); this.loadedStamp = this.stamp(); });
55
54
  }
55
+ get codec() { return this._codec ??= require('@volter/ztrack/board-document'); }
56
+ get preset() { return this._preset ??= require('@volter/ztrack/kanban-preset'); }
56
57
  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); }
57
58
  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)); }
58
59
  stamp() {
@@ -107,6 +108,7 @@ class ZtrackBoard {
107
108
  }
108
109
 
109
110
  transaction(fn) {
111
+ if (this.cachedReader) this.invalidate();
110
112
  for (;;) {
111
113
  try { this.runtime.exec('BEGIN'); }
112
114
  catch (error) { if (!/busy|locked/i.test(error.message)) throw error; continue; }
@@ -131,7 +133,7 @@ class ZtrackBoard {
131
133
  });
132
134
  const documents = cardDirty ? this.documents() : {open:this.text, archived:this.archiveText};
133
135
  const files = [{ path: this.path, before: hash(this.text), base: this.text, text: documents.open }, { path: this.archive, before: hash(this.archiveText), base: this.archiveText, text: documents.archived }];
134
- const snapshot = this.snapshot();
136
+ const snapshot = this.snapshot(documents);
135
137
  this.computing = false;
136
138
  this.runtime.exec('BEGIN');
137
139
  this.recover();
@@ -169,12 +171,12 @@ class ZtrackBoard {
169
171
  try { this.runtime.exec('ROLLBACK'); } catch { /* intent already durable */ }
170
172
  this.invalidate();
171
173
  if (/busy|locked|Hermes wrote|document changed during recovery/i.test(error.message)) continue;
172
- if (committed) error.message += ' (the runtime journal retains any unfinished document write)';
174
+ if (committed) error.message += ' (the operation reached its durable intent; inspect its recorded outcome before retrying)';
173
175
  throw error;
174
176
  } finally { this.active = false; this.computing = false; this.pendingValues = null; }
175
177
  }
176
178
  }
177
- invalidate() { this.mem?.close(); this.mem = null; this.rows = new RuntimeRows(this.runtime); this.loadedStamp = null; }
179
+ invalidate() { this.mem?.close(); this.mem = null; this.rows = new RuntimeRows(this.runtime); this.loadedStamp = null; this.cachedReader=false; }
178
180
  seedIntoMemory() {
179
181
  const source = this.seed;
180
182
  for (const table of this.mem.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'").all()) this.mem.exec(`DELETE FROM ${quote(table.name)}`);
@@ -216,8 +218,38 @@ class ZtrackBoard {
216
218
  if (!keys.length) return;
217
219
  this.mem.prepare(`INSERT OR REPLACE INTO ${quote(table)} (${keys.map(quote).join(',')}) VALUES (${keys.map(() => '?').join(',')})`).run(...keys.map((k) => row[k] ?? null));
218
220
  }
221
+ openCachedReader(saved) {
222
+ if (!this.cachedReader) {
223
+ this.mem?.close(); this.mem=new DatabaseSync(':memory:'); this.init(this.mem,{fresh:true});
224
+ for(const row of this.runtime.prepare('SELECT name,sql FROM runtime_schema').all())this.mem.exec(row.sql.replace('CREATE TABLE ','CREATE TABLE IF NOT EXISTS '));
225
+ this.mem.prepare('ATTACH DATABASE ? AS source').run(`file:${this.runtimePath}?mode=ro`);
226
+ const tables=this.mem.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'").all();
227
+ for(const {name} of tables) {
228
+ if(name==='task_links') {
229
+ this.mem.exec(`CREATE TEMP VIEW task_links AS SELECT json_extract(b.value,'$.id') parent_id,r.key child_id FROM source.runtime_rows r,json_each(json_extract(r.value,'$.blocked_by')) b WHERE r.name='@card_cache' AND json_extract(b.value,'$.kind')='card'`);
230
+ continue;
231
+ }
232
+ const columns=this.mem.prepare(`PRAGMA main.table_info(${quote(name)})`).all();
233
+ const recordName=name==='tasks'?'@task_cache':name==='supercode_cards'?'@card_cache':name;
234
+ const fields=columns.map(({name:column})=>name==='tasks'&&column==='status'?`COALESCE(json_extract(c.value,'$.stage'),json_extract(r.value,'$.status')) AS status`:`json_extract(r.value,'$.${column}') AS ${quote(column)}`);
235
+ const join=name==='tasks'?" LEFT JOIN source.runtime_rows c ON c.name='@card_cache' AND c.key=r.key":'';
236
+ this.mem.exec(`CREATE TEMP VIEW ${quote(name)} AS SELECT ${fields.join(',')} FROM source.runtime_rows r${join} WHERE r.name='${recordName}' AND r.value IS NOT NULL`);
237
+ }
238
+ this.cachedReader=true;
239
+ }
240
+ this.saved=saved; this.errors.clear();
241
+ for(const [id,reason] of saved.errors??[])this.errors.set(id,reason);
242
+ this.mem.boardErrors=this.errors;
243
+ }
219
244
  load({ observe = false } = {}) {
220
245
  const text = read(this.path), archiveText = read(this.archive);
246
+ const saved=this.get('snapshot', {});
247
+ if (!observe && saved.documentHashes?.open===hash(text) && saved.documentHashes?.archived===hash(archiveText) && this.rows.normalized()) {
248
+ this.openCachedReader(saved);
249
+ this.text=text;this.archiveText=archiveText;
250
+ return;
251
+ }
252
+ if (this.cachedReader)this.invalidate();
221
253
  const fresh = !this.mem;
222
254
  const changed = fresh || text !== this.text || archiveText !== this.archiveText;
223
255
  this.text = text; this.archiveText = archiveText;
@@ -394,8 +426,8 @@ class ZtrackBoard {
394
426
  }
395
427
  return { open: this.codec.renderBoardDocument(this.text, open, this.path, [...(this.preserve.get(this.path)??[])]), archived: this.codec.renderBoardDocument(this.archiveText, archived, this.archive, [...(this.preserve.get(this.archive)??[])]) };
396
428
  }
397
- snapshot() {
398
- return { fingerprints: this.fingerprints(), taskIds: Object.fromEntries([...this.taskKeys].map(([id, key]) => [key, id])) };
429
+ snapshot(documents) {
430
+ return { documentHashes:{open:hash(documents.open),archived:hash(documents.archived)}, errors:[...this.errors], fingerprints: this.fingerprints(), taskIds: Object.fromEntries([...this.taskKeys].map(([id, key]) => [key, id])) };
399
431
  }
400
432
 
401
433
  lift() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/supercode-orchestrator",
3
- "version": "0.4.92",
3
+ "version": "0.4.94",
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": {