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
@@ -5,7 +5,7 @@ import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
5
5
  import os from 'node:os';
6
6
  import path from 'node:path';
7
7
  import { generateTrustKeypair } from '../src/trust.mjs';
8
- import { createCodingRuntime, loadCodingPolicy } from '../src/coding-plane.mjs';
8
+ import { assertCodingArmed, createCodingRuntime, loadCodingPolicy, recheckCodingAuthority } from '../src/coding-plane.mjs';
9
9
  import { createCodingTaskRuntime } from '../src/coding-task-runtime.mjs';
10
10
 
11
11
  function sha256Bytes(bytes) { return createHash('sha256').update(bytes).digest('hex'); }
@@ -78,6 +78,41 @@ test('coding plane rejects mutation when lease is absent or precondition is stal
78
78
  await assert.rejects(() => f.runtime.fileWrite({ rootId: 'project', relativePath: 'x.txt', content: 'x', expectedSha256: 'absent' }), /coding plane is disarmed/);
79
79
  } finally { await rm(f.root, { recursive: true, force: true }); }
80
80
  });
81
+
82
+ test('coding authority recheck rejects lease generation or expiry drift before commit', async () => {
83
+ const f = await fixture();
84
+ try {
85
+ const initial = await assertCodingArmed(f.policy, 'project');
86
+ const lease = JSON.parse(await readFile(f.policy.lease_file, 'utf8'));
87
+ await writeFile(f.policy.lease_file, JSON.stringify({ ...lease, instance_nonce: 'b'.repeat(64) }));
88
+ await assert.rejects(() => recheckCodingAuthority(f.policy, initial, 'project'), /coding authority changed during operation/);
89
+ await writeFile(f.policy.lease_file, JSON.stringify({
90
+ ...lease,
91
+ expires_at_utc: new Date(Date.parse(lease.expires_at_utc) + 60000).toISOString()
92
+ }));
93
+ await assert.rejects(() => recheckCodingAuthority(f.policy, initial, 'project'), /coding authority changed during operation/);
94
+ } finally { await rm(f.root, { recursive: true, force: true }); }
95
+ });
96
+ test('coding execution deadline bounds runtime without changing the signed request identity', async () => {
97
+ const f = await fixture();
98
+ try {
99
+ const script = path.join(f.project, 'deadline.mjs');
100
+ await writeFile(script, "await new Promise(resolve => setTimeout(resolve, 10000));\n");
101
+ const scriptHash = await sha256File(script);
102
+ const args = { rootId:'project', commandId:'node_script', scriptPath:'deadline.mjs', scriptSha256:scriptHash,
103
+ timeoutMs:20000, executionDeadlineUtc:new Date(Date.now()+3000).toISOString() };
104
+ const expectedRequest = f.runtime.requestSha('coding_command_run', {
105
+ root_id:'project', command_id:'node_script', script_path:'deadline.mjs', script_sha256:scriptHash,
106
+ args:[], cwd:'.', timeout_ms:20000
107
+ });
108
+ const result = await f.runtime.commandRun(args);
109
+ assert.equal(result.status, 'observed_failure');
110
+ assert.equal(result.timed_out, true);
111
+ assert.ok(result.timeout_ms >= 500 && result.timeout_ms <= 3000);
112
+ assert.equal(result.request_sha256, expectedRequest);
113
+ } finally { await rm(f.root, { recursive:true, force:true }); }
114
+ });
115
+
81
116
  test('coding command and benchmark produce signed observations', async () => {
82
117
  const f = await fixture();
83
118
  try {
@@ -10,7 +10,7 @@ const repoRoot=path.resolve(here,'..');
10
10
  const serverPath=path.join(repoRoot,'src','mcp-server.mjs');
11
11
  const gatewayNames=['capability_manifest','coding_inspect','desktop_control','machine_fs','machine_observe','process_session'];
12
12
 
13
- test('R36 real compact stdio catalog stays <=42 and exposes all gateways',async()=>{
13
+ test('R44 real compact stdio catalog stays <=43 and exposes all gateways plus skill_catalog',async()=>{
14
14
  const client=new Client({name:'deadbyte-r36-compact-test',version:'0.10.0-dev'},{versionNegotiation:{mode:{pin:'2026-07-28'}}});
15
15
  const env={
16
16
  ...getDefaultEnvironment(),
@@ -24,8 +24,8 @@ test('R36 real compact stdio catalog stays <=42 and exposes all gateways',async(
24
24
  await client.connect(transport);
25
25
  const listed=await client.listTools();
26
26
  const names=listed.tools.map(tool=>tool.name).sort();
27
- assert.ok(names.length<=42,`compact tool count ${names.length} exceeds 42`);
28
- for(const name of gatewayNames) assert.ok(names.includes(name),`missing ${name}`);
27
+ assert.ok(names.length<=43,`compact tool count ${names.length} exceeds 43`);
28
+ for(const name of [...gatewayNames,'skill_catalog']) assert.ok(names.includes(name),`missing ${name}`);
29
29
  for(const legacy of ['workspace_open','file_read','proof_execute','contained_transform']) assert.equal(names.includes(legacy),false,`legacy full-only tool leaked: ${legacy}`);
30
30
  const manifest=await client.callTool({name:'capability_manifest',arguments:{}});
31
31
  assert.equal(manifest.isError,false);
@@ -10,7 +10,9 @@ import { createR34Fixture } from './helpers/autonomous-r34-fixture.mjs';
10
10
 
11
11
  const here=path.dirname(fileURLToPath(import.meta.url));
12
12
  const repoRoot=path.resolve(here,'..');
13
- const serverPath=path.join(repoRoot,'src','mcp-server.mjs');
13
+ const parent=JSON.parse(await readFile(path.join(repoRoot,'R44-PARENT.json'),'utf8'));
14
+ const parentRoot=parent.parent_release_root;
15
+ const serverPath=path.join(parentRoot,'src','mcp-server.mjs');
14
16
  const desktopPolicyPath=path.join(repoRoot,'controller','deadbyte-desktop-policy.json');
15
17
  const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
16
18
  const absent=async p=>{try{await access(p);return false;}catch(e){if(e?.code==='ENOENT')return true;throw e;}};
@@ -70,7 +72,7 @@ test('R40 real max-runtime compact stdio catalog is exactly 42 while all authori
70
72
  DEADBYTE_AUTONOMOUS_POLICY:path.join(f.root,'autonomous-policy.json'),
71
73
  DEADBYTE_AUTONOMOUS_SUPERVISOR_MODE:'external'
72
74
  };
73
- const transport=new StdioClientTransport({command:process.execPath,args:[serverPath],cwd:repoRoot,env,stderr:'pipe'});
75
+ const transport=new StdioClientTransport({command:process.execPath,args:[serverPath],cwd:parentRoot,env,stderr:'pipe'});
74
76
  try{
75
77
  await client.connect(transport);
76
78
  const listed=await client.listTools();
@@ -0,0 +1,97 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { createHash } from 'node:crypto';
4
+ import { access, mkdir, readFile, writeFile } from 'node:fs/promises';
5
+ import path from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { Client } from '@modelcontextprotocol/client';
8
+ import { StdioClientTransport, getDefaultEnvironment } from '@modelcontextprotocol/client/stdio';
9
+ import { createR34Fixture } from './helpers/autonomous-r34-fixture.mjs';
10
+
11
+ const here=path.dirname(fileURLToPath(import.meta.url));
12
+ const repoRoot=path.resolve(here,'..');
13
+ const serverPath=path.join(repoRoot,'src','mcp-server.mjs');
14
+ const desktopPolicyPath=path.join(repoRoot,'controller','deadbyte-desktop-policy.json');
15
+ const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
16
+ const absent=async p=>{try{await access(p);return false;}catch(e){if(e?.code==='ENOENT')return true;throw e;}};
17
+
18
+ test('R44 real max-runtime compact stdio catalog is exactly 43 with verified skill_catalog while all authority remains disarmed',async()=>{
19
+ const f=await createR34Fixture({authority:'disarmed'});
20
+ const processPolicyPath=path.join(f.root,'process-policy.json');
21
+ const processGrant=path.join(f.root,'process-grant.json');
22
+ const processState=path.join(f.root,'process-state');
23
+ const hostPolicyPath=path.join(f.root,'host-policy.json');
24
+ const hostArm=path.join(f.root,'host-arm.json');
25
+ const nodeSha=sha(await readFile(process.execPath));
26
+ await mkdir(processState,{recursive:true});
27
+ await writeFile(processPolicyPath,JSON.stringify({
28
+ schema:'deadbyte.process-policy.v1',enabled:true,grant_file:processGrant,state_dir:processState,
29
+ native_host:{path:process.execPath,sha256:nodeSha},
30
+ roots:{core:{path:f.project,deny_paths:[]}},
31
+ profiles:{node:{executable:process.execPath,executable_sha256:nodeSha,root_ids:['core'],
32
+ prefix_args:[],transport:'pipe',max_args:8,max_arg_bytes:1024,max_sessions:2,
33
+ max_input_bytes:65536,max_retained_output_bytes:262144,env_allow:['PATH','TEMP','TMP'],
34
+ input_actions:{ping:'PING\r\n'}}}
35
+ },null,2));
36
+ await writeFile(hostPolicyPath,JSON.stringify({
37
+ schema:'deadbyte.host-policy.v1',enabled:true,arm_file:hostArm,max_read_bytes:262144,max_write_bytes:1048576,
38
+ roots:{core:{path:f.project,read:true,write:true,exec:true}},
39
+ commands:{node_script:{kind:'script',executable:process.execPath,executable_sha256:nodeSha,
40
+ root_ids:['core'],prefix_args:[],extensions:['.mjs'],timeout_ms:5000,max_output_bytes:65536}}
41
+ },null,2));
42
+
43
+ const desktopRaw=JSON.parse(await readFile(desktopPolicyPath,'utf8'));
44
+ const desktopGrant=desktopRaw.grant_file;
45
+ const codingLease=f.codingPolicy.lease_file;
46
+ const autonomousLease=f.policy.lease_file;
47
+ for(const q of [processGrant,hostArm,desktopGrant,codingLease,autonomousLease]) assert.equal(await absent(q),true,'authority unexpectedly armed before start: '+q);
48
+
49
+ const client=new Client({name:'deadbyte-r40-max-catalog',version:'0.12.0'},{versionNegotiation:{mode:{pin:'2026-07-28'}}});
50
+ const env={
51
+ ...getDefaultEnvironment(),
52
+ DEADBYTE_CAPABILITY_PROFILE:'compact',
53
+ DEADBYTE_AUDIT_RUNTIME:'0',
54
+ DEADBYTE_STATE_ROOT:path.join(f.root,'state'),
55
+ DEADBYTE_WORKSPACE_ROOT:f.root,
56
+ DEADBYTE_SIGNING_KEY:f.keys.privateKeyPath,
57
+ DEADBYTE_VERIFY_KEY:f.keys.publicKeyPath,
58
+ DEADBYTE_PRIVILEGED_POLICY:hostPolicyPath,
59
+ DEADBYTE_MACHINE_RUNTIME:'1',
60
+ DEADBYTE_AGENT_RUNTIME:'0',
61
+ DEADBYTE_PROCESS_RUNTIME:'1',
62
+ DEADBYTE_PROCESS_POLICY:processPolicyPath,
63
+ DEADBYTE_CODING_RUNTIME:'1',
64
+ DEADBYTE_CODING_POLICY:path.join(f.root,'coding-policy.json'),
65
+ DEADBYTE_MACHINE_FS_RUNTIME:'1',
66
+ DEADBYTE_OBSERVATION_RUNTIME:'1',
67
+ DEADBYTE_DESKTOP_RUNTIME:'1',
68
+ DEADBYTE_DESKTOP_POLICY:desktopPolicyPath,
69
+ DEADBYTE_AUTONOMOUS_RUNTIME:'1',
70
+ DEADBYTE_AUTONOMOUS_POLICY:path.join(f.root,'autonomous-policy.json'),
71
+ DEADBYTE_AUTONOMOUS_SUPERVISOR_MODE:'external'
72
+ };
73
+ const transport=new StdioClientTransport({command:process.execPath,args:[serverPath],cwd:repoRoot,env,stderr:'pipe'});
74
+ try{
75
+ await client.connect(transport);
76
+ const listed=await client.listTools();
77
+ const names=listed.tools.map(t=>t.name).sort();
78
+ assert.equal(names.length,43,'expected exact compact max catalog 43, got '+names.length+': '+names.join(','));
79
+ for(const name of ['capability_manifest','desktop_control','coding_inspect','machine_fs','machine_observe','process_session',
80
+ 'host_file_read','host_file_write','host_exec','skill_catalog']) assert.ok(names.includes(name),'missing '+name);
81
+ const manifest=await client.callTool({name:'capability_manifest',arguments:{}});
82
+ assert.equal(manifest.isError,false);
83
+ const m=manifest.structuredContent.result;
84
+ assert.equal(m.schema,'deadbyte.capability-manifest.v2');
85
+ assert.equal(m.profile,'compact');
86
+ assert.equal(m.tool_count,43);
87
+ assert.deepEqual(m.tool_catalog,names);
88
+ assert.match(m.catalog_sha256,/^[0-9a-f]{64}$/);
89
+ const skill=m.capabilities.find(item=>item.id==='skill.catalog');
90
+ assert.ok(skill);assert.equal(skill.authority,'none');assert.equal(skill.mutating,false);
91
+ console.log(JSON.stringify({tool_count:m.tool_count,catalog_sha256:m.catalog_sha256,ledger_sha256:m.ledger_sha256,desktop:true,skill:true,authority:'DISARMED'}));
92
+ } finally {
93
+ await client.close().catch(()=>{});
94
+ for(const q of [processGrant,hostArm,desktopGrant,codingLease,autonomousLease]) assert.equal(await absent(q),true,'authority file appeared during catalog probe: '+q);
95
+ await f.cleanup();
96
+ }
97
+ });
@@ -105,7 +105,7 @@ test('proof execution creates deterministic output and signed verifiable receipt
105
105
  assert.equal(result.receipt.request.schema, 'deadbyte.request.v1');
106
106
  assert.match(result.receipt.request.nonce, /^[0-9a-f]{64}$/);
107
107
  assert.match(result.receipt.request.sha256, /^[0-9a-f]{64}$/);
108
- assert.equal(result.receipt.runtime.package_version, '0.13.0');
108
+ assert.equal(result.receipt.runtime.package_version, '0.15.0');
109
109
  assert.match(result.receipt.runtime.mcp_ingress_adapter, /^(?:none|tunnel-stdio-probe-compat-v1)$/);
110
110
  assert.match(result.receipt.runtime.mcp_ingress_adapter_sha256, /^[0-9a-f]{64}$/);
111
111
  assert.match(result.receipt.runtime.mcp_ingress_mode_source_sha256, /^[0-9a-f]{64}$/);
@@ -0,0 +1,75 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { mkdtemp, rm, writeFile } from 'node:fs/promises';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { createDurableJobSupervisorR44 } from '../src/durable-job-r44.mjs';
7
+
8
+ class FakeProvider{
9
+ constructor({external=true}={}){this.external=external;this.handles=new Map();this.starts=0;}
10
+ descriptor(){return {kind:this.external?'external-supervisor':'controller-local',instance_id:'sup-1'};}
11
+ async start(spec){
12
+ const handle={handle_id:`h-${++this.starts}`,pid:1000+this.starts,creation_id:`c-${this.starts}`,profile_id:spec.profile_id};
13
+ const running={...handle,state:'running'};this.handles.set(handle.handle_id,running);return {...running};
14
+ }
15
+ async reattach(handle){
16
+ const current=this.handles.get(handle.handle_id);return current?{...current}:null;
17
+ }
18
+ async status(handle){return this.handles.get(handle.handle_id)??null;}
19
+ async terminate(handle){const current=this.handles.get(handle.handle_id);if(current){current.state='terminated';return {...current};}return null;}
20
+ }
21
+
22
+ test('R44 controller/session jobs never masquerade as persistent after owner restart',async()=>{
23
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r44-job-'));const provider=new FakeProvider();
24
+ try{
25
+ const one=await createDurableJobSupervisorR44({stateDir:root,provider,ownerInstanceId:'owner-1'});
26
+ const c=await one.start({lifetime:'controller',profileId:'build',rootId:'core',cwd:'.',args:[]});
27
+ const s=await one.start({lifetime:'session',profileId:'test',rootId:'core',cwd:'.',args:[]});
28
+ assert.equal(c.state,'running');assert.equal(s.state,'running');
29
+ const two=await createDurableJobSupervisorR44({stateDir:root,provider,ownerInstanceId:'owner-2'});
30
+ assert.equal((await two.status({jobId:c.job_id})).state,'interrupted');
31
+ assert.equal((await two.status({jobId:s.job_id})).state,'interrupted');
32
+ }finally{await rm(root,{recursive:true,force:true});}
33
+ });
34
+
35
+ test('R44 persistent job reattaches only through external supervisor with exact process identity',async()=>{
36
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r44-job-persist-'));const provider=new FakeProvider();
37
+ try{
38
+ const one=await createDurableJobSupervisorR44({stateDir:root,provider,ownerInstanceId:'owner-1'});
39
+ const job=await one.start({lifetime:'persistent',profileId:'watch',rootId:'core',cwd:'.',args:[]});
40
+ const two=await createDurableJobSupervisorR44({stateDir:root,provider,ownerInstanceId:'owner-2'});
41
+ const recovered=await two.status({jobId:job.job_id});
42
+ assert.equal(recovered.state,'running');assert.equal(recovered.recovered,true);
43
+ assert.equal(recovered.pid,job.pid);assert.equal(recovered.creation_id,job.creation_id);
44
+
45
+ const live=provider.handles.get(job.handle_id);live.creation_id='spoofed';
46
+ const three=await createDurableJobSupervisorR44({stateDir:root,provider,ownerInstanceId:'owner-3'});
47
+ assert.equal((await three.status({jobId:job.job_id})).state,'orphaned');
48
+ }finally{await rm(root,{recursive:true,force:true});}
49
+ });
50
+
51
+ test('R44 persistent lifetime rejects controller-local provider and state corruption',async()=>{
52
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r44-job-local-'));
53
+ try{
54
+ const local=await createDurableJobSupervisorR44({stateDir:root,provider:new FakeProvider({external:false}),ownerInstanceId:'owner'});
55
+ await assert.rejects(local.start({lifetime:'persistent',profileId:'x',rootId:'core',cwd:'.',args:[]}),/external supervisor/);
56
+ }finally{await rm(root,{recursive:true,force:true});}
57
+
58
+ const corrupt=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r44-job-corrupt-'));
59
+ try{
60
+ await writeFile(path.join(corrupt,'jobs.json'),'{"schema":','utf8');
61
+ await assert.rejects(createDurableJobSupervisorR44({stateDir:corrupt,provider:new FakeProvider(),ownerInstanceId:'owner'}),/state JSON invalid/);
62
+ }finally{await rm(corrupt,{recursive:true,force:true});}
63
+ });
64
+
65
+ test('R44 terminate binds exact supervisor handle and becomes durable terminal state',async()=>{
66
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r44-job-term-'));const provider=new FakeProvider();
67
+ try{
68
+ const sup=await createDurableJobSupervisorR44({stateDir:root,provider,ownerInstanceId:'owner'});
69
+ const job=await sup.start({lifetime:'persistent',profileId:'watch',rootId:'core',cwd:'.',args:[]});
70
+ const ended=await sup.terminate({jobId:job.job_id});
71
+ assert.equal(ended.state,'terminated');
72
+ const restarted=await createDurableJobSupervisorR44({stateDir:root,provider,ownerInstanceId:'owner-2'});
73
+ assert.equal((await restarted.status({jobId:job.job_id})).state,'terminated');
74
+ }finally{await rm(root,{recursive:true,force:true});}
75
+ });
@@ -0,0 +1,53 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { mkdtemp, readFile, rm } from 'node:fs/promises';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import {
7
+ publishEvidenceAtomic, createExecutionReceipt, createVerificationResult
8
+ } from '../src/evidence-publisher-r42.mjs';
9
+
10
+ const H=value=>String(value).repeat(64).slice(0,64);
11
+
12
+ test('R42 evidence publication is atomic, no-clobber and idempotent for identical bytes',async()=>{
13
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r42-evidence-'));
14
+ try{
15
+ const one=await publishEvidenceAtomic({root,kind:'eval',evidenceId:'case-1',payload:{status:'passed',score:1}});
16
+ assert.equal(one.status,'created');
17
+ const two=await publishEvidenceAtomic({root,kind:'eval',evidenceId:'case-1',payload:{status:'passed',score:1}});
18
+ assert.equal(two.status,'existing');
19
+ assert.equal(two.evidence_sha256,one.evidence_sha256);
20
+ const bytes=await readFile(one.path);
21
+ assert.equal(H('0')===one.evidence_sha256,false);
22
+ assert.match(bytes.toString('utf8'),/"payload_sha256":"[0-9a-f]{64}"/);
23
+ }finally{await rm(root,{recursive:true,force:true});}
24
+ });
25
+
26
+ test('R42 conflicting concurrent evidence identity fails closed without overwrite',async()=>{
27
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r42-evidence-race-'));
28
+ try{
29
+ const settled=await Promise.allSettled([
30
+ publishEvidenceAtomic({root,kind:'closure',evidenceId:'release-final',payload:{candidate:'a'}}),
31
+ publishEvidenceAtomic({root,kind:'closure',evidenceId:'release-final',payload:{candidate:'b'}})
32
+ ]);
33
+ assert.equal(settled.filter(item=>item.status==='fulfilled').length,1);
34
+ assert.equal(settled.filter(item=>item.status==='rejected').length,1);
35
+ assert.match(String(settled.find(item=>item.status==='rejected').reason),/EVIDENCE_ID_CONFLICT/);
36
+ const winner=settled.find(item=>item.status==='fulfilled').value;
37
+ const parsed=JSON.parse(await readFile(winner.path,'utf8'));
38
+ assert.ok(['a','b'].includes(parsed.payload.candidate));
39
+ }finally{await rm(root,{recursive:true,force:true});}
40
+ });
41
+
42
+ test('R42 execution receipt and outcome verification are distinct typed artifacts',()=>{
43
+ const receipt=createExecutionReceipt({execution_id:'exec-1',subject_sha256:H('1'),request_sha256:H('2'),
44
+ observed_exit_code:0,started_at_utc:'2026-09-20T01:00:00.000Z',completed_at_utc:'2026-09-20T01:00:01.000Z'});
45
+ assert.equal(receipt.schema,'deadbyte.execution-receipt.v1');
46
+ assert.equal(Object.hasOwn(receipt,'outcome_passed'),false);
47
+ const verified=createVerificationResult({verification_id:'verify-1',subject_sha256:H('1'),
48
+ execution_receipt_sha256:receipt.receipt_sha256,outcome_passed:true,oracle:'runtime_behavior',evidence_refs:[H('3')]});
49
+ assert.equal(verified.schema,'deadbyte.verification-result.v1');
50
+ assert.equal(verified.outcome_passed,true);
51
+ assert.throws(()=>createExecutionReceipt({execution_id:'exec-2',subject_sha256:H('1'),request_sha256:H('2'),
52
+ observed_exit_code:0,started_at_utc:'2026-09-20T01:00:00.000Z',completed_at_utc:'2026-09-20T01:00:01.000Z',outcome_passed:true}),/unknown field/i);
53
+ });
@@ -0,0 +1,46 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { createHash } from 'node:crypto';
4
+ import { mkdir, mkdtemp, rm, writeFile, readFile } from 'node:fs/promises';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+ import { createDurableJobSupervisorR44 } from '../src/durable-job-r44.mjs';
9
+ import { createExternalJobProviderR44 } from '../src/external-job-provider-r44.mjs';
10
+
11
+ const sha=b=>createHash('sha256').update(b).digest('hex');
12
+ const here=path.dirname(fileURLToPath(import.meta.url));
13
+ const repoRoot=path.resolve(here,'..');
14
+ const workerPath=path.join(repoRoot,'src','job-supervisor-host-r44.mjs');
15
+
16
+ test('R44 real external supervisor preserves persistent job identity across controller recreation',async()=>{
17
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r44-real-supervisor-'));
18
+ const supervisorState=path.join(root,'host'),jobsState=path.join(root,'jobs'),project=path.join(root,'project');
19
+ await mkdir(project,{recursive:true});
20
+ const script=path.join(project,'hold.mjs');
21
+ await writeFile(script,`process.stdout.write('READY\\n');setInterval(()=>{},1000);\n`,'utf8');
22
+ const nodeSha=sha(await readFile(process.execPath)), workerSha=sha(await readFile(workerPath));
23
+ const config={stateDir:supervisorState,nodeExecutable:process.execPath,nodeExecutableSha256:nodeSha,
24
+ workerPath,workerSha256:workerSha,roots:{core:project},profiles:{
25
+ hold:{executable:process.execPath,executable_sha256:nodeSha,root_ids:['core'],prefix_args:[script],max_args:4,max_arg_bytes:1024}
26
+ }};
27
+ let providerA=null,providerB=null;
28
+ try{
29
+ providerA=await createExternalJobProviderR44(config);
30
+ const one=await createDurableJobSupervisorR44({stateDir:jobsState,provider:providerA,ownerInstanceId:'controller-a'});
31
+ const started=await one.start({lifetime:'persistent',profileId:'hold',rootId:'core',cwd:'.',args:[]});
32
+ assert.equal(started.state,'running');assert.ok(started.pid>0);assert.match(started.creation_id,/^[0-9a-f]{64}$/);
33
+
34
+ providerB=await createExternalJobProviderR44(config);
35
+ assert.equal(providerB.descriptor().instance_id,providerA.descriptor().instance_id,'must reconnect same external supervisor');
36
+ const two=await createDurableJobSupervisorR44({stateDir:jobsState,provider:providerB,ownerInstanceId:'controller-b'});
37
+ const recovered=await two.status({jobId:started.job_id});
38
+ assert.equal(recovered.state,'running');assert.equal(recovered.recovered,true);
39
+ assert.equal(recovered.handle_id,started.handle_id);assert.equal(recovered.pid,started.pid);assert.equal(recovered.creation_id,started.creation_id);
40
+ const ended=await two.terminate({jobId:started.job_id});assert.equal(ended.state,'terminated');
41
+ }finally{
42
+ if(providerB)await providerB.shutdown().catch(()=>{});
43
+ else if(providerA)await providerA.shutdown().catch(()=>{});
44
+ await rm(root,{recursive:true,force:true});
45
+ }
46
+ });
@@ -10,8 +10,10 @@ import { createR34Fixture } from './helpers/autonomous-r34-fixture.mjs';
10
10
 
11
11
  const here=path.dirname(fileURLToPath(import.meta.url));
12
12
  const repoRoot=path.resolve(here,'..');
13
- const serverPath=path.join(repoRoot,'src','mcp-server.mjs');
14
- const desktopPolicyPath=path.join(repoRoot,'controller','deadbyte-desktop-policy.json');
13
+ const parent=JSON.parse(await readFile(path.join(repoRoot,'R44-PARENT.json'),'utf8'));
14
+ const parentRoot=parent.parent_release_root;
15
+ const serverPath=path.join(parentRoot,'src','mcp-server.mjs');
16
+ const desktopPolicyPath=path.join(parentRoot,'controller','deadbyte-desktop-policy.json');
15
17
  const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
16
18
  const absent=async p=>{try{await access(p);return false;}catch(e){if(e?.code==='ENOENT')return true;throw e;}};
17
19
 
@@ -70,7 +72,7 @@ test('R40 real max-runtime full stdio catalog is exactly 80 with four Desktop pr
70
72
  DEADBYTE_AUTONOMOUS_POLICY:path.join(f.root,'autonomous-policy.json'),
71
73
  DEADBYTE_AUTONOMOUS_SUPERVISOR_MODE:'external'
72
74
  };
73
- const transport=new StdioClientTransport({command:process.execPath,args:[serverPath],cwd:repoRoot,env,stderr:'pipe'});
75
+ const transport=new StdioClientTransport({command:process.execPath,args:[serverPath],cwd:parentRoot,env,stderr:'pipe'});
74
76
  try{
75
77
  await client.connect(transport);
76
78
  const listed=await client.listTools();
@@ -0,0 +1,91 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { createHash } from 'node:crypto';
4
+ import { access, mkdir, readFile, writeFile } from 'node:fs/promises';
5
+ import path from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { Client } from '@modelcontextprotocol/client';
8
+ import { StdioClientTransport, getDefaultEnvironment } from '@modelcontextprotocol/client/stdio';
9
+ import { createR34Fixture } from './helpers/autonomous-r34-fixture.mjs';
10
+
11
+ const here=path.dirname(fileURLToPath(import.meta.url));
12
+ const repoRoot=path.resolve(here,'..');
13
+ const serverPath=path.join(repoRoot,'src','mcp-server.mjs');
14
+ const desktopPolicyPath=path.join(repoRoot,'controller','deadbyte-desktop-policy.json');
15
+ const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
16
+ const absent=async p=>{try{await access(p);return false;}catch(e){if(e?.code==='ENOENT')return true;throw e;}};
17
+
18
+ test('R44 real max-runtime full stdio catalog is exactly 81 with skill and four Desktop primitives while authority remains disarmed',async()=>{
19
+ const f=await createR34Fixture({authority:'disarmed'});
20
+ const processPolicyPath=path.join(f.root,'process-policy.json');
21
+ const processGrant=path.join(f.root,'process-grant.json');
22
+ const processState=path.join(f.root,'process-state');
23
+ const hostPolicyPath=path.join(f.root,'host-policy.json');
24
+ const hostArm=path.join(f.root,'host-arm.json');
25
+ const nodeSha=sha(await readFile(process.execPath));
26
+ await mkdir(processState,{recursive:true});
27
+ await writeFile(processPolicyPath,JSON.stringify({
28
+ schema:'deadbyte.process-policy.v1',enabled:true,grant_file:processGrant,state_dir:processState,
29
+ native_host:{path:process.execPath,sha256:nodeSha},
30
+ roots:{core:{path:f.project,deny_paths:[]}},
31
+ profiles:{node:{executable:process.execPath,executable_sha256:nodeSha,root_ids:['core'],
32
+ prefix_args:[],transport:'pipe',max_args:8,max_arg_bytes:1024,max_sessions:2,
33
+ max_input_bytes:65536,max_retained_output_bytes:262144,env_allow:['PATH','TEMP','TMP'],
34
+ input_actions:{ping:'PING\r\n'}}}
35
+ },null,2));
36
+ await writeFile(hostPolicyPath,JSON.stringify({
37
+ schema:'deadbyte.host-policy.v1',enabled:true,arm_file:hostArm,max_read_bytes:262144,max_write_bytes:1048576,
38
+ roots:{core:{path:f.project,read:true,write:true,exec:true}},
39
+ commands:{node_script:{kind:'script',executable:process.execPath,executable_sha256:nodeSha,
40
+ root_ids:['core'],prefix_args:[],extensions:['.mjs'],timeout_ms:5000,max_output_bytes:65536}}
41
+ },null,2));
42
+
43
+ const desktopRaw=JSON.parse(await readFile(desktopPolicyPath,'utf8'));
44
+ const desktopGrant=desktopRaw.grant_file;
45
+ const codingLease=f.codingPolicy.lease_file;
46
+ const autonomousLease=f.policy.lease_file;
47
+ for(const q of [processGrant,hostArm,desktopGrant,codingLease,autonomousLease]) assert.equal(await absent(q),true,'authority unexpectedly armed before start: '+q);
48
+
49
+ const client=new Client({name:'deadbyte-r44-full-catalog',version:'0.15.0'},{versionNegotiation:{mode:{pin:'2026-07-28'}}});
50
+ const env={
51
+ ...getDefaultEnvironment(),
52
+ DEADBYTE_CAPABILITY_PROFILE:'full',
53
+ DEADBYTE_AUDIT_RUNTIME:'0',
54
+ DEADBYTE_STATE_ROOT:path.join(f.root,'state'),
55
+ DEADBYTE_WORKSPACE_ROOT:f.root,
56
+ DEADBYTE_SIGNING_KEY:f.keys.privateKeyPath,
57
+ DEADBYTE_VERIFY_KEY:f.keys.publicKeyPath,
58
+ DEADBYTE_PRIVILEGED_POLICY:hostPolicyPath,
59
+ DEADBYTE_MACHINE_RUNTIME:'1',
60
+ DEADBYTE_AGENT_RUNTIME:'1',
61
+ DEADBYTE_PROCESS_RUNTIME:'1',
62
+ DEADBYTE_PROCESS_POLICY:processPolicyPath,
63
+ DEADBYTE_CODING_RUNTIME:'1',
64
+ DEADBYTE_CODING_POLICY:path.join(f.root,'coding-policy.json'),
65
+ DEADBYTE_MACHINE_FS_RUNTIME:'1',
66
+ DEADBYTE_OBSERVATION_RUNTIME:'1',
67
+ DEADBYTE_DESKTOP_RUNTIME:'1',
68
+ DEADBYTE_DESKTOP_POLICY:desktopPolicyPath,
69
+ DEADBYTE_AUTONOMOUS_RUNTIME:'1',
70
+ DEADBYTE_AUTONOMOUS_POLICY:path.join(f.root,'autonomous-policy.json'),
71
+ DEADBYTE_AUTONOMOUS_SUPERVISOR_MODE:'external'
72
+ };
73
+ const transport=new StdioClientTransport({command:process.execPath,args:[serverPath],cwd:repoRoot,env,stderr:'pipe'});
74
+ try{
75
+ await client.connect(transport);
76
+ const listed=await client.listTools();
77
+ const names=listed.tools.map(t=>t.name).sort();
78
+ const {R44_EXPECTED_TOOLS}=await import('../src/remote-mcp-r44-parity-client.mjs');
79
+ assert.equal(names.length,81,'expected exact full R44 catalog 81, got '+names.length+': '+names.join(','));
80
+ assert.deepEqual(names,R44_EXPECTED_TOOLS);
81
+ for(const name of ['desktop_status','desktop_observe','desktop_target','desktop_action',
82
+ 'host_file_read','host_file_write','host_exec','skill_catalog']) assert.ok(names.includes(name),'missing '+name);
83
+ for(const tool of listed.tools) assert.equal(tool.outputSchema?.additionalProperties,false,'non-strict output '+tool.name);
84
+ const catalog_sha256=sha(Buffer.from(JSON.stringify(names),'utf8'));
85
+ console.log(JSON.stringify({tool_count:names.length,catalog_sha256,desktop_tools:4,skill:true,authority:'DISARMED'}));
86
+ } finally {
87
+ await client.close().catch(()=>{});
88
+ for(const q of [processGrant,hostArm,desktopGrant,codingLease,autonomousLease]) assert.equal(await absent(q),true,'authority file appeared during catalog probe: '+q);
89
+ await f.cleanup();
90
+ }
91
+ });
@@ -11,7 +11,8 @@ import {
11
11
  hostExec,
12
12
  hostFileRead,
13
13
  hostFileWrite,
14
- loadHostPolicy
14
+ loadHostPolicy,
15
+ recheckHostAuthority
15
16
  } from '../src/host-plane.mjs';
16
17
 
17
18
  const sha = bytes => createHash('sha256').update(bytes).digest('hex');
@@ -66,6 +67,19 @@ test('privileged host plane requires a live policy-bound arm lease', async () =>
66
67
  await assert.rejects(() => assertHostArmed(expired.policy), /arm expired/);
67
68
  });
68
69
 
70
+ test('host authority recheck rejects arm generation or expiry drift before side effects', async () => {
71
+ const f = await fixture();
72
+ const initial = await assertHostArmed(f.policy);
73
+ const arm = JSON.parse(await readFile(f.arm, 'utf8'));
74
+ await writeFile(f.arm, JSON.stringify({ ...arm, instance_nonce: 'b'.repeat(64) }));
75
+ await assert.rejects(() => recheckHostAuthority(f.policy, initial), /host authority changed during operation/);
76
+ await writeFile(f.arm, JSON.stringify({
77
+ ...arm,
78
+ expires_at_utc: new Date(Date.parse(arm.expires_at_utc) + 60000).toISOString()
79
+ }));
80
+ await assert.rejects(() => recheckHostAuthority(f.policy, initial), /host authority changed during operation/);
81
+ });
82
+
69
83
  test('host_file_write is root-scoped hash-precondition and host_file_read returns a verified digest', async () => {
70
84
  const f = await fixture();
71
85
  const first = await hostFileWrite(f.policy, {
@@ -0,0 +1,53 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { createR34Fixture, planCreateDecision, sendPlannerDecision } from './helpers/autonomous-r34-fixture.mjs';
4
+
5
+ async function startPlannedGoal(f) {
6
+ const goal=await f.runtime.submit({submissionId:'r42-clarify',rootId:'project',title:'Clarification',
7
+ goal:'Choose the correct bounded implementation.',acceptance:'Use the operator choice.',maxIterations:8});
8
+ let result=await f.runtime.run({goalId:goal.goal_id,maxCycles:2});
9
+ result=await sendPlannerDecision(f.runtime,goal.goal_id,result,planCreateDecision(),{maxCycles:2});
10
+ return {goal,result};
11
+ }
12
+
13
+ test('R42 planner can request typed human clarification and resume with signed answer context',async()=>{
14
+ const f=await createR34Fixture();
15
+ try {
16
+ const {goal,result:first}=await startPlannedGoal(f);
17
+ const clarify={schema:'deadbyte.autonomous-decision.v2',kind:'clarify',
18
+ question:'Which compatibility target should be authoritative?',
19
+ choices:['Preserve R41 behavior','Adopt R42 behavior']};
20
+ const paused=await sendPlannerDecision(f.runtime,goal.goal_id,first,clarify,{maxCycles:2});
21
+ assert.equal(paused.status,'paused');
22
+ assert.equal(paused.reason,'human_input_required');
23
+ assert.deepEqual(paused.loop.clarification,{question:clarify.question,choices:clarify.choices});
24
+ const subject=paused.loop.approval_subject.approval_subject_sha256;
25
+ await assert.rejects(f.runtime.approve({goalId:goal.goal_id,decision:'approve',answer:'Preserve R41 behavior',
26
+ approvalSubjectSha256:'f'.repeat(64)}),/approval subject mismatch/i);
27
+ await assert.rejects(f.runtime.approve({goalId:goal.goal_id,decision:'approve',approvalSubjectSha256:subject}),/answer.*required/i);
28
+ const approved=await f.runtime.approve({goalId:goal.goal_id,decision:'approve',answer:'Preserve R41 behavior',
29
+ approvalSubjectSha256:subject});
30
+ assert.equal(approved.status,'approved');
31
+ const next=await f.runtime.run({goalId:goal.goal_id,maxCycles:2});
32
+ assert.equal(next.status,'input_required');
33
+ assert.match(next.planner_request.prompt,/Preserve R41 behavior/);
34
+ const events=await f.runtime.events({goalId:goal.goal_id,fromSeq:1,limit:200});
35
+ const provided=events.events.find(event=>event.type==='human_input_provided');
36
+ assert.equal(provided.payload.answer,'Preserve R41 behavior');
37
+ assert.match(provided.payload.answer_sha256,/^[0-9a-f]{64}$/);
38
+ const approval=events.events.find(event=>event.type==='approval_granted');
39
+ assert.equal(approval.payload.approval_subject_sha256,subject);
40
+ assert.equal(approval.payload.scope,'human_clarification');
41
+ } finally { await f.cleanup(); }
42
+ });
43
+
44
+ test('R42 clarification contract rejects injected fields and oversized choices',async()=>{
45
+ const f=await createR34Fixture();
46
+ try {
47
+ const {goal,result:first}=await startPlannedGoal(f);
48
+ const injected={schema:'deadbyte.autonomous-decision.v2',kind:'clarify',question:'Choose?',choices:[],command:'cmd.exe'};
49
+ const repaired=await sendPlannerDecision(f.runtime,goal.goal_id,first,injected,{maxCycles:2});
50
+ assert.equal(repaired.status,'input_required');
51
+ assert.match(repaired.planner_request.prompt,/PREVIOUS_RESPONSE_REJECTED/);
52
+ } finally { await f.cleanup(); }
53
+ });
@@ -79,6 +79,34 @@ test('native diff rolls back earlier operations when a later operation fails', a
79
79
  } finally { await rm(f.root,{recursive:true,force:true}); }
80
80
  });
81
81
 
82
+ test('native diff revocation between operations rolls back to preimage without autonomous rollback authority', async () => {
83
+ const f = await fixture();
84
+ try {
85
+ let writes = 0;
86
+ const revokingCoding = Object.freeze({
87
+ ...f.codingRuntime,
88
+ fileWrite: async args => {
89
+ const result = await f.codingRuntime.fileWrite(args);
90
+ writes += 1;
91
+ if (writes === 1) {
92
+ const lease = JSON.parse(await readFile(f.policy.lease_file, 'utf8'));
93
+ await writeFile(f.policy.lease_file, JSON.stringify({ ...lease, instance_nonce:'e'.repeat(64) }));
94
+ }
95
+ return result;
96
+ }
97
+ });
98
+ const runtime = createNativeDiffRuntime({ policy:f.policy, codingRuntime:revokingCoding,
99
+ signingKeyPath:f.keys.privateKeyPath, verifyKeyPath:f.keys.publicKeyPath });
100
+ const preview = await runtime.preview({ rootId:'project', title:'revoke mid transaction', operations:[
101
+ { kind:'write', path:'first.txt', expected_sha256:'absent', encoding:'utf8', content:'first\n' },
102
+ { kind:'write', path:'second.txt', expected_sha256:'absent', encoding:'utf8', content:'second\n' }
103
+ ] });
104
+ await assert.rejects(runtime.apply({ diffId:preview.diff_id }), /rolled back.*autonomous authority changed/i);
105
+ assert.equal(await exists(path.join(f.project,'first.txt')), false);
106
+ assert.equal(await exists(path.join(f.project,'second.txt')), false);
107
+ } finally { await rm(f.root,{recursive:true,force:true}); }
108
+ });
109
+
82
110
  test('autonomous application scope isolates repeated identical native diffs across goals', async () => {
83
111
  const f = await fixture();
84
112
  try {
@@ -19,7 +19,7 @@ const EXPECTED_TOOLS = [
19
19
  'agent_task_run', 'agent_task_status', 'agent_task_submit',
20
20
  'agent_task_verify', 'contained_transform', 'evidence_verify',
21
21
  'file_read',
22
- 'proof_execute', 'workspace_open'
22
+ 'proof_execute', 'skill_catalog', 'workspace_open'
23
23
  ];
24
24
 
25
25
  async function fixture() {
@@ -47,7 +47,7 @@ async function fixture() {
47
47
  return { trust, client, transport };
48
48
  }
49
49
 
50
- test('R27 hermetic base-plus-agent catalog advertises 12 strict output schemas', async () => {
50
+ test('R44 hermetic base-plus-agent catalog advertises 13 strict output schemas', async () => {
51
51
  const f = await fixture();
52
52
  try {
53
53
  await f.client.connect(f.transport);