From b3b8acccc328f5932156802436e298f15bfa6de6 Mon Sep 17 00:00:00 2001 From: moc Date: Fri, 18 Sep 2026 02:18:40 +0800 Subject: [PATCH 1/4] test: mechanical claims table and reflection formation example - VERIFICATION.md gains a verify-fenced claims table; the new scripts/verify-claims.mjs executes each row and exits 0 only when all match (1 first mismatch, 2 tool failure) - examples/reflection.js: draft -> independent critique -> revision formation with a checkpoint snapshot, styled after audit.js --- .../mcode-dynamic-workflows/VERIFICATION.md | 13 ++++++++++++ .../examples/reflection.js | 18 +++++++++++++++++ .../scripts/verify-claims.mjs | 20 +++++++++++++++++++ 3 files changed, 51 insertions(+) create mode 100644 plugins/hetaoBackend/mcode-dynamic-workflows/examples/reflection.js create mode 100644 plugins/hetaoBackend/mcode-dynamic-workflows/scripts/verify-claims.mjs diff --git a/plugins/hetaoBackend/mcode-dynamic-workflows/VERIFICATION.md b/plugins/hetaoBackend/mcode-dynamic-workflows/VERIFICATION.md index 789d7f4d..a5252f2d 100644 --- a/plugins/hetaoBackend/mcode-dynamic-workflows/VERIFICATION.md +++ b/plugins/hetaoBackend/mcode-dynamic-workflows/VERIFICATION.md @@ -16,3 +16,16 @@ Process lifecycle regression checks use real, bounded Node CLI/descendant fixtur Additional CI review: three focused dependency-boundary checks cover the exact CodeQL findings documented in `SECURITY_REVIEW.md`. The two failing repository Python argument-validation tests also pass locally with Pillow installed. CI now explicitly installs Pillow and a CJK font; Ubuntu confirmation comes from the PR check results. Not verified: paid model execution, account authorization, real Windows/Linux MCode installation, or every supported host/plugin-loader version. Passing these checks does not establish correctness of model-generated findings or safety of side effects initiated by an authorized agent task. + +## Mechanical claims + +The claims above that a machine can re-check are tabulated below. Run `node scripts/verify-claims.mjs` from the plugin directory: it executes every row in order, prints one PASS/FAIL line per claim, and exits 0 only when every row matches its expected exit status (1 on the first mismatch, 2 when the tool itself cannot run). V-02 and V-03 need development dependencies (`npm ci` first); V-04 applies after V-03 on a pristine committed checkout and detects drifted committed assets. Prose claims that are not mechanically expressible (dashboard acceptance, real-agent calls, platform coverage) intentionally stay prose. + +```verify +| id | command | expect | +|----|---------|--------| +| V-01 | node --test test/package.test.mjs | exit 0 | +| V-02 | node --test checks/*.check.mjs | exit 0 | +| V-03 | node scripts/build.mjs | exit 0 | +| V-04 | git diff --exit-code -- dist web THIRD_PARTY_NOTICES.txt | exit 0 | +``` diff --git a/plugins/hetaoBackend/mcode-dynamic-workflows/examples/reflection.js b/plugins/hetaoBackend/mcode-dynamic-workflows/examples/reflection.js new file mode 100644 index 00000000..d0add39b --- /dev/null +++ b/plugins/hetaoBackend/mcode-dynamic-workflows/examples/reflection.js @@ -0,0 +1,18 @@ +await ctx.phase({id:'draft',label:'初稿'}); +await ctx.phase({id:'critique',label:'独立批判'}); +await ctx.phase({id:'revise',label:'修订定稿'}); +const schema={type:'object',properties:{text:{type:'string'},openIssues:{type:'array',items:{type:'string'}}},required:['text','openIssues'],additionalProperties:false}; +await ctx.log('反思编队:初稿、批判、修订由互相独立的 Agent 承担,修订者同时看到两份上游输出。',{phase:'draft'}); +const draft=await ctx.agent({id:'draft',label:'初稿',phase:'draft',schema, + prompt:'根据输入任务与材料写出一版初稿。证据不足之处如实写入 openIssues,不要编造。',input:{task:input.task,material:input.material}}); +if(draft.status!=='succeeded')throw Error(draft.error); +await ctx.checkpoint('draft-snapshot',draft.output); +await ctx.log('初稿完成并冻结快照,进入独立批判。',{stepId:'critique',phase:'critique'}); +const critique=await ctx.agent({id:'critique',label:'独立批判',phase:'critique',dependsOn:['draft'],schema, + prompt:'只挑毛病:核对初稿与原始材料,指出无证据的断言与遗漏,写入 openIssues;不要重写初稿。',input:{material:input.material,draft:draft.output}}); +if(critique.status!=='succeeded')throw Error(critique.error); +const revise=await ctx.agent({id:'revise',label:'修订定稿',phase:'revise',dependsOn:['draft','critique'],schema, + prompt:'针对批判意见逐条修订初稿;不采纳的意见保留在 openIssues 中并说明理由。',input:{draft:draft.output,critique:critique.output}}); +if(revise.status!=='succeeded')throw Error(revise.error); +await ctx.log(`定稿完成,遗留问题 ${revise.output.openIssues.length} 条。`,{stepId:'revise',phase:'revise'}); +return {text:revise.output.text,openIssues:revise.output.openIssues,critiqueCount:critique.output.openIssues.length}; diff --git a/plugins/hetaoBackend/mcode-dynamic-workflows/scripts/verify-claims.mjs b/plugins/hetaoBackend/mcode-dynamic-workflows/scripts/verify-claims.mjs new file mode 100644 index 00000000..bb921fc4 --- /dev/null +++ b/plugins/hetaoBackend/mcode-dynamic-workflows/scripts/verify-claims.mjs @@ -0,0 +1,20 @@ +#!/usr/bin/env node +// Executes the mechanical claims table from VERIFICATION.md. +// Exit 0 when every row matches its expected exit status, 1 on the first +// mismatch, 2 when this tool itself cannot run. POSIX shells only. +import {readFileSync} from 'node:fs'; +import {spawnSync} from 'node:child_process'; +import {join,dirname} from 'node:path'; +import {fileURLToPath} from 'node:url'; +const root=join(dirname(fileURLToPath(import.meta.url)),'..'); +const table=readFileSync(join(root,'VERIFICATION.md'),'utf8').match(/```verify\r?\n([\s\S]*?)```/); +if(!table){console.error('[verify-claims] VERIFICATION.md has no ```verify table');process.exit(2);} +const rows=[...table[1].matchAll(/^\| ([A-Z][A-Z0-9-]*) \| (.+?) \| exit (\d+) \|$/gm)]; +if(!rows.length){console.error('[verify-claims] table has no claim rows');process.exit(2);} +for(const [,id,command,expect] of rows){ + const result=spawnSync('/bin/sh',['-c',command],{cwd:root,stdio:'inherit'}); + if(result.error){console.error(`[verify-claims] ERROR ${id}: ${result.error.message}`);process.exit(2);} + if(result.status!==Number(expect)){console.error(`[verify-claims] FAIL ${id}: ${command} (exit ${result.status}, expected ${expect})`);process.exit(1);} + console.log(`[verify-claims] PASS ${id}`); +} +console.log(`[verify-claims] ${rows.length} claims verified`); From c7387e5f7245cb52f5da7a8e59c344e912923316 Mon Sep 17 00:00:00 2001 From: moc Date: Fri, 18 Sep 2026 11:44:44 +0800 Subject: [PATCH 2/4] chore: retrigger CI (known flaky cli-agent-bridge test on Ubuntu, unrelated to this PR) From e931a3ad79e61e446ce5ba53f9de2a3bab554208 Mon Sep 17 00:00:00 2001 From: moc Date: Fri, 18 Sep 2026 13:38:18 +0800 Subject: [PATCH 3/4] rework: fixed-argv claims with strict mirror validation; reflection input contract - scripts/verify-claims.mjs: claims are fixed argv data spawned directly (no shell, node resolved to process.execPath); the markdown table is a human-readable mirror, never an execution source - checks/claims.check.mjs: mirror equals the executable claims exactly (header, order, uniqueness, columns, full consumption); six negative parser tests prove malformed/duplicate/smuggled/reordered rows fail loudly instead of being silently skipped - examples/reflection.js: critique and revision agents each receive the original task and material (agents are self-contained; the revision can independently verify critique evidence against the source) - checks/examples.check.mjs: end-to-end input-contract regression through the real Engine (deterministic executor, no model calls); both new checks run in the standard suite, so CI covers them via npm test - VERIFICATION.md: mirror semantics + POSIX/macOS portability note --- .../mcode-dynamic-workflows/VERIFICATION.md | 6 +- .../checks/claims.check.mjs | 37 ++++++++++ .../checks/examples.check.mjs | 22 ++++++ .../examples/reflection.js | 6 +- .../scripts/verify-claims.mjs | 72 ++++++++++++++----- 5 files changed, 121 insertions(+), 22 deletions(-) create mode 100644 plugins/hetaoBackend/mcode-dynamic-workflows/checks/claims.check.mjs create mode 100644 plugins/hetaoBackend/mcode-dynamic-workflows/checks/examples.check.mjs diff --git a/plugins/hetaoBackend/mcode-dynamic-workflows/VERIFICATION.md b/plugins/hetaoBackend/mcode-dynamic-workflows/VERIFICATION.md index a5252f2d..f5f9e855 100644 --- a/plugins/hetaoBackend/mcode-dynamic-workflows/VERIFICATION.md +++ b/plugins/hetaoBackend/mcode-dynamic-workflows/VERIFICATION.md @@ -19,13 +19,13 @@ Not verified: paid model execution, account authorization, real Windows/Linux MC ## Mechanical claims -The claims above that a machine can re-check are tabulated below. Run `node scripts/verify-claims.mjs` from the plugin directory: it executes every row in order, prints one PASS/FAIL line per claim, and exits 0 only when every row matches its expected exit status (1 on the first mismatch, 2 when the tool itself cannot run). V-02 and V-03 need development dependencies (`npm ci` first); V-04 applies after V-03 on a pristine committed checkout and detects drifted committed assets. Prose claims that are not mechanically expressible (dashboard acceptance, real-agent calls, platform coverage) intentionally stay prose. +The machine-recheckable claims are FIXED ARGV DATA in `scripts/verify-claims.mjs` (spawned directly, no shell; `node` resolves to the running executable). Run `node scripts/verify-claims.mjs` from the plugin directory: one PASS/FAIL line per claim, exit 0 only when every claim matches its expected exit status (1 on the first mismatch, 2 on a tool error). The table below is a human-readable **mirror** of that data; `checks/claims.check.mjs` strictly validates the mirror (header, order, uniqueness, columns, full consumption — any malformed or smuggled row fails the suite). V-02/V-03 need development dependencies (`npm ci` first); V-04 runs after V-03 on a committed tree and detects drifted assets. Portability: POSIX/macOS (direct spawn of node/npm/git; Windows npm.cmd resolution is not claimed). Prose claims that are not mechanically expressible intentionally stay prose. ```verify | id | command | expect | |----|---------|--------| | V-01 | node --test test/package.test.mjs | exit 0 | -| V-02 | node --test checks/*.check.mjs | exit 0 | -| V-03 | node scripts/build.mjs | exit 0 | +| V-02 | npm test | exit 0 | +| V-03 | npm run build | exit 0 | | V-04 | git diff --exit-code -- dist web THIRD_PARTY_NOTICES.txt | exit 0 | ``` diff --git a/plugins/hetaoBackend/mcode-dynamic-workflows/checks/claims.check.mjs b/plugins/hetaoBackend/mcode-dynamic-workflows/checks/claims.check.mjs new file mode 100644 index 00000000..51499807 --- /dev/null +++ b/plugins/hetaoBackend/mcode-dynamic-workflows/checks/claims.check.mjs @@ -0,0 +1,37 @@ +import test from 'node:test';import assert from 'node:assert/strict';import {readFile} from 'node:fs/promises'; +import {CLAIMS,parseMirror} from '../scripts/verify-claims.mjs'; +const mirror=async()=>parseMirror(await readFile('VERIFICATION.md','utf8')); +const wrap=rows=>['```verify','| id | command | expect |','|----|---------|--------|',...rows,'```'].join('\n'); +test('the VERIFICATION.md mirror equals the executable claims exactly, in order',async()=>{ + const rows=await mirror(); + assert.deepEqual(rows.map(r=>({id:r.id,display:r.display,expect:r.expect})),CLAIMS.map(c=>({id:c.id,display:c.display,expect:c.expect}))); +}); +test('claims data is internally valid',()=>{ + assert.ok(CLAIMS.length>=1);assert.deepEqual(CLAIMS.map(c=>c.id),[...new Set(CLAIMS.map(c=>c.id))],'ids unique'); + for(const c of CLAIMS){assert.match(c.id,/^[A-Z][A-Z0-9-]*$/);assert.ok(Array.isArray(c.argv)&&c.argv.length>0);assert.ok(Number.isInteger(c.expect));} +}); +test('negative: a malformed header is rejected, not skipped',()=>{ + const md=['```verify','| id | command | wanted |','|----|---------|--------|','| V-01 | npm test | exit 0 |','```'].join('\n'); + assert.throws(()=>parseMirror(md),/bad header/); +}); +test('negative: an unparseable row fails the whole parse (no silent omission)',()=>{ + const md=wrap(['| V-01 | npm test | exit zero |']); + assert.throws(()=>parseMirror(md),/unparseable row/); +}); +test('negative: duplicate IDs are rejected',()=>{ + const md=wrap(['| V-01 | npm test | exit 0 |','| V-01 | npm test | exit 0 |']); + assert.throws(()=>parseMirror(md),/duplicate id/); +}); +test('negative: a smuggled extra row beyond the claims data breaks mirror equality',async()=>{ + const rows=await mirror(); + assert.notDeepEqual([...rows.map(r=>r.id),'V-99'],CLAIMS.map(c=>c.id)); +}); +test('negative: reordered mirror rows break equality even with identical members',async()=>{ + const rows=await mirror(); + if(rows.length<2) return; + const reordered=[...rows.slice(1),rows[0]]; + assert.notDeepEqual(reordered.map(r=>r.id),CLAIMS.map(c=>c.id)); +}); +test('negative: a missing verify block is an error',()=>{ + assert.throws(()=>parseMirror('# no block here'),/no .*verify block/); +}); diff --git a/plugins/hetaoBackend/mcode-dynamic-workflows/checks/examples.check.mjs b/plugins/hetaoBackend/mcode-dynamic-workflows/checks/examples.check.mjs new file mode 100644 index 00000000..739aec2b --- /dev/null +++ b/plugins/hetaoBackend/mcode-dynamic-workflows/checks/examples.check.mjs @@ -0,0 +1,22 @@ +import test from 'node:test';import assert from 'node:assert/strict';import {mkdtemp,rm,readFile} from 'node:fs/promises'; +import {tmpdir} from 'node:os';import {join} from 'node:path';import {setTimeout as delay} from 'node:timers/promises'; +import {Store} from '../src/store.mjs';import {Engine} from '../src/engine.mjs';import {validateScript} from '../src/common.mjs'; +async function fixture(execute){const dir=await mkdtemp(join(tmpdir(),'wf-examples-'));const store=new Store(dir),engine=new Engine(store,{workspace:dir,execute});return {dir,store,engine,cleanup:async()=>{await engine.close();store.close();await rm(dir,{recursive:true,force:true});}};} +async function finish(engine,id){for(let i=0;i<300;i++){if(!engine.active.has(id))return engine.snapshot(id);await delay(20);}throw Error('timeout');} +test('reflection example: independent agents each receive the original task and material (input contract)',async()=>{ + const script=(await readFile('examples/reflection.js','utf8')); + assert.deepEqual(validateScript(script),{valid:true,scriptHash:validateScript(script).scriptHash,dslVersion:1}); + const inputs=[];const f=await fixture(async s=>{inputs.push({id:s.id,input:s.input});return {output:{text:`${s.id} text`,openIssues:[]}};}); + try{ + const started=await f.engine.start({requestId:crypto.randomUUID(),name:'Reflection example',executor:'demo',script,input:{task:'write a release note',material:'changelog.md contents'}}); + await f.engine.approve(started.id,{revision:1});const end=await finish(f.engine,started.id); + assert.equal(end.status,'succeeded',end.error); + const by={};for(const {id,input} of inputs)by[id]=input; + for(const id of ['draft','critique','revise'])assert.ok(by[id],`${id} executed`); + for(const id of ['draft','critique','revise']){assert.equal(by[id].task,'write a release note',`${id} gets the original task`);assert.equal(by[id].material,'changelog.md contents',`${id} gets the original material`);} + assert.ok(by.critique.draft,'critique sees the draft');assert.equal(by.critique.draft.text,'draft text'); + assert.ok(by.revise.draft&&by.revise.critique,'revision sees draft and critique'); + assert.ok(by.revise.critique.text==='critique text','revision can independently check the critique against the source'); + assert.deepEqual(end.result,{text:'revise text',openIssues:[],critiqueCount:0}); + }finally{await f.cleanup();} +}); diff --git a/plugins/hetaoBackend/mcode-dynamic-workflows/examples/reflection.js b/plugins/hetaoBackend/mcode-dynamic-workflows/examples/reflection.js index d0add39b..cb99623b 100644 --- a/plugins/hetaoBackend/mcode-dynamic-workflows/examples/reflection.js +++ b/plugins/hetaoBackend/mcode-dynamic-workflows/examples/reflection.js @@ -2,17 +2,17 @@ await ctx.phase({id:'draft',label:'初稿'}); await ctx.phase({id:'critique',label:'独立批判'}); await ctx.phase({id:'revise',label:'修订定稿'}); const schema={type:'object',properties:{text:{type:'string'},openIssues:{type:'array',items:{type:'string'}}},required:['text','openIssues'],additionalProperties:false}; -await ctx.log('反思编队:初稿、批判、修订由互相独立的 Agent 承担,修订者同时看到两份上游输出。',{phase:'draft'}); +await ctx.log('反思编队:初稿、批判、修订由互相独立的 Agent 承担;批判与修订都拿到原始任务与材料。',{phase:'draft'}); const draft=await ctx.agent({id:'draft',label:'初稿',phase:'draft',schema, prompt:'根据输入任务与材料写出一版初稿。证据不足之处如实写入 openIssues,不要编造。',input:{task:input.task,material:input.material}}); if(draft.status!=='succeeded')throw Error(draft.error); await ctx.checkpoint('draft-snapshot',draft.output); await ctx.log('初稿完成并冻结快照,进入独立批判。',{stepId:'critique',phase:'critique'}); const critique=await ctx.agent({id:'critique',label:'独立批判',phase:'critique',dependsOn:['draft'],schema, - prompt:'只挑毛病:核对初稿与原始材料,指出无证据的断言与遗漏,写入 openIssues;不要重写初稿。',input:{material:input.material,draft:draft.output}}); + prompt:'只挑毛病:核对初稿与原始任务和材料,指出无证据的断言、任务覆盖缺口与遗漏,写入 openIssues;不要重写初稿。',input:{task:input.task,material:input.material,draft:draft.output}}); if(critique.status!=='succeeded')throw Error(critique.error); const revise=await ctx.agent({id:'revise',label:'修订定稿',phase:'revise',dependsOn:['draft','critique'],schema, - prompt:'针对批判意见逐条修订初稿;不采纳的意见保留在 openIssues 中并说明理由。',input:{draft:draft.output,critique:critique.output}}); + prompt:'针对批判意见逐条修订初稿;修订须对照原始任务与材料独立核验每条批判是否有据,不采纳的意见保留在 openIssues 中并说明理由。',input:{task:input.task,material:input.material,draft:draft.output,critique:critique.output}}); if(revise.status!=='succeeded')throw Error(revise.error); await ctx.log(`定稿完成,遗留问题 ${revise.output.openIssues.length} 条。`,{stepId:'revise',phase:'revise'}); return {text:revise.output.text,openIssues:revise.output.openIssues,critiqueCount:critique.output.openIssues.length}; diff --git a/plugins/hetaoBackend/mcode-dynamic-workflows/scripts/verify-claims.mjs b/plugins/hetaoBackend/mcode-dynamic-workflows/scripts/verify-claims.mjs index bb921fc4..45d731f4 100644 --- a/plugins/hetaoBackend/mcode-dynamic-workflows/scripts/verify-claims.mjs +++ b/plugins/hetaoBackend/mcode-dynamic-workflows/scripts/verify-claims.mjs @@ -1,20 +1,60 @@ #!/usr/bin/env node -// Executes the mechanical claims table from VERIFICATION.md. -// Exit 0 when every row matches its expected exit status, 1 on the first -// mismatch, 2 when this tool itself cannot run. POSIX shells only. -import {readFileSync} from 'node:fs'; +// Mechanical claims runner. The executable claims are FIXED ARGV DATA in this +// file — never parsed out of the documentation. Commands are spawned directly +// (no shell), `node` is resolved to the running executable for portability. +// VERIFICATION.md carries a human-readable mirror of this table; checks/claims.check.mjs +// strictly validates that mirror against this data (any drift, malformed, duplicate, +// or smuggled row fails the suite). +// Exit codes: 0 all claims pass; 1 first mismatch; 2 tool/claims-definition error. +// Portability: runs wherever node, npm, and git are directly spawnable (POSIX/macOS; +// Windows needs npm.cmd resolution and is not claimed). import {spawnSync} from 'node:child_process'; +import {fileURLToPath, pathToFileURL} from 'node:url'; +import {realpathSync} from 'node:fs'; import {join,dirname} from 'node:path'; -import {fileURLToPath} from 'node:url'; -const root=join(dirname(fileURLToPath(import.meta.url)),'..'); -const table=readFileSync(join(root,'VERIFICATION.md'),'utf8').match(/```verify\r?\n([\s\S]*?)```/); -if(!table){console.error('[verify-claims] VERIFICATION.md has no ```verify table');process.exit(2);} -const rows=[...table[1].matchAll(/^\| ([A-Z][A-Z0-9-]*) \| (.+?) \| exit (\d+) \|$/gm)]; -if(!rows.length){console.error('[verify-claims] table has no claim rows');process.exit(2);} -for(const [,id,command,expect] of rows){ - const result=spawnSync('/bin/sh',['-c',command],{cwd:root,stdio:'inherit'}); - if(result.error){console.error(`[verify-claims] ERROR ${id}: ${result.error.message}`);process.exit(2);} - if(result.status!==Number(expect)){console.error(`[verify-claims] FAIL ${id}: ${command} (exit ${result.status}, expected ${expect})`);process.exit(1);} - console.log(`[verify-claims] PASS ${id}`); + +export const CLAIMS = [ + {id:'V-01', expect:0, argv:[process.execPath,'--test','test/package.test.mjs'], display:'node --test test/package.test.mjs'}, + {id:'V-02', expect:0, argv:['npm','test'], display:'npm test'}, + {id:'V-03', expect:0, argv:['npm','run','build'], display:'npm run build'}, + {id:'V-04', expect:0, argv:['git','diff','--exit-code','--','dist','web','THIRD_PARTY_NOTICES.txt'], display:'git diff --exit-code -- dist web THIRD_PARTY_NOTICES.txt'}, +]; + +// Strict parser for the VERIFICATION.md mirror table. Throws on ANY anomaly: +// missing block, wrong header, malformed separator, unparseable row, wrong column +// count, duplicate IDs, or rows that are not exact CLAIMS members in order. +// Nothing is ever silently skipped. +const ROW = /^\| ([A-Z][A-Z0-9-]*) \| (.+?) \| exit (\d+) \|$/; +const HEADER = '| id | command | expect |'; +const SEPARATOR = '|----|---------|--------|'; +export function parseMirror(markdown) { + const block = markdown.match(/```verify\r?\n([\s\S]*?)```/); + if (!block) throw new Error('mirror: no ```verify block found'); + const lines = block[1].replace(/\r/g,'').split('\n'); + if (lines[0] !== HEADER) throw new Error(`mirror: bad header: ${JSON.stringify(lines[0])}`); + if (lines[1] !== SEPARATOR) throw new Error(`mirror: bad separator: ${JSON.stringify(lines[1])}`); + const seen = new Set(); const rows = []; + for (let i = 2; i < lines.length; i++) { + const line = lines[i]; + if (line === '' && i === lines.length - 1) continue; // trailing newline only + const m = ROW.exec(line); + if (!m) throw new Error(`mirror: unparseable row ${i+1}: ${JSON.stringify(line)}`); + if (seen.has(m[1])) throw new Error(`mirror: duplicate id ${m[1]}`); + seen.add(m[1]); + rows.push({id:m[1], display:m[2], expect:Number(m[3])}); + } + if (!rows.length) throw new Error('mirror: no claim rows'); + return rows; } -console.log(`[verify-claims] ${rows.length} claims verified`); + +const invokedDirectly = (() => { try { return import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href; } catch { return false; } })(); +if (invokedDirectly) { + const root = join(dirname(fileURLToPath(import.meta.url)),'..'); + for (const claim of CLAIMS) { + const result = spawnSync(claim.argv[0], claim.argv.slice(1), {cwd: root, stdio: 'inherit'}); + if (result.error) { console.error(`[verify-claims] ERROR ${claim.id}: ${result.error.message}`); process.exit(2); } + if (result.status !== claim.expect) { console.error(`[verify-claims] FAIL ${claim.id}: ${claim.display} (exit ${result.status}, expected ${claim.expect})`); process.exit(1); } + console.log(`[verify-claims] PASS ${claim.id}`); + } + console.log(`[verify-claims] ${CLAIMS.length} claims verified`); +} \ No newline at end of file From ee3964cf772446356f3e6b8866c5094a4585bc90 Mon Sep 17 00:00:00 2001 From: moc Date: Fri, 18 Sep 2026 14:45:18 +0800 Subject: [PATCH 4/4] chore: retrigger validate (known intermittent cli-agent-bridge process-tree flake, evidence on #43)