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,104 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { generateKeyPairSync, createHash } from 'node:crypto';
4
+ import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import { createRemoteDevicePlaneR44 } from '../src/remote-device-r44.mjs';
8
+
9
+ const sha=b=>createHash('sha256').update(b).digest('hex');
10
+ function deviceKey(){
11
+ const {publicKey}=generateKeyPairSync('ed25519');
12
+ const der=Buffer.from(publicKey.export({type:'spki',format:'der'}));
13
+ return {spki:der.toString('base64'),deviceId:sha(der)};
14
+ }
15
+ class FakeRelay{
16
+ constructor(){this.probes=[];this.requests=[];this.drop=false;}
17
+ async probe(value){this.probes.push(structuredClone(value));return {device_id:value.device_id,epoch:value.epoch,nonce:value.nonce,ready:true};}
18
+ async request(value){this.requests.push(structuredClone(value));if(this.drop)return null;return {ack:'accepted',request_id:value.request_id,terminal:{status:'completed',result_sha256:'a'.repeat(64)}};}
19
+ }
20
+
21
+ test('R44 remote device reported online is not reachable until identity-bound probe ACK',async()=>{
22
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r44-remote-'));
23
+ const relay=new FakeRelay(), key=deviceKey();
24
+ try{
25
+ const plane=await createRemoteDevicePlaneR44({stateDir:root,relay,authorizeCapability:async()=>true});
26
+ const paired=await plane.pair({publicKeySpkiBase64:key.spki,displayName:'lab'});
27
+ assert.equal(paired.device_id,key.deviceId);assert.equal(paired.epoch,1);
28
+ await plane.reportOnline({deviceId:key.deviceId,epoch:1,connectionId:'conn-1'});
29
+ assert.equal((await plane.status({deviceId:key.deviceId})).reachable,false);
30
+ await assert.rejects(plane.dispatch({deviceId:key.deviceId,epoch:1,capabilityId:'coding.search',payload:{q:'x'},requestId:'1'.repeat(64)}),/not reachable/);
31
+ const ready=await plane.probe({deviceId:key.deviceId,epoch:1});
32
+ assert.equal(ready.reachable,true);assert.equal(relay.probes.length,1);
33
+ }finally{await rm(root,{recursive:true,force:true});}
34
+ });
35
+
36
+ test('R44 remote dispatch is durable-before-send, replay-idempotent and silent drop is terminal',async()=>{
37
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r44-remote-dispatch-'));
38
+ const relay=new FakeRelay(), key=deviceKey();
39
+ try{
40
+ const plane=await createRemoteDevicePlaneR44({stateDir:root,relay,authorizeCapability:async()=>true});
41
+ await plane.pair({publicKeySpkiBase64:key.spki,displayName:'lab'});
42
+ await plane.reportOnline({deviceId:key.deviceId,epoch:1,connectionId:'c'});
43
+ await plane.probe({deviceId:key.deviceId,epoch:1});
44
+ const requestId='2'.repeat(64), payload={path:'README.md'};
45
+ const done=await plane.dispatch({deviceId:key.deviceId,epoch:1,capabilityId:'coding.file_read',payload,requestId});
46
+ assert.equal(done.state,'completed');assert.equal(relay.requests.length,1);
47
+ const stored=JSON.parse(await readFile(path.join(root,'remote-devices.json'),'utf8'));
48
+ assert.equal(stored.requests[requestId].state,'completed');
49
+ const replay=await plane.dispatch({deviceId:key.deviceId,epoch:1,capabilityId:'coding.file_read',payload,requestId});
50
+ assert.equal(replay.replay,true);assert.equal(relay.requests.length,1);
51
+ await assert.rejects(plane.dispatch({deviceId:key.deviceId,epoch:1,capabilityId:'coding.file_read',payload:{path:'other'},requestId}),/request id conflict/);
52
+
53
+ relay.drop=true;
54
+ const droppedId='3'.repeat(64);
55
+ const dropped=await plane.dispatch({deviceId:key.deviceId,epoch:1,capabilityId:'coding.file_read',payload,requestId:droppedId});
56
+ assert.equal(dropped.state,'failed');assert.equal(dropped.error_code,'transport_no_ack');
57
+ const after=JSON.parse(await readFile(path.join(root,'remote-devices.json'),'utf8'));
58
+ assert.equal(after.requests[droppedId].state,'failed');
59
+ }finally{await rm(root,{recursive:true,force:true});}
60
+ });
61
+
62
+ test('R44 remote routing requires local capability authorization and revoke invalidates epoch',async()=>{
63
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r44-remote-auth-'));
64
+ const relay=new FakeRelay(), key=deviceKey();
65
+ try{
66
+ const plane=await createRemoteDevicePlaneR44({stateDir:root,relay,authorizeCapability:async({capabilityId})=>capabilityId==='machine.status'});
67
+ await plane.pair({publicKeySpkiBase64:key.spki,displayName:'lab'});
68
+ await plane.reportOnline({deviceId:key.deviceId,epoch:1,connectionId:'c'});await plane.probe({deviceId:key.deviceId,epoch:1});
69
+ await assert.rejects(plane.dispatch({deviceId:key.deviceId,epoch:1,capabilityId:'host.exec',payload:{},requestId:'4'.repeat(64)}),/capability denied/);
70
+ assert.equal(relay.requests.length,0);
71
+ const revoked=await plane.revoke({deviceId:key.deviceId,epoch:1});
72
+ assert.equal(revoked.revoked,true);
73
+ await assert.rejects(plane.reportOnline({deviceId:key.deviceId,epoch:1,connectionId:'new'}),/revoked|epoch/i);
74
+ }finally{await rm(root,{recursive:true,force:true});}
75
+ });
76
+
77
+ test('R44 credential rotation commits encrypted replacement before supersede callback',async()=>{
78
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r44-remote-rotate-'));
79
+ const relay=new FakeRelay(), key=deviceKey(), observed=[];
80
+ try{
81
+ const plane=await createRemoteDevicePlaneR44({stateDir:root,relay,authorizeCapability:async()=>true,
82
+ onCredentialSuperseded:async info=>{
83
+ const current=JSON.parse(await readFile(path.join(root,'credentials',`${key.deviceId}.json`),'utf8'));
84
+ observed.push({info,current});
85
+ }});
86
+ await plane.pair({publicKeySpkiBase64:key.spki,displayName:'lab'});
87
+ const one=Buffer.from('encrypted-token-one').toString('base64');
88
+ const first=await plane.rotateCredential({deviceId:key.deviceId,epoch:1,encryptedBundleBase64:one,expectedPreviousSha256:null});
89
+ const two=Buffer.from('encrypted-token-two').toString('base64');
90
+ const second=await plane.rotateCredential({deviceId:key.deviceId,epoch:1,encryptedBundleBase64:two,expectedPreviousSha256:first.credential_sha256});
91
+ assert.notEqual(second.credential_sha256,first.credential_sha256);
92
+ assert.equal(observed.length,1);
93
+ assert.equal(observed[0].current.credential_sha256,second.credential_sha256,'new credential must be durable before supersede');
94
+ await assert.rejects(plane.rotateCredential({deviceId:key.deviceId,epoch:1,encryptedBundleBase64:one,expectedPreviousSha256:first.credential_sha256}),/precondition/);
95
+ }finally{await rm(root,{recursive:true,force:true});}
96
+ });
97
+
98
+ test('R44 remote state corruption fails closed',async()=>{
99
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r44-remote-corrupt-'));
100
+ try{
101
+ await writeFile(path.join(root,'remote-devices.json'),'{"schema":','utf8');
102
+ await assert.rejects(createRemoteDevicePlaneR44({stateDir:root,relay:new FakeRelay(),authorizeCapability:async()=>true}),/state JSON invalid/);
103
+ }finally{await rm(root,{recursive:true,force:true});}
104
+ });
@@ -53,6 +53,29 @@ test('R27 slot-update CLI requires exact stage/development/version/manifest bind
53
53
  assert.ok(path.isAbsolute(parsed.developmentRoot));
54
54
  });
