deadbyte-mcp 0.13.0 → 0.15.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.
Files changed (153) hide show
  1. package/.gitattributes +7 -0
  2. package/AGENTS.md +18 -0
  3. package/CONTEXT.md +3 -3
  4. package/MANIFEST.SHA256 +152 -75
  5. package/R42-PARENT.json +19 -0
  6. package/R43-PARENT.json +20 -0
  7. package/R44-DONORS.json +99 -0
  8. package/R44-PARENT.json +18 -0
  9. package/README.txt +7 -2
  10. package/bin/appcontainer-stage.obj +0 -0
  11. package/bin/bootstrap-advapi32.obj +0 -0
  12. package/bin/bootstrap-exitcode.obj +0 -0
  13. package/bin/bootstrap-kernel32.obj +0 -0
  14. package/bin/child-control-probe.obj +0 -0
  15. package/bin/child-control-stage.obj +0 -0
  16. package/bin/contained-reverse-worker.obj +0 -0
  17. package/bin/contained-transform-worker.obj +0 -0
  18. package/bin/containment-probe.obj +0 -0
  19. package/bin/deadbyte-contain.obj +0 -0
  20. package/bin/deadbyte-exec.obj +0 -0
  21. package/bin/deadbyte-process-host.obj +0 -0
  22. package/bin/deadbyte-tunnel-host.obj +0 -0
  23. package/controller/README.TXT +4 -5
  24. package/controller/deadbyte-controller.ps1 +1 -1
  25. package/controller/deadbyte-desktop-policy.json +5 -5
  26. package/controller/deadbyte-process-policy.json +4 -4
  27. package/docs/ARCHITECTURE.md +8 -1
  28. package/docs/R44-ARCHITECTURE.md +37 -0
  29. package/harness/r42-agent-evals.json +48 -0
  30. package/harness/task-contract.schema.json +33 -0
  31. package/package.json +15 -8
  32. package/proof/CONTAINMENT-BUILD.txt +6 -6
  33. package/scripts/agent-eval-r42.mjs +46 -0
  34. package/scripts/deferred-slot-operation-r42.mjs +126 -0
  35. package/scripts/deferred-slot-operation-r43.mjs +126 -0
  36. package/scripts/deferred-slot-operation-r44.mjs +126 -0
  37. package/scripts/final-closure-r42.mjs +116 -0
  38. package/scripts/final-closure-r43.mjs +115 -0
  39. package/scripts/final-closure-r44.mjs +121 -0
  40. package/scripts/final-closure-verify-r42.mjs +223 -0
  41. package/scripts/final-closure-verify-r43.mjs +213 -0
  42. package/scripts/final-closure-verify-r44.mjs +220 -0
  43. package/scripts/finalize-r43.mjs +217 -0
  44. package/scripts/finalize-r44.mjs +217 -0
  45. package/scripts/full-regression-r43.mjs +20 -0
  46. package/scripts/full-regression-r44.mjs +20 -0
  47. package/scripts/gate-windows-r42.ps1 +54 -0
  48. package/scripts/gate-windows-r43.ps1 +36 -0
  49. package/scripts/gate-windows-r44.ps1 +36 -0
  50. package/scripts/release-parity-r42.mjs +235 -0
  51. package/scripts/release-parity-r43.mjs +218 -0
  52. package/scripts/release-parity-r44.mjs +242 -0
  53. package/scripts/release-parity-tests-r42.ps1 +6 -0
  54. package/scripts/release-parity-tests-r43.ps1 +8 -0
  55. package/scripts/release-parity-tests-r44.ps1 +8 -0
  56. package/scripts/verify-r42-parent.mjs +101 -0
  57. package/scripts/verify-r43-parent.mjs +126 -0
  58. package/scripts/verify-r44-parent.mjs +142 -0
  59. package/scripts/windows-gate-evidence-r42.mjs +64 -0
  60. package/scripts/windows-gate-evidence-r43.mjs +55 -0
  61. package/scripts/windows-gate-evidence-r44.mjs +55 -0
  62. package/src/agent-backend-r44.mjs +254 -0
  63. package/src/agent-eval-r42.mjs +73 -0
  64. package/src/agent-eval-runner-r42.mjs +138 -0
  65. package/src/agent-governance-r42.mjs +142 -0
  66. package/src/autonomous-context-engine.mjs +2 -0
  67. package/src/autonomous-loop-state.mjs +10 -0
  68. package/src/autonomous-mcp-tools.mjs +21 -6
  69. package/src/autonomous-output-contracts.mjs +18 -2
  70. package/src/autonomous-planner-contracts.mjs +3 -0
  71. package/src/autonomous-planner.mjs +46 -6
  72. package/src/autonomous-policy.mjs +50 -5
  73. package/src/autonomous-runtime.mjs +138 -25
  74. package/src/autonomous-supervisor-host.mjs +12 -2
  75. package/src/autonomous-supervisor.mjs +1 -1
  76. package/src/capability-ledger.mjs +2 -1
  77. package/src/codex-app-server-r44.mjs +154 -0
  78. package/src/coding-plane.mjs +66 -19
  79. package/src/durable-job-r44.mjs +145 -0
  80. package/src/evidence-publisher-r42.mjs +76 -0
  81. package/src/external-job-provider-r44.mjs +126 -0
  82. package/src/host-plane.mjs +27 -6
  83. package/src/job-supervisor-host-r44.mjs +112 -0
  84. package/src/machine-fs-smoke-client.mjs +1 -1
  85. package/src/machine-fs.mjs +21 -10
  86. package/src/mcp-host-smoke-client.mjs +1 -1
  87. package/src/mcp-server.mjs +20 -3
  88. package/src/mcp-smoke-client.mjs +1 -1
  89. package/src/native-diff-v2.mjs +3 -3
  90. package/src/native-diff.mjs +3 -2
  91. package/src/observation-smoke-client.mjs +1 -1
  92. package/src/operator-surface.mjs +2 -2
  93. package/src/process-policy.mjs +19 -0
  94. package/src/process-runtime.mjs +5 -2
  95. package/src/process-smoke-client.mjs +2 -2
  96. package/src/provider-circuit.mjs +10 -4
  97. package/src/r44-compact-disarmed-probe.mjs +74 -0
  98. package/src/r44-full-surface-probe.mjs +34 -0
  99. package/src/remote-device-r44.mjs +205 -0
  100. package/src/remote-mcp-autonomous-smoke-client.mjs +4 -4
  101. package/src/remote-mcp-r44-parity-client.mjs +203 -0
  102. package/src/remote-mcp-smoke-client.mjs +7 -4
  103. package/src/runtime-update.mjs +64 -31
  104. package/src/skill-mcp-tools-r44.mjs +37 -0
  105. package/src/skill-plane-r44.mjs +203 -0
  106. package/src/tunnel-stdio-compat-smoke.mjs +1 -1
  107. package/src/version.mjs +1 -1
  108. package/test/agent-backend-r44.test.mjs +115 -0
  109. package/test/agent-eval-r42.test.mjs +39 -0
  110. package/test/agent-eval-runner-r42.test.mjs +61 -0
  111. package/test/agent-governance-r42.test.mjs +77 -0
  112. package/test/agent-runtime.test.mjs +3 -3
  113. package/test/autonomous-loop.test.mjs +97 -11
  114. package/test/autonomous-r34-e2e.test.mjs +2 -1
  115. package/test/autonomous-runtime.test.mjs +2 -2
  116. package/test/capability-ledger-r36.test.mjs +3 -3
  117. package/test/cli-entrypoint.test.mjs +1 -1
  118. package/test/codex-app-server-r44.test.mjs +115 -0
  119. package/test/codex-autonomous-r44.test.mjs +87 -0
  120. package/test/coding-plane.test.mjs +2 -2
  121. package/test/coding-runtime.test.mjs +36 -1
  122. package/test/compact-catalog-r36.test.mjs +3 -3
  123. package/test/compact-max-catalog-r40.test.mjs +4 -2
  124. package/test/compact-max-catalog-r44.test.mjs +97 -0
  125. package/test/core.test.mjs +1 -1
  126. package/test/durable-job-r44.test.mjs +75 -0
  127. package/test/evidence-publisher-r42.test.mjs +53 -0
  128. package/test/external-job-provider-r44.test.mjs +46 -0
  129. package/test/full-max-catalog-r40.test.mjs +5 -3
  130. package/test/full-max-catalog-r44.test.mjs +91 -0
  131. package/test/host-plane.test.mjs +15 -1
  132. package/test/human-clarification-r42.test.mjs +53 -0
  133. package/test/native-diff.test.mjs +28 -0
  134. package/test/output-schema.test.mjs +2 -2
  135. package/test/process-policy.test.mjs +22 -1
  136. package/test/prompts-resources.test.mjs +1 -1
  137. package/test/provider-responses-r42.test.mjs +90 -0
  138. package/test/r33-closeout-regression.test.mjs +14 -16
  139. package/test/r33-finalization.test.mjs +7 -7
  140. package/test/r42-finalization.test.mjs +38 -0
  141. package/test/r43-finalization.test.mjs +31 -0
  142. package/test/r43-finalizer.test.mjs +34 -0
  143. package/test/r43-parent.test.mjs +29 -0
  144. package/test/r44-finalization.test.mjs +58 -0
  145. package/test/r44-finalizer.test.mjs +48 -0
  146. package/test/r44-parent.test.mjs +25 -0
  147. package/test/release-version.test.mjs +53 -19
  148. package/test/remote-device-r44.test.mjs +104 -0
  149. package/test/runtime-self-update.test.mjs +42 -0
  150. package/test/skill-mcp-r44.test.mjs +38 -0
  151. package/test/skill-plane-r44.test.mjs +93 -0
  152. package/test/standing-grants.test.mjs +5 -1
  153. package/test/task-contract-r42.test.mjs +41 -0
