deadbyte-mcp 0.14.0 → 0.15.1

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 (157) hide show
  1. package/AGENTS.md +11 -18
  2. package/CONTEXT.md +3 -3
  3. package/MANIFEST.SHA256 +156 -70
  4. package/PROVENANCE.json +10 -0
  5. package/R43-PARENT.json +20 -0
  6. package/R44-DONORS.json +99 -0
  7. package/R44-PARENT.json +18 -0
  8. package/R45-PARENT.json +18 -0
  9. package/README.txt +19 -14
  10. package/RELEASE.json +37 -0
  11. package/SBOM.cdx.json +46 -0
  12. package/TEST-MATRIX.json +31 -0
  13. package/bin/deadbyte-hardware-observer-r45.exe +0 -0
  14. package/bin/deadbyte-hardware-observer-r45.obj +0 -0
  15. package/controller/README.TXT +5 -6
  16. package/controller/deadbyte-controller.ps1 +2 -2
  17. package/docs/ARCHITECTURE.md +8 -1
  18. package/docs/R44-ARCHITECTURE.md +37 -0
  19. package/native/deadbyte-hardware-observer.cpp +308 -0
  20. package/package.json +18 -8
  21. package/scripts/autonomous-tests.ps1 +2 -2
  22. package/scripts/build-hardware-r45.ps1 +71 -0
  23. package/scripts/deferred-slot-operation-r43.mjs +126 -0
  24. package/scripts/deferred-slot-operation-r44.mjs +126 -0
  25. package/scripts/deferred-slot-operation-r45.mjs +126 -0
  26. package/scripts/derive-r45-parent.mjs +24 -0
  27. package/scripts/derive-r45-release.mjs +72 -0
  28. package/scripts/derive-r45-tests.mjs +20 -0
  29. package/scripts/final-closure-r43.mjs +115 -0
  30. package/scripts/final-closure-r44.mjs +121 -0
  31. package/scripts/final-closure-r45.mjs +121 -0
  32. package/scripts/final-closure-verify-r43.mjs +213 -0
  33. package/scripts/final-closure-verify-r44.mjs +220 -0
  34. package/scripts/final-closure-verify-r45.mjs +220 -0
  35. package/scripts/finalize-r43.mjs +217 -0
  36. package/scripts/finalize-r44.mjs +217 -0
  37. package/scripts/finalize-r45.mjs +219 -0
  38. package/scripts/full-regression-r43.mjs +20 -0
  39. package/scripts/full-regression-r44.mjs +20 -0
  40. package/scripts/full-regression-r45.mjs +20 -0
  41. package/scripts/full-regression-summary-r45.mjs +14 -0
  42. package/scripts/gate-windows-r43.ps1 +36 -0
  43. package/scripts/gate-windows-r44.ps1 +36 -0
  44. package/scripts/gate-windows-r45.ps1 +44 -0
  45. package/scripts/init-host-policy.ps1 +39 -1
  46. package/scripts/inspect-active-release-r45.mjs +20 -0
  47. package/scripts/release-manifest.mjs +3 -1
  48. package/scripts/release-parity-r43.mjs +218 -0
  49. package/scripts/release-parity-r44.mjs +242 -0
  50. package/scripts/release-parity-r45.mjs +253 -0
  51. package/scripts/release-parity-tests-r43.ps1 +8 -0
  52. package/scripts/release-parity-tests-r44.ps1 +8 -0
  53. package/scripts/release-parity-tests-r45.ps1 +8 -0
  54. package/scripts/verify-r43-parent.mjs +126 -0
  55. package/scripts/verify-r44-parent.mjs +142 -0
  56. package/scripts/verify-r45-parent.mjs +142 -0
  57. package/scripts/windows-gate-evidence-r43.mjs +55 -0
  58. package/scripts/windows-gate-evidence-r44.mjs +55 -0
  59. package/scripts/windows-gate-evidence-r45.mjs +60 -0
  60. package/src/agent-backend-r44.mjs +254 -0
  61. package/src/autonomous-output-contracts.mjs +1 -1
  62. package/src/autonomous-policy.mjs +41 -4
  63. package/src/autonomous-runtime.mjs +37 -19
  64. package/src/autonomous-supervisor-host.mjs +12 -2
  65. package/src/capability-ledger.mjs +3 -1
  66. package/src/codex-app-server-r44.mjs +154 -0
  67. package/src/coding-plane.mjs +66 -19
  68. package/src/compact-gateway-mcp-tools.mjs +4 -2
  69. package/src/deadbyte-cli.mjs +17 -9
  70. package/src/doctor-r45.mjs +65 -0
  71. package/src/durable-job-r44.mjs +145 -0
  72. package/src/external-job-provider-r44.mjs +126 -0
  73. package/src/host-plane.mjs +156 -87
  74. package/src/job-supervisor-host-r44.mjs +112 -0
  75. package/src/machine-fs-smoke-client.mjs +1 -1
  76. package/src/machine-fs.mjs +21 -10
  77. package/src/mcp-host-smoke-client.mjs +1 -1
  78. package/src/mcp-server.mjs +84 -26
  79. package/src/mcp-smoke-client.mjs +1 -1
  80. package/src/native-diff-v2.mjs +12 -7
  81. package/src/native-diff.mjs +32 -9
  82. package/src/observation-mcp-tools.mjs +5 -1
  83. package/src/observation-output-contracts.mjs +8 -0
  84. package/src/observation-runtime.mjs +33 -2
  85. package/src/observation-smoke-client.mjs +7 -3
  86. package/src/operator-surface.mjs +2 -2
  87. package/src/output-contracts.mjs +27 -11
  88. package/src/process-policy.mjs +19 -0
  89. package/src/process-runtime.mjs +5 -2
  90. package/src/process-smoke-client.mjs +2 -2
  91. package/src/provider-circuit.mjs +8 -2
  92. package/src/r44-compact-disarmed-probe.mjs +74 -0
  93. package/src/r44-full-surface-probe.mjs +34 -0
  94. package/src/r45-compact-disarmed-probe.mjs +74 -0
  95. package/src/r45-full-surface-probe.mjs +34 -0
  96. package/src/release-generation.mjs +1 -0
  97. package/src/remote-device-r44.mjs +205 -0
  98. package/src/remote-mcp-autonomous-smoke-client.mjs +4 -4
  99. package/src/remote-mcp-r44-parity-client.mjs +203 -0
  100. package/src/remote-mcp-r45-parity-client.mjs +206 -0
  101. package/src/remote-mcp-smoke-client.mjs +7 -4
  102. package/src/runtime-update.mjs +100 -39
  103. package/src/skill-mcp-tools-r44.mjs +37 -0
  104. package/src/skill-plane-r44.mjs +203 -0
  105. package/src/tunnel-stdio-compat-smoke.mjs +1 -1
  106. package/src/version.mjs +1 -1
  107. package/test/agent-backend-r44.test.mjs +115 -0
  108. package/test/agent-runtime.test.mjs +4 -3
  109. package/test/autonomous-loop.test.mjs +121 -11
  110. package/test/autonomous-runtime.test.mjs +3 -2
  111. package/test/capability-ledger-r36.test.mjs +3 -3
  112. package/test/cli-entrypoint.test.mjs +4 -4
  113. package/test/codex-app-server-r44.test.mjs +115 -0
  114. package/test/codex-autonomous-r44.test.mjs +87 -0
  115. package/test/coding-plane.test.mjs +3 -2
  116. package/test/coding-runtime.test.mjs +36 -1
  117. package/test/compact-catalog-r36.test.mjs +3 -3
  118. package/test/compact-max-catalog-r40.test.mjs +4 -2
  119. package/test/compact-max-catalog-r44.test.mjs +97 -0
  120. package/test/compact-max-catalog-r45.test.mjs +99 -0
  121. package/test/contained.test.mjs +23 -11
  122. package/test/controller.test.mjs +5 -1
  123. package/test/core.test.mjs +1 -1
  124. package/test/durable-job-r44.test.mjs +75 -0
  125. package/test/external-job-provider-r44.test.mjs +46 -0
  126. package/test/full-max-catalog-r40.test.mjs +5 -3
  127. package/test/full-max-catalog-r44.test.mjs +91 -0
  128. package/test/full-max-catalog-r45.test.mjs +91 -0
  129. package/test/hardware-observation-r45.test.mjs +44 -0
  130. package/test/host-inline-r45.test.mjs +67 -0
  131. package/test/host-plane.test.mjs +15 -1
  132. package/test/machine-mcp-integration.test.mjs +1 -0
  133. package/test/native-diff.test.mjs +28 -0
  134. package/test/observation-mcp-tools.test.mjs +4 -3
  135. package/test/output-schema.test.mjs +3 -2
  136. package/test/package-boundary.test.mjs +1 -1
  137. package/test/process-policy.test.mjs +22 -1
  138. package/test/production-docs.test.mjs +3 -3
  139. package/test/prompts-resources.test.mjs +1 -1
  140. package/test/r33-closeout-regression.test.mjs +20 -36
  141. package/test/r33-finalization.test.mjs +7 -7
  142. package/test/r43-finalization.test.mjs +31 -0
  143. package/test/r43-finalizer.test.mjs +34 -0
  144. package/test/r43-parent.test.mjs +29 -0
  145. package/test/r44-finalization.test.mjs +58 -0
  146. package/test/r44-finalizer.test.mjs +48 -0
  147. package/test/r44-parent.test.mjs +25 -0
  148. package/test/r45-autonomous-quick-test.test.mjs +10 -0
  149. package/test/r45-finalization.test.mjs +84 -0
  150. package/test/release-generation.test.mjs +1 -0
  151. package/test/release-version.test.mjs +65 -20
  152. package/test/remote-device-r44.test.mjs +104 -0
  153. package/test/runtime-host-bootstrap-r45.test.mjs +46 -0
  154. package/test/runtime-self-update.test.mjs +42 -0
  155. package/test/skill-mcp-r44.test.mjs +38 -0
  156. package/test/skill-plane-r44.test.mjs +93 -0
  157. package/test/standing-grants.test.mjs +5 -1