55
55
 
56
+ test('R43 slot authority restore plan carries prior Process grant without restoring Desktop or Host', () => {
57
+ assert.equal(typeof runtimeUpdate.standingAuthorityRestorePlan,'function');
58
+ const parsed=runtimeUpdate.parseControllerStatus([
59
+ 'DEADBYTE MCP = ONLINE',
60
+ 'Process plane : ARMED [standing-grant]',
61
+ 'Coding plane : ARMED [standing-grant]',
62
+ 'Autonomous : ARMED [standing-grant]',
63
+ 'Desktop plane : ARMED [standing-grant]',
64
+ 'Host plane : DISARMED [disarmed]'
65
+ ].join('\n'));
66
+ assert.equal(parsed.process_armed,true);
67
+ const plan=runtimeUpdate.standingAuthorityRestorePlan(parsed,true);
68
+ assert.deepEqual(plan.commands,['armcoding','armautonomous','armprocess']);
69
+ assert.deepEqual(plan.expected,{
70
+ process_armed:true,coding_armed:true,autonomous_armed:true,desktop_armed:false,host_armed:false
71
+ });
72
+ const disabled=runtimeUpdate.standingAuthorityRestorePlan(parsed,false);
73
+ assert.deepEqual(disabled.commands,[]);
74
+ assert.deepEqual(disabled.expected,{
75
+ process_armed:false,coding_armed:false,autonomous_armed:false,desktop_armed:false,host_armed:false
76
+ });
77
+ });
78
+
56
79
  test('R27 development root is outside immutable/runtime state stores', async () => {
57
80
  const state=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r27-state-'));
58
81
  const dev=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r27-dev-'));
@@ -410,6 +433,25 @@ test('R29 bridge bootstrap rebinds only the pinned external bridge script', asyn
410
433
  await assert.rejects(runtimeUpdate.prepareBridgeSpec(config,{root,scriptSha256:hex('f')}),/bridge script SHA-256 mismatch/i);
411
434
  });
412
435
 
436
+ test('R44 downstream core selection accepts only one exact visible or one opaque child of the verified Bridge', () => {
437
+ assert.equal(typeof runtimeUpdate.selectCoreProcess,'function');
438
+ const root=path.resolve('C:/release'),bridgePid=1200;
439
+ const visible={ProcessId:1300,ParentProcessId:bridgePid,CreationDate:'20260921010000.000000+000',
440
+ ExecutablePath:'C:\\Program Files\\nodejs\\node.exe',CommandLine:`node "${path.join(root,'src','mcp-server.mjs')}"`};
441
+ const exact=runtimeUpdate.selectCoreProcess([visible],root,bridgePid);
442
+ assert.equal(exact.pid,1300);
443
+ assert.equal(exact.identity_basis,'command_line');
444
+ const opaque={ProcessId:1301,ParentProcessId:bridgePid,CreationDate:'20260921010100.000000+000',ExecutablePath:null,CommandLine:null};
445
+ const verifiedBridge={component:'bridge',pid:bridgePid,script_path:'C:\\bridge\\src\\bridge.mjs',script_sha256:hex('a')};
446
+ const fallback=runtimeUpdate.selectCoreProcess([opaque],root,bridgePid,verifiedBridge);
447
+ assert.equal(fallback.pid,1301);
448
+ assert.equal(fallback.identity_basis,'verified_bridge_single_opaque_node_child');
449
+ assert.throws(()=>runtimeUpdate.selectCoreProcess([opaque],root,bridgePid),/requires verified Bridge script binding/i);
450
+ assert.throws(()=>runtimeUpdate.selectCoreProcess([opaque,{...opaque,ProcessId:1302}],root,bridgePid,verifiedBridge),/unambiguous downstream core/i);
451
+ assert.throws(()=>runtimeUpdate.selectCoreProcess([{...visible,CommandLine:'node C:\\other\\server.mjs'}],root,bridgePid),/unambiguous downstream core/i);
452
+ assert.throws(()=>runtimeUpdate.selectCoreProcess([{...opaque,CreationDate:''}],root,bridgePid,verifiedBridge),/identity incomplete/i);
453
+ });
454
+
413
455
  test('R31 updater threads pinned process policy and machine-FS enable into successor generation', async () => {
414
456
  const source=await readFile(new URL('../src/runtime-update.mjs',import.meta.url),'utf8');
415
457
  assert.match(source,/prepareProcessPolicyBootstrap\(processPolicyBootstrap\)/);
@@ -0,0 +1,38 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { Client } from '@modelcontextprotocol/client';
4
+ import { StdioClientTransport, getDefaultEnvironment } from '@modelcontextprotocol/client/stdio';
5
+ import { mkdtemp, rm } from 'node:fs/promises';
6
+ import os from 'node:os';
7
+ import path from 'node:path';
8
+ import { fileURLToPath } from 'node:url';
9
+
10
+ const here=path.dirname(fileURLToPath(import.meta.url));
11
+ const repoRoot=path.resolve(here,'..');
12
+ const serverPath=path.join(repoRoot,'src','mcp-server.mjs');
13
+
14
+ test('R44 compact surface exposes one read-only skill_catalog without authority',async()=>{
15
+ const state=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r44-skill-mcp-'));
16
+ const client=new Client({name:'deadbyte-r44-skill-mcp',version:'0.15.0-dev'},{versionNegotiation:{mode:{pin:'2026-07-28'}}});
17
+ const env={...getDefaultEnvironment(),DEADBYTE_STATE_ROOT:state,DEADBYTE_CAPABILITY_PROFILE:'compact',
18
+ DEADBYTE_AUDIT_RUNTIME:'0',DEADBYTE_MACHINE_RUNTIME:'0',DEADBYTE_AGENT_RUNTIME:'0',
19
+ DEADBYTE_PROCESS_RUNTIME:'0',DEADBYTE_CODING_RUNTIME:'0',DEADBYTE_MACHINE_FS_RUNTIME:'0',
20
+ DEADBYTE_OBSERVATION_RUNTIME:'0',DEADBYTE_DESKTOP_RUNTIME:'0',DEADBYTE_AUTONOMOUS_RUNTIME:'0'};
21
+ const transport=new StdioClientTransport({command:process.execPath,args:[serverPath],cwd:repoRoot,env,stderr:'pipe'});
22
+ try{
23
+ await client.connect(transport);
24
+ const names=(await client.listTools()).tools.map(t=>t.name).sort();
25
+ assert.ok(names.includes('skill_catalog'),'R44 skill_catalog missing from compact surface');
26
+ assert.equal(names.filter(name=>name.startsWith('skill_')).length,1,'skill plane must remain one-tool surface');
27
+ const listed=await client.callTool({name:'skill_catalog',arguments:{op:'list'}});
28
+ assert.equal(listed.isError,false,JSON.stringify(listed));
29
+ assert.equal(listed.structuredContent.status,'ok');
30
+ assert.equal(listed.structuredContent.op,'list');
31
+ assert.deepEqual(listed.structuredContent.result.skills,[]);
32
+ assert.match(listed.structuredContent.result.registry_sha256,/^[0-9a-f]{64}$/);
33
+ const manifest=await client.callTool({name:'capability_manifest',arguments:{}});
34
+ const cap=manifest.structuredContent.result.capabilities.find(x=>x.id==='skill.catalog');
35
+ assert.ok(cap,'skill.catalog capability missing');
36
+ assert.equal(cap.authority,'none');assert.equal(cap.mutating,false);assert.equal(cap.compact_tool,'skill_catalog');
37
+ }finally{await client.close().catch(()=>{});await rm(state,{recursive:true,force:true});}
38
+ });
@@ -0,0 +1,93 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { createHash, createPrivateKey, sign as cryptoSign } 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 { createSkillPlane } from '../src/skill-plane-r44.mjs';
9
+
10
+ const DOMAIN=Buffer.from('DEADBYTE-SKILL-MANIFEST-V1\0','utf8');
11
+ const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
12
+ const canonical=value=>Array.isArray(value)?value.map(canonical):value&&typeof value==='object'
13
+ ?Object.fromEntries(Object.keys(value).sort().map(key=>[key,canonical(value[key])])):value;
14
+ const canonicalJson=value=>JSON.stringify(canonical(value));
15
+
16
+ async function fixture(){
17
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r44-skill-'));
18
+ const registry=path.join(root,'skills');
19
+ const trust=path.join(root,'trust');
20
+ await mkdir(registry,{recursive:true});await mkdir(trust,{recursive:true});
21
+ const keys=await generateTrustKeypair({privateKeyPath:path.join(trust,'private.pem'),publicKeyPath:path.join(trust,'public.pem')});
22
+ return {root,registry,keys};
23
+ }
24
+ async function installSkill(f,{name='repo-audit',authorityPlanes=['coding'],requiredCapabilities=['coding.search'],extraBody={}}={}){
25
+ const dir=path.join(f.registry,name);await mkdir(path.join(dir,'references'),{recursive:true});
26
+ const skill=`---\nname: ${name}\ndescription: Audit one repository with bounded evidence\nversion: 1.0.0\naudience: agent\n---\n\n# Repo Audit\n\nInspect before mutation.\n`;
27
+ const ref='Use bounded search and cite exact observed files.\n';
28
+ await writeFile(path.join(dir,'SKILL.md'),skill,'utf8');
29
+ await writeFile(path.join(dir,'references','guide.md'),ref,'utf8');
30
+ const body={
31
+ schema:'deadbyte.skill-manifest.v1',name,version:'1.0.0',description:'Audit one repository with bounded evidence',audience:'agent',
32
+ skill_md_sha256:sha(Buffer.from(skill)),resources:[{path:'references/guide.md',sha256:sha(Buffer.from(ref)),kind:'reference'}],
33
+ required_capabilities:requiredCapabilities,authority_planes:authorityPlanes,
34
+ provenance:{source:'test-fixture',origin_url:null,source_revision:null},...extraBody
35
+ };
36
+ const bodyBytes=Buffer.from(canonicalJson(body),'utf8');
37
+ const privateKey=createPrivateKey(await readFile(f.keys.privateKeyPath));
38
+ const envelope={body,body_sha256:sha(bodyBytes),attestation:{algorithm:'ed25519',domain:'DEADBYTE-SKILL-MANIFEST-V1',
39
+ key_id:f.keys.keyId,signature_base64:cryptoSign(null,Buffer.concat([DOMAIN,bodyBytes]),privateKey).toString('base64')}};
40
+ envelope.manifest_sha256=sha(Buffer.from(canonicalJson(envelope),'utf8'));
41
+ await writeFile(path.join(dir,'skill.json'),`${JSON.stringify(envelope,null,2)}\n`,'utf8');
42
+ return {dir,skill,ref,envelope};
43
+ }
44
+
45
+ test('R44 skill catalog is metadata-only and load is signature/content bound',async()=>{
46
+ const f=await fixture();
47
+ try{
48
+ await installSkill(f,{});
49
+ const plane=await createSkillPlane({root:f.registry,verifyKeyPath:f.keys.publicKeyPath});
50
+ const listed=await plane.list();
51
+ assert.equal(listed.status,'ok');assert.equal(listed.skills.length,1);
52
+ assert.equal(listed.skills[0].name,'repo-audit');assert.equal(listed.skills[0].authority_planes[0],'coding');
53
+ assert.equal('content' in listed.skills[0],false,'catalog must not leak instructions');
54
+ const loaded=await plane.load({name:'repo-audit'});
55
+ assert.equal(loaded.status,'ok');assert.match(loaded.content,/Inspect before mutation/);
56
+ assert.equal(loaded.verified,true);assert.match(loaded.manifest_sha256,/^[0-9a-f]{64}$/);
57
+ const resource=await plane.readResource({name:'repo-audit',resourcePath:'references/guide.md'});
58
+ assert.equal(resource.content,'Use bounded search and cite exact observed files.\n');
59
+ assert.equal(typeof plane.arm,'undefined');assert.equal(typeof plane.renew,'undefined');
60
+ }finally{await rm(f.root,{recursive:true,force:true});}
61
+ });
62
+
63
+ test('R44 skill plane fails closed on SKILL.md and resource tampering',async()=>{
64
+ const f=await fixture();
65
+ try{
66
+ const installed=await installSkill(f,{});
67
+ const plane=await createSkillPlane({root:f.registry,verifyKeyPath:f.keys.publicKeyPath});
68
+ await writeFile(path.join(installed.dir,'SKILL.md'),'tampered\n','utf8');
69
+ await assert.rejects(plane.load({name:'repo-audit'}),/SKILL\.md hash mismatch/);
70
+ await installSkill(f,{});
71
+ await writeFile(path.join(installed.dir,'references','guide.md'),'tampered\n','utf8');
72
+ await assert.rejects(plane.readResource({name:'repo-audit',resourcePath:'references/guide.md'}),/resource hash mismatch/);
73
+ }finally{await rm(f.root,{recursive:true,force:true});}
74
+ });
75
+
76
+ test('R44 skill plane rejects undeclared payloads, unknown authority and signature drift',async()=>{
77
+ const f=await fixture();
78
+ try{
79
+ const installed=await installSkill(f,{});
80
+ let plane=await createSkillPlane({root:f.registry,verifyKeyPath:f.keys.publicKeyPath});
81
+ await writeFile(path.join(installed.dir,'hidden.ps1'),'Write-Host pwn\n','utf8');
82
+ await assert.rejects(plane.load({name:'repo-audit'}),/undeclared skill file/);
83
+ await rm(path.join(installed.dir,'hidden.ps1'));
84
+ await installSkill(f,{authorityPlanes:['root']});
85
+ await assert.rejects(plane.load({name:'repo-audit'}),/unknown authority plane/);
86
+ await installSkill(f,{});
87
+ const env=JSON.parse(await readFile(path.join(installed.dir,'skill.json'),'utf8'));
88
+ env.body.description='signature drift';
89
+ await writeFile(path.join(installed.dir,'skill.json'),JSON.stringify(env,null,2)+'\n');
90
+ plane=await createSkillPlane({root:f.registry,verifyKeyPath:f.keys.publicKeyPath});
91
+ await assert.rejects(plane.load({name:'repo-audit'}),/body hash mismatch/);
92
+ }finally{await rm(f.root,{recursive:true,force:true});}
93
+ });
@@ -4,7 +4,7 @@ import { mkdtemp, mkdir, writeFile } from 'node:fs/promises';
4
4
  import os from 'node:os';
5
5
  import path from 'node:path';
6
6
  import { loadCodingPolicy, codingSessionStatus } from '../src/coding-plane.mjs';
7
- import { loadAutonomousPolicy, autonomousSessionStatus } from '../src/autonomous-policy.mjs';
7
+ import { assertAutonomousArmed, loadAutonomousPolicy, autonomousSessionStatus, recheckAutonomousAuthority } from '../src/autonomous-policy.mjs';
8
8
  import { loadHostPolicy, assertHostArmed } from '../src/host-plane.mjs';
9
9
  import { createHash } from 'node:crypto';
10
10
 
@@ -48,6 +48,10 @@ test('autonomous standing grant remains armed until explicit revocation', async
48
48
  assert.equal(status.armed, true);
49
49
  assert.equal(status.reason, 'standing-grant');
50
50
  assert.equal(status.expires_at_utc, null);
51
+ const initial = await assertAutonomousArmed(policy, 'project', farFuture);
52
+ const grant = JSON.parse(await import('node:fs/promises').then(fs => fs.readFile(policy.lease_file, 'utf8')));
53
+ await writeFile(policy.lease_file, JSON.stringify({ ...grant, instance_nonce:'d'.repeat(64) }));
54
+ await assert.rejects(() => recheckAutonomousAuthority(policy, initial, 'project', farFuture), /autonomous authority changed during operation/);
51
55
  });
52
56
  test('host standing grant remains armed until explicit revocation', async () => {
53
57
  const root = await mkdtemp(path.join(os.tmpdir(), 'deadbyte-host-standing-'));
@@ -0,0 +1,41 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { createR34Fixture } from './helpers/autonomous-r34-fixture.mjs';
4
+
5
+ const contract={
6
+ schema:'deadbyte.task-contract.v1',
7
+ scope:['src/autonomous-*','test/*r42*'],
8
+ non_goals:['No generic shell','No implicit grant renewal'],
9
+ constraints:['Preserve R41 compatibility','Use exact preconditions'],
10
+ evidence_requirements:['Focused tests pass','Signed journal verifies'],
11
+ risk_level:'high',
12
+ clarification_policy:'ask_when_blocked',
13
+ project_default:'lightweight',
14
+ explicit_override:'structured'
15
+ };
16
+
17
+ test('R42 task contract is identity-bound and visible to every planner round',async()=>{
18
+ const f=await createR34Fixture();
19
+ try {
20
+ const goal=await f.runtime.submit({submissionId:'r42-task-contract',rootId:'project',title:'Contract',
21
+ goal:'Implement bounded behavior.',acceptance:'All required evidence passes.',taskContract:contract,maxIterations:8});
22
+ const status=await f.runtime.status({goalId:goal.goal_id});
23
+ assert.equal(status.state,'pending');
24
+ const run=await f.runtime.run({goalId:goal.goal_id,maxCycles:1});
25
+ assert.equal(run.status,'input_required');
26
+ assert.match(run.planner_request.prompt,/deadbyte\.task-contract\.v1/);
27
+ assert.match(run.planner_request.prompt,/No generic shell/);
28
+ assert.match(run.planner_request.prompt,/Signed journal verifies/);
29
+ assert.match(run.planner_request.prompt,/"effective_rigor":"agentic"/);
30
+ } finally { await f.cleanup(); }
31
+ });
32
+
33
+ test('R42 task contract rejects authority widening fields and invalid risk',async()=>{
34
+ const f=await createR34Fixture();
35
+ try {
36
+ await assert.rejects(f.runtime.submit({submissionId:'r42-task-injected',rootId:'project',title:'Contract',
37
+ goal:'Reject injection.',acceptance:'Fail closed.',taskContract:{...contract,shell:'cmd.exe'}}),/unknown field|task contract/i);
38
+ await assert.rejects(f.runtime.submit({submissionId:'r42-task-risk',rootId:'project',title:'Contract',
39
+ goal:'Reject risk.',acceptance:'Fail closed.',taskContract:{...contract,risk_level:'unbounded'}}),/risk_level/i);
40
+ } finally { await f.cleanup(); }
41
+ });