Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions plugins/hetaoBackend/mcode-dynamic-workflows/VERIFICATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 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 | 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 |
```
Original file line number Diff line number Diff line change
@@ -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/);
});
Original file line number Diff line number Diff line change
@@ -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();}
});
Original file line number Diff line number Diff line change
@@ -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:{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:{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};
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/usr/bin/env node
// 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';

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;
}

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`);
}
Loading