@volter/supercode-orchestrator 0.5.11 → 0.5.13
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/event-stream.mjs +25 -6
- package/package.json +1 -1
package/board/event-stream.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Public native workflow observation. Filesystem custody authorizes this door;
|
|
2
2
|
// Teams separately authorizes the Org event and its installed subscribers.
|
|
3
|
-
import { watch, existsSync, readFileSync, realpathSync } from 'node:fs';
|
|
3
|
+
import { watch, existsSync, readFileSync, realpathSync, statSync } from 'node:fs';
|
|
4
4
|
import { dirname, basename, join, resolve } from 'node:path';
|
|
5
5
|
import { createHash } from 'node:crypto';
|
|
6
6
|
import { listBoards, openBoard, backingPaths } from './store.mjs';
|
|
@@ -9,28 +9,43 @@ import { recordCardPublications } from './published.mjs';
|
|
|
9
9
|
const identity = root => createHash('sha256').update(realpathSync(root)).digest('hex');
|
|
10
10
|
const cursorOf = (home, boards) => Buffer.from(JSON.stringify({version:1,home,boards})).toString('base64url');
|
|
11
11
|
const resync = reason => Object.assign(new Error(reason), {code:'resync_required'});
|
|
12
|
+
const RESETTLE_MS = 30_000;
|
|
12
13
|
|
|
13
14
|
/** Snapshot once, then replay only committed publications on backing notifications. */
|
|
14
15
|
export async function* followBoardPublications({root,after,signal}) {
|
|
15
16
|
const home=identity(root), positions={}, watchers=new Map();
|
|
16
17
|
let dirty=true, wake=null, failure=null, inventory=null;
|
|
17
|
-
|
|
18
|
+
// Each store file as this follower's own pass left it, stamped right after that board's close. Its pass writes (the
|
|
19
|
+
// publication transaction) and closes the board, which rewrites the WAL and raises a watch event of its own; an event
|
|
20
|
+
// for a store file that is still as its pass left it is that echo, not a change, and does not start another pass.
|
|
21
|
+
// Without this the follower woke itself every quarter second and burned a core with nothing to publish
|
|
22
|
+
// (t_0328d520). Stamped per board, a write to one board while later boards' passes run is never taken for an echo.
|
|
23
|
+
let stores=[];
|
|
24
|
+
const settled=new Map();
|
|
25
|
+
const statOf=path=>{try{const s=statSync(path);return `${s.size}:${s.mtimeMs}`;}catch{return '-';}};
|
|
26
|
+
const notify=(name=null)=>{
|
|
27
|
+
const named=name===null?[]:stores.filter(path=>basename(path)===name);
|
|
28
|
+
// A store file no pass stamps (the root's kanban.db where only named boards exist) reads '-' until one does.
|
|
29
|
+
if(named.length&&named.every(path=>statOf(path)===(settled.get(path)??'-')))return;
|
|
30
|
+
dirty=true;wake?.();
|
|
31
|
+
};
|
|
18
32
|
const directory=(path,accept)=>{
|
|
19
33
|
if(!existsSync(path))return;
|
|
20
34
|
if(watchers.has(path)){watchers.get(path).accept.push(accept);return;}
|
|
21
35
|
const entry={accept:[accept],handle:null};
|
|
22
|
-
entry.handle=watch(path,(_kind,name)=>{if(name==null
|
|
36
|
+
entry.handle=watch(path,(_kind,name)=>{if(name==null)notify();else if(entry.accept.some(fn=>fn(String(name))))notify(String(name));});
|
|
23
37
|
entry.handle.on('error',error=>{failure=error;notify();});watchers.set(path,entry);
|
|
24
38
|
};
|
|
25
39
|
const file=path=>directory(dirname(path),name=>name===basename(path));
|
|
26
40
|
const register=slugs=>{
|
|
27
41
|
for(const entry of watchers.values())entry.accept=[];
|
|
42
|
+
stores=[join(root,'kanban.db'),join(root,'kanban.db-wal')];
|
|
28
43
|
directory(root,name=>['kanban.db','kanban.db-wal','kanban'].includes(name));
|
|
29
44
|
directory(join(root,'kanban'),name=>['boards','ztrack.json','board'].includes(name));
|
|
30
45
|
directory(join(root,'kanban','boards'),()=>true);
|
|
31
46
|
for(const slug of slugs){
|
|
32
47
|
const paths=backingPaths(root,slug);
|
|
33
|
-
file(paths.sqlite);file(`${paths.sqlite}-wal`);file(paths.ztrack);
|
|
48
|
+
file(paths.sqlite);file(`${paths.sqlite}-wal`);file(paths.ztrack);stores.push(paths.sqlite,`${paths.sqlite}-wal`);
|
|
34
49
|
directory(paths.files,name=>name.endsWith('.json'));
|
|
35
50
|
try {const cfg=JSON.parse(readFileSync(paths.ztrack,'utf8'));const document=resolve(dirname(paths.ztrack),cfg.document);file(document);file(`${document}.archive.md`);}catch{/* other backing */}
|
|
36
51
|
}
|
|
@@ -79,11 +94,15 @@ export async function* followBoardPublications({root,after,signal}) {
|
|
|
79
94
|
for(const row of rows){positions[slug].sequence=row.sequence;yield {type:'event',id:row.id,kind:row.kind,cursor:cursorOf(home,positions),data:{board:slug,card:JSON.parse(row.snapshot)}};}
|
|
80
95
|
}
|
|
81
96
|
}
|
|
82
|
-
}finally{board.close();}
|
|
97
|
+
}finally{board.close();const paths=backingPaths(root,slug);for(const path of [paths.sqlite,`${paths.sqlite}-wal`])settled.set(path,statOf(path));}
|
|
83
98
|
}
|
|
84
99
|
if(initial){initial=false;yield {type:'snapshot_end',cursor:cursorOf(home,positions)};dirty=true;}
|
|
85
100
|
else if(checkpointPending){checkpointPending=false;yield {type:'checkpoint',cursor:cursorOf(home,positions)};}
|
|
86
|
-
|
|
101
|
+
// A write that lands between a board's close and its stamp has its event taken for that pass's echo: a pass
|
|
102
|
+
// every RESETTLE_MS anyway bounds how long that write can wait.
|
|
103
|
+
let timer=null;
|
|
104
|
+
if(!dirty&&!signal?.aborted)await new Promise(done=>{wake=done;timer=setTimeout(()=>{dirty=true;done();},RESETTLE_MS);timer.unref?.();if(dirty||signal?.aborted)done();});
|
|
105
|
+
clearTimeout(timer);
|
|
87
106
|
wake=null;
|
|
88
107
|
}
|
|
89
108
|
}finally{signal?.removeEventListener('abort',aborted);for(const {handle} of watchers.values())handle.close();}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-orchestrator",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.13",
|
|
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": {
|