@@ -45,14 +45,17 @@ function normalizePersistentPublicOrigin(value){
45
45
  return publicOrigin.origin;
46
46
  }
47
47
  export function parseUpdateArgs(argv){
48
- const out={stage:null,developmentRoot:null,expectedVersion:null,expectedManifest:null,sourceRoot:null,restoreStandingGrants:false,recoverOfflinePredecessor:false,enableMachineFs:false,enableObservation:false,enableDesktop:false,enableAutonomousSupervisor:false,cloudTransport:null,bridgeBootstrap:null,processPolicyBootstrap:null,desktopPolicyBootstrap:null,semanticLspBootstrap:null,hostCommandPins:[]};
49
- const cloud={},bridge={},processPolicy={},desktopPolicy={},semanticLsp={},seen=new Set(),hostPinIds=new Set();
48
+ const out={stage:null,developmentRoot:null,expectedVersion:null,expectedManifest:null,sourceRoot:null,restoreStandingGrants:false,recoverOfflinePredecessor:false,enableMachineFs:false,enableObservation:false,enableDesktop:false,enableAutonomousSupervisor:false,cloudTransport:null,bridgeBootstrap:null,processPolicyBootstrap:null,hostPolicyBootstrap:null,desktopPolicyBootstrap:null,semanticLspBootstrap:null,hostCommandPins:[]};
49
+ const cloud={},bridge={},processPolicy={},hostPolicy={},desktopPolicy={},semanticLsp={},seen=new Set(),hostPinIds=new Set();
50
50
  const bridgeFlags=new Map([
51
51
  ['--bridge-root','root'],['--bridge-script-sha256','scriptSha256']
52
52
  ]);
53
53
  const processPolicyFlags=new Map([
54
54
  ['--process-policy','path'],['--process-policy-sha256','sha256']
55
55
  ]);
56
+ const hostPolicyFlags=new Map([
57
+ ['--host-policy','path'],['--host-policy-sha256','sha256']
58
+ ]);
56
59
  const desktopPolicyFlags=new Map([
57
60
  ['--desktop-policy','path'],['--desktop-policy-sha256','sha256']
58
61
  ]);
@@ -64,7 +67,7 @@ export function parseUpdateArgs(argv){
64
67
  ['--cloud-token-file','tokenFilePath'],['--cloud-token-sha256','tokenFileSha256'],
65
68
  ['--cloud-child-working-directory','childWorkingDirectory'],['--cloud-public-origin','publicOrigin']
66
69
  ]);