@@ -0,0 +1,61 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { runAgentEvalSuiteR42, readAndVerifyR42EvalEvidence } from '../src/agent-eval-runner-r42.mjs';
7
+
8
+ const H=value=>String(value).repeat(64).slice(0,64);
9
+ const suite={schema:'deadbyte.agent-eval-suite.v1',suite_id:'suite',
10
+ cases:[{schema:'deadbyte.agent-eval-case.v1',case_id:'case-1',task:'Safe action',
11
+ allowed_context:{visible:'YES'},hidden_oracle:{secret:'ORACLE_ONLY',expected:'bounded'},acceptance:['bounded']}]
12
+ };
13
+
14
+ test('R42 model eval keeps hidden oracle out of generator and binds passed evidence to manifest',async()=>{
15
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r42-eval-'));
16
+ const calls=[];
17
+ try{
18
+ const run=await runAgentEvalSuiteR42({suite,subjectManifestSha256:H('a'),evidenceRoot:root,
19
+ clock:()=>new Date('2026-09-20T02:00:00.000Z'),
20
+ requestModel:async call=>{
21
+ calls.push(call);
22
+ if(call.role==='generator')return {backend:'fake',model:'fake',response_id:'g1',raw_text:JSON.stringify({artifact:{plan:['inspect','verify']}})};
23
+ return {backend:'fake',model:'fake',response_id:'j1',raw_text:JSON.stringify({schema:'deadbyte.agent-eval-judge-result.v1',
24
+ verdict:'pass',advisory:true,findings:[],evidence_refs:[H('1')]})};
25
+ }});
26
+ assert.equal(calls.length,2);
27
+ assert.equal(calls[0].prompt.includes('ORACLE_ONLY'),false);
28
+ assert.equal(calls[1].prompt.includes('ORACLE_ONLY'),true);
29
+ assert.equal(run.payload.status,'passed');
30
+ assert.equal(run.payload.subject_manifest_sha256,H('a'));
31
+ assert.equal(run.publication.status,'created');
32
+ const verified=await readAndVerifyR42EvalEvidence(run.publication.path,{subjectManifestSha256:H('a'),suiteSha256:run.payload.suite_sha256});
33
+ assert.equal(verified.status,'passed');
34
+ }finally{await rm(root,{recursive:true,force:true});}
35
+ });
36
+
37
+ test('R42 model eval rejects failed judge and publishes no PASS evidence',async()=>{
38
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r42-eval-fail-'));
39
+ try{
40
+ await assert.rejects(runAgentEvalSuiteR42({suite,subjectManifestSha256:H('b'),evidenceRoot:root,
41
+ requestModel:async call=>call.role==='generator'
42
+ ?{raw_text:JSON.stringify({artifact:{plan:['unsafe']}})}
43
+ :{raw_text:JSON.stringify({schema:'deadbyte.agent-eval-judge-result.v1',verdict:'fail',advisory:true,
44
+ findings:[{code:'AUTH',severity:'critical',evidence:'authority widening'}],evidence_refs:[H('2')]})}}),/suite failed/i);
45
+ await assert.rejects(readFile(path.join(root,'agent_eval',`r42-${H('b').slice(0,16)}-`),'utf8'));
46
+ }finally{await rm(root,{recursive:true,force:true});}
47
+ });
48
+
49
+ test('R42 eval evidence tampering fails independent verification',async()=>{
50
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r42-eval-tamper-'));
51
+ try{
52
+ const run=await runAgentEvalSuiteR42({suite,subjectManifestSha256:H('c'),evidenceRoot:root,
53
+ requestModel:async call=>call.role==='generator'
54
+ ?{raw_text:JSON.stringify({artifact:{plan:['safe']}})}
55
+ :{raw_text:JSON.stringify({schema:'deadbyte.agent-eval-judge-result.v1',verdict:'pass',advisory:true,findings:[],evidence_refs:[H('3')]})}});
56
+ const raw=JSON.parse(await readFile(run.publication.path,'utf8'));
57
+ raw.payload.cases[0].result.verdict='fail';
58
+ await writeFile(run.publication.path,JSON.stringify(raw));
59
+ await assert.rejects(readAndVerifyR42EvalEvidence(run.publication.path,{subjectManifestSha256:H('c')}),/hash mismatch/i);
60
+ }finally{await rm(root,{recursive:true,force:true});}
61
+ });
@@ -0,0 +1,77 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {
4
+ bindApprovalSubject, validateBoundApproval, createTaskDag, invalidateTaskDag,
5
+ createEvidenceClaim, evidenceSatisfies, deriveEffectiveRigor, taskDagFromPlan,
6
+ createDecisionRecord, evaluateDecisionFreshness
7
+ } from '../src/agent-governance-r42.mjs';
8
+
9
+ const H=value=>String(value).repeat(64).slice(0,64);
10
+
11
+ test('R42 approval binds exact artifact, event head, scope and expiry',()=>{
12
+ const subject=bindApprovalSubject({goal_id:H('1'),reviewed_event_seq:9,reviewed_event_sha256:H('2'),
13
+ journal_head_sha256:H('3'),scope:'resume_goal',subject:{diff_id:H('4'),operation_count:2}});
14
+ const approval={schema:'deadbyte.bound-approval.v1',approval_subject_sha256:subject.approval_subject_sha256,
15
+ reviewed_event_seq:9,reviewed_event_sha256:H('2'),journal_head_sha256:H('3'),scope:'resume_goal',
16
+ issued_at_utc:'2026-09-20T01:00:00.000Z',expires_at_utc:'2026-09-20T01:05:00.000Z',provenance:'operator_mcp'};
17
+ assert.equal(validateBoundApproval({approval,subject,now:new Date('2026-09-20T01:01:00Z')}),true);
18
+ assert.throws(()=>validateBoundApproval({approval:{...approval,journal_head_sha256:H('9')},subject,
19
+ now:new Date('2026-09-20T01:01:00Z')}),/journal|subject/i);
20
+ assert.throws(()=>validateBoundApproval({approval,subject:{...subject,subject:{diff_id:H('5'),operation_count:2}},
21
+ now:new Date('2026-09-20T01:01:00Z')}),/subject/i);
22
+ assert.throws(()=>validateBoundApproval({approval,subject,now:new Date('2026-09-20T01:06:00Z')}),/expired/i);
23
+ });
24
+
25
+ test('R42 canonical DAG propagates STALE only through affected descendants',()=>{
26
+ const dag=createTaskDag({run_id:'run-1',goal_id:H('a'),tasks:[
27
+ {task_id:'inspect',depends_on:[],input_artifacts:['source'],output_artifacts:['analysis']},
28
+ {task_id:'patch',depends_on:['inspect'],input_artifacts:['analysis'],output_artifacts:['diff']},
29
+ {task_id:'test',depends_on:['patch'],input_artifacts:['diff'],output_artifacts:['test-result']},
30
+ {task_id:'docs',depends_on:[],input_artifacts:['readme'],output_artifacts:['docs-result']}
31
+ ]});
32
+ const next=invalidateTaskDag(dag,{changed_artifact_ids:['analysis']});
33
+ assert.deepEqual(next.tasks.filter(task=>task.state==='STALE').map(task=>task.task_id),['patch','test']);
34
+ assert.equal(next.tasks.find(task=>task.task_id==='inspect').state,'PENDING');
35
+ assert.equal(next.tasks.find(task=>task.task_id==='docs').state,'PENDING');
36
+ assert.throws(()=>createTaskDag({run_id:'x',goal_id:H('a'),tasks:[
37
+ {task_id:'a',depends_on:['b'],input_artifacts:[],output_artifacts:[]},
38
+ {task_id:'b',depends_on:['a'],input_artifacts:[],output_artifacts:[]}
39
+ ]}),/cycle/i);
40
+ });
41
+
42
+ test('R42 one canonical DAG is deterministically derived from the authoritative plan',()=>{
43
+ const plan={steps:[{step_id:'inspect',status:'completed'},{step_id:'patch',status:'active'},{step_id:'verify',status:'pending'}]};
44
+ const one=taskDagFromPlan({run_id:H('7'),goal_id:H('7'),plan});
45
+ const two=taskDagFromPlan({run_id:H('7'),goal_id:H('7'),plan});
46
+ assert.equal(one.dag_sha256,two.dag_sha256);
47
+ assert.deepEqual(one.tasks.map(task=>[task.task_id,task.depends_on,task.state]),[
48
+ ['inspect',[],'COMPLETE'],['patch',['inspect'],'RUNNING'],['verify',['patch'],'PENDING']
49
+ ]);
50
+ });
51
+
52
+ test('R42 evidence ladder never lets weak evidence masquerade as runtime proof',()=>{
53
+ const source=createEvidenceClaim({claim_id:'claim-1',statement:'provider shape matches source',level:'source',refs:[H('b')]});
54
+ assert.equal(evidenceSatisfies(source,'source'),true);
55
+ assert.equal(evidenceSatisfies(source,'automated'),false);
56
+ assert.equal(evidenceSatisfies(source,'runtime'),false);
57
+ assert.throws(()=>createEvidenceClaim({claim_id:'claim-2',statement:'bad',level:'runtime',refs:[]}),/reference/i);
58
+ });
59
+
60
+ test('R42 rigor is derived once and cannot downgrade below risk minimum',()=>{
61
+ assert.deepEqual(deriveEffectiveRigor({project_default:'lightweight',risk_level:'high',explicit_override:'structured'}),{
62
+ project_default:'lightweight',risk_minimum:'agentic',explicit_override:'structured',effective_rigor:'agentic'
63
+ });
64
+ assert.equal(deriveEffectiveRigor({project_default:'structured',risk_level:'low',explicit_override:'agentic'}).effective_rigor,'agentic');
65
+ });
66
+
67
+ test('R42 decisions become stale deterministically without deleting history',()=>{
68
+ const record=createDecisionRecord({decision_id:'provider-choice',decision:'Use Responses adapter',reason:'strict bounded JSON',
69
+ evidence_refs:[H('c')],invalidates_if:[
70
+ {kind:'artifact_sha_changed',artifact_id:'provider-contract',expected_sha256:H('d')},
71
+ {kind:'policy_sha_changed',expected_sha256:H('e')}
72
+ ]});
73
+ assert.equal(evaluateDecisionFreshness(record,{artifact_sha256:{'provider-contract':H('d')},policy_sha256:H('e')}).state,'CURRENT');
74
+ const stale=evaluateDecisionFreshness(record,{artifact_sha256:{'provider-contract':H('f')},policy_sha256:H('e')});
75
+ assert.equal(stale.state,'STALE');
76
+ assert.deepEqual(stale.invalidated_by,[{kind:'artifact_sha_changed',artifact_id:'provider-contract',expected_sha256:H('d'),observed_sha256:H('f')}]);
77
+ });
@@ -1,4 +1,4 @@
1
- import test from 'node:test';
1
+ import test from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
3
  import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises';
