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.
- package/AGENTS.md +11 -18
- package/CONTEXT.md +3 -3
- package/MANIFEST.SHA256 +156 -70
- package/PROVENANCE.json +10 -0
- package/R43-PARENT.json +20 -0
- package/R44-DONORS.json +99 -0
- package/R44-PARENT.json +18 -0
- package/R45-PARENT.json +18 -0
- package/README.txt +19 -14
- package/RELEASE.json +37 -0
- package/SBOM.cdx.json +46 -0
- package/TEST-MATRIX.json +31 -0
- package/bin/deadbyte-hardware-observer-r45.exe +0 -0
- package/bin/deadbyte-hardware-observer-r45.obj +0 -0
- package/controller/README.TXT +5 -6
- package/controller/deadbyte-controller.ps1 +2 -2
- package/docs/ARCHITECTURE.md +8 -1
- package/docs/R44-ARCHITECTURE.md +37 -0
- package/native/deadbyte-hardware-observer.cpp +308 -0
- package/package.json +18 -8
- package/scripts/autonomous-tests.ps1 +2 -2
- package/scripts/build-hardware-r45.ps1 +71 -0
- package/scripts/deferred-slot-operation-r43.mjs +126 -0
- package/scripts/deferred-slot-operation-r44.mjs +126 -0
- package/scripts/deferred-slot-operation-r45.mjs +126 -0
- package/scripts/derive-r45-parent.mjs +24 -0
- package/scripts/derive-r45-release.mjs +72 -0
- package/scripts/derive-r45-tests.mjs +20 -0
- package/scripts/final-closure-r43.mjs +115 -0
- package/scripts/final-closure-r44.mjs +121 -0
- package/scripts/final-closure-r45.mjs +121 -0
- package/scripts/final-closure-verify-r43.mjs +213 -0
- package/scripts/final-closure-verify-r44.mjs +220 -0
- package/scripts/final-closure-verify-r45.mjs +220 -0
- package/scripts/finalize-r43.mjs +217 -0
- package/scripts/finalize-r44.mjs +217 -0
- package/scripts/finalize-r45.mjs +219 -0
- package/scripts/full-regression-r43.mjs +20 -0
- package/scripts/full-regression-r44.mjs +20 -0
- package/scripts/full-regression-r45.mjs +20 -0
- package/scripts/full-regression-summary-r45.mjs +14 -0
- package/scripts/gate-windows-r43.ps1 +36 -0
- package/scripts/gate-windows-r44.ps1 +36 -0
- package/scripts/gate-windows-r45.ps1 +44 -0
- package/scripts/init-host-policy.ps1 +39 -1
- package/scripts/inspect-active-release-r45.mjs +20 -0
- package/scripts/release-manifest.mjs +3 -1
- package/scripts/release-parity-r43.mjs +218 -0
- package/scripts/release-parity-r44.mjs +242 -0
- package/scripts/release-parity-r45.mjs +253 -0
- package/scripts/release-parity-tests-r43.ps1 +8 -0
- package/scripts/release-parity-tests-r44.ps1 +8 -0
- package/scripts/release-parity-tests-r45.ps1 +8 -0
- package/scripts/verify-r43-parent.mjs +126 -0
- package/scripts/verify-r44-parent.mjs +142 -0
- package/scripts/verify-r45-parent.mjs +142 -0
- package/scripts/windows-gate-evidence-r43.mjs +55 -0
- package/scripts/windows-gate-evidence-r44.mjs +55 -0
- package/scripts/windows-gate-evidence-r45.mjs +60 -0
- package/src/agent-backend-r44.mjs +254 -0
- package/src/autonomous-output-contracts.mjs +1 -1
- package/src/autonomous-policy.mjs +41 -4
- package/src/autonomous-runtime.mjs +37 -19
- package/src/autonomous-supervisor-host.mjs +12 -2
- package/src/capability-ledger.mjs +3 -1
- package/src/codex-app-server-r44.mjs +154 -0
- package/src/coding-plane.mjs +66 -19
- package/src/compact-gateway-mcp-tools.mjs +4 -2
- package/src/deadbyte-cli.mjs +17 -9
- package/src/doctor-r45.mjs +65 -0
- package/src/durable-job-r44.mjs +145 -0
- package/src/external-job-provider-r44.mjs +126 -0
- package/src/host-plane.mjs +156 -87
- package/src/job-supervisor-host-r44.mjs +112 -0
- package/src/machine-fs-smoke-client.mjs +1 -1
- package/src/machine-fs.mjs +21 -10
- package/src/mcp-host-smoke-client.mjs +1 -1
- package/src/mcp-server.mjs +84 -26
- package/src/mcp-smoke-client.mjs +1 -1
- package/src/native-diff-v2.mjs +12 -7
- package/src/native-diff.mjs +32 -9
- package/src/observation-mcp-tools.mjs +5 -1
- package/src/observation-output-contracts.mjs +8 -0
- package/src/observation-runtime.mjs +33 -2
- package/src/observation-smoke-client.mjs +7 -3
- package/src/operator-surface.mjs +2 -2
- package/src/output-contracts.mjs +27 -11
- package/src/process-policy.mjs +19 -0
- package/src/process-runtime.mjs +5 -2
- package/src/process-smoke-client.mjs +2 -2
- package/src/provider-circuit.mjs +8 -2
- package/src/r44-compact-disarmed-probe.mjs +74 -0
- package/src/r44-full-surface-probe.mjs +34 -0
- package/src/r45-compact-disarmed-probe.mjs +74 -0
- package/src/r45-full-surface-probe.mjs +34 -0
- package/src/release-generation.mjs +1 -0
- package/src/remote-device-r44.mjs +205 -0
- package/src/remote-mcp-autonomous-smoke-client.mjs +4 -4
- package/src/remote-mcp-r44-parity-client.mjs +203 -0
- package/src/remote-mcp-r45-parity-client.mjs +206 -0
- package/src/remote-mcp-smoke-client.mjs +7 -4
- package/src/runtime-update.mjs +100 -39
- package/src/skill-mcp-tools-r44.mjs +37 -0
- package/src/skill-plane-r44.mjs +203 -0
- package/src/tunnel-stdio-compat-smoke.mjs +1 -1
- package/src/version.mjs +1 -1
- package/test/agent-backend-r44.test.mjs +115 -0
- package/test/agent-runtime.test.mjs +4 -3
- package/test/autonomous-loop.test.mjs +121 -11
- package/test/autonomous-runtime.test.mjs +3 -2
- package/test/capability-ledger-r36.test.mjs +3 -3
- package/test/cli-entrypoint.test.mjs +4 -4
- package/test/codex-app-server-r44.test.mjs +115 -0
- package/test/codex-autonomous-r44.test.mjs +87 -0
- package/test/coding-plane.test.mjs +3 -2
- package/test/coding-runtime.test.mjs +36 -1
- package/test/compact-catalog-r36.test.mjs +3 -3
- package/test/compact-max-catalog-r40.test.mjs +4 -2
- package/test/compact-max-catalog-r44.test.mjs +97 -0
- package/test/compact-max-catalog-r45.test.mjs +99 -0
- package/test/contained.test.mjs +23 -11
- package/test/controller.test.mjs +5 -1
- package/test/core.test.mjs +1 -1
- package/test/durable-job-r44.test.mjs +75 -0
- package/test/external-job-provider-r44.test.mjs +46 -0
- package/test/full-max-catalog-r40.test.mjs +5 -3
- package/test/full-max-catalog-r44.test.mjs +91 -0
- package/test/full-max-catalog-r45.test.mjs +91 -0
- package/test/hardware-observation-r45.test.mjs +44 -0
- package/test/host-inline-r45.test.mjs +67 -0
- package/test/host-plane.test.mjs +15 -1
- package/test/machine-mcp-integration.test.mjs +1 -0
- package/test/native-diff.test.mjs +28 -0
- package/test/observation-mcp-tools.test.mjs +4 -3
- package/test/output-schema.test.mjs +3 -2
- package/test/package-boundary.test.mjs +1 -1
- package/test/process-policy.test.mjs +22 -1
- package/test/production-docs.test.mjs +3 -3
- package/test/prompts-resources.test.mjs +1 -1
- package/test/r33-closeout-regression.test.mjs +20 -36
- package/test/r33-finalization.test.mjs +7 -7
- package/test/r43-finalization.test.mjs +31 -0
- package/test/r43-finalizer.test.mjs +34 -0
- package/test/r43-parent.test.mjs +29 -0
- package/test/r44-finalization.test.mjs +58 -0
- package/test/r44-finalizer.test.mjs +48 -0
- package/test/r44-parent.test.mjs +25 -0
- package/test/r45-autonomous-quick-test.test.mjs +10 -0
- package/test/r45-finalization.test.mjs +84 -0
- package/test/release-generation.test.mjs +1 -0
- package/test/release-version.test.mjs +65 -20
- package/test/remote-device-r44.test.mjs +104 -0
- package/test/runtime-host-bootstrap-r45.test.mjs +46 -0
- package/test/runtime-self-update.test.mjs +42 -0
- package/test/skill-mcp-r44.test.mjs +38 -0
- package/test/skill-plane-r44.test.mjs +93 -0
- package/test/standing-grants.test.mjs +5 -1
package/src/runtime-update.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
|
603
|
+
export function selectCoreProcess(processes,coreRoot,bridgePid,verifiedBridge=null){
|
|
577
604
|
const needle=path.join(coreRoot,'src','mcp-server.mjs').replaceAll('/','\\').toLowerCase();
|
|
578
|
-
const
|
|
579
|
-
|
|
580
|
-
const
|
|
581
|
-
|
|
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,
|
|
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,
|
|
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
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
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
|
-
|
|
772
|
-
|
|
773
|
-
|
|
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
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
if(status.
|
|
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,
|
|
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