67
- const valued=new Set(['--stage','--development-root','--expected-version','--expected-manifest','--source-root','--host-command-pin',...bridgeFlags.keys(),...processPolicyFlags.keys(),...desktopPolicyFlags.keys(),...semanticLspFlags.keys(),...cloudFlags.keys()]);
70
+ const valued=new Set(['--stage','--development-root','--expected-version','--expected-manifest','--source-root','--host-command-pin',...bridgeFlags.keys(),...processPolicyFlags.keys(),...hostPolicyFlags.keys(),...desktopPolicyFlags.keys(),...semanticLspFlags.keys(),...cloudFlags.keys()]);
68
71
  for(let index=0;index<argv.length;index++){
69
72
  const arg=argv[index];
70
73
  if(arg==='--restore-standing-grants'||arg==='--recover-offline-predecessor'||arg==='--enable-machine-fs'||arg==='--enable-observation'||arg==='--enable-desktop'||arg==='--enable-autonomous-supervisor'){
@@ -99,6 +102,7 @@ export function parseUpdateArgs(argv){
99
102
  else if(arg==='--source-root') out.sourceRoot=path.resolve(value);
100
103
  else if(bridgeFlags.has(arg)) bridge[bridgeFlags.get(arg)]=value;
101
104
  else if(processPolicyFlags.has(arg)) processPolicy[processPolicyFlags.get(arg)]=value;
105
+ else if(hostPolicyFlags.has(arg)) hostPolicy[hostPolicyFlags.get(arg)]=value;
102
106
  else if(desktopPolicyFlags.has(arg)) desktopPolicy[desktopPolicyFlags.get(arg)]=value;
103
107
  else if(semanticLspFlags.has(arg)) semanticLsp[semanticLspFlags.get(arg)]=value;
104
108
  else cloud[cloudFlags.get(arg)]=value;
@@ -121,6 +125,13 @@ export function parseUpdateArgs(argv){
121
125
  if(!HEX64.test(sha256)) throw new Error('process policy SHA-256 must be lowercase hex');
122
126
  out.processPolicyBootstrap={path:path.resolve(processPolicy.path),sha256};
123
127
  }
128
+ const hostPolicyFields=[...hostPolicyFlags.values()],hostPolicyPresent=hostPolicyFields.filter(key=>hostPolicy[key]!==undefined);
129
+ if(hostPolicyPresent.length!==0&&hostPolicyPresent.length!==hostPolicyFields.length) throw new Error('host policy bootstrap requires path plus SHA-256 as one pair');
130
+ if(hostPolicyPresent.length===hostPolicyFields.length){
131
+ const sha256=String(hostPolicy.sha256).toLowerCase();
132
+ if(!HEX64.test(sha256)) throw new Error('host policy SHA-256 must be lowercase hex');
133
+ out.hostPolicyBootstrap={path:path.resolve(hostPolicy.path),sha256};
134
+ }
124
135
  const desktopPolicyFields=[...desktopPolicyFlags.values()],desktopPolicyPresent=desktopPolicyFields.filter(key=>desktopPolicy[key]!==undefined);
125
136
  if(desktopPolicyPresent.length!==0&&desktopPolicyPresent.length!==desktopPolicyFields.length) throw new Error('desktop policy bootstrap requires path plus SHA-256 as one pair');
126
137
  if(desktopPolicyPresent.length===desktopPolicyFields.length){
@@ -168,7 +179,7 @@ export function parseRollbackArgs(argv){
168
179
  function powershellPath(){
169
180
  return path.join(process.env.SystemRoot||'C:\\Windows','System32','WindowsPowerShell','v1.0','powershell.exe');
170
181
  }
171
- function windowsPowerShellEnvironment(base=process.env){
182
+ export function windowsPowerShellEnvironment(base=process.env){
172
183
  const systemRoot=base.SystemRoot||base.SYSTEMROOT||'C:\\Windows';
173
184
  const userProfile=base.USERPROFILE||os.homedir();
174
185
  const programFiles=base.ProgramFiles||base.PROGRAMFILES||'C:\\Program Files';
@@ -333,6 +344,20 @@ export async function prepareProcessPolicyBootstrap(bootstrap){
333
344
  return {...pinned,policy};
334
345
  }
335
346
 
347
+ export async function prepareHostPolicyBootstrap(bootstrap){
348
+ if(!bootstrap) return null;
349
+ const pinned=await verifyPinnedRegularFile(bootstrap.path,bootstrap.sha256,'host policy');
350
+ const policy=await readJson(pinned.path);
351
+ if(!['deadbyte.host-policy.v1','deadbyte.host-policy.v2'].includes(policy?.schema)||policy?.enabled!==true||
352
+ !policy?.commands||typeof policy.commands!=='object'||Array.isArray(policy.commands)) {
353
+ throw new Error('host policy schema/enabled/commands mismatch');
354
+ }
355
+ if(policy.schema==='deadbyte.host-policy.v2'&&(policy.native_exec?.enabled!==true||!Array.isArray(policy.native_exec?.root_ids))) {
356
+ throw new Error('host policy v2 native_exec contract invalid');
357
+ }
358
+ return {...pinned,policy};
359
+ }
360
+
336
361
  export async function prepareDesktopPolicyBootstrap(bootstrap){
337
362
  if(!bootstrap) return null;
338
363
  const pinned=await verifyPinnedRegularFile(bootstrap.path,bootstrap.sha256,'desktop policy');
@@ -358,7 +383,8 @@ export async function prepareSemanticLspConfigBootstrap(bootstrap){
358
383
 
359
384
  async function preparePinnedCommandPolicy(policy,{commandPins=[],schema,label}={}){
360
385
  if(!policy) return {policy:null,repinned:[],matchedCommandIds:[]};
361
- if(policy.schema!==schema||policy.enabled!==true||!policy.commands||typeof policy.commands!=='object'||Array.isArray(policy.commands)) {
386
+ const acceptedSchemas=Array.isArray(schema)?schema:[schema];
387
+ if(!acceptedSchemas.includes(policy.schema)||policy.enabled!==true||!policy.commands||typeof policy.commands!=='object'||Array.isArray(policy.commands)) {
362
388
  throw new Error(`${label} policy schema/enabled/commands mismatch`);
363
389
  }
364
390
  const requested=new Map();
@@ -390,7 +416,7 @@ async function preparePinnedCommandPolicy(policy,{commandPins=[],schema,label}={
390
416
 
391
417
  export async function prepareHostPolicy(policy,{commandPins=[]}={}){
392
418
  if(!policy&&commandPins.length) throw new Error('host command pins supplied but host policy is not configured');
393
- const result=await preparePinnedCommandPolicy(policy,{commandPins,schema:'deadbyte.host-policy.v1',label:'host'});
419
+ const result=await preparePinnedCommandPolicy(policy,{commandPins,schema:['deadbyte.host-policy.v1','deadbyte.host-policy.v2'],label:'host'});
394
420
  const matched=new Set(result.matchedCommandIds);
395
421
  const unknown=commandPins.find(pin=>!matched.has(String(pin?.commandId??'')));
396
422
  if(unknown) throw new Error(`unknown host command pin '${unknown.commandId}'`);
@@ -398,7 +424,7 @@ export async function prepareHostPolicy(policy,{commandPins=[]}={}){
398
424
  }
399
425
 
400
426
  export async function prepareAuthorityPolicies({hostPolicy,codingPolicy},{commandPins=[]}={}){
401
- const host=await preparePinnedCommandPolicy(hostPolicy,{commandPins,schema:'deadbyte.host-policy.v1',label:'host'});
427
+ const host=await preparePinnedCommandPolicy(hostPolicy,{commandPins,schema:['deadbyte.host-policy.v1','deadbyte.host-policy.v2'],label:'host'});
402
428
  const coding=await preparePinnedCommandPolicy(codingPolicy,{commandPins,schema:'deadbyte.coding-policy.v1',label:'coding'});
403
429
  const matched=new Set([...host.matchedCommandIds,...coding.matchedCommandIds]);
404
430
  const unknown=commandPins.find(pin=>!matched.has(String(pin?.commandId??'')));
@@ -485,6 +511,7 @@ export function parseControllerStatus(text){
485
511
  bridge_pid:number(/Bridge PID\s*:\s*(\d+)\s*\[verified\]/),
486
512
  cloud_pid:number(/Cloud PID\s*:\s*(\d+)\s*\[verified\]/),
487
513
  local_ready:/Local ready\s*:\s*True/.test(text),
514
+ process_armed:/Process plane\s*:\s*ARMED \[standing-grant\]/.test(text),
488
515
  coding_armed:/Coding plane\s*:\s*ARMED \[standing-grant\]/.test(text),
489
516
  autonomous_armed:/Autonomous\s*:\s*ARMED \[standing-grant\]/.test(text),
490
517
  desktop_armed:/Desktop plane\s*:\s*ARMED \[standing-grant\]/.test(text),
@@ -562,7 +589,7 @@ async function readLedger(ctx,name){
562
589
  }
563
590
  return {schema:value.schema,component:value.component,instance_nonce:String(value.instance_nonce),
564
591
  pid:Number(value.pid),creation_time_utc:String(value.creation_time_utc??''),image_path:String(value.image_path??''),
565
- image_sha256:String(value.image_sha256)};
592
+ image_sha256:String(value.image_sha256),script_path:String(value.script_path??''),script_sha256:String(value.script_sha256??'')};
566
593
  }
567
594
  function sameGeneration(a,b){
568
595
  return a.pid===b.pid&&a.instance_nonce===b.instance_nonce&&a.creation_time_utc===b.creation_time_utc&&a.image_sha256===b.image_sha256;
@@ -573,19 +600,37 @@ function nodeProcesses(){
573
600
  const text=assertRun(result,'node process query').trim(); if(!text) return [];
574
601
  const parsed=JSON.parse(text); return Array.isArray(parsed)?parsed:[parsed];
575
602
  }
576
- function coreProcess(coreRoot,bridgePid){
603
+ export function selectCoreProcess(processes,coreRoot,bridgePid,verifiedBridge=null){
577
604
  const needle=path.join(coreRoot,'src','mcp-server.mjs').replaceAll('/','\\').toLowerCase();
578
- const matches=nodeProcesses().filter(item=>Number(item.ParentProcessId)===bridgePid&&String(item.CommandLine??'').toLowerCase().includes(needle));
579
- if(matches.length!==1) throw new Error(`expected one downstream core for bridge ${bridgePid}, found ${matches.length}`);
580
- const item=matches[0]; return {pid:Number(item.ProcessId),parent_pid:Number(item.ParentProcessId),creation_date:String(item.CreationDate??''),
581
- executable_path:String(item.ExecutablePath??''),command_line:String(item.CommandLine??'')};
605
+ const children=processes.filter(item=>Number(item.ParentProcessId)===bridgePid);
606
+ const visible=children.filter(item=>String(item.CommandLine??'').length>0);
607
+ const matches=visible.filter(item=>String(item.CommandLine).replaceAll('/','\\').toLowerCase().includes(needle));
608
+ let item=null,identityBasis=null;
609
+ if(children.length===1&&visible.length===1&&matches.length===1){
610
+ item=matches[0]; identityBasis='command_line';
611
+ } else if(children.length===1&&visible.length===0){
612
+ if(verifiedBridge?.component!=='bridge'||verifiedBridge.pid!==bridgePid||!path.isAbsolute(String(verifiedBridge.script_path??''))||
613
+ !HEX64.test(String(verifiedBridge.script_sha256??''))) {
614
+ throw new Error(`opaque downstream core requires verified Bridge script binding for bridge ${bridgePid}`);
615
+ }
616
+ item=children[0]; identityBasis='verified_bridge_single_opaque_node_child';
617
+ } else {
618
+ throw new Error(`expected one unambiguous downstream core for bridge ${bridgePid}, found children=${children.length} visible_matches=${matches.length}`);
619
+ }
620
+ const pid=Number(item.ProcessId),creationDate=String(item.CreationDate??'');
621
+ if(!Number.isInteger(pid)||pid<1||creationDate.length<1) throw new Error(`downstream core identity incomplete for bridge ${bridgePid}`);
622
+ return {pid,parent_pid:Number(item.ParentProcessId),creation_date:creationDate,
623
+ executable_path:String(item.ExecutablePath??''),command_line:String(item.CommandLine??''),identity_basis:identityBasis};
624
+ }
625
+ function coreProcess(coreRoot,bridge){
626
+ return selectCoreProcess(nodeProcesses(),coreRoot,bridge.pid,bridge);
582
627
  }
583
628
  async function captureRuntime(ctx){
584
629
  const statusText=controllerCaptured(ctx,'status',15000); const status=parseControllerStatus(statusText);
585
630
  if(!status.online||!status.bridge_pid||!status.cloud_pid||!status.local_ready) throw new Error(`slot update requires ONLINE runtime: ${statusText}`);
586
631
  if(status.host_armed) throw new Error('slot update requires host plane DISARMED');
587
632
  if(status.autonomous_armed&&!status.coding_armed) throw new Error('invalid runtime authority: autonomous armed without coding');
588
- const bridge=await readLedger(ctx,'bridge'); const cloud=await readLedger(ctx,'cloud'); const core=coreProcess(ctx.core_root,status.bridge_pid);
633
+ const bridge=await readLedger(ctx,'bridge'); const cloud=await readLedger(ctx,'cloud'); const core=coreProcess(ctx.core_root,bridge);
589
634
  return {status,status_text:statusText.trim(),bridge,cloud,core};
590
635
  }
591
636
  async function capturePredecessor(ctx,recoverOfflinePredecessor){
@@ -631,7 +676,7 @@ async function startAndVerify(ctx,oldRuntime){
631
676
  catch {} await sleep(500);
632
677
  }
633
678
  if(!status?.online||!status.bridge_pid||!status.cloud_pid||!status.local_ready) throw new Error(`new runtime failed ONLINE gate: ${statusText}`);
634
- const bridge=await readLedger(ctx,'bridge'); const cloud=await readLedger(ctx,'cloud'); const core=coreProcess(ctx.core_root,status.bridge_pid);
679
+ const bridge=await readLedger(ctx,'bridge'); const cloud=await readLedger(ctx,'cloud'); const core=coreProcess(ctx.core_root,bridge);
635
680
  if(oldRuntime?.bridge&&sameGeneration(oldRuntime.bridge,bridge)) throw new Error('fresh bridge generation gate failed');
636
681
  if(oldRuntime?.cloud&&sameGeneration(oldRuntime.cloud,cloud)) throw new Error('fresh cloud generation gate failed');
637
682
  if(oldRuntime?.core&&oldRuntime.core.pid===core.pid&&oldRuntime.core.creation_date===core.creation_date) throw new Error('fresh downstream core generation gate failed');
@@ -756,26 +801,42 @@ async function revokeAuthority(ctx,prior){
756
801
  if(prior.desktop_armed){ controllerCaptured(ctx,'disarmdesktop',15000); revoked=true; }
757
802
  if(prior.autonomous_armed){ controllerCaptured(ctx,'disarmautonomous',15000); revoked=true; }
758
803
  if(prior.coding_armed){ controllerCaptured(ctx,'disarmcoding',15000); revoked=true; }
804
+ if(prior.process_armed){ controllerCaptured(ctx,'disarmprocess',15000); revoked=true; }
759
805
  const status=parseControllerStatus(controllerCaptured(ctx,'status',15000));
760
- if(status.coding_armed||status.autonomous_armed||status.desktop_armed) throw new Error('standing authority revoke gate failed');
806
+ if(status.process_armed||status.coding_armed||status.autonomous_armed||status.desktop_armed) throw new Error('standing authority revoke gate failed');
761
807
  if(status.host_armed) throw new Error('host plane became armed during revoke');
762
808
  return {revoked,status};
763
809
  }
764
- async function restoreStandingAuthority(ctx,prior,enabled){
765
- if(!enabled){
766
- const status=parseControllerStatus(controllerCaptured(ctx,'status',15000));
767
- if(status.coding_armed||status.autonomous_armed||status.desktop_armed) throw new Error('standing authority unexpectedly survived slot switch');
768
- if(status.host_armed) throw new Error('host plane became armed during slot switch');
769
- return status;
810
+ export function standingAuthorityRestorePlan(prior,enabled){
811
+ const before={
812
+ process_armed:prior?.process_armed===true,
813
+ coding_armed:prior?.coding_armed===true,
814
+ autonomous_armed:prior?.autonomous_armed===true,
815
+ desktop_armed:prior?.desktop_armed===true,
816
+ host_armed:prior?.host_armed===true
817
+ };
818
+ if(before.autonomous_armed&&!before.coding_armed) throw new Error('invalid prior authority: autonomous armed without coding');
819
+ const expected={process_armed:false,coding_armed:false,autonomous_armed:false,desktop_armed:false,host_armed:false};
820
+ const commands=[];
821
+ if(enabled){
822
+ expected.process_armed=before.process_armed;
823
+ expected.coding_armed=before.coding_armed;
824
+ expected.autonomous_armed=before.autonomous_armed;
825
+ if(before.coding_armed) commands.push('armcoding');
826
+ if(before.autonomous_armed) commands.push('armautonomous');
827
+ if(before.process_armed) commands.push('armprocess');
770
828
  }
771
- if(prior.autonomous_armed&&!prior.coding_armed) throw new Error('invalid prior authority: autonomous armed without coding');
772
- if(prior.coding_armed) controllerCaptured(ctx,'armcoding',15000);
773
- if(prior.autonomous_armed) controllerCaptured(ctx,'armautonomous',15000);
829
+ return {commands,expected};
830
+ }
831
+ async function restoreStandingAuthority(ctx,prior,enabled){
832
+ const plan=standingAuthorityRestorePlan(prior,enabled);
833
+ for(const command of plan.commands) controllerCaptured(ctx,command,15000);
774
834
  const status=parseControllerStatus(controllerCaptured(ctx,'status',15000));
775
- if(status.coding_armed!==prior.coding_armed) throw new Error('coding standing-grant restore gate failed');
776
- if(status.autonomous_armed!==prior.autonomous_armed) throw new Error('autonomous standing-grant restore gate failed');
777
- if(status.desktop_armed) throw new Error('desktop plane must remain disarmed after slot switch');
778
- if(status.host_armed) throw new Error('host plane became armed during slot switch');
835
+ for(const key of ['process_armed','coding_armed','autonomous_armed']){
836
+ if(status[key]!==plan.expected[key]) throw new Error(`${key.replace('_armed','')} standing-grant restore gate failed`);
837
+ }
838
+ if(status.desktop_armed!==plan.expected.desktop_armed) throw new Error('desktop plane must remain disarmed after slot switch');
839
+ if(status.host_armed!==plan.expected.host_armed) throw new Error('host plane became armed during slot switch');
779
840
  return status;
780
841
  }
781
842
  async function stopAndVerify(ctx,runtime){
@@ -818,7 +879,8 @@ function generationTuple(runtime){
818
879
  if(!runtime) return null;
819
880
  return {bridge:{pid:runtime.bridge.pid,instance_nonce:runtime.bridge.instance_nonce,creation_time_utc:runtime.bridge.creation_time_utc,image_sha256:runtime.bridge.image_sha256},
820
881
  cloud:{pid:runtime.cloud.pid,instance_nonce:runtime.cloud.instance_nonce,creation_time_utc:runtime.cloud.creation_time_utc,image_sha256:runtime.cloud.image_sha256},
821
- core:{pid:runtime.core.pid,parent_pid:runtime.core.parent_pid,creation_date:runtime.core.creation_date,command_line:runtime.core.command_line}};
882
+ core:{pid:runtime.core.pid,parent_pid:runtime.core.parent_pid,creation_date:runtime.core.creation_date,
883
+ command_line:runtime.core.command_line,identity_basis:runtime.core.identity_basis}};
822
884
  }
823
885
  function publicPointer(pointer){
824
886
  if(!pointer) return null;
@@ -826,7 +888,7 @@ function publicPointer(pointer){
826
888
  release_root:pointer.release_root,generation_root:pointer.generation_root,generation_manifest_sha256:pointer.generation_manifest_sha256,
827
889
  launcher_sha256:pointer.launcher_sha256,config_path:pointer.config_path};
828
890
  }
829
- export async function performSlotUpdate({stage,developmentRoot,expectedVersion,expectedManifest,sourceRoot=null,stateRoot=DEFAULT_STATE_ROOT,restoreStandingGrants=false,recoverOfflinePredecessor=false,cloudTransport=null,bridgeBootstrap=null,processPolicyBootstrap=null,desktopPolicyBootstrap=null,semanticLspBootstrap=null,enableMachineFs=false,enableObservation=false,enableDesktop=false,enableAutonomousSupervisor=false,hostCommandPins=[]}){
891
+ export async function performSlotUpdate({stage,developmentRoot,expectedVersion,expectedManifest,sourceRoot=null,stateRoot=DEFAULT_STATE_ROOT,restoreStandingGrants=false,recoverOfflinePredecessor=false,cloudTransport=null,bridgeBootstrap=null,processPolicyBootstrap=null,hostPolicyBootstrap=null,desktopPolicyBootstrap=null,semanticLspBootstrap=null,enableMachineFs=false,enableObservation=false,enableDesktop=false,enableAutonomousSupervisor=false,hostCommandPins=[]}){
830
892
  if(process.platform!=='win32') throw new Error('R27 slot update is Windows-only');
831
893
  const state=path.resolve(stateRoot),stageRoot=path.resolve(stage),development=assertDevelopmentRootSafe(developmentRoot,state);
832
894
  const key=await loadUpdateKey(state); const updateId=`r27-${expectedManifest.slice(0,16)}-${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
@@ -846,6 +908,8 @@ export async function performSlotUpdate({stage,developmentRoot,expectedVersion,e
846
908
  const developmentVerified=await verifyPackageRoot(development,expectedVersion,expectedManifest,'development');
847
909
  const slot=await materializeReleaseSlot({source:stageRoot,stateRoot:state,version:expectedVersion,manifestSha256:expectedManifest});
848
910
  control=await loadControlInputs({stateRoot:state,sourceRoot,pointerInfo});
911
+ const hostBootstrap=await prepareHostPolicyBootstrap(hostPolicyBootstrap);
912
+ if(hostBootstrap) control.hostPolicy=hostBootstrap.policy;
849
913
  const processBootstrap=await prepareProcessPolicyBootstrap(processPolicyBootstrap);
850
914
  if(processBootstrap){
851
915
  control.processPolicy=processBootstrap.policy;
@@ -933,7 +997,7 @@ export async function performSlotUpdate({stage,developmentRoot,expectedVersion,e
933
997
  const stabilization=await stabilizeRuntime(newCtx,newRuntime);
934
998
  await writeState(statePath,journal,'fresh_runtime_verified',{new_runtime:generationTuple(newRuntime),stabilization});
935
999
  const finalAuthority=await restoreStandingAuthority(newCtx,priorStatus,restoreStandingGrants);
936
- await writeState(statePath,journal,'authority_verified',{final_authority:{coding_armed:finalAuthority.coding_armed,
1000
+ await writeState(statePath,journal,'authority_verified',{final_authority:{process_armed:finalAuthority.process_armed,coding_armed:finalAuthority.coding_armed,
937
1001
  autonomous_armed:finalAuthority.autonomous_armed,desktop_armed:finalAuthority.desktop_armed,host_armed:finalAuthority.host_armed}});
938
1002
  const body={schema:UPDATE_SCHEMA,update_id:updateId,release_version:expectedVersion,candidate_manifest_sha256:expectedManifest,
939
1003
  completed_at_utc:new Date().toISOString(),activation_boundary:'active-release-pointer',mechanism:'immutable-release-slot-plus-atomic-pointer-rename',
@@ -944,10 +1008,10 @@ export async function performSlotUpdate({stage,developmentRoot,expectedVersion,e
944
1008
  development_root:development,
945
1009
  host_repin:{requested:hostCommandPins.length>0,repinned:authorityPreparation.hostRepinned},
946
1010
  coding_repin:{requested:hostCommandPins.length>0,repinned:authorityPreparation.codingRepinned},old_runtime:generationTuple(oldRuntime),new_runtime:generationTuple(newRuntime),stabilization,
947
- authority:{prior:{coding_armed:priorStatus.coding_armed,
1011
+ authority:{prior:{process_armed:priorStatus.process_armed,coding_armed:priorStatus.coding_armed,
948
1012
  autonomous_armed:priorStatus.autonomous_armed,desktop_armed:priorStatus.desktop_armed,host_armed:priorStatus.host_armed},revoked_before_switch:revoked,restore_requested:restoreStandingGrants,
949
1013
  desktop_restore_policy:'explicit-rearm-required',
950
- final:{coding_armed:finalAuthority.coding_armed,autonomous_armed:finalAuthority.autonomous_armed,desktop_armed:finalAuthority.desktop_armed,host_armed:finalAuthority.host_armed}},rollback_performed:false};
1014
+ final:{process_armed:finalAuthority.process_armed,coding_armed:finalAuthority.coding_armed,autonomous_armed:finalAuthority.autonomous_armed,desktop_armed:finalAuthority.desktop_armed,host_armed:finalAuthority.host_armed}},rollback_performed:false};
951
1015
  const receipt=await writeEnvelope(receiptPath,body,key);
952
1016
  await writeState(statePath,journal,'completed',{receipt_path:receiptPath,receipt_sha256:receipt.receipt_sha256});
953
1017
  return {ok:true,update_id:updateId,state_path:statePath,receipt_path:receiptPath,receipt_sha256:receipt.receipt_sha256,
@@ -1027,10 +1091,7 @@ export async function performSlotRollback({activationReceipt,stateRoot=DEFAULT_S
1027
1091
  if(!pointerInfo.pointer||canonicalJson(publicPointer(pointerInfo.pointer))!==canonicalJson(publicPointer(activePointer))) throw new Error('rollback current pointer does not match signed activation pointer');
1028
1092
  await verifyActiveInstallation(state,priorPointer);
1029
1093
  const currentCtx=slotContext(state,pointerInfo.pointer),currentRuntime=await captureRuntime(currentCtx);
1030
- if(/Process plane\s*:\s*ARMED/.test(currentRuntime.status_text)) controllerCaptured(currentCtx,'disarmprocess',15000);
1031
1094
  const revoked=await revokeAuthority(currentCtx,currentRuntime.status);
1032
- const authorityText=controllerCaptured(currentCtx,'status',15000);
1033
- if(/Process plane\s*:\s*ARMED/.test(authorityText)) throw new Error('process standing authority revoke gate failed');
1034
1095
  const rollbackId=`r31-rollback-${activationEnvelope.receipt_sha256.slice(0,16)}-${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
1035
1096
  const evidenceDir=path.join(state,'evidence','updates',rollbackId),statePath=path.join(evidenceDir,'state.json'),receiptPath=path.join(evidenceDir,'receipt.json');
1036
1097
  await mkdir(evidenceDir,{recursive:true});
@@ -1048,7 +1109,7 @@ export async function performSlotRollback({activationReceipt,stateRoot=DEFAULT_S
1048
1109
  priorRuntime=await startAndVerify(priorCtx,currentRuntime);
1049
1110
  const stabilization=await stabilizeRuntime(priorCtx,priorRuntime);
1050
1111
  const finalStatus=parseControllerStatus(controllerCaptured(priorCtx,'status',15000));
1051
- if(finalStatus.coding_armed||finalStatus.autonomous_armed||finalStatus.host_armed) throw new Error('rollback predecessor authority must remain disarmed');
1112
+ if(finalStatus.process_armed||finalStatus.coding_armed||finalStatus.autonomous_armed||finalStatus.desktop_armed||finalStatus.host_armed) throw new Error('rollback predecessor authority must remain disarmed');
1052
1113
  const body={schema:ROLLBACK_SCHEMA,rollback_id:rollbackId,activation_receipt_sha256:activationEnvelope.receipt_sha256,
1053
1114
  completed_at_utc:new Date().toISOString(),from_pointer:publicPointer(activePointer),active_pointer:publicPointer(priorPointer),
1054
1115
  old_runtime:generationTuple(currentRuntime),new_runtime:generationTuple(priorRuntime),stabilization,authority_revoked:revoked.revoked,rollback_performed:true};
@@ -1080,7 +1141,7 @@ export async function runSlotUpdateCli(argv=process.argv.slice(2),{stateRoot=DEF
1080
1141
  const args=parseUpdateArgs(argv);
1081
1142
  const result=await performSlotUpdate({stage:args.stage,developmentRoot:args.developmentRoot,expectedVersion:args.expectedVersion,
1082
1143
  expectedManifest:args.expectedManifest,sourceRoot:args.sourceRoot,stateRoot,restoreStandingGrants:args.restoreStandingGrants,recoverOfflinePredecessor:args.recoverOfflinePredecessor,
1083
- cloudTransport:args.cloudTransport,bridgeBootstrap:args.bridgeBootstrap,processPolicyBootstrap:args.processPolicyBootstrap,desktopPolicyBootstrap:args.desktopPolicyBootstrap,semanticLspBootstrap:args.semanticLspBootstrap,enableMachineFs:args.enableMachineFs,
1144
+ cloudTransport:args.cloudTransport,bridgeBootstrap:args.bridgeBootstrap,processPolicyBootstrap:args.processPolicyBootstrap,hostPolicyBootstrap:args.hostPolicyBootstrap,desktopPolicyBootstrap:args.desktopPolicyBootstrap,semanticLspBootstrap:args.semanticLspBootstrap,enableMachineFs:args.enableMachineFs,
1084
1145
  enableObservation:args.enableObservation,enableDesktop:args.enableDesktop,enableAutonomousSupervisor:args.enableAutonomousSupervisor,hostCommandPins:args.hostCommandPins});
1085
1146
  console.log(`R27_SLOT_UPDATE_PASS version=${args.expectedVersion}`);
1086
1147
  console.log(`UPDATE_ID ${result.update_id}`); console.log(`RECEIPT_SHA256 ${result.receipt_sha256}`);
@@ -0,0 +1,37 @@
1
+ import * as z from 'zod/v4';
2
+
3
+ const name=z.string().regex(/^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/);
4
+ const rel=z.string().min(1).max(512);
5
+ export const skillCatalogInputSchema=z.discriminatedUnion('op',[
6
+ z.strictObject({op:z.literal('list')}),
7
+ z.strictObject({op:z.literal('load'),name}),
8
+ z.strictObject({op:z.literal('resource'),name,path:rel})
9
+ ]);
10
+ export const skillCatalogOutputSchema=z.strictObject({
11
+ status:z.enum(['ok','denied']),
12
+ op:z.enum(['list','load','resource']),
13
+ result:z.record(z.string(),z.json()).optional(),
14
+ error:z.string().optional()
15
+ }).superRefine((value,ctx)=>{
16
+ if(value.status==='ok'&&value.result===undefined) ctx.addIssue({code:'custom',path:['result'],message:'result required for status=ok'});
17
+ if(value.status==='denied'&&value.error===undefined) ctx.addIssue({code:'custom',path:['error'],message:'error required for status=denied'});
18
+ if(value.status==='ok'&&value.error!==undefined) ctx.addIssue({code:'custom',path:['error'],message:'error invalid for status=ok'});
19
+ });
20
+ function result(value,isError=false){
21
+ const parsed=skillCatalogOutputSchema.safeParse(value);
22
+ if(!parsed.success) throw new Error(`R44 skill output contract violation: ${z.prettifyError(parsed.error)}`);
23
+ return {isError,structuredContent:parsed.data,content:[{type:'text',text:JSON.stringify(parsed.data)}]};
24
+ }
25
+ export function registerSkillToolsR44(server,{runtime}={}){
26
+ if(!server||!runtime) throw new Error('R44 skill tool registration requires runtime');
27
+ server.registerTool('skill_catalog',{
28
+ description:'Read-only content-addressed R44 skill catalog. list is metadata-only; load/resource verify signed bytes. This tool never arms or renews authority.',
29
+ inputSchema:skillCatalogInputSchema,outputSchema:skillCatalogOutputSchema
30
+ },async args=>{
31
+ try{
32
+ if(args.op==='list') return result({status:'ok',op:'list',result:await runtime.list()});
33
+ if(args.op==='load') return result({status:'ok',op:'load',result:await runtime.load({name:args.name})});
34
+ return result({status:'ok',op:'resource',result:await runtime.readResource({name:args.name,resourcePath:args.path})});
35
+ }catch(error){return result({status:'denied',op:args.op,error:String(error)},true);}
36
+ });
37
+ }
@@ -0,0 +1,203 @@
1
+ import { createHash, createPublicKey, verify as cryptoVerify } from 'node:crypto';
2
+ import { lstat, readFile, readdir, realpath } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+
5
+ const DOMAIN=Buffer.from('DEADBYTE-SKILL-MANIFEST-V1\0','utf8');
6
+ const SKILL_NAME=/^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/;
7
+ const VERSION=/^[0-9]+\.[0-9]+\.[0-9]+(?:-[A-Za-z0-9.-]+)?$/;
8
+ const CAPABILITY=/^[A-Za-z0-9._-]{1,96}$/;
9
+ const AUTHORITY_PLANES=new Set(['process','coding','autonomous','desktop','host']);
10
+ const RESOURCE_KINDS=new Set(['reference','script','agent','asset']);
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((a,b)=>a.localeCompare(b,'en')).map(key=>[key,canonical(value[key])])):value;
14
+ const canonicalJson=value=>JSON.stringify(canonical(value));
15
+ const strictUtf8=bytes=>new TextDecoder('utf-8',{fatal:true}).decode(bytes);
16
+ function insist(ok,message){if(!ok) throw new Error(message);}
17
+ function exactKeys(object,expected,label){
18
+ insist(object&&typeof object==='object'&&!Array.isArray(object),`${label} must be an object`);
19
+ const actual=Object.keys(object).sort();
20
+ const wanted=[...expected].sort();
21
+ insist(JSON.stringify(actual)===JSON.stringify(wanted),`${label} fields mismatch`);
22
+ }
23
+ function uniqueSorted(values,label,validator){
24
+ insist(Array.isArray(values),`${label} must be an array`);
25
+ const copy=values.map(value=>String(value));
26
+ insist(copy.every(validator),`${label} contains invalid value`);
27
+ insist(new Set(copy).size===copy.length,`${label} contains duplicate value`);
28
+ const sorted=[...copy].sort((a,b)=>a.localeCompare(b,'en'));
29
+ insist(JSON.stringify(copy)===JSON.stringify(sorted),`${label} must be sorted`);
30
+ return copy;
31
+ }
32
+ function resourcePath(value){
33
+ insist(typeof value==='string'&&value.length>0&&value.length<=512,'resource path invalid');
34
+ insist(!value.includes('\\'),'resource path must use forward slashes');
35
+ insist(!path.posix.isAbsolute(value),'resource path must be relative');
36
+ const parts=value.split('/');
37
+ insist(parts.every(part=>part&&part!=='.'&&part!=='..'),'resource path traversal denied');
38
+ insist(value!=='SKILL.md'&&value!=='skill.json','resource path is reserved');
39
+ return value;
40
+ }
41
+ function parseFrontmatter(text){
42
+ const lines=text.split(/\r?\n/);
43
+ insist(lines[0]==='---','SKILL.md frontmatter missing');
44
+ const end=lines.indexOf('---',1);
45
+ insist(end>1&&end<=32,'SKILL.md frontmatter boundary invalid');
46
+ const meta={};
47
+ for(const line of lines.slice(1,end)){
48
+ const m=/^([a-z_]+):\s*(.*)$/.exec(line);
49
+ insist(m,'SKILL.md frontmatter line malformed');
50
+ insist(!Object.hasOwn(meta,m[1]),`SKILL.md duplicate frontmatter key ${m[1]}`);
51
+ meta[m[1]]=m[2];
52
+ }
53
+ exactKeys(meta,['name','description','version','audience'],'SKILL.md frontmatter');
54
+ return meta;
55
+ }
56
+ async function walkFiles(root,rel=''){
57
+ const dir=path.join(root,...(rel?rel.split('/'):[]));
58
+ const names=(await readdir(dir)).sort((a,b)=>a.localeCompare(b,'en'));
59
+ const out=[];
60
+ for(const name of names){
61
+ const childRel=rel?`${rel}/${name}`:name;
62
+ const full=path.join(root,...childRel.split('/'));
63
+ const st=await lstat(full);
64
+ insist(!st.isSymbolicLink(),`skill symlink forbidden: ${childRel}`);
65
+ if(st.isDirectory()) out.push(...await walkFiles(root,childRel));
66
+ else { insist(st.isFile(),`skill non-file forbidden: ${childRel}`);out.push(childRel); }
67
+ }
68
+ return out;
69
+ }
70
+ async function readBounded(file,max,label){
71
+ const bytes=await readFile(file);
72
+ insist(bytes.length<=max,`${label} exceeds byte limit`);
73
+ return bytes;
74
+ }
75
+
76
+ export async function createSkillPlane({root,verifyKeyPath}={}){
77
+ insist(typeof root==='string'&&path.isAbsolute(root),'skill root must be absolute');
78
+ insist(typeof verifyKeyPath==='string'&&path.isAbsolute(verifyKeyPath),'skill verify key path must be absolute');
79
+ const rootStat=await lstat(root);
80
+ insist(rootStat.isDirectory()&&!rootStat.isSymbolicLink(),'skill root must be a real directory');
81
+ const rootReal=await realpath(root);
82
+ const publicKey=createPublicKey(await readFile(verifyKeyPath));
83
+ insist(publicKey.asymmetricKeyType==='ed25519','skill verify key must be Ed25519');
84
+ const keyId=sha(Buffer.from(publicKey.export({type:'spki',format:'der'})));
85
+
86
+ async function verified(name){
87
+ insist(typeof name==='string'&&SKILL_NAME.test(name),'skill name invalid');
88
+ const dir=path.join(rootReal,name);
89
+ const st=await lstat(dir);
90
+ insist(st.isDirectory()&&!st.isSymbolicLink(),'skill directory must be real');
91
+ const dirReal=await realpath(dir);
92
+ insist(path.dirname(dirReal).toLowerCase()===rootReal.toLowerCase(),'skill directory escape denied');
93
+
94
+ for(const reserved of ['skill.json','SKILL.md']){
95
+ const rst=await lstat(path.join(dirReal,reserved));
96
+ insist(rst.isFile()&&!rst.isSymbolicLink(),`${reserved} must be a real file`);
97
+ }
98
+ const envelopeBytes=await readBounded(path.join(dirReal,'skill.json'),256*1024,'skill.json');
99
+ let envelope;
100
+ try{envelope=JSON.parse(strictUtf8(envelopeBytes));}catch(error){throw new Error(`skill.json invalid JSON/UTF-8: ${error.message}`);}
101
+ exactKeys(envelope,['body','body_sha256','attestation','manifest_sha256'],'skill envelope');
102
+ insist(/^[0-9a-f]{64}$/.test(envelope.body_sha256??''),'skill body hash malformed');
103
+ insist(/^[0-9a-f]{64}$/.test(envelope.manifest_sha256??''),'skill manifest hash malformed');
104
+ exactKeys(envelope.attestation,['algorithm','domain','key_id','signature_base64'],'skill attestation');
105
+ insist(envelope.attestation.algorithm==='ed25519'&&envelope.attestation.domain==='DEADBYTE-SKILL-MANIFEST-V1','skill attestation contract mismatch');
106
+ insist(envelope.attestation.key_id===keyId,'skill signer mismatch');
107
+
108
+ const body=envelope.body;
109
+ exactKeys(body,['schema','name','version','description','audience','skill_md_sha256','resources','required_capabilities','authority_planes','provenance'],'skill manifest body');
110
+ insist(body.schema==='deadbyte.skill-manifest.v1','skill manifest schema mismatch');
111
+ insist(body.name===name&&SKILL_NAME.test(body.name),'skill manifest name mismatch');
112
+ insist(typeof body.version==='string'&&VERSION.test(body.version),'skill version invalid');
113
+ insist(typeof body.description==='string'&&body.description.length>=1&&body.description.length<=512,'skill description invalid');
114
+ insist(body.audience==='agent','skill audience invalid');
115
+ insist(/^[0-9a-f]{64}$/.test(body.skill_md_sha256??''),'SKILL.md hash malformed');
116
+ const requiredCapabilities=uniqueSorted(body.required_capabilities,'required capabilities',value=>CAPABILITY.test(value));
117
+ const authorityPlanes=uniqueSorted(body.authority_planes,'authority planes',value=>typeof value==='string'&&value.length>=1&&value.length<=32);
118
+ for(const plane of authorityPlanes) insist(AUTHORITY_PLANES.has(plane),`unknown authority plane: ${plane}`);
119
+ exactKeys(body.provenance,['source','origin_url','source_revision'],'skill provenance');
120
+ insist(typeof body.provenance.source==='string'&&body.provenance.source.length>=1&&body.provenance.source.length<=128,'skill provenance source invalid');
121
+ insist(body.provenance.origin_url===null||typeof body.provenance.origin_url==='string','skill provenance origin_url invalid');
122
+ insist(body.provenance.source_revision===null||typeof body.provenance.source_revision==='string','skill provenance source_revision invalid');
123
+
124
+ insist(Array.isArray(body.resources)&&body.resources.length<=128,'skill resources invalid');
125
+ const resources=body.resources.map(item=>{
126
+ exactKeys(item,['path','sha256','kind'],'skill resource');
127
+ const rel=resourcePath(item.path);
128
+ insist(/^[0-9a-f]{64}$/.test(item.sha256??''),'skill resource hash malformed');
129
+ insist(RESOURCE_KINDS.has(item.kind),'skill resource kind invalid');
130
+ return {path:rel,sha256:item.sha256,kind:item.kind};
131
+ });
132
+ const resourceNames=resources.map(item=>item.path);
133
+ insist(new Set(resourceNames).size===resourceNames.length,'skill resource paths duplicate');
134
+ insist(JSON.stringify(resourceNames)===JSON.stringify([...resourceNames].sort((a,b)=>a.localeCompare(b,'en'))),'skill resources must be sorted');
135
+
136
+ const bodyBytes=Buffer.from(canonicalJson(body),'utf8');
137
+ insist(sha(bodyBytes)===envelope.body_sha256,'skill body hash mismatch');
138
+ const {manifest_sha256,...unsignedEnvelope}=envelope;
139
+ insist(sha(Buffer.from(canonicalJson(unsignedEnvelope),'utf8'))===manifest_sha256,'skill manifest envelope hash mismatch');
140
+ insist(cryptoVerify(null,Buffer.concat([DOMAIN,bodyBytes]),publicKey,Buffer.from(envelope.attestation.signature_base64,'base64')),'skill signature invalid');
141
+
142
+ const skillBytes=await readBounded(path.join(dirReal,'SKILL.md'),128*1024,'SKILL.md');
143
+ insist(sha(skillBytes)===body.skill_md_sha256,'SKILL.md hash mismatch');
144
+ const skillText=strictUtf8(skillBytes);
145
+ const frontmatter=parseFrontmatter(skillText);
146
+ insist(frontmatter.name===body.name,'SKILL.md name mismatch');
147
+ insist(frontmatter.version===body.version,'SKILL.md version mismatch');
148
+ insist(frontmatter.description===body.description,'SKILL.md description mismatch');
149
+ insist(frontmatter.audience===body.audience,'SKILL.md audience mismatch');
150
+
151
+ for(const item of resources){
152
+ const full=path.join(dirReal,...item.path.split('/'));
153
+ const rst=await lstat(full);
154
+ insist(rst.isFile()&&!rst.isSymbolicLink(),`skill resource must be a real file: ${item.path}`);
155
+ const bytes=await readBounded(full,512*1024,`skill resource ${item.path}`);
156
+ insist(sha(bytes)===item.sha256,`resource hash mismatch: ${item.path}`);
157
+ }
158
+ const actual=await walkFiles(dirReal);
159
+ const allowed=['SKILL.md','skill.json',...resourceNames].sort((a,b)=>a.localeCompare(b,'en'));
160
+ const extra=actual.filter(file=>!allowed.includes(file));
161
+ insist(extra.length===0,`undeclared skill file: ${extra[0]}`);
162
+ const missing=allowed.filter(file=>!actual.includes(file));
163
+ insist(missing.length===0,`declared skill file missing: ${missing[0]}`);
164
+
165
+ return {dir:dirReal,envelope,body,skillText,resources,requiredCapabilities,authorityPlanes};
166
+ }
167
+
168
+ async function list(){
169
+ const names=(await readdir(rootReal)).sort((a,b)=>a.localeCompare(b,'en'));
170
+ const skills=[];
171
+ for(const name of names){
172
+ const full=path.join(rootReal,name);
173
+ const st=await lstat(full);
174
+ insist(st.isDirectory()&&!st.isSymbolicLink(),`skill registry contains non-directory entry: ${name}`);
175
+ const item=await verified(name);
176
+ skills.push({
177
+ name:item.body.name,version:item.body.version,description:item.body.description,audience:item.body.audience,
178
+ manifest_sha256:item.envelope.manifest_sha256,required_capabilities:item.requiredCapabilities,
179
+ authority_planes:item.authorityPlanes,resource_count:item.resources.length,provenance:item.body.provenance,verified:true
180
+ });
181
+ }
182
+ const registrySha=sha(Buffer.from(canonicalJson(skills.map(item=>({name:item.name,manifest_sha256:item.manifest_sha256}))),'utf8'));
183
+ return {status:'ok',skills,registry_sha256:registrySha,verify_key_id:keyId};
184
+ }
185
+ async function load({name}={}){
186
+ const item=await verified(name);
187
+ return {
188
+ status:'ok',name:item.body.name,version:item.body.version,description:item.body.description,audience:item.body.audience,
189
+ content:item.skillText,manifest_sha256:item.envelope.manifest_sha256,required_capabilities:item.requiredCapabilities,
190
+ authority_planes:item.authorityPlanes,resources:item.resources,provenance:item.body.provenance,verified:true,verify_key_id:keyId
191
+ };
192
+ }
193
+ async function readResource({name,resourcePath:requested}={}){
194
+ const item=await verified(name);
195
+ const rel=resourcePath(requested);
196
+ const resource=item.resources.find(entry=>entry.path===rel);
197
+ insist(resource,`resource not declared: ${rel}`);
198
+ const bytes=await readBounded(path.join(item.dir,...rel.split('/')),512*1024,`skill resource ${rel}`);
199
+ insist(sha(bytes)===resource.sha256,`resource hash mismatch: ${rel}`);
200
+ return {status:'ok',name:item.body.name,path:rel,kind:resource.kind,sha256:resource.sha256,content:strictUtf8(bytes),verified:true};
201
+ }
202
+ return Object.freeze({list,load,readResource,root:rootReal,verify_key_id:keyId});
203
+ }
@@ -112,7 +112,7 @@ async function modernPassThrough() {
112
112
  if (era !== 'modern') throw new Error(`expected modern era through compat prefilter, got ${String(era)}`);
113
113
  const listed = await client.listTools();
114
114
  const names = listed.tools.map(tool => tool.name).sort();
115
- const baseTools = ['contained_transform', 'evidence_verify', 'file_read', 'proof_execute', 'workspace_open'];
115
+ const baseTools = ['contained_transform', 'evidence_verify', 'file_read', 'proof_execute', 'skill_catalog', 'workspace_open'];
116
116
  const privilegedTools = ['host_exec', 'host_file_read', 'host_file_write'];
117
117
  const allowedCatalogs = [
118
118
  baseTools,
package/src/version.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export const DEADBYTE_VERSION = '0.14.0';
1
+ export const DEADBYTE_VERSION = '0.15.1';
2
2
  export const MCP_PROTOCOL_VERSION = '2026-07-28';
3
3
  export const RECEIPT_SCHEMA_V1 = 'deadbyte.receipt.v1';
4
4
  export const RECEIPT_SCHEMA_V2 = 'deadbyte.receipt.v2';