deadbyte-mcp 0.12.0 → 0.14.0
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/.gitattributes +7 -0
- package/AGENTS.md +25 -0
- package/CONTEXT.md +3 -3
- package/MANIFEST.SHA256 +89 -45
- package/R41-PARENT.json +15 -0
- package/R42-PARENT.json +19 -0
- package/README.txt +2 -2
- package/bin/appcontainer-stage.obj +0 -0
- package/bin/bootstrap-advapi32.obj +0 -0
- package/bin/bootstrap-exitcode.obj +0 -0
- package/bin/bootstrap-kernel32.obj +0 -0
- package/bin/child-control-probe.obj +0 -0
- package/bin/child-control-stage.obj +0 -0
- package/bin/contained-reverse-worker.obj +0 -0
- package/bin/contained-transform-worker.obj +0 -0
- package/bin/containment-probe.obj +0 -0
- package/bin/deadbyte-contain.obj +0 -0
- package/bin/deadbyte-exec.obj +0 -0
- package/bin/deadbyte-process-host.obj +0 -0
- package/bin/deadbyte-tunnel-host.obj +0 -0
- package/controller/README.TXT +1 -1
- package/controller/deadbyte-controller.ps1 +1 -1
- package/controller/deadbyte-desktop-policy.json +5 -5
- package/controller/deadbyte-process-policy.json +4 -4
- package/docs/ARCHITECTURE.md +1 -1
- package/docs/superpowers/specs/2026-09-20-r41-deterministic-memory-design.md +132 -0
- package/harness/r42-agent-evals.json +48 -0
- package/harness/task-contract.schema.json +33 -0
- package/package.json +11 -7
- package/proof/CONTAINMENT-BUILD.txt +6 -6
- package/scripts/agent-eval-r42.mjs +46 -0
- package/scripts/build-containment.ps1 +24 -14
- package/scripts/build-desktop-r40.ps1 +15 -9
- package/scripts/deferred-slot-operation-r41.mjs +127 -0
- package/scripts/deferred-slot-operation-r42.mjs +126 -0
- package/scripts/final-closure-r41.mjs +116 -0
- package/scripts/final-closure-r42.mjs +116 -0
- package/scripts/final-closure-verify-r41.mjs +215 -0
- package/scripts/final-closure-verify-r42.mjs +223 -0
- package/scripts/gate-windows-r41.ps1 +48 -0
- package/scripts/gate-windows-r42.ps1 +54 -0
- package/scripts/init-desktop-policy-r40.ps1 +10 -4
- package/scripts/release-parity-r41.mjs +221 -0
- package/scripts/release-parity-r42.mjs +235 -0
- package/scripts/release-parity-tests-r41.ps1 +9 -0
- package/scripts/release-parity-tests-r42.ps1 +6 -0
- package/scripts/verify-memory-r41.mjs +88 -0
- package/scripts/verify-r41-parent.mjs +61 -0
- package/scripts/verify-r42-parent.mjs +101 -0
- package/scripts/windows-gate-evidence-r41.mjs +53 -0
- package/scripts/windows-gate-evidence-r42.mjs +64 -0
- package/src/agent-eval-r42.mjs +73 -0
- package/src/agent-eval-runner-r42.mjs +138 -0
- package/src/agent-governance-r42.mjs +142 -0
- package/src/autonomous-context-engine.mjs +4 -1
- package/src/autonomous-loop-state.mjs +10 -0
- package/src/autonomous-mcp-tools.mjs +21 -6
- package/src/autonomous-output-contracts.mjs +18 -2
- package/src/autonomous-planner-contracts.mjs +3 -0
- package/src/autonomous-planner.mjs +48 -6
- package/src/autonomous-policy.mjs +10 -2
- package/src/autonomous-runtime.mjs +144 -25
- package/src/autonomous-supervisor.mjs +1 -1
- package/src/evidence-publisher-r42.mjs +76 -0
- package/src/mcp-server.mjs +15 -2
- package/src/memory-r41.mjs +692 -0
- package/src/provider-circuit.mjs +4 -4
- package/src/version.mjs +1 -1
- package/test/agent-eval-r42.test.mjs +39 -0
- package/test/agent-eval-runner-r42.test.mjs +61 -0
- package/test/agent-governance-r42.test.mjs +77 -0
- package/test/autonomous-r34-e2e.test.mjs +2 -1
- package/test/cli-entrypoint.test.mjs +1 -1
- package/test/controller-desktop-r40.test.mjs +2 -1
- package/test/core.test.mjs +1 -1
- package/test/evidence-publisher-r42.test.mjs +53 -0
- package/test/helpers/autonomous-r34-fixture.mjs +3 -3
- package/test/human-clarification-r42.test.mjs +53 -0
- package/test/memory-autonomous-integration-r41.test.mjs +31 -0
- package/test/memory-history-r41.test.mjs +126 -0
- package/test/memory-projections-r41.test.mjs +92 -0
- package/test/memory-retrieval-r41.test.mjs +124 -0
- package/test/memory-snapshot-r41.test.mjs +106 -0
- package/test/provider-responses-r42.test.mjs +90 -0
- package/test/r33-closeout-regression.test.mjs +27 -12
- package/test/r33-finalization.test.mjs +4 -4
- package/test/r41-finalization.test.mjs +38 -0
- package/test/r42-finalization.test.mjs +38 -0
- package/test/release-version.test.mjs +14 -13
- package/test/task-contract-r42.test.mjs +41 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { mkdir, open, readFile } from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { canonicalJson } from '../src/canonical-json.mjs';
|
|
7
|
+
import { readAndVerifyR42EvalEvidence } from '../src/agent-eval-runner-r42.mjs';
|
|
8
|
+
|
|
9
|
+
const here=path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const root=path.resolve(here,'..');
|
|
11
|
+
const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
12
|
+
const marker='DEADBYTE V0.14.0 / R42 WINDOWS BATCH GATE: PASS';
|
|
13
|
+
function insist(ok,message){if(!ok) throw new Error(message);}
|
|
14
|
+
|
|
15
|
+
export async function writeWindowsGateEvidence({
|
|
16
|
+
candidateRoot=root,
|
|
17
|
+
stateRoot=path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(os.homedir(),'.deadbyte-mcp')),
|
|
18
|
+
clock=()=>new Date()
|
|
19
|
+
}={}){
|
|
20
|
+
const candidate=path.resolve(candidateRoot);
|
|
21
|
+
const manifestBytes=await readFile(path.join(candidate,'MANIFEST.SHA256'));
|
|
22
|
+
const manifestSha256=sha256(manifestBytes);
|
|
23
|
+
const pkg=JSON.parse(await readFile(path.join(candidate,'package.json'),'utf8'));
|
|
24
|
+
insist(pkg.version==='0.14.0',`Windows gate evidence requires V0.14.0, found ${pkg.version}`);
|
|
25
|
+
|
|
26
|
+
const suite=JSON.parse(await readFile(path.join(candidate,'harness','r42-agent-evals.json'),'utf8'));
|
|
27
|
+
const suiteSha256=sha256(Buffer.from(canonicalJson(suite),'utf8'));
|
|
28
|
+
const evalId=`r42-${manifestSha256.slice(0,16)}-${suiteSha256.slice(0,16)}`;
|
|
29
|
+
const evalPath=path.join(path.resolve(stateRoot),'evidence','release','r42-agent-evals','agent_eval',`${evalId}.json`);
|
|
30
|
+
const evalVerified=await readAndVerifyR42EvalEvidence(evalPath,{subjectManifestSha256:manifestSha256,suiteSha256});
|
|
31
|
+
|
|
32
|
+
const completed=clock();
|
|
33
|
+
insist(completed instanceof Date&&!Number.isNaN(completed.getTime()),'Windows gate evidence clock invalid');
|
|
34
|
+
const evidence={
|
|
35
|
+
schema:'deadbyte.r42-windows-gate.v1',status:'passed',exit_code:0,version:'0.14.0',
|
|
36
|
+
marker,completed_at_utc:completed.toISOString(),manifest_sha256:manifestSha256,
|
|
37
|
+
model_eval:{path:evalPath,evidence_sha256:evalVerified.evidence_sha256,payload_sha256:evalVerified.payload_sha256,
|
|
38
|
+
suite_sha256:suiteSha256,case_count:evalVerified.payload.case_count}
|
|
39
|
+
};
|
|
40
|
+
const bytes=Buffer.from(`${JSON.stringify(evidence,null,2)}\n`,'utf8');
|
|
41
|
+
const dir=path.join(path.resolve(stateRoot),'evidence','release');
|
|
42
|
+
await mkdir(dir,{recursive:true});
|
|
43
|
+
const file=path.join(dir,`r42-windows-gate-${manifestSha256.slice(0,16)}.json`);
|
|
44
|
+
try{
|
|
45
|
+
const handle=await open(file,'wx',0o600);
|
|
46
|
+
try{await handle.writeFile(bytes);await handle.sync();}finally{await handle.close();}
|
|
47
|
+
return {status:'written',path:file,sha256:sha256(bytes),manifest_sha256:manifestSha256,model_eval:evidence.model_eval};
|
|
48
|
+
}catch(error){
|
|
49
|
+
if(error?.code!=='EEXIST') throw error;
|
|
50
|
+
const existingBytes=await readFile(file);
|
|
51
|
+
const existing=JSON.parse(existingBytes.toString('utf8').replace(/^\uFEFF/,''));
|
|
52
|
+
insist(existing.schema===evidence.schema&&existing.status==='passed'&&existing.exit_code===0,'existing R42 Windows gate evidence invalid');
|
|
53
|
+
insist(existing.version==='0.14.0'&&existing.marker===marker&&existing.manifest_sha256===manifestSha256,'existing R42 Windows gate identity mismatch');
|
|
54
|
+
insist(existing.model_eval?.evidence_sha256===evidence.model_eval.evidence_sha256&&existing.model_eval?.suite_sha256===suiteSha256,'existing R42 Windows gate model-eval mismatch');
|
|
55
|
+
return {status:'existing',path:file,sha256:sha256(existingBytes),manifest_sha256:manifestSha256,model_eval:existing.model_eval};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const invoked=process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url);
|
|
60
|
+
if(invoked){
|
|
61
|
+
writeWindowsGateEvidence()
|
|
62
|
+
.then(result=>console.log(JSON.stringify(result)))
|
|
63
|
+
.catch(error=>{console.error(error instanceof Error?error.stack:String(error));process.exitCode=1;});
|
|
64
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { canonicalJson } from './canonical-json.mjs';
|
|
3
|
+
|
|
4
|
+
const HEX64=/^[0-9a-f]{64}$/;
|
|
5
|
+
const ID=/^[A-Za-z0-9._-]{1,96}$/;
|
|
6
|
+
const SEVERITY=new Set(['info','low','medium','high','critical']);
|
|
7
|
+
const sha=value=>createHash('sha256').update(Buffer.from(canonicalJson(value),'utf8')).digest('hex');
|
|
8
|
+
function object(value,label){if(!value||typeof value!=='object'||Array.isArray(value))throw new Error(`${label} invalid`);return value;}
|
|
9
|
+
function keys(value,allowed,label){const extra=Object.keys(value).filter(key=>!allowed.includes(key));if(extra.length)throw new Error(`${label} unknown field '${extra[0]}'`);}
|
|
10
|
+
function id(value,label){if(typeof value!=='string'||!ID.test(value))throw new Error(`${label} invalid`);return value;}
|
|
11
|
+
function text(value,label,max=8192){if(typeof value!=='string'||value.length<1||value.length>max||value.includes('\0'))throw new Error(`${label} invalid`);return value;}
|
|
12
|
+
function textList(value,label,maxItems=32,maxLength=1024){if(!Array.isArray(value)||value.length>maxItems||value.some(item=>typeof item!=='string'||item.length<1||item.length>maxLength))throw new Error(`${label} invalid`);return [...value];}
|
|
13
|
+
|
|
14
|
+
function normalizeCase(raw){
|
|
15
|
+
const value=object(raw,'agent eval case');keys(value,['schema','case_id','task','allowed_context','hidden_oracle','acceptance'],'agent eval case');
|
|
16
|
+
if(value.schema!=='deadbyte.agent-eval-case.v1')throw new Error('agent eval case schema invalid');
|
|
17
|
+
const core={schema:value.schema,case_id:id(value.case_id,'eval case_id'),task:text(value.task,'eval task'),
|
|
18
|
+
allowed_context:structuredClone(object(value.allowed_context,'eval allowed_context')),
|
|
19
|
+
hidden_oracle:structuredClone(object(value.hidden_oracle,'eval hidden_oracle')),
|
|
20
|
+
acceptance:textList(value.acceptance,'eval acceptance')};
|
|
21
|
+
return Object.freeze({...core,case_sha256:sha(core)});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function normalizeJudgeResult(raw){
|
|
25
|
+
const value=object(raw,'judge result');keys(value,['schema','verdict','advisory','findings','evidence_refs'],'judge result');
|
|
26
|
+
if(value.schema!==undefined&&value.schema!=='deadbyte.agent-eval-judge-result.v1')throw new Error('judge result schema invalid');
|
|
27
|
+
if(!['pass','fail'].includes(value.verdict))throw new Error('judge verdict invalid');
|
|
28
|
+
if(value.advisory!==undefined&&value.advisory!==true)throw new Error('judge result must remain advisory');
|
|
29
|
+
if(!Array.isArray(value.findings)||value.findings.length>64)throw new Error('judge findings invalid');
|
|
30
|
+
const findings=value.findings.map(finding=>{
|
|
31
|
+
object(finding,'judge finding');keys(finding,['code','severity','evidence'],'judge finding');
|
|
32
|
+
if(!SEVERITY.has(finding.severity))throw new Error('judge finding severity invalid');
|
|
33
|
+
return {code:id(finding.code,'judge finding code'),severity:finding.severity,evidence:text(finding.evidence,'judge finding evidence',2048)};
|
|
34
|
+
});
|
|
35
|
+
if(!Array.isArray(value.evidence_refs)||value.evidence_refs.length<1||value.evidence_refs.length>64||value.evidence_refs.some(ref=>!HEX64.test(ref)))throw new Error('judge evidence refs invalid');
|
|
36
|
+
return Object.freeze({schema:'deadbyte.agent-eval-judge-result.v1',verdict:value.verdict,advisory:true,findings,evidence_refs:[...value.evidence_refs]});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function runIsolatedEvaluation({caseSpec,generator,judge}){
|
|
40
|
+
const normalized=normalizeCase(caseSpec);
|
|
41
|
+
if(typeof generator!=='function'||typeof judge!=='function')throw new Error('eval generator and judge required');
|
|
42
|
+
const generatorInput=Object.freeze({schema:'deadbyte.agent-eval-generator-input.v1',case_id:normalized.case_id,
|
|
43
|
+
task:normalized.task,allowed_context:structuredClone(normalized.allowed_context),acceptance:[...normalized.acceptance]});
|
|
44
|
+
const generated=object(await generator(generatorInput),'generator result');
|
|
45
|
+
keys(generated,['artifact','trace'],'generator result');
|
|
46
|
+
const artifact=structuredClone(object(generated.artifact,'generator artifact'));
|
|
47
|
+
const candidateSha=sha(artifact);
|
|
48
|
+
const judgeInput=Object.freeze({schema:'deadbyte.agent-eval-judge-input.v1',case_id:normalized.case_id,
|
|
49
|
+
task:normalized.task,acceptance:[...normalized.acceptance],candidate:artifact,candidate_sha256:candidateSha,
|
|
50
|
+
hidden_oracle:structuredClone(normalized.hidden_oracle)});
|
|
51
|
+
const judged=normalizeJudgeResult(await judge(judgeInput));
|
|
52
|
+
const core={schema:'deadbyte.agent-eval-result.v1',case_id:normalized.case_id,case_sha256:normalized.case_sha256,
|
|
53
|
+
candidate_sha256:candidateSha,verdict:judged.verdict,advisory:true,findings:judged.findings,evidence_refs:judged.evidence_refs};
|
|
54
|
+
return Object.freeze({...core,result_sha256:sha(core)});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function buildFreshCriticContext(raw){
|
|
58
|
+
const value=object(raw,'fresh critic context');keys(value,['case_id','artifact','evidence','criteria'],'fresh critic context');
|
|
59
|
+
id(value.case_id,'critic case_id');object(value.artifact,'critic artifact');
|
|
60
|
+
if(!Array.isArray(value.evidence)||value.evidence.length>128)throw new Error('critic evidence invalid');
|
|
61
|
+
return Object.freeze({schema:'deadbyte.fresh-critic-context.v1',case_id:value.case_id,
|
|
62
|
+
artifact:structuredClone(value.artifact),evidence:structuredClone(value.evidence),
|
|
63
|
+
criteria:textList(value.criteria,'critic criteria',64,1024)});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function triageJudgeResult(raw,{critical_action,noncritical_action}){
|
|
67
|
+
const judge=normalizeJudgeResult(raw);
|
|
68
|
+
if(judge.advisory!==true)throw new Error('judge result must remain advisory');
|
|
69
|
+
if(!['pause','repair','continue'].includes(critical_action)||!['pause','repair','continue'].includes(noncritical_action))throw new Error('judge triage action invalid');
|
|
70
|
+
const critical=judge.findings.some(finding=>finding.severity==='critical'||finding.severity==='high');
|
|
71
|
+
return Object.freeze({action:judge.verdict==='pass'?'continue':(critical?critical_action:noncritical_action),
|
|
72
|
+
reason_codes:judge.findings.map(finding=>finding.code),judge_advisory:true});
|
|
73
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { canonicalJson } from './canonical-json.mjs';
|
|
5
|
+
import { runIsolatedEvaluation } from './agent-eval-r42.mjs';
|
|
6
|
+
import { publishEvidenceAtomic } from './evidence-publisher-r42.mjs';
|
|
7
|
+
|
|
8
|
+
const HEX64=/^[0-9a-f]{64}$/;
|
|
9
|
+
const ID=/^[A-Za-z0-9._-]{1,96}$/;
|
|
10
|
+
const sha=value=>createHash('sha256').update(Buffer.from(canonicalJson(value),'utf8')).digest('hex');
|
|
11
|
+
const shaText=value=>createHash('sha256').update(Buffer.from(String(value),'utf8')).digest('hex');
|
|
12
|
+
|
|
13
|
+
function object(value,label){if(!value||typeof value!=='object'||Array.isArray(value))throw new Error(`${label} invalid`);return value;}
|
|
14
|
+
function keys(value,allowed,label){const extra=Object.keys(value).filter(key=>!allowed.includes(key));if(extra.length)throw new Error(`${label} unknown field '${extra[0]}'`);}
|
|
15
|
+
function parseJson(raw,label){
|
|
16
|
+
if(typeof raw!=='string'||raw.length<2||raw.length>1024*1024)throw new Error(`${label} response invalid`);
|
|
17
|
+
let value;try{value=JSON.parse(raw);}catch{throw new Error(`${label} response must be strict JSON`);}
|
|
18
|
+
return object(value,label);
|
|
19
|
+
}
|
|
20
|
+
function providerMeta(raw){
|
|
21
|
+
const value=object(raw,'model response');
|
|
22
|
+
const meta={
|
|
23
|
+
backend:typeof value.backend==='string'?value.backend:'injected',
|
|
24
|
+
model:typeof value.model==='string'?value.model.slice(0,128):'injected',
|
|
25
|
+
response_id:typeof value.response_id==='string'?value.response_id.slice(0,160):null,
|
|
26
|
+
raw_response_sha256:HEX64.test(value.raw_response_sha256??'')?value.raw_response_sha256:shaText(value.raw_text??''),
|
|
27
|
+
usage:value.usage&&Number.isInteger(value.usage.input_tokens)&&Number.isInteger(value.usage.output_tokens)&&Number.isInteger(value.usage.total_tokens)
|
|
28
|
+
?{input_tokens:value.usage.input_tokens,output_tokens:value.usage.output_tokens,total_tokens:value.usage.total_tokens}:null
|
|
29
|
+
};
|
|
30
|
+
return Object.freeze(meta);
|
|
31
|
+
}
|
|
32
|
+
function normalizeSuite(raw){
|
|
33
|
+
const value=object(raw,'eval suite');keys(value,['schema','suite_id','cases'],'eval suite');
|
|
34
|
+
if(value.schema!=='deadbyte.agent-eval-suite.v1')throw new Error('eval suite schema invalid');
|
|
35
|
+
if(typeof value.suite_id!=='string'||!ID.test(value.suite_id))throw new Error('eval suite_id invalid');
|
|
36
|
+
if(!Array.isArray(value.cases)||value.cases.length<1||value.cases.length>32)throw new Error('eval suite cases invalid');
|
|
37
|
+
const ids=new Set();
|
|
38
|
+
for(const item of value.cases){
|
|
39
|
+
object(item,'eval suite case');
|
|
40
|
+
if(item.schema!=='deadbyte.agent-eval-case.v1'||typeof item.case_id!=='string'||!ID.test(item.case_id)||ids.has(item.case_id))throw new Error('eval suite case identity invalid');
|
|
41
|
+
ids.add(item.case_id);
|
|
42
|
+
}
|
|
43
|
+
const copy=structuredClone(value);
|
|
44
|
+
return Object.freeze({...copy,suite_sha256:sha(copy)});
|
|
45
|
+
}
|
|
46
|
+
function generatorPrompt(input){
|
|
47
|
+
return [
|
|
48
|
+
'You are the implementation-side evaluator for DEADBYTE R42.',
|
|
49
|
+
'Return exactly one JSON object with shape {"artifact":{...}} and no Markdown.',
|
|
50
|
+
'Do not invent authority, credentials, or evidence. Prefer bounded, typed, fail-closed behavior.',
|
|
51
|
+
`INPUT=${canonicalJson(input)}`
|
|
52
|
+
].join('\n');
|
|
53
|
+
}
|
|
54
|
+
function judgePrompt(input){
|
|
55
|
+
return [
|
|
56
|
+
'You are an independent advisory judge for DEADBYTE R42.',
|
|
57
|
+
'Return exactly one JSON object with schema deadbyte.agent-eval-judge-result.v1.',
|
|
58
|
+
'Shape: {"schema":"deadbyte.agent-eval-judge-result.v1","verdict":"pass|fail","advisory":true,"findings":[{"code":"ID","severity":"info|low|medium|high|critical","evidence":"text"}],"evidence_refs":["64hex"]}.',
|
|
59
|
+
'Judge only the supplied candidate against acceptance and hidden oracle. Do not assume implementer reasoning.',
|
|
60
|
+
'Use candidate_sha256 as at least one evidence_refs entry.',
|
|
61
|
+
`INPUT=${canonicalJson(input)}`
|
|
62
|
+
].join('\n');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function verifyR42EvalPayload(payload,{subjectManifestSha256=null,suiteSha256=null}={}){
|
|
66
|
+
const value=object(payload,'R42 eval payload');
|
|
67
|
+
keys(value,['schema','status','subject_manifest_sha256','suite_id','suite_sha256','case_count','cases','completed_at_utc'],'R42 eval payload');
|
|
68
|
+
if(value.schema!=='deadbyte.r42-model-eval.v1'||value.status!=='passed')throw new Error('R42 eval payload is not passed');
|
|
69
|
+
if(!HEX64.test(value.subject_manifest_sha256??''))throw new Error('R42 eval manifest invalid');
|
|
70
|
+
if(subjectManifestSha256!==null&&value.subject_manifest_sha256!==subjectManifestSha256)throw new Error('R42 eval manifest mismatch');
|
|
71
|
+
if(!HEX64.test(value.suite_sha256??''))throw new Error('R42 eval suite hash invalid');
|
|
72
|
+
if(suiteSha256!==null&&value.suite_sha256!==suiteSha256)throw new Error('R42 eval suite hash mismatch');
|
|
73
|
+
if(typeof value.suite_id!=='string'||!ID.test(value.suite_id))throw new Error('R42 eval suite id invalid');
|
|
74
|
+
if(!Number.isInteger(value.case_count)||value.case_count<1||!Array.isArray(value.cases)||value.cases.length!==value.case_count)throw new Error('R42 eval cases invalid');
|
|
75
|
+
const seen=new Set();
|
|
76
|
+
for(const item of value.cases){
|
|
77
|
+
object(item,'R42 eval case result');keys(item,['case_id','result','generator','judge'],'R42 eval case result');
|
|
78
|
+
if(typeof item.case_id!=='string'||!ID.test(item.case_id)||seen.has(item.case_id))throw new Error('R42 eval case result identity invalid');
|
|
79
|
+
seen.add(item.case_id);
|
|
80
|
+
if(item.result?.schema!=='deadbyte.agent-eval-result.v1'||item.result?.verdict!=='pass'||item.result?.advisory!==true||!HEX64.test(item.result?.result_sha256??''))throw new Error(`R42 eval case did not pass: ${item.case_id}`);
|
|
81
|
+
for(const role of ['generator','judge']){
|
|
82
|
+
if(!HEX64.test(item[role]?.raw_response_sha256??''))throw new Error(`R42 eval ${role} provenance invalid`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if(typeof value.completed_at_utc!=='string'||!Number.isFinite(Date.parse(value.completed_at_utc)))throw new Error('R42 eval completion time invalid');
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function runAgentEvalSuiteR42({suite,subjectManifestSha256,requestModel,evidenceRoot=null,clock=()=>new Date()}){
|
|
90
|
+
const normalized=normalizeSuite(suite);
|
|
91
|
+
if(!HEX64.test(subjectManifestSha256??''))throw new Error('R42 eval subject manifest invalid');
|
|
92
|
+
if(typeof requestModel!=='function')throw new Error('R42 eval requestModel required');
|
|
93
|
+
const rows=[];
|
|
94
|
+
for(const caseSpec of normalized.cases){
|
|
95
|
+
let generatorMeta=null,judgeMeta=null;
|
|
96
|
+
const result=await runIsolatedEvaluation({
|
|
97
|
+
caseSpec,
|
|
98
|
+
generator:async input=>{
|
|
99
|
+
const response=object(await requestModel({role:'generator',case_id:caseSpec.case_id,prompt:generatorPrompt(input)}),'generator model response');
|
|
100
|
+
generatorMeta=providerMeta(response);
|
|
101
|
+
const parsed=parseJson(response.raw_text,'generator');
|
|
102
|
+
keys(parsed,['artifact'],'generator response');
|
|
103
|
+
return {artifact:object(parsed.artifact,'generator artifact')};
|
|
104
|
+
},
|
|
105
|
+
judge:async input=>{
|
|
106
|
+
const response=object(await requestModel({role:'judge',case_id:caseSpec.case_id,prompt:judgePrompt(input)}),'judge model response');
|
|
107
|
+
judgeMeta=providerMeta(response);
|
|
108
|
+
return parseJson(response.raw_text,'judge');
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
rows.push({case_id:caseSpec.case_id,result,generator:generatorMeta,judge:judgeMeta});
|
|
112
|
+
}
|
|
113
|
+
if(rows.some(row=>row.result.verdict!=='pass'))throw new Error('R42 model-backed eval suite failed');
|
|
114
|
+
const completed=clock();if(!(completed instanceof Date)||Number.isNaN(completed.getTime()))throw new Error('R42 eval clock invalid');
|
|
115
|
+
const payload={
|
|
116
|
+
schema:'deadbyte.r42-model-eval.v1',status:'passed',subject_manifest_sha256:subjectManifestSha256,
|
|
117
|
+
suite_id:normalized.suite_id,suite_sha256:normalized.suite_sha256,case_count:rows.length,cases:rows,
|
|
118
|
+
completed_at_utc:completed.toISOString()
|
|
119
|
+
};
|
|
120
|
+
verifyR42EvalPayload(payload,{subjectManifestSha256,suiteSha256:normalized.suite_sha256});
|
|
121
|
+
let publication=null;
|
|
122
|
+
if(evidenceRoot!==null){
|
|
123
|
+
if(typeof evidenceRoot!=='string'||!path.isAbsolute(evidenceRoot))throw new Error('R42 eval evidence root must be absolute');
|
|
124
|
+
const evidenceId=`r42-${subjectManifestSha256.slice(0,16)}-${normalized.suite_sha256.slice(0,16)}`;
|
|
125
|
+
publication=await publishEvidenceAtomic({root:evidenceRoot,kind:'agent_eval',evidenceId,payload});
|
|
126
|
+
}
|
|
127
|
+
return Object.freeze({payload,publication});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export async function readAndVerifyR42EvalEvidence(file,{subjectManifestSha256,suiteSha256}={}){
|
|
131
|
+
const envelope=JSON.parse(await readFile(path.resolve(file),'utf8'));
|
|
132
|
+
if(envelope?.schema!=='deadbyte.evidence-publication.v1'||envelope?.kind!=='agent_eval')throw new Error('R42 eval evidence envelope invalid');
|
|
133
|
+
if(!HEX64.test(envelope.payload_sha256??'')||sha(envelope.payload)!==envelope.payload_sha256)throw new Error('R42 eval payload hash mismatch');
|
|
134
|
+
const {evidence_sha256,...body}=envelope;
|
|
135
|
+
if(!HEX64.test(evidence_sha256??'')||sha(body)!==evidence_sha256)throw new Error('R42 eval evidence hash mismatch');
|
|
136
|
+
verifyR42EvalPayload(envelope.payload,{subjectManifestSha256,suiteSha256});
|
|
137
|
+
return Object.freeze({status:'passed',evidence_sha256,payload_sha256:envelope.payload_sha256,payload:envelope.payload});
|
|
138
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { canonicalJson } from './canonical-json.mjs';
|
|
3
|
+
|
|
4
|
+
const HEX64=/^[0-9a-f]{64}$/;
|
|
5
|
+
const ID=/^[A-Za-z0-9._-]{1,64}$/;
|
|
6
|
+
const RIGOR=Object.freeze({lightweight:0,structured:1,agentic:2});
|
|
7
|
+
const RISK_MINIMUM=Object.freeze({low:'lightweight',medium:'structured',high:'agentic'});
|
|
8
|
+
const EVIDENCE=Object.freeze({assumption:1,source:2,traced_path:3,automated:4,runtime:5});
|
|
9
|
+
|
|
10
|
+
const sha=value=>createHash('sha256').update(Buffer.from(canonicalJson(value),'utf8')).digest('hex');
|
|
11
|
+
function object(value,label){if(!value||typeof value!=='object'||Array.isArray(value)) throw new Error(`${label} invalid`);return value;}
|
|
12
|
+
function keys(value,allowed,label){const extra=Object.keys(value).filter(key=>!allowed.includes(key));if(extra.length) throw new Error(`${label} unknown field '${extra[0]}'`);}
|
|
13
|
+
function id(value,label){if(typeof value!=='string'||!ID.test(value)) throw new Error(`${label} invalid`);return value;}
|
|
14
|
+
function hex(value,label){if(typeof value!=='string'||!HEX64.test(value)) throw new Error(`${label} invalid`);return value;}
|
|
15
|
+
function text(value,label,max=2048){if(typeof value!=='string'||value.length<1||value.length>max||value.includes('\0')) throw new Error(`${label} invalid`);return value;}
|
|
16
|
+
function idList(value,label,max=128){if(!Array.isArray(value)||value.length>max||value.some(item=>typeof item!=='string'||!ID.test(item))||new Set(value).size!==value.length) throw new Error(`${label} invalid`);return [...value];}
|
|
17
|
+
|
|
18
|
+
export function bindApprovalSubject(raw){
|
|
19
|
+
const value=object(raw,'approval subject');
|
|
20
|
+
keys(value,['goal_id','reviewed_event_seq','reviewed_event_sha256','journal_head_sha256','scope','subject'],'approval subject');
|
|
21
|
+
hex(value.goal_id,'approval goal_id');
|
|
22
|
+
if(!Number.isInteger(value.reviewed_event_seq)||value.reviewed_event_seq<1) throw new Error('approval reviewed_event_seq invalid');
|
|
23
|
+
hex(value.reviewed_event_sha256,'approval reviewed_event_sha256');
|
|
24
|
+
hex(value.journal_head_sha256,'approval journal_head_sha256');
|
|
25
|
+
id(value.scope,'approval scope'); object(value.subject,'approval canonical subject');
|
|
26
|
+
const normalized=structuredClone(value);
|
|
27
|
+
return Object.freeze({...normalized,approval_subject_sha256:sha({schema:'deadbyte.approval-subject.v1',...normalized})});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function validateBoundApproval({approval,subject,now=new Date()}){
|
|
31
|
+
const {approval_subject_sha256:claimedSubjectSha,...subjectBody}=object(subject,'approval subject');
|
|
32
|
+
const bound=bindApprovalSubject(subjectBody);
|
|
33
|
+
if(claimedSubjectSha!==undefined&&claimedSubjectSha!==bound.approval_subject_sha256) throw new Error('approval subject hash mismatch');
|
|
34
|
+
const value=object(approval,'bound approval');
|
|
35
|
+
keys(value,['schema','approval_subject_sha256','reviewed_event_seq','reviewed_event_sha256','journal_head_sha256','scope','issued_at_utc','expires_at_utc','provenance'],'bound approval');
|
|
36
|
+
if(value.schema!=='deadbyte.bound-approval.v1') throw new Error('bound approval schema invalid');
|
|
37
|
+
if(value.approval_subject_sha256!==bound.approval_subject_sha256) throw new Error('bound approval subject mismatch');
|
|
38
|
+
if(value.reviewed_event_seq!==bound.reviewed_event_seq||value.reviewed_event_sha256!==bound.reviewed_event_sha256) throw new Error('bound approval reviewed event mismatch');
|
|
39
|
+
if(value.journal_head_sha256!==bound.journal_head_sha256) throw new Error('bound approval journal head mismatch');
|
|
40
|
+
if(value.scope!==bound.scope) throw new Error('bound approval scope mismatch');
|
|
41
|
+
if(value.provenance!=='operator_mcp') throw new Error('bound approval provenance invalid');
|
|
42
|
+
const issued=Date.parse(value.issued_at_utc),expires=Date.parse(value.expires_at_utc),observed=now.getTime();
|
|
43
|
+
if(!Number.isFinite(issued)||!Number.isFinite(expires)||expires<=issued||expires-issued>3_600_000) throw new Error('bound approval lifetime invalid');
|
|
44
|
+
if(observed<issued||observed>=expires) throw new Error('bound approval expired or not yet valid');
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function createTaskDag(raw){
|
|
49
|
+
const value=object(raw,'task DAG'); keys(value,['run_id','goal_id','tasks'],'task DAG');
|
|
50
|
+
id(value.run_id,'task DAG run_id'); hex(value.goal_id,'task DAG goal_id');
|
|
51
|
+
if(!Array.isArray(value.tasks)||value.tasks.length<1||value.tasks.length>256) throw new Error('task DAG tasks invalid');
|
|
52
|
+
const tasks=value.tasks.map(item=>{
|
|
53
|
+
object(item,'task DAG task');keys(item,['task_id','depends_on','input_artifacts','output_artifacts','state'],'task DAG task');
|
|
54
|
+
return {task_id:id(item.task_id,'task_id'),depends_on:idList(item.depends_on,'depends_on'),
|
|
55
|
+
input_artifacts:idList(item.input_artifacts,'input_artifacts'),output_artifacts:idList(item.output_artifacts,'output_artifacts'),
|
|
56
|
+
state:item.state??'PENDING'};
|
|
57
|
+
});
|
|
58
|
+
const ids=new Set(tasks.map(task=>task.task_id)); if(ids.size!==tasks.length) throw new Error('task DAG duplicate task_id');
|
|
59
|
+
for(const task of tasks) if(task.depends_on.some(dep=>!ids.has(dep))) throw new Error('task DAG dependency missing');
|
|
60
|
+
if(tasks.some(task=>!['PENDING','RUNNING','COMPLETE','FAILED','STALE'].includes(task.state))) throw new Error('task DAG state invalid');
|
|
61
|
+
const visiting=new Set(),visited=new Set(),byId=new Map(tasks.map(task=>[task.task_id,task]));
|
|
62
|
+
const visit=taskId=>{if(visiting.has(taskId)) throw new Error('task DAG cycle detected');if(visited.has(taskId))return;visiting.add(taskId);for(const dep of byId.get(taskId).depends_on)visit(dep);visiting.delete(taskId);visited.add(taskId);};
|
|
63
|
+
for(const task of tasks) visit(task.task_id);
|
|
64
|
+
const core={schema:'deadbyte.task-dag.v1',run_id:value.run_id,goal_id:value.goal_id,tasks};
|
|
65
|
+
return Object.freeze({...core,dag_sha256:sha(core)});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function invalidateTaskDag(dag,{changed_artifact_ids}){
|
|
69
|
+
const changed=new Set(idList(changed_artifact_ids,'changed_artifact_ids'));
|
|
70
|
+
const stale=new Set(dag.tasks.filter(task=>task.input_artifacts.some(artifact=>changed.has(artifact))).map(task=>task.task_id));
|
|
71
|
+
let grew=true;while(grew){grew=false;for(const task of dag.tasks){if(!stale.has(task.task_id)&&task.depends_on.some(dep=>stale.has(dep))){stale.add(task.task_id);grew=true;}}}
|
|
72
|
+
return createTaskDag({run_id:dag.run_id,goal_id:dag.goal_id,tasks:dag.tasks.map(task=>({...task,state:stale.has(task.task_id)?'STALE':task.state}))});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function taskDagFromPlan({run_id,goal_id,plan}){
|
|
76
|
+
object(plan,'task DAG plan');
|
|
77
|
+
if(!Array.isArray(plan.steps)||plan.steps.length<1) throw new Error('task DAG plan steps invalid');
|
|
78
|
+
const tasks=plan.steps.map((step,index)=>{
|
|
79
|
+
const previous=index===0?null:plan.steps[index-1].step_id;
|
|
80
|
+
return {task_id:step.step_id,depends_on:previous?[previous]:[],
|
|
81
|
+
input_artifacts:previous?[`${previous}.result`]:[],output_artifacts:[`${step.step_id}.result`],
|
|
82
|
+
state:step.status==='completed'?'COMPLETE':step.status==='active'?'RUNNING':'PENDING'};
|
|
83
|
+
});
|
|
84
|
+
return createTaskDag({run_id,goal_id,tasks});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function createEvidenceClaim(raw){
|
|
88
|
+
const value=object(raw,'evidence claim');keys(value,['claim_id','statement','level','refs'],'evidence claim');
|
|
89
|
+
id(value.claim_id,'claim_id');text(value.statement,'claim statement',2048);
|
|
90
|
+
if(!Object.hasOwn(EVIDENCE,value.level)) throw new Error('evidence level invalid');
|
|
91
|
+
if(!Array.isArray(value.refs)||value.refs.length>64||value.refs.some(ref=>!HEX64.test(ref))) throw new Error('evidence reference invalid');
|
|
92
|
+
if(value.level!=='assumption'&&value.refs.length<1) throw new Error('evidence reference required');
|
|
93
|
+
const core={schema:'deadbyte.evidence-claim.v1',claim_id:value.claim_id,statement:value.statement,level:value.level,refs:[...value.refs]};
|
|
94
|
+
return Object.freeze({...core,claim_sha256:sha(core)});
|
|
95
|
+
}
|
|
96
|
+
export function evidenceSatisfies(claim,minimum){if(!Object.hasOwn(EVIDENCE,minimum)) throw new Error('minimum evidence level invalid');return EVIDENCE[claim.level]>=EVIDENCE[minimum];}
|
|
97
|
+
|
|
98
|
+
export function deriveEffectiveRigor({project_default,risk_level,explicit_override=null}){
|
|
99
|
+
if(!Object.hasOwn(RIGOR,project_default)) throw new Error('project default rigor invalid');
|
|
100
|
+
if(!Object.hasOwn(RISK_MINIMUM,risk_level)) throw new Error('risk level invalid');
|
|
101
|
+
if(explicit_override!==null&&!Object.hasOwn(RIGOR,explicit_override)) throw new Error('explicit rigor override invalid');
|
|
102
|
+
const risk_minimum=RISK_MINIMUM[risk_level];
|
|
103
|
+
const values=[project_default,risk_minimum,...(explicit_override?[explicit_override]:[])];
|
|
104
|
+
const effective_rigor=values.reduce((best,item)=>RIGOR[item]>RIGOR[best]?item:best,'lightweight');
|
|
105
|
+
return Object.freeze({project_default,risk_minimum,explicit_override,effective_rigor});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function createDecisionRecord(raw){
|
|
109
|
+
const value=object(raw,'decision record');keys(value,['decision_id','decision','reason','evidence_refs','invalidates_if'],'decision record');
|
|
110
|
+
id(value.decision_id,'decision_id');text(value.decision,'decision',2048);text(value.reason,'decision reason',2048);
|
|
111
|
+
if(!Array.isArray(value.evidence_refs)||value.evidence_refs.length<1||value.evidence_refs.length>64||value.evidence_refs.some(ref=>!HEX64.test(ref))) throw new Error('decision evidence refs invalid');
|
|
112
|
+
if(!Array.isArray(value.invalidates_if)||value.invalidates_if.length>32) throw new Error('decision invalidation rules invalid');
|
|
113
|
+
const rules=value.invalidates_if.map(rule=>{
|
|
114
|
+
object(rule,'decision invalidation rule');
|
|
115
|
+
if(rule.kind==='artifact_sha_changed'){
|
|
116
|
+
keys(rule,['kind','artifact_id','expected_sha256'],'artifact invalidation rule');
|
|
117
|
+
return {kind:rule.kind,artifact_id:id(rule.artifact_id,'artifact_id'),expected_sha256:hex(rule.expected_sha256,'expected artifact sha256')};
|
|
118
|
+
}
|
|
119
|
+
if(rule.kind==='policy_sha_changed'){
|
|
120
|
+
keys(rule,['kind','expected_sha256'],'policy invalidation rule');
|
|
121
|
+
return {kind:rule.kind,expected_sha256:hex(rule.expected_sha256,'expected policy sha256')};
|
|
122
|
+
}
|
|
123
|
+
throw new Error('decision invalidation rule kind invalid');
|
|
124
|
+
});
|
|
125
|
+
const core={schema:'deadbyte.decision-record.v1',decision_id:value.decision_id,decision:value.decision,reason:value.reason,
|
|
126
|
+
evidence_refs:[...value.evidence_refs],invalidates_if:rules};
|
|
127
|
+
return Object.freeze({...core,decision_sha256:sha(core)});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function evaluateDecisionFreshness(record,observed){
|
|
131
|
+
object(record,'decision record');object(observed,'decision observed state');
|
|
132
|
+
const invalidated_by=[];
|
|
133
|
+
for(const rule of record.invalidates_if){
|
|
134
|
+
if(rule.kind==='artifact_sha_changed'){
|
|
135
|
+
const actual=observed.artifact_sha256?.[rule.artifact_id]??null;
|
|
136
|
+
if(actual!==rule.expected_sha256) invalidated_by.push({...rule,observed_sha256:actual});
|
|
137
|
+
}else if(rule.kind==='policy_sha_changed'&&observed.policy_sha256!==rule.expected_sha256){
|
|
138
|
+
invalidated_by.push({...rule,observed_sha256:observed.policy_sha256??null});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return Object.freeze({decision_id:record.decision_id,state:invalidated_by.length?'STALE':'CURRENT',invalidated_by});
|
|
142
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { canonicalJson } from './canonical-json.mjs';
|
|
3
3
|
import { provenanceFor, dedupeEvidence } from './context-provenance.mjs';
|
|
4
|
+
import { taskDagFromPlan } from './agent-governance-r42.mjs';
|
|
4
5
|
|
|
5
6
|
const SECRET_KEY = /(?:^|[_-])(token|password|secret|authorization|cookie|credential|credentials|api[_-]?key|private[_-]?key|recovery)(?:$|[_-])/i;
|
|
6
7
|
const HEX64 = /^[0-9a-f]{64}$/;
|
|
@@ -213,7 +214,7 @@ function historicalEvidence({ contexts, observations, loopState }) {
|
|
|
213
214
|
};
|
|
214
215
|
}
|
|
215
216
|
|
|
216
|
-
export function buildLoopContext({ goal, policy, loopState = {}, tree = [], contexts = [], observations = [], decisions = [], projectMemory = null, desktopMemory = null, capabilities = null }) {
|
|
217
|
+
export function buildLoopContext({ goal, policy, loopState = {}, tree = [], contexts = [], observations = [], decisions = [], projectMemory = null, desktopMemory = null, longTermMemory = null, capabilities = null }) {
|
|
217
218
|
if (!policy?.limits || !Number.isInteger(policy.limits.max_context_bytes)) throw new Error('loop context policy byte limit missing');
|
|
218
219
|
const relevanceEpoch=Number.isInteger(loopState?.mutation_epoch)&&loopState.mutation_epoch>=0?loopState.mutation_epoch:0;
|
|
219
220
|
const retainedContexts=normalizeEvidence(contexts,{kind:'context',relevanceEpoch});
|
|
@@ -250,7 +251,9 @@ export function buildLoopContext({ goal, policy, loopState = {}, tree = [], cont
|
|
|
250
251
|
historical_evidence:historicalEvidence({contexts:retainedContexts,observations:retainedObservations,loopState}),
|
|
251
252
|
project_memory:memory,
|
|
252
253
|
desktop_memory:desktopMemory===null?null:structuredClone(desktopMemory),
|
|
254
|
+
long_term_memory:longTermMemory===null?null:structuredClone(longTermMemory),
|
|
253
255
|
capabilities:capabilities===null?null:structuredClone(capabilities),
|
|
256
|
+
task_dag:loopState.plan&&typeof goal?.goal_id==='string' ? taskDagFromPlan({run_id:goal.goal_id,goal_id:goal.goal_id,plan:loopState.plan}) : null,
|
|
254
257
|
profiles
|
|
255
258
|
};
|
|
256
259
|
return compactLoopContext(state,policy.limits.max_context_bytes);
|
|
@@ -146,6 +146,7 @@ export function deriveLoopState(goal,events) {
|
|
|
146
146
|
replan_count:0,
|
|
147
147
|
pending_action:null,
|
|
148
148
|
pending_approval:false,
|
|
149
|
+
clarification:null,
|
|
149
150
|
progress:null,
|
|
150
151
|
critic:null,
|
|
151
152
|
repair:null,
|
|
@@ -223,6 +224,10 @@ export function deriveLoopState(goal,events) {
|
|
|
223
224
|
state.phase='WAITING';
|
|
224
225
|
state.wait_reason=requireText(payload.reason,'loop wait reason');
|
|
225
226
|
state.pending_approval=payload.requires_approval===true;
|
|
227
|
+
state.clarification=payload.reason==='human_input_required' ? {
|
|
228
|
+
question:requireText(payload.question,'loop clarification question',1000),
|
|
229
|
+
choices:Array.isArray(payload.choices) ? payload.choices.map((choice,index)=>requireText(choice,`loop clarification choice ${index+1}`,256)) : []
|
|
230
|
+
} : null;
|
|
226
231
|
break;
|
|
227
232
|
case 'waiting_resumed': {
|
|
228
233
|
if (state.phase !== 'WAITING') throw new Error('loop waiting_resumed outside WAITING phase');
|
|
@@ -231,6 +236,7 @@ export function deriveLoopState(goal,events) {
|
|
|
231
236
|
state.phase=resumed;
|
|
232
237
|
state.wait_reason=null;
|
|
233
238
|
state.pending_approval=false;
|
|
239
|
+
state.clarification=null;
|
|
234
240
|
break;
|
|
235
241
|
}
|
|
236
242
|
case 'planner_requested':
|
|
@@ -255,24 +261,28 @@ export function deriveLoopState(goal,events) {
|
|
|
255
261
|
state.phase='PLAN';
|
|
256
262
|
state.wait_reason=null;
|
|
257
263
|
state.pending_approval=false;
|
|
264
|
+
state.clarification=null;
|
|
258
265
|
}
|
|
259
266
|
break;
|
|
260
267
|
case 'goal_cancelled':
|
|
261
268
|
state.phase='FAILED';
|
|
262
269
|
state.wait_reason=typeof payload.reason==='string'&&payload.reason.length>0?payload.reason:'cancelled';
|
|
263
270
|
state.pending_approval=false;
|
|
271
|
+
state.clarification=null;
|
|
264
272
|
state.pending_action=null;
|
|
265
273
|
break;
|
|
266
274
|
case 'goal_failed':
|
|
267
275
|
state.phase='FAILED';
|
|
268
276
|
state.wait_reason=typeof payload.reason==='string'&&payload.reason.length>0?payload.reason:'failed';
|
|
269
277
|
state.pending_approval=false;
|
|
278
|
+
state.clarification=null;
|
|
270
279
|
state.pending_action=null;
|
|
271
280
|
break;
|
|
272
281
|
case 'release_completed':
|
|
273
282
|
state.phase='SUCCEEDED';
|
|
274
283
|
state.wait_reason=null;
|
|
275
284
|
state.pending_approval=false;
|
|
285
|
+
state.clarification=null;
|
|
276
286
|
state.pending_action=null;
|
|
277
287
|
break;
|
|
278
288
|
default:
|
|
@@ -25,6 +25,17 @@ const rmdirOperation = z.strictObject({ kind:z.literal('rmdir'), path:relPath, e
|
|
|
25
25
|
const nativeOperation = z.union([writeOperation,replaceOperation,deleteOperation,mkdirOperation,moveOperation,rmdirOperation]);
|
|
26
26
|
const plannerResponse = z.strictObject({ request_id:hex64, raw_text:z.string().min(2).max(8*1024*1024),
|
|
27
27
|
model:z.string().min(1).max(128).default('client-model') });
|
|
28
|
+
const taskContract = z.strictObject({
|
|
29
|
+
schema:z.literal('deadbyte.task-contract.v1'),
|
|
30
|
+
scope:z.array(z.string().min(1).max(512)).max(32).default([]),
|
|
31
|
+
non_goals:z.array(z.string().min(1).max(512)).max(24).default([]),
|
|
32
|
+
constraints:z.array(z.string().min(1).max(512)).max(32).default([]),
|
|
33
|
+
evidence_requirements:z.array(z.string().min(1).max(512)).max(32).default([]),
|
|
34
|
+
risk_level:z.enum(['low','medium','high']).default('medium'),
|
|
35
|
+
clarification_policy:z.enum(['ask_when_blocked','fail_closed']).default('ask_when_blocked'),
|
|
36
|
+
project_default:z.enum(['lightweight','structured','agentic']).default('structured'),
|
|
37
|
+
explicit_override:z.enum(['lightweight','structured','agentic']).nullable().default(null)
|
|
38
|
+
});
|
|
28
39
|
function result(schema,value,isError=false) { return autonomousStructuredResult(schema,value,isError); }
|
|
29
40
|
function fail(schema,error,status='failed') { return result(schema,{ status, error:String(error) },true); }
|
|
30
41
|
|
|
@@ -60,11 +71,12 @@ export function registerAutonomousTools(server,{ autonomousRuntime, nativeDiffRu
|
|
|
60
71
|
description:'Submit one bounded R24 production goal. Execution requires separate live coding and autonomous leases.',
|
|
61
72
|
inputSchema:z.strictObject({ submission_id:id, root_id:id, title:z.string().max(160).default(''),
|
|
62
73
|
goal:z.string().min(1).max(8192), acceptance:z.string().max(4096).default(''),
|
|
74
|
+
task_contract:taskContract.nullable().default(null),
|
|
63
75
|
max_iterations:z.number().int().min(1).max(64).nullable().default(null) }),
|
|
64
76
|
outputSchema:autonomousGoalSubmitOutputSchema
|
|
65
|
-
},async({submission_id,root_id,title,goal,acceptance,max_iterations})=>{ try {
|
|
77
|
+
},async({submission_id,root_id,title,goal,acceptance,task_contract,max_iterations})=>{ try {
|
|
66
78
|
return result(autonomousGoalSubmitOutputSchema,await autonomousRuntime.submit({ submissionId:submission_id,
|
|
67
|
-
rootId:root_id,title,goal,acceptance,maxIterations:max_iterations })); }
|
|
79
|
+
rootId:root_id,title,goal,acceptance,taskContract:task_contract,maxIterations:max_iterations })); }
|
|
68
80
|
catch(error){ return fail(autonomousGoalSubmitOutputSchema,error); } });
|
|
69
81
|
server.registerTool('autonomous_goal_run',{
|
|
70
82
|
description:'Advance one autonomous production goal through inspect/native-diff/build/test/benchmark/replan cycles. Client-planner mode returns a typed input_required sentinel for the Bridge MRTR adapter.',
|
|
@@ -100,11 +112,14 @@ export function registerAutonomousTools(server,{ autonomousRuntime, nativeDiffRu
|
|
|
100
112
|
catch(error){ return fail(autonomousGoalCancelOutputSchema,error); } });
|
|
101
113
|
|
|
102
114
|
server.registerTool('autonomous_goal_approve',{
|
|
103
|
-
description:'Approve or reject resuming a paused autonomous goal. This never arms or renews any lease and cannot bypass release evidence.',
|
|
104
|
-
inputSchema:z.strictObject({ goal_id:hex64, decision:z.enum(['approve','reject']).default('approve')
|
|
115
|
+
description:'Approve or reject resuming a paused autonomous goal, optionally supplying the bounded answer required by a typed human clarification. This never arms or renews any lease and cannot bypass release evidence.',
|
|
116
|
+
inputSchema:z.strictObject({ goal_id:hex64, decision:z.enum(['approve','reject']).default('approve'),
|
|
117
|
+
answer:z.string().min(1).max(4096).nullable().default(null),
|
|
118
|
+
approval_subject_sha256:hex64.nullable().default(null),
|
|
119
|
+
expires_in_ms:z.number().int().min(1000).max(3600000).default(300000) }),
|
|
105
120
|
outputSchema:autonomousGoalApproveOutputSchema
|
|
106
|
-
},async({goal_id,decision})=>{ try { return result(autonomousGoalApproveOutputSchema,
|
|
107
|
-
await autonomousRuntime.approve({goalId:goal_id,decision})); }
|
|
121
|
+
},async({goal_id,decision,answer,approval_subject_sha256,expires_in_ms})=>{ try { return result(autonomousGoalApproveOutputSchema,
|
|
122
|
+
await autonomousRuntime.approve({goalId:goal_id,decision,answer,approvalSubjectSha256:approval_subject_sha256,expiresInMs:expires_in_ms})); }
|
|
108
123
|
catch(error){ return fail(autonomousGoalApproveOutputSchema,error); } });
|
|
109
124
|
|
|
110
125
|
server.registerTool('autonomous_goal_verify',{
|
|
@@ -15,12 +15,28 @@ const loopProgress = z.strictObject({
|
|
|
15
15
|
const loopPendingAction = z.strictObject({
|
|
16
16
|
action_id:hex64, request_sha256:hex64, kind:z.string().regex(/^[A-Za-z0-9._-]{1,64}$/)
|
|
17
17
|
});
|
|
18
|
+
const loopClarification = z.strictObject({
|
|
19
|
+
question:z.string().min(1).max(1000), choices:z.array(z.string().min(1).max(256)).max(5)
|
|
20
|
+
});
|
|
21
|
+
const loopApprovalSubject = z.strictObject({
|
|
22
|
+
goal_id:hex64,reviewed_event_seq:z.number().int().min(1),reviewed_event_sha256:hex64,
|
|
23
|
+
journal_head_sha256:hex64,scope:z.enum(['resume_goal','human_clarification','desktop_resume','interrupted_effect_resume']),
|
|
24
|
+
subject:z.strictObject({reason:z.string().min(1).max(1024),waiting_payload_sha256:hex64}),approval_subject_sha256:hex64
|
|
25
|
+
});
|
|
26
|
+
const taskDagTask=z.strictObject({task_id:z.string().regex(/^[A-Za-z0-9._-]{1,64}$/),depends_on:z.array(z.string()).max(256),
|
|
27
|
+
input_artifacts:z.array(z.string()).max(128),output_artifacts:z.array(z.string()).max(128),
|
|
28
|
+
state:z.enum(['PENDING','RUNNING','COMPLETE','FAILED','STALE'])});
|
|
29
|
+
const taskDag=z.strictObject({schema:z.literal('deadbyte.task-dag.v1'),run_id:z.string().regex(/^[A-Za-z0-9._-]{1,96}$/),
|
|
30
|
+
goal_id:hex64,tasks:z.array(taskDagTask).min(1).max(256),dag_sha256:hex64});
|
|
18
31
|
const publicLoopSchema = z.strictObject({
|
|
19
32
|
schema:z.literal('deadbyte.autonomous-loop-state.v1'), phase:loopPhase,
|
|
20
33
|
plan_id:hex64.nullable(), plan_version:z.number().int().min(1).nullable(),
|
|
21
34
|
current_step:loopStep.nullable(), replan_count:z.number().int().min(0),
|
|
22
35
|
progress:loopProgress.nullable(), pending_action:loopPendingAction.nullable(),
|
|
23
|
-
pending_approval:z.boolean(), wait_reason:z.string().min(1).max(1024).nullable()
|
|
36
|
+
pending_approval:z.boolean(), wait_reason:z.string().min(1).max(1024).nullable(),
|
|
37
|
+
clarification:loopClarification.nullable().default(null),
|
|
38
|
+
approval_subject:loopApprovalSubject.nullable().default(null),
|
|
39
|
+
task_dag:taskDag.nullable().default(null)
|
|
24
40
|
});
|
|
25
41
|
function requireFields(value,ctx,fields) {
|
|
26
42
|
for (const field of fields) if (value[field] === undefined) ctx.addIssue({ code:'custom', path:[field], message:`${field} is required` });
|
|
@@ -52,7 +68,7 @@ export const autonomousSessionStatusOutputSchema = statusContract({
|
|
|
52
68
|
shape:{ configured:z.literal(true).optional(), armed:z.boolean().optional(), autonomous_armed:z.boolean().optional(),
|
|
53
69
|
autonomous_reason:z.string().optional(), autonomous_expires_at_utc:z.string().nullable().optional(),
|
|
54
70
|
coding_armed:z.boolean().optional(), coding_reason:z.string().optional(), root_ids:z.array(z.string()).max(64).optional(),
|
|
55
|
-
planner_backend:z.enum(['client_sampling','openai_compatible']).optional(), profile_ids:z.array(z.string()).max(128).optional(),
|
|
71
|
+
planner_backend:z.enum(['client_sampling','openai_compatible','openai_responses']).optional(), profile_ids:z.array(z.string()).max(128).optional(),
|
|
56
72
|
policy_sha256:hex64.optional(), coding_policy_sha256:hex64.optional() }
|
|
57
73
|
});
|
|
58
74
|
|
|
@@ -163,6 +163,9 @@ export const r34PlannerDecisionSchema = z.discriminatedUnion('kind',[
|
|
|
163
163
|
step_id:z.string().regex(ID),evidence:z.array(r34EvidenceSchema).min(1).max(32)}),
|
|
164
164
|
z.strictObject({schema:z.literal('deadbyte.autonomous-decision.v2'),kind:z.literal('replan'),
|
|
165
165
|
reason:z.string().min(1).max(512),steps:z.array(r34PlanStepSchema).min(1).max(24)}),
|
|
166
|
+
z.strictObject({schema:z.literal('deadbyte.autonomous-decision.v2'),kind:z.literal('clarify'),
|
|
167
|
+
question:z.string().min(1).max(1000),
|
|
168
|
+
choices:z.array(z.string().min(1).max(256)).min(2).max(5).default([])}),
|
|
166
169
|
z.strictObject({schema:z.literal('deadbyte.autonomous-decision.v2'),kind:z.literal('pause'),
|
|
167
170
|
reason:z.string().min(1).max(1000)}),
|
|
168
171
|
z.strictObject({schema:z.literal('deadbyte.autonomous-decision.v2'),kind:z.literal('finish'),
|