Skip to content

Commit 8aca0dd

Browse files
author
moc
committed
fix: bind record identity into digests; restore default list shape
- event digests now cover events.runId (sha256(prev:event:runId:seq:body)) and verification re-derives the key from the CURRENT row's identity columns, never trusting the ledger's stored key — migrating events.runId or repair_cache.runId/id with bodies and heads intact now fails verification (maintainer's reproduced ownership-mutation case, regression-tested both surfaces incl. restore-to-valid) - workflow_status list form returns the original bare array by default; the extended {runs, integrityHeads, integrity} object is opt-in via verifyIntegrity:true; workspace-router consumer assertion restored to the upstream default shape - verification covers the anchored prefix and fails closed on any unanchored row (unchained>0 => verified:false), documented in README and the tool description alongside the trust-boundary statement
1 parent 6df2db2 commit 8aca0dd

6 files changed

Lines changed: 76 additions & 31 deletions

File tree

plugins/hetaoBackend/mcode-dynamic-workflows/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ The repaired script runs from its beginning; checkpoints are recomputed and unre
5454
- Real agents run through the user's MCode CLI with its configured provider, tools and smart permissions. Project materials and prompts may be sent to that provider; agents may access other destinations and modify files as the task permits. These destinations depend on the user's configuration and task. Credentials remain managed by the CLI; the plugin does not ask for or store credentials, but prompts/outputs/logs can contain sensitive information supplied by users or tools.
5555
- QuickJS isolates the orchestration script from direct Node/file/network access. **The spawned MCode agents are not an OS sandbox** and do not inherit the full parent conversation. Review prompts, budgets, side effects and permissions before execution or retries.
5656
- A lifetime SQLite lock enforces one state owner even if a discovery lockfile is lost. The run list prioritizes active runs and recovery attention within its 100-entry window; crash recovery inspects every unfinished run.
57+
- Tamper evidence: the append-only `events` and `repair_cache` surfaces each carry a SHA-256 hash chain whose per-row links are written in the same transaction as the insert. Digests bind each row's identity (`runId` plus `seq`/`id`) as well as its body, so moving a row to another run is detected like any body edit. `workflow_status` with `verifyIntegrity: true` recomputes both chains and returns heads, per-face verdicts and the first divergence. Verification covers the anchored prefix; any unanchored row fails closed (`unchained > 0``verified: false`). The default run list stays a plain JSON array; the object form with `integrityHeads` is only returned for `verifyIntegrity: true`.
5758
- The project service and approved workflows survive a chat disconnect. No OS autostart is installed; machine shutdown interrupts execution. After abnormal termination, verify old agents have stopped before recovery.
5859

5960
## Source, build and tests

plugins/hetaoBackend/mcode-dynamic-workflows/checks/integrity.check.mjs

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ async function finish(engine,id){for(let i=0;i<300;i++){if(!engine.active.has(id
1515
async function start(engine,script,input={}){const r=await engine.start({requestId:randomUUID(),name:'Integrity',executor:'demo',script,input});await engine.approve(r.id,{revision:1});return finish(engine,r.id);}
1616
const GENESIS='0'.repeat(64);
1717
// Independent recomputation of the contracted digest formulas over raw stored rows.
18-
function recomputeEvents(store){let prev=GENESIS;for(const r of store.db.prepare('SELECT seq,body FROM events ORDER BY seq').all())prev=createHash('sha256').update(`${prev}:event:${r.seq}:${r.body}`).digest('hex');return prev;}
18+
// r3 contract: digests bind row identity (events runId+seq, repair runId/id) as well as body.
19+
function recomputeEvents(store){let prev=GENESIS;for(const r of store.db.prepare('SELECT seq,runId,body FROM events ORDER BY seq').all())prev=createHash('sha256').update(`${prev}:event:${r.runId}:${r.seq}:${r.body}`).digest('hex');return prev;}
1920
function recomputeRepair(store){let prev=GENESIS;for(const r of store.db.prepare('SELECT rowid,runId,id,body FROM repair_cache ORDER BY rowid').all())prev=createHash('sha256').update(`${prev}:repair:${r.runId}/${r.id}:${r.body}`).digest('hex');return prev;}
2021
const prefix=`const a=await ctx.agent({id:'a',prompt:'a'});const b=await ctx.agent({id:'b',prompt:'b',dependsOn:['a']});`;
2122
const broken=prefix+`throw Error('bad synthesis');`;
@@ -53,15 +54,44 @@ test('single-byte repair_cache tamper is detected at its row key and restoring t
5354
}finally{await f.cleanup();}
5455
});
5556

56-
test('deleting the smaller of two event rows reports the first divergence at key 1',async()=>{
57+
test('re-attributing events.runId and repair_cache runId/id without touching bodies, chains or heads is caught on both faces; restoring attribution heals',async()=>{
58+
const f=await fixture(async s=>({output:s.id}));try{
59+
const runA=randomUUID(),runB=randomUUID();
60+
f.store.event(runA,'run.created',{name:'n'});
61+
f.store.saveRepairCandidate(runA,candidate('a'));
62+
assert.equal(f.store.verifyIntegrity().events.verified,true);
63+
assert.equal(f.store.verifyIntegrity().repair.verified,true);
64+
// Maintainer reproduction: only the identity columns move; body, chain rows and heads stay.
65+
f.store.db.prepare('UPDATE events SET runId=? WHERE seq=?').run(runB,1);
66+
f.store.db.prepare('UPDATE repair_cache SET runId=?,id=? WHERE runId=? AND id=?').run(runB,'b',runA,'a');
67+
const v=f.store.verifyIntegrity();
68+
assert.equal(v.events.verified,false);
69+
assert.equal(v.events.firstDivergence.key,`${runA}:1`);
70+
assert.match(v.events.firstDivergence.expectedHead,/^[0-9a-f]{64}$/);
71+
assert.match(v.events.firstDivergence.actualHead,/^[0-9a-f]{64}$/);
72+
assert.notEqual(v.events.firstDivergence.expectedHead,v.events.firstDivergence.actualHead);
73+
assert.equal(v.repair.verified,false);
74+
assert.equal(v.repair.firstDivergence.key,`${runA}/a`);
75+
assert.match(v.repair.firstDivergence.expectedHead,/^[0-9a-f]{64}$/);
76+
assert.match(v.repair.firstDivergence.actualHead,/^[0-9a-f]{64}$/);
77+
assert.notEqual(v.repair.firstDivergence.expectedHead,v.repair.firstDivergence.actualHead);
78+
f.store.db.prepare('UPDATE events SET runId=? WHERE seq=?').run(runA,1);
79+
f.store.db.prepare('UPDATE repair_cache SET runId=?,id=? WHERE runId=? AND id=?').run(runA,'a',runB,'b');
80+
const healed=f.store.verifyIntegrity();
81+
assert.equal(healed.events.verified,true);assert.equal(healed.events.firstDivergence,null);
82+
assert.equal(healed.repair.verified,true);assert.equal(healed.repair.firstDivergence,null);
83+
}finally{await f.cleanup();}
84+
});
85+
86+
test('deleting the smaller of two event rows reports the first divergence at its identity key',async()=>{
5787
const f=await fixture(async s=>({output:s.id}));try{
5888
const runId=randomUUID();
5989
f.store.event(runId,'run.created',{name:'n'});
6090
f.store.event(runId,'run.started');
6191
f.store.db.prepare('DELETE FROM events WHERE seq=?').run(1);
6292
const v=f.store.verifyIntegrity();
6393
assert.equal(v.events.verified,false);
64-
assert.equal(v.events.firstDivergence.key,'1');
94+
assert.equal(v.events.firstDivergence.key,`${runId}:1`);
6595
}finally{await f.cleanup();}
6696
});
6797

@@ -77,13 +107,13 @@ test('a forged integrity_events head fails verification while integrityHeads lig
77107
}finally{await f.cleanup();}
78108
});
79109

80-
test('a raw-inserted repair row beyond upto counts as unchained and never fails verification',async()=>{
110+
test('a raw-inserted repair row beyond upto counts as unchained and fails closed without a chain divergence',async()=>{
81111
const f=await fixture(async s=>({output:s.id}));try{
82112
const runId=randomUUID();
83113
f.store.saveRepairCandidate(runId,candidate('a'));
84114
rawRepair(f.store,runId,'ghost','{"id":"ghost"}');
85115
const v=f.store.verifyIntegrity();
86-
assert.equal(v.repair.verified,true);
116+
assert.equal(v.repair.verified,false);
87117
assert.equal(v.repair.checked,1);assert.equal(v.repair.unchained,1);assert.equal(v.repair.firstDivergence,null);
88118
}finally{await f.cleanup();}
89119
});
@@ -102,23 +132,27 @@ test('the first anchoring write implicitly commits pre-existing rows into the re
102132
}finally{await f.cleanup();}
103133
});
104134

105-
test('workflow_status list form carries integrityHeads plus optional integrity; schema declares verifyIntegrity; single-run form unchanged',async()=>{
135+
test('workflow_status list form stays a plain array by default; verifyIntegrity:true opts into the object form with heads and verdicts',async()=>{
106136
const f=await fixture(async s=>({output:s.id}));try{
107137
const runId=randomUUID();
108138
f.store.event(runId,'run.created',{name:'seed'});
109139
f.store.saveRepairCandidate(runId,candidate('a','seed'));
110140
const handler=createToolHandler(f.engine,()=>'http://127.0.0.1:1/');
111141
const list=await handler('workflow_status',{});
112-
assert.ok(list.integrityHeads&&list.integrityHeads.events&&list.integrityHeads.repair);
113-
assert.deepEqual(list.integrityHeads,f.store.integrityHeads());
142+
assert.ok(Array.isArray(list));
143+
assert.equal(list.integrityHeads,undefined);
114144
assert.equal(list.integrity,undefined);
115145
const audited=await handler('workflow_status',{verifyIntegrity:true});
116-
assert.ok(audited.integrity);
146+
assert.ok(audited&&Array.isArray(audited.runs));
147+
assert.ok(audited.integrityHeads&&audited.integrityHeads.events&&audited.integrityHeads.repair);
148+
assert.deepEqual(audited.integrityHeads,f.store.integrityHeads());
117149
assert.equal(typeof audited.integrity.events.verified,'boolean');
118150
assert.equal(typeof audited.integrity.repair.verified,'boolean');
119151
assert.deepEqual(audited.integrity,f.store.verifyIntegrity());
120152
const source=await start(f.engine,broken);
121153
assert.equal(source.status,'failed');
154+
const plain=await handler('workflow_status',{});
155+
assert.ok(Array.isArray(plain));assert.deepEqual(plain.map(r=>r.id),[source.id]);
122156
const single=await handler('workflow_status',{runId:source.id});
123157
assert.equal(single.id,source.id);assert.equal(single.integrityHeads,undefined);assert.equal(single.integrity,undefined);
124158
const def=TOOLS.find(t=>t.name==='workflow_status');

plugins/hetaoBackend/mcode-dynamic-workflows/checks/workspace-router.check.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ test('packaged MCP launched in plugin root routes concurrent projects and execut
5252
assert.deepEqual(results.steps.find(s=>s.id==='cwd').output,{cwd:projects[i],arg:projects[i]});
5353
}
5454
assert.equal((await a.callTool({name:'workflow_resume',arguments:{workspace:projects[1],runId:drafts[0].id}})).isError,true);
55-
assert.deepEqual((await value(a,'workflow_status',{workspace:projects[1]})).runs.map(r=>r.id),[drafts[1].id]);
55+
assert.deepEqual((await value(a,'workflow_status',{workspace:projects[1]})).map(r=>r.id),[drafts[1].id]);
5656
await a.close();await b.close();
5757
for(let i=0;i<2;i++){
5858
const path=projectDataDir(dataRoot,projects[i]);await exec(process.execPath,[binary,'--stop-service','--workspace',projects[i],'--data-dir',path]);

plugins/hetaoBackend/mcode-dynamic-workflows/dist/main.mjs

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7840,7 +7840,7 @@ var Store = class {
78407840
const event = { ...data2, type, time: Date.now() };
78417841
return this.transaction(() => {
78427842
const seq = Number(this.db.prepare("INSERT INTO events(runId,body) VALUES(?,?)").run(runId, JSON.stringify(event)).lastInsertRowid);
7843-
this.chainAdvance("event", "events", "SELECT seq AS pos,body FROM events WHERE seq>? AND seq<=? ORDER BY seq", seq, (r) => String(r.pos));
7843+
this.chainAdvance("event", "events", "SELECT seq AS pos,runId,body FROM events WHERE seq>? AND seq<=? ORDER BY seq", seq, (r) => `${r.runId}:${r.pos}`);
78447844
return { seq, ...event };
78457845
});
78467846
}
@@ -7865,25 +7865,27 @@ var Store = class {
78657865
}
78667866
verifyIntegrity() {
78677867
const genesis = "0".repeat(64);
7868-
const face = (kind, surface, table, posCol) => {
7868+
const face = (kind, surface, table, posCol, rowSql, keyOf) => {
78697869
const skey = `integrity_${surface}`;
78707870
const rec = this.setting(skey);
78717871
const total = Number(this.db.prepare(`SELECT COUNT(*) AS n FROM ${table}`).get().n);
78727872
if (!rec) return { head: null, upto: 0, verified: null, checked: 0, unchained: total, firstDivergence: null };
78737873
const rows = this.db.prepare("SELECT pos,key,hash FROM integrity_rows WHERE surface=? ORDER BY pos").all(surface);
7874+
const unchained = Number(this.db.prepare(`SELECT COUNT(*) AS n FROM ${table} WHERE ${posCol}>?`).get(rec.upto).n);
78747875
let prev = genesis, firstDivergence = null;
78757876
for (const r of rows) {
7876-
const row = this.db.prepare(`SELECT body FROM ${table} WHERE ${posCol}=?`).get(r.pos);
7877-
const actual = row ? this.rowHash(prev, kind, r.key, row.body) : null;
7878-
if (!firstDivergence && (!row || actual !== r.hash)) firstDivergence = { key: r.key, expectedHead: r.hash, actualHead: actual };
7877+
const row = this.db.prepare(rowSql).get(r.pos);
7878+
const key = row ? keyOf(row, r.pos) : null;
7879+
const actual = row ? this.rowHash(prev, kind, key, row.body) : null;
7880+
if (!firstDivergence && (!row || key !== r.key || actual !== r.hash)) firstDivergence = { key: r.key, expectedHead: r.hash, actualHead: actual };
78797881
prev = r.hash;
78807882
}
7881-
const verified = !firstDivergence && prev === rec.head;
7882-
return { head: rec.head, upto: rec.upto, verified, checked: rows.length, unchained: Number(this.db.prepare(`SELECT COUNT(*) AS n FROM ${table} WHERE ${posCol}>?`).get(rec.upto).n), firstDivergence };
7883+
const verified = !firstDivergence && prev === rec.head && unchained === 0;
7884+
return { head: rec.head, upto: rec.upto, verified, checked: rows.length, unchained, firstDivergence };
78837885
};
78847886
return {
7885-
events: face("event", "events", "events", "seq"),
7886-
repair: face("repair", "repair", "repair_cache", "rowid")
7887+
events: face("event", "events", "events", "seq", "SELECT runId,body FROM events WHERE seq=?", (row, pos) => `${row.runId}:${pos}`),
7888+
repair: face("repair", "repair", "repair_cache", "rowid", "SELECT runId,id,body FROM repair_cache WHERE rowid=?", (row) => `${row.runId}/${row.id}`)
78877889
};
78887890
}
78897891
releaseLock() {
@@ -26483,7 +26485,7 @@ var TOOLS = [
2648326485
{ name: "workflow_start", description: "\u521B\u5EFA\u5F85\u5BA1\u6838\u5DE5\u4F5C\u6D41\u548C\u7ED3\u6784\u62D3\u6251\uFF0C\u4E0D\u6267\u884C Agent\u3002\u5FC5\u987B\u63D0\u4F9B\u9762\u677F\u8BA9\u7528\u6237\u5BA1\u9605\u3001\u4FEE\u6539\u5E76\u70B9\u51FB\u5F00\u59CB\u6267\u884C\u3002mcode \u6A21\u5F0F\u4F1A\u542F\u52A8\u771F\u5B9E MCode\uFF0C\u4F1A\u4F7F\u7528\u5DF2\u767B\u5F55\u8EAB\u4EFD\u4E0E smart \u6743\u9650\uFF0C\u4E0D\u63D0\u4F9B\u53EA\u8BFB OS \u6C99\u7BB1\u3002demo \u6A21\u5F0F\u4E0D\u8C03\u7528\u6A21\u578B\u3002\u663E\u5F0F requestId \u5E42\u7B49\u3002", inputSchema: obj({ requestId: string3, name: string3, script: string3, input: { type: "object" }, metadata: METADATA_SCHEMA, executor: { enum: ["mcode", "demo"] }, concurrency: { type: "integer", minimum: 1, maximum: 16 }, maxCalls: { type: "integer", minimum: 1, maximum: 100 }, ...LIMIT_SCHEMAS }, ["requestId", "name", "script", "executor"]) },
2648426486
{ name: "workflow_update", description: "\u4FEE\u6539\u5F85\u5BA1\u6838\u5DE5\u4F5C\u6D41\u7684\u811A\u672C\u3001\u8F93\u5165\u6216\u9884\u7B97\u5E76\u91CD\u5EFA\u62D3\u6251\uFF0C\u4FDD\u5B58\u540E\u4ECD\u5F85\u5BA1\u6838\uFF1Brevision \u5FC5\u987B\u5339\u914D\u5F53\u524D\u7248\u672C\u3002\u4E0D\u53EF\u4FEE\u6539\u5DF2\u5F00\u59CB\u7684\u8FD0\u884C\u3002", inputSchema: obj({ ...id, revision: { type: "integer", minimum: 1 }, reason: { type: "string", maxLength: 2e3 }, reuseStepIds: { type: "array", items: string3, maxItems: 100, uniqueItems: true }, name: string3, script: string3, input: { type: "object" }, metadata: METADATA_SCHEMA, executor: { enum: ["mcode", "demo"] }, concurrency: { type: "integer", minimum: 1, maximum: 16 }, maxCalls: { type: "integer", minimum: 1, maximum: 100 }, ...LIMIT_SCHEMAS }, ["runId", "revision"]) },
2648526487
{ name: "workflow_repair", description: "\u57FA\u4E8E\u505C\u6B62\u540E\u7684\u8FD0\u884C\u521B\u5EFA\u4FEE\u590D\u8349\u7A3F\uFF0C\u4FDD\u7559\u6E90\u8FD0\u884C\uFF1B\u63D0\u4F9B\u5B8C\u6574\u4FEE\u590D\u811A\u672C\u3001\u5931\u8D25\u539F\u56E0\u4E0E sourceUpdatedAt\u3002\u663E\u5F0F reuseStepIds \u4EC5\u9009\u62E9\u786E\u8BA4\u4ECD\u9002\u7528\u7684\u6210\u529F\u8282\u70B9\uFF0C\u9ED8\u8BA4\u4E0D\u590D\u7528\u3002\u8FD0\u884C\u65F6\u91CD\u65B0\u6821\u9A8C\u8F93\u5165\u3001\u6587\u4EF6\u3001\u53C2\u6570\u4E0E\u4F9D\u8D56\uFF1B\u53D8\u66F4\u6216\u91CD\u8DD1\u7684\u4E0A\u6E38\u4F7F\u4E0B\u6E38\u5931\u6548\u3002\u5FC5\u987B\u6253\u5F00\u9762\u677F\u4EA4\u7528\u6237\u5BA1\u6838\u540E\u5F00\u59CB\uFF0C\u4E0D\u80FD\u81EA\u52A8\u6267\u884C\u3002", inputSchema: obj({ ...id, requestId: string3, sourceUpdatedAt: { type: "integer" }, script: string3, reason: { type: "string", maxLength: 2e3 }, reuseStepIds: { type: "array", items: string3, maxItems: 100, uniqueItems: true }, input: { type: "object" }, ...LIMIT_SCHEMAS, maxCalls: { type: "integer", minimum: 1, maximum: 100 } }, ["runId", "requestId", "sourceUpdatedAt", "script", "reason"]) },
26486-
{ name: "workflow_status", description: "\u8BFB\u53D6\u8FD0\u884C\u72B6\u6001\u3001\u9636\u6BB5\u548C\u8282\u70B9\uFF1B\u8F93\u51FA\u4E0D\u542B\u5B8C\u6574 prompt/result\u3002\u65E0 runId \u65F6\u5217\u51FA\u6700\u8FD1\u8FD0\u884C\u3002\u65E0 runId \u8FD4\u56DE {runs,integrityHeads} \u5BF9\u8C61\u5F62\u3002", inputSchema: obj({ ...id, verifyIntegrity: { type: "boolean", description: "\u5168\u91CF\u91CD\u7B97\u5B8C\u6574\u6027\u94FE\u5E76\u9644 integrity \u5B57\u6BB5" } }) },
26488+
{ name: "workflow_status", description: "\u8BFB\u53D6\u8FD0\u884C\u72B6\u6001\u3001\u9636\u6BB5\u548C\u8282\u70B9\uFF1B\u8F93\u51FA\u4E0D\u542B\u5B8C\u6574 prompt/result\u3002\u65E0 runId \u65F6\u5217\u51FA\u6700\u8FD1\u8FD0\u884C\uFF0C\u9ED8\u8BA4\u8FD4\u56DE\u6570\u7EC4\uFF08\u65E2\u6709\u5F62\u72B6\u4E0D\u53D8\uFF09\u3002verifyIntegrity:true \u65F6\u6539\u8FD4 {runs,integrityHeads,integrity} \u5BF9\u8C61\u5F62\u5E76\u5168\u91CF\u91CD\u7B97\u4E24\u6761\u5B8C\u6574\u6027\u94FE\uFF1B\u6821\u9A8C\u8986\u76D6\u5DF2\u951A\u5B9A\u524D\u7F00\uFF0C\u4EFB\u4F55\u672A\u951A\u5B9A\u884C fail-closed\uFF08unchained>0 \u5373 verified:false\uFF09\u3002", inputSchema: obj({ ...id, verifyIntegrity: { type: "boolean", description: "\u5168\u91CF\u91CD\u7B97\u5B8C\u6574\u6027\u94FE\uFF0C\u8FD4\u56DE {runs,integrityHeads,integrity} \u5BF9\u8C61\u5F62\uFF08\u9ED8\u8BA4\u4E3A\u7EAF\u6570\u7EC4\uFF09" } }) },
2648726489
{ name: "workflow_results", description: "\u5206\u9875\u8BFB\u53D6\u8282\u70B9\u7ED3\u679C\uFF1B\u7EC8\u6001\u62A5\u544A\u4E0E\u5931\u8D25\u660E\u786E\u5206\u5F00\u3002", inputSchema: obj({ ...id, includeDefinition: { type: "boolean" }, offset: { type: "integer", minimum: 0 }, limit: { type: "integer", minimum: 1, maximum: 20 } }, ["runId"]) },
2648826490
{ name: "workflow_wait", description: "\u6309\u4E8B\u4EF6\u6E38\u6807\u7B49\u5F85\u53D8\u5316\uFF0C\u6700\u957F25\u79D2\u3002\u9700\u8981\u7EE7\u7EED\u5173\u6CE8\u65F6\u4F7F\u7528\u8FD4\u56DE\u7684nextSequence\u3002", inputSchema: obj({ ...id, afterSequence: { type: "integer", minimum: 0 }, timeoutMs: { type: "integer", minimum: 0, maximum: 25e3 } }, ["runId"]) },
2648926491
{ name: "workflow_cancel", description: "\u53D6\u6D88\u672C\u63D2\u4EF6\u5DE5\u4F5C\u6D41\uFF0C\u7B49\u5F85\u5728\u9014 exec \u9000\u51FA\uFF1B\u4E0D\u53D6\u6D88\u5176\u4ED6 MCode \u4F1A\u8BDD\u3002", inputSchema: obj(id, ["runId"]) },
@@ -26527,7 +26529,8 @@ function createToolHandler(engine, getURL) {
2652726529
return summary(await engine.repair(args.runId, args));
2652826530
case "workflow_status":
2652926531
if (args.runId) return summary(engine.snapshot(args.runId));
26530-
return { runs: engine.store.list().map((r) => summary(r)), integrityHeads: engine.store.integrityHeads(), ...args.verifyIntegrity === true ? { integrity: engine.store.verifyIntegrity() } : {} };
26532+
if (args.verifyIntegrity === true) return { runs: engine.store.list().map((r) => summary(r)), integrityHeads: engine.store.integrityHeads(), integrity: engine.store.verifyIntegrity() };
26533+
return engine.store.list().map((r) => summary(r));
2653126534
case "workflow_results": {
2653226535
const r = engine.snapshot(args.runId);
2653326536
const offset2 = args.offset ?? 0, limit = args.limit ?? 10;

0 commit comments

Comments
 (0)