4
4
  import os from 'node:os';
@@ -20,7 +20,7 @@ const AGENT_TOOLS = [
20
20
  'agent_task_submit',
21
21
  'agent_task_verify'
22
22
  ];
23
- const BASE_TOOLS = ['contained_transform', 'evidence_verify', 'file_read', 'proof_execute', 'workspace_open'];
23
+ const BASE_TOOLS = ['contained_transform', 'evidence_verify', 'file_read', 'proof_execute', 'skill_catalog', 'workspace_open'];
24
24
 
25
25
  function structured(result, label) {
26
26
  if (result.isError) throw new Error(`${label}: ${JSON.stringify(result.structuredContent ?? result.content)}`);
@@ -68,7 +68,7 @@ async function cleanup(f, client) {
68
68
  await rm(f.trust, { recursive: true, force: true }).catch(() => {});
69
69
  }
70
70
 
71
- test('agent runtime without an explicit host policy exposes only base plus durable task tools', async () => {
71
+ test('R44 agent runtime exposes base skill catalog plus durable task tools without host authority', async () => {
72
72
  const f = await fixture();
73
73
  let client;
74
74
  try {
@@ -32,7 +32,7 @@ async function startPlanner(decisions) {
32
32
  await new Promise(resolve => server.listen(0,'127.0.0.1',resolve));
33
33
  return { server, prompts, endpoint:`http://127.0.0.1:${server.address().port}/v1/chat/completions` };
34
34
  }
35
- async function fixture(decisionPrefix = []) {
35
+ async function fixture(decisionPrefix = [], options = {}) {
36
36
  const root = await mkdtemp(path.join(os.tmpdir(),'deadbyte-autonomous-loop-'));
37
37
  const project = path.join(root,'project'); const trust = path.join(root,'trust');
38
38
  await mkdir(project); await mkdir(trust);
@@ -101,14 +101,17 @@ async function fixture(decisionPrefix = []) {
101
101
  const policy = await loadAutonomousPolicy(autonomousPolicyPath,codingPolicy);
102
102
  await writeFile(policy.lease_file,JSON.stringify({ schema:'deadbyte.autonomous-lease.v1',
103
103
  policy_sha256:policy.policy_sha256, instance_nonce:randomBytes(32).toString('hex'), root_ids:['project'],
104
- armed_at_utc:new Date().toISOString(), expires_at_utc:new Date(Date.now()+1800000).toISOString() }));
104
+ armed_at_utc:new Date().toISOString(), expires_at_utc:new Date(Date.now()+(options.autonomousLeaseMs??1800000)).toISOString() }));
105
105
  const codingRuntime = createCodingRuntime({ policy:codingPolicy,
106
106
  signingKeyPath:keys.privateKeyPath, verifyKeyPath:keys.publicKeyPath });
107
+ const autonomousCodingRuntime = options.codingRuntimeFactory
108
+ ? options.codingRuntimeFactory(codingRuntime,{policy})
109
+ : codingRuntime;
107
110
  const nativeDiff = createNativeDiffRuntime({ policy,codingRuntime,
108
111
  signingKeyPath:keys.privateKeyPath, verifyKeyPath:keys.publicKeyPath });
109
- const runtime = createAutonomousRuntime({ policy,codingRuntime,nativeDiffRuntime:nativeDiff,
112
+ const runtime = createAutonomousRuntime({ policy,codingRuntime:autonomousCodingRuntime,nativeDiffRuntime:nativeDiff,
110
113
  signingKeyPath:keys.privateKeyPath, verifyKeyPath:keys.publicKeyPath });
111
- return { root,project,program,keys,policy,codingRuntime,nativeDiff,runtime,planner,
114
+ return { root,project,program,keys,policy,codingRuntime,autonomousCodingRuntime,nativeDiff,runtime,planner,
112
115
  initialHash,almostHash,fixedHash };
113
116
  }
114
117
 
@@ -144,6 +147,83 @@ test('R24 autonomous goal diagnoses a failing test, replans with native diff, be
144
147
  }
145
148
  });
146
149
 
150
+ test('autonomous profile execution is capped by the absolute autonomous lease without changing coding request identity', async () => {
151
+ const calls=[];
152
+ const f=await fixture([],{
153
+ autonomousLeaseMs:30000,
154
+ codingRuntimeFactory:base=>Object.freeze({
155
+ ...base,
156
+ commandRun:async args=>{
157
+ const result=await base.commandRun(args);
158
+ calls.push({kind:'command',deadline:args.executionDeadlineUtc,has_precheck:typeof args.preExecutionCheck==='function',
159
+ timeout_ms:result.timeout_ms,request_sha256:result.request_sha256});
160
+ return result;
161
+ },
162
+ benchmarkRun:async args=>{
163
+ const result=await base.benchmarkRun(args);
164
+ calls.push({kind:'benchmark',deadline:args.executionDeadlineUtc,has_precheck:typeof args.preExecutionCheck==='function',
165
+ request_sha256:result.request_sha256});
166
+ return result;
167
+ }
168
+ })
169
+ });
170
+ try {
171
+ const submitted=await f.runtime.submit({submissionId:'profile-deadline',rootId:'project',
172
+ title:'deadline-bound profiles',goal:'make exported value equal fixed',
173
+ acceptance:'build, test, and benchmark must pass',maxIterations:10});
174
+ const result=await f.runtime.run({goalId:submitted.goal_id,maxCycles:16});
175
+ assert.equal(result.status,'completed');
176
+ assert.ok(calls.length>=3);
177
+ assert.ok(calls.every(call=>typeof call.deadline==='string'&&Number.isFinite(Date.parse(call.deadline))));
178
+ assert.ok(calls.every(call=>call.has_precheck===true));
179
+ const command=calls.find(call=>call.kind==='command');
180
+ assert.ok(command.timeout_ms>=100&&command.timeout_ms<30000);
181
+ const build=f.policy.profiles.build;
182
+ assert.equal(command.request_sha256,f.codingRuntime.requestSha('coding_command_run',{
183
+ root_id:build.root_id,command_id:build.command_id,script_path:build.script_path,script_sha256:build.script_sha256,
184
+ args:build.args,cwd:build.cwd,timeout_ms:build.timeout_ms
185
+ }));
186
+ } finally {
187
+ await new Promise(resolve=>f.planner.server.close(resolve));
188
+ await rm(f.root,{recursive:true,force:true});
189
+ }
190
+ });
191
+
192
+ test('autonomous profile nonce rotation immediately before spawn fails closed without a coding receipt', async () => {
193
+ let rotated=false;
194
+ const f=await fixture([],{
195
+ codingRuntimeFactory:(base,{policy})=>Object.freeze({
196
+ ...base,
197
+ commandRun:async args=>{
198
+ if(!rotated){
199
+ rotated=true;
200
+ const lease=JSON.parse(await readFile(policy.lease_file,'utf8'));
201
+ await writeFile(policy.lease_file,JSON.stringify({...lease,instance_nonce:'f'.repeat(64)}));
202
+ }
203
+ return base.commandRun(args);
204
+ }
205
+ })
206
+ });
207
+ try {
208
+ const submitted=await f.runtime.submit({submissionId:'profile-revoke-before-spawn',rootId:'project',
209
+ title:'profile revoke',goal:'make exported value equal fixed',
210
+ acceptance:'profile dispatch must fail closed after authority rotation',maxIterations:10});
211
+ const result=await f.runtime.run({goalId:submitted.goal_id,maxCycles:16});
212
+ assert.equal(rotated,true);
213
+ assert.equal(result.status,'paused');
214
+ const journal=await f.runtime.events({goalId:submitted.goal_id,fromSeq:1,limit:200});
215
+ const started=journal.events.find(event=>event.type==='profile_started'&&event.payload?.profile_id==='build');
216
+ assert.ok(started);
217
+ assert.match(started.payload.authority_instance_nonce,/^[0-9a-f]{64}$/);
218
+ assert.ok(journal.events.some(event=>event.type==='goal_paused'&&event.payload?.reason==='profile_execution_without_receipt'));
219
+ assert.equal(journal.events.some(event=>event.type==='profile_completed'&&event.payload?.profile_id==='build'),false);
220
+ assert.equal((await f.codingRuntime.verifyReceiptDetailed(started.payload.receipt_id)).ok,false);
221
+ } finally {
222
+ await new Promise(resolve=>f.planner.server.close(resolve));
223
+ await rm(f.root,{recursive:true,force:true});
224
+ }
225
+ });
226
+
147
227
  test('provider planner repairs an invalid structured decision inside the same run', async () => {
148
228
  const f = await fixture(['not-json']);
149
229
  try {
@@ -166,7 +246,7 @@ test('provider planner repairs an invalid structured decision inside the same ru
166
246
  }
167
247
  });
168
248
 
169
- test('provider circuit survives runtime restart, suppresses OPEN calls, then permits one HALF_OPEN probe', async () => {
249
+ test('provider circuit survives runtime restart and permits exactly one probe at the HALF_OPEN boundary', async () => {
170
250
  const failures=[{__http_status:503},{__http_status:503},{__http_status:503}];
171
251
  const f=await fixture(failures);
172
252
  try {
@@ -178,17 +258,23 @@ test('provider circuit survives runtime restart, suppresses OPEN calls, then per
178
258
  const opened=await f.runtime.run({goalId:submitted.goal_id,maxCycles:2});
179
259
  assert.equal(opened.status,'paused');
180
260
  assert.equal(opened.reason,'provider_circuit_open');
261
+ const openedJournal=await f.runtime.events({goalId:submitted.goal_id,fromSeq:1,limit:200});
262
+ const openPause=[...openedJournal.events].reverse().find(event=>event.type==='goal_paused'&&event.payload?.reason==='provider_circuit_open');
263
+ assert.equal(openPause?.payload?.circuit_state,'OPEN',JSON.stringify(openPause));
181
264
  assert.equal(f.planner.prompts.length,3);
182
265
 
183
- const immediate=await f.runtime.run({goalId:submitted.goal_id,maxCycles:2});
184
- assert.equal(immediate.status,'paused');
185
- assert.equal(f.planner.prompts.length,3,'OPEN circuit must suppress provider invocation');
186
-
187
266
  const restarted=createAutonomousRuntime({policy:f.policy,codingRuntime:f.codingRuntime,nativeDiffRuntime:f.nativeDiff,
188
267
  signingKeyPath:f.keys.privateKeyPath,verifyKeyPath:f.keys.publicKeyPath});
189
- await new Promise(resolve=>setTimeout(resolve,130));
190
- const circuitBeforeResume=await restarted.providerCircuitStatus({goalId:submitted.goal_id});
268
+ const deadline=Date.now()+2_000;
269
+ let circuitBeforeResume;
270
+ do {
271
+ circuitBeforeResume=await restarted.providerCircuitStatus({goalId:submitted.goal_id});
272
+ if(circuitBeforeResume.state==='HALF_OPEN') break;
273
+ assert.equal(circuitBeforeResume.state,'OPEN',JSON.stringify(circuitBeforeResume));
274
+ await new Promise(resolve=>setTimeout(resolve,10));
275
+ } while(Date.now()<deadline);
191
276
  assert.equal(circuitBeforeResume.state,'HALF_OPEN',JSON.stringify(circuitBeforeResume));
277
+
192
278
  const resumed=await restarted.run({goalId:submitted.goal_id,maxCycles:1});
193
279
  assert.notEqual(resumed.status,'paused');
194
280
  assert.equal(f.planner.prompts.length,4,'HALF_OPEN must permit exactly one provider probe');
@@ -75,7 +75,8 @@ test('R34 missing authority waits durably and approval resumes the same phase af
75
75
  assert.equal(journal.events.filter(event=>event.type==='action_started').length,0);
76
76
 
77
77
  await f.arm();
78
- const approved = await f.runtime.approve({goalId:goal.goal_id,decision:'approve'});
78
+ const approved = await f.runtime.approve({goalId:goal.goal_id,decision:'approve',
79
+ approvalSubjectSha256:result.loop.approval_subject.approval_subject_sha256});
79
80
  assert.equal(approved.status,'approved');
80
81
  assert.equal(approved.loop.phase,'ACTING');
81
82
  const resumed = await f.runtime.run({goalId:goal.goal_id,maxCycles:2});
@@ -20,7 +20,7 @@ const r24Tools = [
20
20
  'native_diff_apply', 'native_diff_preview', 'native_diff_verify'
21
21
  ];
22
22
  const r23Tools = [
23
- 'contained_transform','evidence_verify','file_read','proof_execute','workspace_open',
23
+ 'contained_transform','evidence_verify','file_read','proof_execute','skill_catalog','workspace_open',
24
24
  'agent_task_approve','agent_task_cancel','agent_task_events','agent_task_run',
25
25
  'agent_task_status','agent_task_submit','agent_task_verify',
26
26
  'coding_benchmark_run','coding_command_run','coding_file_read','coding_file_write','coding_files_read',
@@ -75,7 +75,7 @@ async function fixture() {
75
75
  return { root, client, transport };
76
76
  }
77
77
 
78
- test('R36 hermetic autonomous fixture exposes the exact 49-tool no-host catalog', async () => {
78
+ test('R44 hermetic autonomous fixture exposes the exact 50-tool no-host catalog', async () => {
79
79
  const f = await fixture();
80
80
  try {
81
81
  await f.client.connect(f.transport);
@@ -92,12 +92,12 @@ test('R36 closure fails when a production route is lost or advertised tool is ab
92
92
  }), /compact tool/i);
93
93
  });
94
94
 
95
- test('R36 compact surface hard-fails above the observed 42-tool ceiling', () => {
95
+ test('R44 compact surface hard-fails above the observed 43-tool ceiling', () => {
96
96
  const ledger=createCapabilityLedger([entry({})]);
97
- const tooMany=Array.from({length:43},(_,i)=>i===0?'coding_inspect':`extra_${i}`);
97
+ const tooMany=Array.from({length:44},(_,i)=>i===0?'coding_inspect':`extra_${i}`);
98
98
  assert.throws(() => assertCapabilityClosure({
99
99
  ledger,
100
100
  fullToolSnapshot:snapshot(['coding_search']),
101
101
  compactToolSnapshot:snapshot(tooMany)
102
- }), /42/i);
102
+ }), /43/i);
103
103
  });
@@ -21,7 +21,7 @@ test('npx CLI reports the canonical package version through --version, -v and ve
21
21
  for(const arg of ['--version','-v','version']){
22
22
  const result=spawnSync(process.execPath,[cliPath,arg],{cwd:root,encoding:'utf8',windowsHide:true});
23
23
  assert.equal(result.status,0,result.stderr);
24
- assert.equal(result.stdout.trim(),'0.13.0');
24
+ assert.equal(result.stdout.trim(),'0.15.0');
25
25
  }
26
26
  });
27
27
 
@@ -0,0 +1,115 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { EventEmitter } from 'node:events';
4
+ import { mkdtemp, rm } from 'node:fs/promises';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import { createCodexAppServerR44 } from '../src/codex-app-server-r44.mjs';
8
+
9
+ async function waitFor(predicate,timeoutMs=2000){
10
+ const deadline=Date.now()+timeoutMs;
11
+ while(Date.now()<deadline){const value=predicate();if(value)return value;await new Promise(resolve=>setTimeout(resolve,5));}
12
+ throw new Error('waitFor timeout');
13
+ }
14
+ class FakeTransport extends EventEmitter{
15
+ constructor(){super();this.sent=[];}
16
+ async start(){}
17
+ async send(value){this.sent.push(structuredClone(value));}
18
+ async stop(){}
19
+ request(method){return this.sent.findLast?.(x=>x.method===method&&Object.hasOwn(x,'id'))??[...this.sent].reverse().find(x=>x.method===method&&Object.hasOwn(x,'id'));}
20
+ reply(id,result){this.emit('message',{id,result});}
21
+ notify(method,params){this.emit('message',{method,params});}
22
+ serverRequest(id,method,params){this.emit('message',{id,method,params});}
23
+ }
24
+
25
+ test('R44 Codex adapter initializes no-jsonrpc wire and starts safe persistent planner thread',async()=>{
26
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r44-codex-'));const transport=new FakeTransport();
27
+ try{
28
+ const codex=await createCodexAppServerR44({stateDir:root,transportFactory:async()=>transport,
29
+ model:'gpt-test',cwd:root,clientVersion:'0.15.0'});
30
+ const start=codex.start();
31
+ const init=await waitFor(()=>transport.request('initialize'));
32
+ assert.equal('jsonrpc' in init,false);
33
+ assert.deepEqual(init.params.clientInfo,{name:'deadbyte-mcp',version:'0.15.0'});
34
+ assert.equal(init.params.capabilities.experimentalApi,false);
35
+ transport.reply(init.id,{userAgent:'codex-test'});
36
+ await start;
37
+ const initialized=transport.sent.find(x=>x.method==='initialized'&&!Object.hasOwn(x,'id'));
38
+ assert.ok(initialized);assert.equal('jsonrpc' in initialized,false);
39
+
40
+ const ensure=codex.ensureThread({logicalId:'planner'});
41
+ const req=await waitFor(()=>transport.request('thread/start'));
42
+ assert.equal(req.params.model,'gpt-test');assert.equal(req.params.cwd,root);
43
+ assert.equal(req.params.approvalPolicy,'never');assert.equal(req.params.sandbox,'read-only');
44
+ assert.equal(req.params.ephemeral,false);assert.equal(req.params.approvalsReviewer,'user');
45
+ transport.reply(req.id,{thread:{id:'thread-1'},model:'gpt-test',modelProvider:'openai',cwd:root,
46
+ approvalPolicy:'never',approvalsReviewer:'user',sandbox:{type:'readOnly'}});
47
+ const thread=await ensure;assert.equal(thread.thread_id,'thread-1');assert.equal(thread.resumed,false);
48
+ await codex.close();
49
+ }finally{await rm(root,{recursive:true,force:true});}
50
+ });
51
+
52
+ test('R44 Codex planner routes final answer by turn id and declines provider approvals',async()=>{
53
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r44-codex-turn-'));const transport=new FakeTransport();
54
+ try{
55
+ const codex=await createCodexAppServerR44({stateDir:root,transportFactory:async()=>transport,model:'gpt-test',cwd:root});
56
+ const start=codex.start();const init=await waitFor(()=>transport.request('initialize'));transport.reply(init.id,{});await start;
57
+ const ensure=codex.ensureThread({logicalId:'planner'});const ts=await waitFor(()=>transport.request('thread/start'));
58
+ transport.reply(ts.id,{thread:{id:'thread-1'},model:'gpt-test'});await ensure;
59
+
60
+ const planned=codex.runPlanner({logicalId:'planner',prompt:'Return JSON',timeoutMs:2000});
61
+ const turn=await waitFor(()=>transport.request('turn/start'));
62
+ assert.equal(turn.params.threadId,'thread-1');assert.deepEqual(turn.params.input,[{type:'text',text:'Return JSON'}]);
63
+ assert.equal(turn.params.approvalPolicy,'never');
64
+ transport.reply(turn.id,{turn:{id:'turn-1',status:'inProgress'}});
65
+ transport.notify('item/completed',{threadId:'thread-1',turnId:'other',item:{id:'x',type:'agentMessage',text:'wrong'}});
66
+ transport.notify('item/completed',{threadId:'thread-1',turnId:'turn-1',item:{id:'m1',type:'agentMessage',text:'working',phase:'commentary'}});
67
+ transport.notify('item/completed',{threadId:'thread-1',turnId:'turn-1',item:{id:'m2',type:'agentMessage',text:'{"schema":"ok"}',phase:'final_answer'}});
68
+ transport.notify('turn/completed',{threadId:'thread-1',turn:{id:'turn-1',status:'completed'}});
69
+ const result=await planned;
70
+ assert.equal(result.backend,'codex_app_server');assert.equal(result.raw_text,'{"schema":"ok"}');assert.equal(result.response_id,'turn-1');
71
+
72
+ transport.serverRequest('approval-1','item/commandExecution/requestApproval',{threadId:'thread-1',turnId:'turn-1'});
73
+ const denial=await waitFor(()=>transport.sent.find(x=>x.id==='approval-1'));
74
+ assert.deepEqual(denial.result,{decision:'decline'});assert.equal(denial.error,undefined);
75
+ await codex.close();
76
+ }finally{await rm(root,{recursive:true,force:true});}
77
+ });
78
+
79
+ test('R44 Codex steer and interrupt bind exact active turn identity',async()=>{
80
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r44-codex-control-'));const transport=new FakeTransport();
81
+ try{
82
+ const codex=await createCodexAppServerR44({stateDir:root,transportFactory:async()=>transport,model:'gpt-test',cwd:root});
83
+ const start=codex.start();const init=await waitFor(()=>transport.request('initialize'));transport.reply(init.id,{});await start;
84
+ const steerP=codex.steer({threadId:'thread-1',turnId:'turn-1',text:'new direction',timeoutMs:2000});
85
+ const steer=await waitFor(()=>transport.request('turn/steer'));
86
+ assert.deepEqual(steer.params,{threadId:'thread-1',expectedTurnId:'turn-1',input:[{type:'text',text:'new direction'}]});
87
+ transport.reply(steer.id,{turnId:'turn-1'});await steerP;
88
+ const interruptP=codex.interrupt({threadId:'thread-1',turnId:'turn-1',timeoutMs:2000});
89
+ const interrupt=await waitFor(()=>transport.request('turn/interrupt'));
90
+ assert.deepEqual(interrupt.params,{threadId:'thread-1',turnId:'turn-1'});
91
+ transport.reply(interrupt.id,{});await interruptP;
92
+ await codex.close();
93
+ }finally{await rm(root,{recursive:true,force:true});}
94
+ });
95
+
96
+ test('R44 Codex adapter resumes durable thread after backend process recreation',async()=>{
97
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r44-codex-resume-'));
98
+ try{
99
+ const firstTransport=new FakeTransport();
100
+ const first=await createCodexAppServerR44({stateDir:root,transportFactory:async()=>firstTransport,model:'gpt-test',cwd:root});
101
+ const s1=first.start();const i1=await waitFor(()=>firstTransport.request('initialize'));firstTransport.reply(i1.id,{});await s1;
102
+ const e1=first.ensureThread({logicalId:'planner'});const t1=await waitFor(()=>firstTransport.request('thread/start'));
103
+ firstTransport.reply(t1.id,{thread:{id:'thread-persist'},model:'gpt-test'});await e1;await first.close();
104
+
105
+ const secondTransport=new FakeTransport();
106
+ const second=await createCodexAppServerR44({stateDir:root,transportFactory:async()=>secondTransport,model:'gpt-test',cwd:root});
107
+ const s2=second.start();const i2=await waitFor(()=>secondTransport.request('initialize'));secondTransport.reply(i2.id,{});await s2;
108
+ const e2=second.ensureThread({logicalId:'planner'});const resume=await waitFor(()=>secondTransport.request('thread/resume'));
109
+ assert.equal(resume.params.threadId,'thread-persist');assert.equal(resume.params.approvalPolicy,'never');assert.equal(resume.params.sandbox,'read-only');
110
+ secondTransport.reply(resume.id,{thread:{id:'thread-persist'},model:'gpt-test'});
111
+ const result=await e2;assert.equal(result.thread_id,'thread-persist');assert.equal(result.resumed,true);
112
+ assert.equal(secondTransport.sent.some(x=>x.method==='thread/start'),false);
113
+ await second.close();
114
+ }finally{await rm(root,{recursive:true,force:true});}
115
+ });
@@ -0,0 +1,87 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { createHash, randomBytes } from 'node:crypto';
4
+ import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import { generateTrustKeypair } from '../src/trust.mjs';
8
+ import { createCodingRuntime, loadCodingPolicy } from '../src/coding-plane.mjs';
9
+ import { loadAutonomousPolicy } from '../src/autonomous-policy.mjs';
10
+ import { createNativeDiffRuntime } from '../src/native-diff.mjs';
11
+ import { createAutonomousRuntime } from '../src/autonomous-runtime.mjs';
12
+ import { providerCircuitId } from '../src/provider-circuit.mjs';
13
+
14
+ const sha=b=>createHash('sha256').update(b).digest('hex');
15
+ const nonce=()=>randomBytes(32).toString('hex');
16
+
17
+ async function fixture(){
18
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r44-codex-auto-'));
19
+ const project=path.join(root,'project'),trust=path.join(root,'trust');
20
+ await mkdir(project);await mkdir(trust);
21
+ const keys=await generateTrustKeypair({privateKeyPath:path.join(trust,'private.pem'),publicKeyPath:path.join(trust,'public.pem')});
22
+ const codingPath=path.join(root,'coding.json');
23
+ await writeFile(codingPath,JSON.stringify({schema:'deadbyte.coding-policy.v1',enabled:true,
24
+ lease_file:path.join(root,'coding-grant.json'),evidence_dir:path.join(root,'coding-evidence'),
25
+ roots:{project:{path:project,read:true,write:true,exec:false,deny_paths:[]}},commands:{}},null,2));
26
+ const codingPolicy=await loadCodingPolicy(codingPath);
27
+ const nodeSha=sha(await readFile(process.execPath));
28
+ const autonomousPath=path.join(root,'autonomous.json');
29
+ await writeFile(autonomousPath,JSON.stringify({schema:'deadbyte.autonomous-policy.v1',enabled:true,
30
+ lease_file:path.join(root,'autonomous-grant.json'),evidence_dir:path.join(root,'autonomous-evidence'),
31
+ coding_policy_sha256:codingPolicy.policy_sha256,root_ids:['project'],
32
+ planner:{backend:'codex_app_server',model:'gpt-test',root_id:'project',executable:process.execPath,
33
+ executable_sha256:nodeSha,args:['app-server'],max_tokens:4096,timeout_ms:30000,
34
+ circuit:{failure_threshold:2,open_ms:1000,half_open_probe_limit:1}},
35
+ profiles:{},limits:{max_iterations:8,max_mutations:4,max_wall_ms:600000,max_context_bytes:262144,
36
+ max_diff_bytes:262144,max_files_per_diff:4,max_stalled_cycles:2,max_post_replan_stalls:2,max_replans:4}},null,2));
37
+ const policy=await loadAutonomousPolicy(autonomousPath,codingPolicy);
38
+ await writeFile(codingPolicy.lease_file,JSON.stringify({schema:'deadbyte.coding-grant.v2',policy_sha256:codingPolicy.policy_sha256,
39
+ instance_nonce:nonce(),root_ids:['project'],armed_at_utc:new Date().toISOString()}));
40
+ await writeFile(policy.lease_file,JSON.stringify({schema:'deadbyte.autonomous-grant.v2',policy_sha256:policy.policy_sha256,
41
+ instance_nonce:nonce(),root_ids:['project'],armed_at_utc:new Date().toISOString()}));
42
+ const codingRuntime=createCodingRuntime({policy:codingPolicy,signingKeyPath:keys.privateKeyPath,verifyKeyPath:keys.publicKeyPath});
43
+ const nativeDiffRuntime=createNativeDiffRuntime({policy,codingRuntime,signingKeyPath:keys.privateKeyPath,verifyKeyPath:keys.publicKeyPath});
44
+ return {root,project,keys,codingPolicy,policy,codingRuntime,nativeDiffRuntime,cleanup:()=>rm(root,{recursive:true,force:true})};
45
+ }
46
+
47
+ test('R44 policy binds Codex executable, model, root and circuit identity',async()=>{
48
+ const f=await fixture();
49
+ try{
50
+ assert.equal(f.policy.planner.backend,'codex_app_server');
51
+ assert.equal(f.policy.planner.root_id,'project');
52
+ assert.equal(f.policy.planner.executable,process.execPath);
53
+ assert.match(f.policy.planner.executable_sha256,/^[0-9a-f]{64}$/);
54
+ assert.deepEqual(f.policy.planner.args,['app-server']);
55
+ assert.match(providerCircuitId(f.policy.planner),/^codex_app_server:[0-9a-f]{64}$/);
56
+ }finally{await f.cleanup();}
57
+ });
58
+
59
+ test('R44 autonomous runtime consumes injected Codex planner and does not ask client sampling',async()=>{
60
+ const f=await fixture();const calls=[];
61
+ try{
62
+ const plannerProviderRuntime={runPlanner:async({logicalId,prompt,timeoutMs})=>{
63
+ calls.push({logicalId,prompt,timeoutMs});
64
+ return {backend:'codex_app_server',model:'gpt-test',raw_text:JSON.stringify({
65
+ schema:'deadbyte.autonomous-decision.v2',kind:'plan_create',title:'Provider path',
66
+ reason:'prove Codex planner integration',steps:[{step_id:'prove',intent:'observe provider call'}]
67
+ }),response_id:'turn-test-1'};
68
+ }};
69
+ const runtime=createAutonomousRuntime({policy:f.policy,codingRuntime:f.codingRuntime,nativeDiffRuntime:f.nativeDiffRuntime,
70
+ plannerProviderRuntime,signingKeyPath:f.keys.privateKeyPath,verifyKeyPath:f.keys.publicKeyPath});
71
+ const submitted=await runtime.submit({submissionId:'codex-plan',rootId:'project',title:'codex',goal:'prove provider path',acceptance:'pause'});
72
+ const result=await runtime.run({goalId:submitted.goal_id,maxCycles:1});
73
+ assert.notEqual(result.status,'input_required');
74
+ assert.equal(result.status,'yielded');
75
+ assert.equal(calls.length,1);
76
+ assert.equal(calls[0].logicalId,`goal-${submitted.goal_id}`);
77
+ assert.match(calls[0].prompt,/DEADBYTE/);
78
+ }finally{await f.cleanup();}
79
+ });
80
+
81
+ test('R44 autonomous Codex policy fails closed without injected provider runtime',async()=>{
82
+ const f=await fixture();
83
+ try{
84
+ assert.throws(()=>createAutonomousRuntime({policy:f.policy,codingRuntime:f.codingRuntime,nativeDiffRuntime:f.nativeDiffRuntime,
85
+ signingKeyPath:f.keys.privateKeyPath,verifyKeyPath:f.keys.publicKeyPath}),/codex.*provider/i);
86
+ }finally{await f.cleanup();}
87
+ });
@@ -19,7 +19,7 @@ const codingTools = [
19
19
  'coding_task_events', 'coding_task_run', 'coding_task_status', 'coding_task_submit',
20
20
  'coding_task_verify', 'coding_text_replace', 'coding_tree'
21
21
  ];
22
- const baseTools = ['contained_transform', 'evidence_verify', 'file_read', 'proof_execute', 'workspace_open'];
22
+ const baseTools = ['contained_transform', 'evidence_verify', 'file_read', 'proof_execute', 'skill_catalog', 'workspace_open'];
23
23
  const agentTools = [
24
24
  'agent_task_approve', 'agent_task_cancel', 'agent_task_events', 'agent_task_run',
25
25
  'agent_task_status', 'agent_task_submit', 'agent_task_verify'
@@ -69,7 +69,7 @@ async function fixture() {
69
69
  return { root, client, transport };
70
70
  }
71
71
 
72
- test('R36 hermetic coding fixture exposes the exact 37-tool no-host catalog', async () => {
72
+ test('R44 hermetic coding fixture exposes the exact 38-tool no-host catalog', async () => {
73
73
  const f = await fixture();
74
74
  try {
75
75
  await f.client.connect(f.transport);