deadbyte-mcp 0.15.68 → 0.15.69
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/MANIFEST.SHA256 +81 -38
- package/PROVENANCE.json +35 -23
- package/README.txt +44 -31
- package/RELEASE.json +30 -17
- package/SBOM.cdx.json +2 -2
- package/TEST-MATRIX.json +25 -2
- package/bridge/package.json +1 -1
- package/controller/deadbyte-controller.ps1 +65 -1
- package/controller/deadbyte-process-policy.json +4 -4
- package/docs/R184-package-lock.json +97 -0
- package/docs/R186-OWNER-CONSENT-HANDOFF.txt +87 -0
- package/package.json +1 -1
- package/scripts/install-r186-owner-trust.ps1 +59 -0
- package/scripts/r175-focused-runner.mjs +38 -0
- package/scripts/r176-log-preview.mjs +18 -0
- package/scripts/r177-owner-trust-inventory.ps1 +36 -0
- package/scripts/r178-full-regression.mjs +21 -0
- package/scripts/r178-npm-ci.mjs +10 -0
- package/scripts/r178-npm-preflight.mjs +16 -0
- package/scripts/r178-release-gates-readonly.mjs +9 -0
- package/scripts/r178-shard-regression.mjs +25 -0
- package/scripts/r179-predecessor-registry.mjs +24 -0
- package/scripts/r179-protected-inventory.ps1 +21 -0
- package/scripts/r186-focused-runner.mjs +22 -0
- package/scripts/r186-owner-anchor-inspect.mjs +6 -0
- package/scripts/r186-owner-consent-check.mjs +86 -0
- package/scripts/r186-owner-grant-cli.mjs +45 -0
- package/scripts/r186-owner-key-local.mjs +22 -0
- package/scripts/r186-owner-review-export.mjs +34 -0
- package/src/deadbyte-cli.mjs +69 -56
- package/src/r174-owner-webauthn.mjs +176 -0
- package/src/r175-foreground-boundary.mjs +28 -0
- package/src/r186-owner-anchor.mjs +12 -0
- package/src/r186-owner-consent-preflight.mjs +63 -0
- package/src/r186-owner-grant-broker.mjs +96 -0
- package/src/r186-owner-local-signing.mjs +25 -0
- package/src/r186-owner-request.mjs +40 -0
- package/src/r186-owner-review-export.mjs +49 -0
- package/src/remote-live-log.mjs +81 -5
- package/src/remote-session-guardian.mjs +23 -0
- package/src/version.mjs +1 -1
- package/test/helpers/r175-active-pointer.mjs +28 -0
- package/test/helpers/release-lock.mjs +12 -0
- package/test/r129-sdk-security-baseline.test.mjs +2 -1
- package/test/r130-release-security-contract.test.mjs +11 -10
- package/test/r132-release-identity.test.mjs +12 -11
- package/test/r153-prepublish-identity.test.mjs +1 -1
- package/test/r155-default-live-log.test.mjs +17 -16
- package/test/r160-package-decision-manifest.test.mjs +1 -1
- package/test/r160-release-contract.test.mjs +4 -5
- package/test/r163-isolated-slot-rollback-restore.test.mjs +6 -6
- package/test/r165-live-cli-skew.test.mjs +6 -6
- package/test/r167-release-contract.test.mjs +15 -13
- package/test/r168-protected-entrypoint.test.mjs +5 -2
- package/test/r168-release-contract.test.mjs +4 -3
- package/test/r168-remote-one-shot-coherence.test.mjs +2 -1
- package/test/r172-release-contract.test.mjs +7 -7
- package/test/r173-release-contract.test.mjs +18 -17
- package/test/r174-owner-webauthn.test.mjs +144 -0
- package/test/r175-cli-flags.test.mjs +23 -0
- package/test/r175-foreground-boundary.test.mjs +87 -0
- package/test/r175-guardian-owner-boundary.test.mjs +56 -0
- package/test/r176-pretty-live-log.test.mjs +54 -0
- package/test/r177-windows-absolute-live-path.test.mjs +37 -0
- package/test/r179-absolute-path-live-log.test.mjs +40 -0
- package/test/r179-release-contract.test.mjs +62 -0
- package/test/r186-controller-owner-grant.test.mjs +20 -0
- package/test/r186-owner-consent-preflight.test.mjs +98 -0
- package/test/r186-owner-enrollment.test.mjs +20 -0
- package/test/r186-owner-grant-broker.test.mjs +85 -0
- package/test/r186-owner-local-signing.test.mjs +30 -0
- package/test/r186-owner-request.test.mjs +28 -0
- package/test/r186-owner-review-export.test.mjs +52 -0
- package/test/r48-release-contract.test.mjs +2 -1
- package/test/r51-runtime-coherence.test.mjs +4 -2
- package/test/r58-cleanup-retention.test.mjs +5 -12
- package/test/r58-concurrent-recovery.test.mjs +5 -1
- package/test/r58-stale-owner.test.mjs +9 -3
- package/test/r59-tool-surface-compat.test.mjs +1 -1
- package/test/remote-live-log-r37.test.mjs +2 -2
- package/test/remote-live-log-recovery-r36.test.mjs +1 -1
- package/test/remote-session-cli.test.mjs +92 -126
- package/node_modules/which/CHANGELOG.md +0 -166
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import {spawnSync} from 'node:child_process';
|
|
2
|
+
import {existsSync} from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
const cwd=path.resolve(import.meta.dirname,'..');
|
|
5
|
+
const npm=process.env.npm_execpath||path.join(path.dirname(process.execPath),'node_modules','npm','bin','npm-cli.js');
|
|
6
|
+
function exec(args,ms=30000){
|
|
7
|
+
const r=spawnSync(process.execPath,[npm,...args],{cwd,encoding:'utf8',windowsHide:true,timeout:ms,env:{...process.env,npm_config_update_notifier:'false'}});
|
|
8
|
+
const s=String(r.stdout||'').trim(),e=String(r.stderr||'').trim();
|
|
9
|
+
return {ok:r.status===0,exit:r.status,error:r.error?.code??null,
|
|
10
|
+
output:args[0]==='whoami'?(r.status===0?s:'[NOT_AUTHENTICATED]'):String((s||e).slice(0,500)).replace(/https?:\/\/[^\s]+/g,'[url-redacted]')};
|
|
11
|
+
}
|
|
12
|
+
console.log('NODE='+process.version+' npm_cli_available='+existsSync(npm));
|
|
13
|
+
console.log('node_modules_present='+existsSync(path.join(cwd,'node_modules')));
|
|
14
|
+
console.log('NPM_VERSION='+JSON.stringify(exec(['--version'])));
|
|
15
|
+
console.log('NPM_WHOAMI='+JSON.stringify(exec(['whoami'])));
|
|
16
|
+
console.log('NPM_01569_REGISTRY='+JSON.stringify(exec(['view','deadbyte-mcp@0.15.69','version','--json'])));
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import {spawnSync} from 'node:child_process';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
const root=path.resolve(import.meta.dirname,'..');
|
|
4
|
+
for(const args of [['scripts/release-manifest.mjs','verify'],['scripts/npm-release-r48.mjs','--verify-only']]){
|
|
5
|
+
const r=spawnSync(process.execPath,args,{cwd:root,encoding:'utf8',timeout:30000,windowsHide:true,maxBuffer:2*1024*1024});
|
|
6
|
+
console.log('R178_CHECK='+args.join(' ')+' EXIT='+r.status);
|
|
7
|
+
const out=String(r.stdout||'')+'\n'+String(r.stderr||'');
|
|
8
|
+
for(const line of out.split(/\r?\n/).filter(x=>/MANIFEST_|FAIL|release identity|version mismatch|not published|PROVENANCE|PUBLISH|READY|error:/i.test(x)).slice(0,12))console.log(line.slice(0,500));
|
|
9
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import {spawnSync} from 'node:child_process';
|
|
2
|
+
import {readdirSync,mkdirSync,writeFileSync,readFileSync} from 'node:fs';
|
|
3
|
+
import {createHash} from 'node:crypto';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
const root=path.resolve(import.meta.dirname,'..');
|
|
6
|
+
const testRoot=path.join(root,'test');
|
|
7
|
+
const files=readdirSync(testRoot).filter(n=>n.endsWith('.test.mjs')).sort();
|
|
8
|
+
const size=Number(process.argv[3]??16),index=Number(process.argv[2]);
|
|
9
|
+
if(!Number.isInteger(index)||index<0||!Number.isInteger(size)||size<1||size>32)throw Error('invalid shard');
|
|
10
|
+
const selection=files.slice(index*size,(index+1)*size);
|
|
11
|
+
if(!selection.length)throw Error('empty shard');
|
|
12
|
+
const outDir=path.resolve(root,'..','..','testing','r178-01569-candidate');
|
|
13
|
+
mkdirSync(outDir,{recursive:true});
|
|
14
|
+
const testArgs=['--test','--test-concurrency=3',...selection.map(n=>path.join('test',n))];
|
|
15
|
+
const result=spawnSync(process.execPath,testArgs,{cwd:root,encoding:'utf8',windowsHide:true,
|
|
16
|
+
timeout:26000,maxBuffer:12*1024*1024});
|
|
17
|
+
const log=String(result.stdout||'')+'\n=== STDERR ===\n'+String(result.stderr||'');
|
|
18
|
+
const fp=path.join(outDir,'shard-'+String(index).padStart(2,'0')+'.log');
|
|
19
|
+
writeFileSync(fp,log);
|
|
20
|
+
const sha=createHash('sha256').update(readFileSync(fp)).digest('hex');
|
|
21
|
+
const stat=log.split(/\r?\n/).filter(x=>/^(ℹ (tests|pass|fail|skipped|duration_ms)|# (tests|pass|fail|skipped|duration_ms)|✖|not ok)/.test(x));
|
|
22
|
+
console.log('R178_SHARD='+index+' FILES='+selection.length+' EXIT='+result.status+' TIMEOUT='+Boolean(result.error?.code==='ETIMEDOUT'));
|
|
23
|
+
console.log(stat.slice(-8).map(x=>x.slice(0,300)).join('\n'));
|
|
24
|
+
console.log('EVIDENCE='+fp+' SHA256='+sha);
|
|
25
|
+
if(result.status!==0)process.exitCode=1;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {spawnSync} from 'node:child_process';
|
|
2
|
+
import {mkdtempSync,readdirSync,readFileSync,rmSync} from 'node:fs';
|
|
3
|
+
import {tmpdir} from 'node:os';
|
|
4
|
+
import {createHash} from 'node:crypto';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
const npm=process.env.npm_execpath||path.join(path.dirname(process.execPath),'node_modules','npm','bin','npm-cli.js');
|
|
7
|
+
function run(args,timeout=80000){const p=spawnSync(process.execPath,[npm,...args],{encoding:'utf8',shell:false,windowsHide:true,timeout,maxBuffer:3*1024*1024});if(p.error||p.status!==0)throw Error('npm read-only '+args[0]+' failed '+(p.stderr||p.error));return p.stdout.trim();}
|
|
8
|
+
const dir=mkdtempSync(path.join(tmpdir(),'deadbyte-r179-source-'));
|
|
9
|
+
try {
|
|
10
|
+
const metadata=JSON.parse(run(['view','deadbyte-mcp@0.15.68','dist','--json']));
|
|
11
|
+
console.log('PREDECESSOR_DIST_TARBALL='+JSON.stringify(metadata.tarball));
|
|
12
|
+
console.log('PREDECESSOR_DIST_SHASUM='+metadata.shasum);
|
|
13
|
+
const info=JSON.parse(run(['pack','deadbyte-mcp@0.15.68','--pack-destination',dir,'--ignore-scripts','--json']));
|
|
14
|
+
if(!Array.isArray(info)||info.length!==1)throw Error('parent npm pack response shape invalid');
|
|
15
|
+
const file=path.join(dir,info[0].filename);
|
|
16
|
+
const bytes=readFileSync(file);
|
|
17
|
+
const sha256=createHash('sha256').update(bytes).digest('hex');
|
|
18
|
+
const sha1=createHash('sha1').update(bytes).digest('hex');
|
|
19
|
+
if(sha1!==metadata.shasum)throw Error('parent npm registry SHA-1 identity mismatch');
|
|
20
|
+
console.log('PREDECESSOR_VERSION=0.15.68');
|
|
21
|
+
console.log('PREDECESSOR_TARBALL_SHA256='+sha256);
|
|
22
|
+
console.log('PREDECESSOR_PACKED_BYTES='+bytes.length);
|
|
23
|
+
console.log('PREDECESSOR_REGISTRY_MATCH=PASS');
|
|
24
|
+
}finally{rmSync(dir,{recursive:true,force:true});}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
$ErrorActionPreference='Stop'
|
|
2
|
+
$root='C:\Program Files'
|
|
3
|
+
$paths=@(
|
|
4
|
+
'C:\Program Files\DEADBYTE-R153-Protected-01560-Test',
|
|
5
|
+
'C:\Program Files\DEADBYTE-R168-Protected-Test',
|
|
6
|
+
'C:\Program Files\DEADBYTE-R174-Test'
|
|
7
|
+
)
|
|
8
|
+
if(Test-Path -LiteralPath $root){
|
|
9
|
+
Get-ChildItem -LiteralPath $root -Directory -ErrorAction SilentlyContinue |
|
|
10
|
+
Where-Object {$_.Name -match 'DEADBYTE|R174|R175|R178'} |
|
|
11
|
+
Select-Object -First 25 | ForEach-Object {
|
|
12
|
+
$package=Join-Path $_.FullName 'package.json'
|
|
13
|
+
$ver='missing';if(Test-Path -LiteralPath $package){try{$ver=(Get-Content -Raw -LiteralPath $package|ConvertFrom-Json).version}catch{$ver='invalid'}}
|
|
14
|
+
$owner=(Get-Acl -LiteralPath $_.FullName).Owner
|
|
15
|
+
Write-Output ('PROTECTED_CANDIDATE='+$_.FullName+' version='+$ver+' owner='+$owner)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
$state=Join-Path $HOME '.deadbyte-mcp'
|
|
19
|
+
$a=Get-Content (Join-Path $state 'active-release.json') -Raw|ConvertFrom-Json
|
|
20
|
+
Write-Output ('ACTIVE='+$a.release_id+' version='+$a.version)
|
|
21
|
+
Write-Output ('R179_READONLY_COMPLETE=TRUE')
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {spawnSync} from 'node:child_process';
|
|
2
|
+
import {createHash} from 'node:crypto';
|
|
3
|
+
import {readFileSync,writeFileSync,existsSync} from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
const root=path.resolve(import.meta.dirname,'..');
|
|
6
|
+
const dir=path.resolve(root,'..','..','testing','r186-owner-consent');
|
|
7
|
+
const tests=['test/r186-owner-consent-preflight.test.mjs','test/r186-owner-request.test.mjs',
|
|
8
|
+
'test/r70-owner-approval.test.mjs',
|
|
9
|
+
'test/r70-controller-owner-issue-boundary.test.mjs',
|
|
10
|
+
'test/r70-autonomous-grant-expiry.test.mjs',
|
|
11
|
+
'test/r174-owner-webauthn.test.mjs'];
|
|
12
|
+
const result=spawnSync(process.execPath,['--test','--test-concurrency=2',...tests],{
|
|
13
|
+
cwd:root,encoding:'utf8',windowsHide:true,timeout:60000,maxBuffer:2*1024*1024});
|
|
14
|
+
const s=String(result.stdout??'')+'\n'+String(result.stderr??'');
|
|
15
|
+
const sha=createHash('sha256').update(s).digest('hex');
|
|
16
|
+
const output=path.join(dir,'R186-OWNER-FOCUSED-V2-20261028.log');
|
|
17
|
+
if(existsSync(output))throw Error('R186_FOCUSED_EVIDENCE_ALREADY_EXISTS');
|
|
18
|
+
writeFileSync(output,s,{flag:'wx'});
|
|
19
|
+
for(const line of s.split(/\r?\n/).filter(x=>/^(✔|✖|ℹ (tests|pass|fail|skipped|cancelled))/.test(x)).slice(-45))
|
|
20
|
+
console.log(line);
|
|
21
|
+
console.log('R186_FOCUSED_EXIT='+result.status+' LOG_SHA256='+sha);
|
|
22
|
+
if(result.status!==0)process.exitCode=1;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import {readFile} from 'node:fs/promises';
|
|
2
|
+
import {createR186OwnerAnchor} from '../src/r186-owner-anchor.mjs';
|
|
3
|
+
const [file]=process.argv.slice(2);
|
|
4
|
+
if(process.argv.slice(2).length!==1)throw new Error('usage: r186-owner-anchor-inspect.mjs <owner-public.pem>');
|
|
5
|
+
const out=createR186OwnerAnchor(await readFile(file,'utf8'));
|
|
6
|
+
process.stdout.write(JSON.stringify(out));
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// R186 read-only inspection of independently enrolled owner authorization.
|
|
2
|
+
// No key enrollment, challenge consumption, grant or policy mutation occurs here.
|
|
3
|
+
// This CLI MUST run from an R89-protected exact release. Any agent-writable
|
|
4
|
+
// source tree or owner public key path is rejected by the native OS ACL audit.
|
|
5
|
+
import {createHash} from 'node:crypto';
|
|
6
|
+
import {readFile,lstat} from 'node:fs/promises';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import {fileURLToPath} from 'node:url';
|
|
9
|
+
import {inspectTrustedInstallation} from '../src/privileged-install-trust-r89.mjs';
|
|
10
|
+
import {parseManifest,verifyManifestTreeExact} from '../src/release-slot.mjs';
|
|
11
|
+
import {verifyR186OwnerConsent,verifyR186PolicyRootBinding} from '../src/r186-owner-consent-preflight.mjs';
|
|
12
|
+
|
|
13
|
+
const THIS=fileURLToPath(import.meta.url);
|
|
14
|
+
const ROOT=path.resolve(path.dirname(THIS),'..');
|
|
15
|
+
const OWNER_DIR='C:\\Program Files\\DEADBYTE-OwnerTrust';
|
|
16
|
+
const KEY=path.win32.join(OWNER_DIR,'owner-public.pem');
|
|
17
|
+
const ANCHOR=path.win32.join(OWNER_DIR,'owner-anchor.json');
|
|
18
|
+
const SHA=/^[0-9a-f]{64}$/;
|
|
19
|
+
const sha=b=>createHash('sha256').update(b).digest('hex');
|
|
20
|
+
const reject=m=>{throw Error('R186_OWNER_PREFLIGHT_BLOCKED: '+m)};
|
|
21
|
+
async function bounded(file,limit){
|
|
22
|
+
const st=await lstat(file);
|
|
23
|
+
if(!st.isFile()||st.isSymbolicLink()||st.size<1||st.size>limit)reject('unsafe input file');
|
|
24
|
+
return readFile(file);
|
|
25
|
+
}
|
|
26
|
+
async function main(){
|
|
27
|
+
if(process.platform!=='win32'||process.arch!=='x64')reject('requires Windows x64');
|
|
28
|
+
// No dynamic owner key paths. Owner trust must have been installed by the
|
|
29
|
+
// physical owner through an independently approved protected workflow.
|
|
30
|
+
const args=process.argv.slice(2);
|
|
31
|
+
if(args.length!==5)reject('usage: <approval-json> <root-id> <project-root> <coding-policy-json> <autonomous-policy-json>');
|
|
32
|
+
const [approvalPath,rootId,projectRoot,codingPolicyPath,autonomousPolicyPath]=args;
|
|
33
|
+
if(![approvalPath,projectRoot,codingPolicyPath,autonomousPolicyPath].every(p=>path.win32.isAbsolute(p)))
|
|
34
|
+
reject('absolute paths required');
|
|
35
|
+
if(!/^[A-Za-z0-9._-]{1,64}$/.test(rootId))reject('root identity invalid');
|
|
36
|
+
// Do not trust agent-provided signature keys. R89 rejects writable ancestor,
|
|
37
|
+
// unsafe owner and any writable ACE on the pin or enrolled public key.
|
|
38
|
+
const trustedFiles=[
|
|
39
|
+
process.execPath,THIS,ANCHOR,KEY,
|
|
40
|
+
path.join(ROOT,'MANIFEST.SHA256'),
|
|
41
|
+
path.join(ROOT,'src','r186-owner-consent-preflight.mjs'),
|
|
42
|
+
path.join(ROOT,'src','r70-owner-approval.mjs'),
|
|
43
|
+
path.join(ROOT,'src','canonical-json.mjs'),
|
|
44
|
+
path.join(ROOT,'src','release-slot.mjs'),
|
|
45
|
+
path.join(ROOT,'src','privileged-install-trust-r89.mjs')
|
|
46
|
+
];
|
|
47
|
+
const os=await inspectTrustedInstallation(trustedFiles);
|
|
48
|
+
if(!os.trusted)reject('owner trust ACL untrusted');
|
|
49
|
+
const manifestBytes=await bounded(path.join(ROOT,'MANIFEST.SHA256'),512*1024);
|
|
50
|
+
const manifest=parseManifest(manifestBytes.toString('utf8'));
|
|
51
|
+
await verifyManifestTreeExact(ROOT,manifest);
|
|
52
|
+
const codingBytes=await bounded(codingPolicyPath,1024*1024);
|
|
53
|
+
const autonomousBytes=await bounded(autonomousPolicyPath,1024*1024);
|
|
54
|
+
const binding=verifyR186PolicyRootBinding({
|
|
55
|
+
codingPolicy:JSON.parse(codingBytes.toString('utf8')),
|
|
56
|
+
autonomousPolicy:JSON.parse(autonomousBytes.toString('utf8')),
|
|
57
|
+
rootId,projectRoot,codingPolicySha256:sha(codingBytes)
|
|
58
|
+
});
|
|
59
|
+
const [approvalBuf,trustBuf,keyBuf]=await Promise.all([
|
|
60
|
+
bounded(approvalPath,64*1024),bounded(ANCHOR,4*1024),bounded(KEY,8*1024)
|
|
61
|
+
]);
|
|
62
|
+
const decision=await verifyR186OwnerConsent({
|
|
63
|
+
ownerTrust:JSON.parse(trustBuf.toString('utf8')),
|
|
64
|
+
ownerPublicKeyPem:keyBuf.toString('utf8'),
|
|
65
|
+
approval:JSON.parse(approvalBuf.toString('utf8')),
|
|
66
|
+
expected:{
|
|
67
|
+
rootId:binding.root_id,projectRoot:binding.project_root,
|
|
68
|
+
codingPolicySha256:sha(codingBytes),
|
|
69
|
+
autonomousPolicySha256:sha(autonomousBytes),
|
|
70
|
+
reviewedArtifactSha256:sha(manifestBytes)
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
console.log(JSON.stringify({
|
|
74
|
+
schema:'deadbyte.r186-owner-preflight.v1',
|
|
75
|
+
status:decision.status,
|
|
76
|
+
authority_granted:false,
|
|
77
|
+
owner_key_id:decision.owner_key_id,
|
|
78
|
+
subject_sha256:decision.subject_sha256,
|
|
79
|
+
release_manifest_sha256:sha(manifestBytes),
|
|
80
|
+
os_acl_trusted:true,
|
|
81
|
+
project_root:decision.project_root,
|
|
82
|
+
expires_at_utc:decision.expires_at_utc,
|
|
83
|
+
next_gate:'OWNER_APPROVAL_SINGLE_USE_CONSUMPTION_AND_SIGNED_EPOCH_ISSUANCE'
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
main().catch(e=>{console.error('R186_OWNER_PREFLIGHT_BLOCKED '+String(e?.message??e).slice(0,300));process.exitCode=2;});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Protected, fixed-scope R186 grant broker entrypoint. The controller chooses
|
|
2
|
+
// neither a trust key nor an approval path; both are fixed by the contract.
|
|
3
|
+
import {createHash} from 'node:crypto';
|
|
4
|
+
import {lstat,readFile} from 'node:fs/promises';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import {fileURLToPath} from 'node:url';
|
|
7
|
+
import {inspectTrustedInstallation} from '../src/privileged-install-trust-r89.mjs';
|
|
8
|
+
import {parseManifest,verifyManifestTreeExact} from '../src/release-slot.mjs';
|
|
9
|
+
import {issueR186AutonomousGrant} from '../src/r186-owner-grant-broker.mjs';
|
|
10
|
+
|
|
11
|
+
const THIS=fileURLToPath(import.meta.url),ROOT=path.resolve(path.dirname(THIS),'..');
|
|
12
|
+
const OWNER_DIR='C:\\Program Files\\DEADBYTE-OwnerTrust';
|
|
13
|
+
const KEY=path.win32.join(OWNER_DIR,'owner-public.pem');
|
|
14
|
+
const ANCHOR=path.win32.join(OWNER_DIR,'owner-anchor.json');
|
|
15
|
+
const sha=b=>createHash('sha256').update(b).digest('hex');
|
|
16
|
+
const block=m=>{throw new Error(`R186_OWNER_GRANT_BLOCKED: ${m}`);};
|
|
17
|
+
async function bounded(file,limit){const st=await lstat(file);if(!st.isFile()||st.isSymbolicLink()||st.size<1||st.size>limit)block('unsafe input file');return readFile(file);}
|
|
18
|
+
async function main(){
|
|
19
|
+
if(process.platform!=='win32'||process.arch!=='x64')block('requires Windows x64');
|
|
20
|
+
const [runtimeDir,codingPolicyPath,autonomousPolicyPath]=process.argv.slice(2);
|
|
21
|
+
if(process.argv.slice(2).length!==3||![runtimeDir,codingPolicyPath,autonomousPolicyPath].every(p=>path.win32.isAbsolute(p)))
|
|
22
|
+
block('usage: <runtime-dir> <coding-policy-json> <autonomous-policy-json>');
|
|
23
|
+
const approvalPath=path.join(runtimeDir,'owner-consent','autonomous-approval.json');
|
|
24
|
+
const protectedFiles=[process.execPath,THIS,ANCHOR,KEY,path.join(ROOT,'MANIFEST.SHA256'),
|
|
25
|
+
path.join(ROOT,'src','r186-owner-grant-broker.mjs'),path.join(ROOT,'src','r186-owner-consent-preflight.mjs'),
|
|
26
|
+
path.join(ROOT,'src','r70-owner-approval.mjs'),path.join(ROOT,'src','authority-continuity-r65.mjs'),
|
|
27
|
+
path.join(ROOT,'src','epoch-lock-r85.mjs'),path.join(ROOT,'bin','deadbyte-epoch-lock-r85.node')];
|
|
28
|
+
const trust=await inspectTrustedInstallation(protectedFiles);
|
|
29
|
+
if(!trust.trusted)block('protected package or owner trust ACL untrusted');
|
|
30
|
+
const manifestBytes=await bounded(path.join(ROOT,'MANIFEST.SHA256'),512*1024);
|
|
31
|
+
await verifyManifestTreeExact(ROOT,parseManifest(manifestBytes.toString('utf8')));
|
|
32
|
+
const [codingBytes,autonomousBytes,approvalBytes,anchorBytes,keyBytes]=await Promise.all([
|
|
33
|
+
bounded(codingPolicyPath,1024*1024),bounded(autonomousPolicyPath,1024*1024),bounded(approvalPath,64*1024),
|
|
34
|
+
bounded(ANCHOR,4096),bounded(KEY,8192)]);
|
|
35
|
+
const autonomous=JSON.parse(autonomousBytes.toString('utf8'));
|
|
36
|
+
const result=await issueR186AutonomousGrant({runtimeDir,grantFile:autonomous.lease_file,
|
|
37
|
+
codingPolicy:JSON.parse(codingBytes.toString('utf8')),codingPolicyBytes:codingBytes,
|
|
38
|
+
autonomousPolicy:autonomous,autonomousPolicyBytes:autonomousBytes,releaseManifestBytes:manifestBytes,
|
|
39
|
+
ownerTrust:JSON.parse(anchorBytes.toString('utf8')),ownerPublicKeyPem:keyBytes.toString('utf8'),
|
|
40
|
+
approval:JSON.parse(approvalBytes.toString('utf8'))});
|
|
41
|
+
if(sha(await readFile(codingPolicyPath))!==sha(codingBytes)||sha(await readFile(autonomousPolicyPath))!==sha(autonomousBytes))
|
|
42
|
+
block('policy changed during grant transaction; outcome requires reconciliation');
|
|
43
|
+
process.stdout.write(JSON.stringify(result));
|
|
44
|
+
}
|
|
45
|
+
main().catch(error=>{console.error(String(error?.message??error).slice(0,500));process.exitCode=2;});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Owner-operated only. Do not invoke from an agent workflow.
|
|
2
|
+
import {generateKeyPairSync} from 'node:crypto';
|
|
3
|
+
import {mkdir,open,readFile} from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import {createR186OwnerAnchor} from '../src/r186-owner-anchor.mjs';
|
|
6
|
+
import {createR186OwnerApproval} from '../src/r186-owner-local-signing.mjs';
|
|
7
|
+
|
|
8
|
+
function args(values){const out={};for(let i=0;i<values.length;i+=2){if(!values[i]?.startsWith('--')||!values[i+1])throw new Error('invalid arguments');out[values[i].slice(2)]=path.resolve(values[i+1]);}return out;}
|
|
9
|
+
async function exclusive(file,data,mode=0o600){await mkdir(path.dirname(file),{recursive:true});const h=await open(file,'wx',mode);try{await h.writeFile(data);await h.sync();}finally{await h.close();}}
|
|
10
|
+
const [command,...rest]=process.argv.slice(2),opt=args(rest);
|
|
11
|
+
if(command==='keygen'){
|
|
12
|
+
if(!opt['private-out']||!opt['public-out'])throw new Error('usage: keygen --private-out PATH --public-out PATH');
|
|
13
|
+
const pair=generateKeyPairSync('ed25519'),privatePem=pair.privateKey.export({type:'pkcs8',format:'pem'}).toString();
|
|
14
|
+
const material=createR186OwnerAnchor(pair.publicKey.export({type:'spki',format:'pem'}).toString());
|
|
15
|
+
await exclusive(opt['private-out'],privatePem,0o600);await exclusive(opt['public-out'],material.public_key_pem,0o644);
|
|
16
|
+
console.log(`R186_OWNER_KEY_CREATED public=${opt['public-out']} owner_key_id=${material.anchor.owner_key_id}`);
|
|
17
|
+
}else if(command==='sign'){
|
|
18
|
+
if(!opt.request||!opt['private-key']||!opt['approval-out'])throw new Error('usage: sign --request PATH --private-key PATH --approval-out PATH');
|
|
19
|
+
const approval=createR186OwnerApproval({request:JSON.parse(await readFile(opt.request,'utf8')),privateKeyPem:await readFile(opt['private-key'],'utf8')});
|
|
20
|
+
await exclusive(opt['approval-out'],Buffer.from(JSON.stringify(approval,null,2)+'\n'),0o600);
|
|
21
|
+
console.log(`R186_OWNER_APPROVAL_CREATED approval=${opt['approval-out']} subject_sha256=${approval.subject_sha256} owner_key_id=${approval.owner_key_id}`);
|
|
22
|
+
}else throw new Error('usage: r186-owner-key-local.mjs <keygen|sign> ...');
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Protected exact-byte review-request export. No owner key or grant is used.
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import {fileURLToPath} from 'node:url';
|
|
4
|
+
import {inspectTrustedInstallation} from '../src/privileged-install-trust-r89.mjs';
|
|
5
|
+
import {parseManifest,verifyManifestTreeExact} from '../src/release-slot.mjs';
|
|
6
|
+
import {writeR186OwnerReviewRequest} from '../src/r186-owner-review-export.mjs';
|
|
7
|
+
import {readFile} from 'node:fs/promises';
|
|
8
|
+
|
|
9
|
+
const THIS=fileURLToPath(import.meta.url),ROOT=path.resolve(path.dirname(THIS),'..');
|
|
10
|
+
const fail=message=>{throw new Error(`R186_OWNER_REVIEW_EXPORT_BLOCKED: ${message}`);};
|
|
11
|
+
async function main(){
|
|
12
|
+
if(process.platform!=='win32'||process.arch!=='x64')fail('requires Windows x64');
|
|
13
|
+
const [rootId,projectRoot,codingPolicyPath,autonomousPolicyPath,outputPath]=process.argv.slice(2);
|
|
14
|
+
if(process.argv.slice(2).length!==5||
|
|
15
|
+
![projectRoot,codingPolicyPath,autonomousPolicyPath,outputPath].every(value=>path.win32.isAbsolute(value)))
|
|
16
|
+
fail('usage: <root-id> <project-root> <coding-policy-json> <autonomous-policy-json> <output-json>');
|
|
17
|
+
const manifestPath=path.join(ROOT,'MANIFEST.SHA256');
|
|
18
|
+
const protectedFiles=[process.execPath,THIS,manifestPath,
|
|
19
|
+
path.join(ROOT,'src','r186-owner-review-export.mjs'),
|
|
20
|
+
path.join(ROOT,'src','r186-owner-request.mjs'),
|
|
21
|
+
path.join(ROOT,'src','r186-owner-consent-preflight.mjs'),
|
|
22
|
+
path.join(ROOT,'src','canonical-json.mjs'),
|
|
23
|
+
path.join(ROOT,'src','privileged-install-trust-r89.mjs'),
|
|
24
|
+
path.join(ROOT,'src','release-slot.mjs')];
|
|
25
|
+
const trust=await inspectTrustedInstallation(protectedFiles);
|
|
26
|
+
if(!trust.trusted)fail('protected package ACL untrusted');
|
|
27
|
+
const manifestBytes=await readFile(manifestPath);
|
|
28
|
+
await verifyManifestTreeExact(ROOT,parseManifest(manifestBytes.toString('utf8')));
|
|
29
|
+
const request=await writeR186OwnerReviewRequest({rootId,projectRoot,codingPolicyPath,
|
|
30
|
+
autonomousPolicyPath,manifestPath,outputPath});
|
|
31
|
+
process.stdout.write(JSON.stringify({status:'REVIEW_REQUEST_EXPORTED',authority_granted:false,
|
|
32
|
+
owner_signature_present:false,subject_sha256:request.subject_sha256,output_path:outputPath}));
|
|
33
|
+
}
|
|
34
|
+
main().catch(error=>{console.error(String(error?.message??error).slice(0,500));process.exitCode=2;});
|
package/src/deadbyte-cli.mjs
CHANGED
|
@@ -27,6 +27,7 @@ import {foregroundIdentityMatches,queryForegroundIdentity,validForegroundIdentit
|
|
|
27
27
|
import {readRemoteMarkerSnapshot,deletePinnedRemoteMarker} from './foreground-marker-cas-win32.mjs';
|
|
28
28
|
import {acquireForegroundSessionLock} from './foreground-session-lock.mjs';
|
|
29
29
|
import { AUTHORITY_PLANES, authorityVector, authorityRestorePlan, parseAuthorityObservation, assertAuthorityVector, assertRuntimeReadyObservation, authorityBanner } from './remote-authority.mjs';
|
|
30
|
+
import {observeOnlyAuthority,assertObserveOnlyMarker,assertMatchingForegroundRelease} from './r175-foreground-boundary.mjs';
|
|
30
31
|
import { decodeRemoteLogBytes, boundedRemoteDiagnostic, readRemoteLogAppend } from './remote-log-decode-r60.mjs';
|
|
31
32
|
import {reconcileStaleMarker,resumeMarkerReconciliation,recoverPublishedMarkerTerminal} from './clean-activation-recovery-r153.mjs';
|
|
32
33
|
import {selectLegacyMarkerEvidence,verifyLocatedLegacyMarkerReceipt} from './legacy-marker-evidence-r159.mjs';
|
|
@@ -61,10 +62,10 @@ function printHelp(){
|
|
|
61
62
|
` npx -y ${exact} doctor # verify current package/release truth`,
|
|
62
63
|
` npx -y ${exact} mcp # stdio MCP server for local clients`,
|
|
63
64
|
` npx -y ${exact} remote # safe observation-only foreground session`,
|
|
64
|
-
` npx -y ${exact} remote --operator #
|
|
65
|
-
` npx -y ${exact} remote --desktop #
|
|
66
|
-
` npx -y ${exact} remote --host #
|
|
67
|
-
` npx -y ${exact} remote --full #
|
|
65
|
+
` npx -y ${exact} remote --operator # R175 blocked pending session-owned authority issuance`,
|
|
66
|
+
` npx -y ${exact} remote --desktop # R175 blocked pending session-owned authority issuance`,
|
|
67
|
+
` npx -y ${exact} remote --host # R175 blocked pending session-owned authority issuance`,
|
|
68
|
+
` npx -y ${exact} remote --full # R175 blocked pending session-owned authority issuance`,
|
|
68
69
|
` npx -y ${exact} remote --resume <cursor> # replay missed results, then reconnect`,
|
|
69
70
|
` npx -y ${exact} remote --trace # show trace/span identity without payload detail`,
|
|
70
71
|
|
|
@@ -400,8 +401,8 @@ function timelineFilterEvent(timeline){
|
|
|
400
401
|
}
|
|
401
402
|
async function tailRemoteLogs(root,signal,{journal=null,offsets=null,activityTracker=null,timelineFilter=null,timelineWriter=null,verbose=false,displayMode='unicode'}={}){
|
|
402
403
|
process.stdout.write(verbose
|
|
403
|
-
? '[LOG] Verbose live timeline. Full sanitized request detail enabled; Ctrl+C disconnects
|
|
404
|
-
: '[LOG] Live tool calls with individual START/DONE, sanitized arguments and results. Transport hidden; signed audit remains on disk. Ctrl+C disconnects
|
|
404
|
+
? '[LOG] Verbose live timeline. Full sanitized request detail enabled; Ctrl+C disconnects; owner standing grants remain unchanged.\n'
|
|
405
|
+
: '[LOG] Live tool calls with individual START/DONE, sanitized arguments and results. Transport hidden; signed audit remains on disk. Ctrl+C disconnects; owner standing grants remain unchanged.\n');
|
|
405
406
|
const activeOffsets=offsets??await snapshotRemoteLogOffsets(root);
|
|
406
407
|
const partialLines=new Map();
|
|
407
408
|
let liveFiles=[...activeOffsets.keys()];
|
|
@@ -455,7 +456,13 @@ function remoteTag(kind,displayMode){
|
|
|
455
456
|
return (displayMode==='ascii'?ascii:unicode)[kind];
|
|
456
457
|
}
|
|
457
458
|
function writeRemote(stream,text,displayMode){stream.write(remoteDisplayText(text,displayMode));}
|
|
458
|
-
async function cleanupRemoteSession(ctx,{displayMode='unicode'}={}){
|
|
459
|
+
async function cleanupRemoteSession(ctx,{displayMode='unicode',marker=null}={}){
|
|
460
|
+
// R175 observation sessions issue no grants and must never revoke owner grants or park runtime.
|
|
461
|
+
// An invalid R175 marker must fail closed, not fall through to legacy revoke.
|
|
462
|
+
if(marker!==null){
|
|
463
|
+
if(!assertObserveOnlyMarker(marker))throw new Error('R175_INVALID_OBSERVE_ONLY_MARKER: no authority cleanup performed');
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
459
466
|
const failed=[];
|
|
460
467
|
for(const command of SESSION_CLEANUP){
|
|
461
468
|
try{
|
|
@@ -669,6 +676,15 @@ async function recoverRemoteSession(ctx,{displayMode='unicode'}={}){
|
|
|
669
676
|
const markerPath=remoteSessionMarker(ctx.root);
|
|
670
677
|
const snapshot=await readRemoteMarkerSnapshot(markerPath);
|
|
671
678
|
if(!snapshot) return null;
|
|
679
|
+
if(assertObserveOnlyMarker(prior)){
|
|
680
|
+
// Owner and guardian lifetime were verified above. A monitor-only session
|
|
681
|
+
// owns the CAS marker, not any authority epoch or runtime lifecycle.
|
|
682
|
+
if(snapshot.marker?.owner_pid!==prior.owner_pid||snapshot.marker?.session_nonce!==prior.session_nonce)
|
|
683
|
+
throw new Error('R175 observation-only stale marker identity changed; marker retained');
|
|
684
|
+
if(deletePinnedRemoteMarker(markerPath,snapshot.sha256)!=='REMOVED')
|
|
685
|
+
throw new Error('R175 observation-only stale marker CAS failed; marker retained');
|
|
686
|
+
return null;
|
|
687
|
+
}
|
|
672
688
|
const active=await readActiveRuntimeBinding(ctx.root);
|
|
673
689
|
if(!active)throw new Error(`remote session ownership unverified for stale pid=${ownerPid}; active pointer missing; marker retained`);
|
|
674
690
|
const observed=observedAuthority(ctx);
|
|
@@ -740,7 +756,7 @@ async function claimRemoteSession(ctx){
|
|
|
740
756
|
const dir=path.join(ctx.root,'runtime');await mkdir(dir,{recursive:true});
|
|
741
757
|
const nonce=randomBytes(32).toString('hex');
|
|
742
758
|
const resultSessionId=randomBytes(12).toString('hex');
|
|
743
|
-
const marker={schema:'deadbyte.remote-session.v2',owner_pid:process.pid,guardian_pid:0,session_nonce:nonce,
|
|
759
|
+
const marker={schema:'deadbyte.remote-session.v2',authority_scope:'observe-only',owner_pid:process.pid,guardian_pid:0,session_nonce:nonce,
|
|
744
760
|
result_session_id:resultSessionId,started_at_utc:new Date().toISOString()};
|
|
745
761
|
marker.owner_identity=queryForegroundIdentity(process.pid);
|
|
746
762
|
if(!marker.owner_identity)throw new Error('remote foreground identity unavailable');
|
|
@@ -749,7 +765,7 @@ async function claimRemoteSession(ctx){
|
|
|
749
765
|
catch(error){if(error?.code==='EEXIST') throw new Error('remote session already claimed');throw error;}
|
|
750
766
|
let guardian=null;
|
|
751
767
|
try{
|
|
752
|
-
guardian=spawn(process.execPath,[GUARDIAN_SCRIPT,'--state-root',ctx.root,'--owner-pid',String(process.pid),'--session-nonce',nonce,'--owner-channel','ipc'],{
|
|
768
|
+
guardian=spawn(process.execPath,[GUARDIAN_SCRIPT,'--state-root',ctx.root,'--owner-pid',String(process.pid),'--session-nonce',nonce,'--owner-channel','ipc','--authority-scope','observe-only'],{
|
|
753
769
|
detached:true,stdio:['ignore','ignore','ignore','ipc'],windowsHide:true,shell:false,env:process.env
|
|
754
770
|
});
|
|
755
771
|
if(!Number.isInteger(guardian.pid)||guardian.pid<1) throw new Error('remote session guardian did not start');
|
|
@@ -786,6 +802,10 @@ async function releaseRemoteSession(ctx,marker,{displayMode='unicode'}={}){
|
|
|
786
802
|
throw new Error('remote session ownership marker replaced before native deletion; marker retained');
|
|
787
803
|
}
|
|
788
804
|
async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,filterExpression='',mode='default',hostCommandPins=[]}={}){
|
|
805
|
+
// R175: elevated session flags need a distinct signed session-owned lease
|
|
806
|
+
// issuer before they can safely coexist with owner standing grants.
|
|
807
|
+
if(mode!=='default') throw new Error('R175_FOREGROUND_OWNER_BOUNDARY: elevated foreground modes require session-owned grants; use explicit remote arm* commands');
|
|
808
|
+
if(hostCommandPins.length) throw new Error('R175_FOREGROUND_OBSERVE_ONLY: host command repins require an explicit owner action outside foreground logging');
|
|
789
809
|
const display=prepareRemoteConsole();
|
|
790
810
|
const displayMode=display.mode;
|
|
791
811
|
const ctx=await remoteContext();
|
|
@@ -793,12 +813,15 @@ async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,fil
|
|
|
793
813
|
// an EPOCH_NATIVE production runtime. This guard precedes session recovery,
|
|
794
814
|
// marker claims, revocations and any controller startup.
|
|
795
815
|
const active=await readActiveRuntimeBinding(ctx.root);
|
|
816
|
+
const currentManifestSha256=createHash('sha256')
|
|
817
|
+
.update(await readFile(path.join(CURRENT_PACKAGE_ROOT,'MANIFEST.SHA256'))).digest('hex');
|
|
818
|
+
assertMatchingForegroundRelease(active,{version:DEADBYTE_VERSION,manifest_sha256:currentManifestSha256});
|
|
796
819
|
if(active){
|
|
797
820
|
const decision=selectR168OneShotRemotePath({command:'start',active,cliVersion:DEADBYTE_VERSION});
|
|
798
821
|
if(decision.mode==='blocked_version_skew') throw new Error(decision.message);
|
|
799
822
|
}
|
|
800
|
-
//
|
|
801
|
-
//
|
|
823
|
+
// R175 foreground is monitor-only: deployment and upgrades need separate owner action.
|
|
824
|
+
// Never synchronize runtime or mint/revoke a grant from this path.
|
|
802
825
|
if(displayMode==='ascii') writeRemote(process.stdout,`[DISPLAY] ASCII fallback: ${display.reason}\n`,displayMode);
|
|
803
826
|
if(resumeCursor!==null) await replayRemoteResults(ctx.root,resumeCursor,{displayMode});
|
|
804
827
|
const startupLock=await acquireForegroundSessionLock({root:ctx.root,powershell:ctx.powershell});
|
|
@@ -807,9 +830,21 @@ async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,fil
|
|
|
807
830
|
startupLock.assertHeld();
|
|
808
831
|
await verifyPackageDecisionManifest({root:CURRENT_PACKAGE_ROOT,expectedVersion:DEADBYTE_VERSION});
|
|
809
832
|
startupLock.assertHeld();
|
|
810
|
-
|
|
833
|
+
// R179: an observation session may recover ONLY its own observation marker.
|
|
834
|
+
// Legacy migration/revocation is a separately authorized controller action.
|
|
835
|
+
const staleObserver=await readRemoteSessionMarker(ctx.root);
|
|
836
|
+
if(staleObserver){
|
|
837
|
+
if(!assertObserveOnlyMarker(staleObserver))
|
|
838
|
+
throw new Error('R179_LEGACY_FOREGROUND_RECOVERY_REQUIRES_EXPLICIT_OWNER_COMMAND: no authority touched');
|
|
839
|
+
await recoverRemoteSession(ctx,{displayMode});
|
|
840
|
+
}
|
|
811
841
|
startupLock.assertHeld();
|
|
812
|
-
await
|
|
842
|
+
const pinned=await readActiveRuntimeBinding(ctx.root);
|
|
843
|
+
assertMatchingForegroundRelease(pinned,{version:DEADBYTE_VERSION,manifest_sha256:currentManifestSha256});
|
|
844
|
+
for(const field of ['release_id','generation_manifest_sha256','generation_root']){
|
|
845
|
+
if(pinned[field]!==active[field])
|
|
846
|
+
throw new Error('R175_FOREGROUND_ACTIVE_DRIFT: no runtime update or grant mutation attempted');
|
|
847
|
+
}
|
|
813
848
|
startupLock.assertHeld();
|
|
814
849
|
marker=await claimRemoteSession(ctx);
|
|
815
850
|
startupLock.assertHeld();
|
|
@@ -838,51 +873,29 @@ async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,fil
|
|
|
838
873
|
process.stdout.write('[BOOT] Starting local + remote runtime...\n');
|
|
839
874
|
const preflight=invokeController(ctx,'status',{capture:true,allowFailure:true,echoCaptured:false,timeoutMs:REMOTE_STATUS_TIMEOUT_MS});
|
|
840
875
|
let startText=preflight.stdout||'';
|
|
841
|
-
if(preflight.status
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
const recovery=preflight.status===0&&controllerRuntimeDegraded(preflight.stdout);
|
|
845
|
-
process.stdout.write(recovery?'[RECOVERY] Runtime failed readiness; restarting verified owned components...\n':'[BOOT] Starting bridge/runtime...\n');
|
|
846
|
-
const startCommand=recovery?'restart':'start';
|
|
847
|
-
const controllerLogDir=path.join(ctx.root,'runtime','logs');
|
|
848
|
-
await mkdir(controllerLogDir,{recursive:true});
|
|
849
|
-
const launchOutput={
|
|
850
|
-
stdout_path:path.join(controllerLogDir,`controller-${startCommand}.stdout.log`),
|
|
851
|
-
stderr_path:path.join(controllerLogDir,`controller-${startCommand}.stderr.log`)
|
|
852
|
-
};
|
|
853
|
-
const launch=invokeController(ctx,startCommand,{allowFailure:true,redirectOutput:launchOutput,timeoutMs:REMOTE_START_TIMEOUT_MS});
|
|
854
|
-
if(launch.status!==0){
|
|
855
|
-
const evidence=await readControllerStartFailure(ctx.root,startCommand);
|
|
856
|
-
const fallback=evidence?'':await readTailBounded(launchOutput.stderr_path,2048);
|
|
857
|
-
const detail=evidence?`; controller-start-failure: ${evidence.message}`:(fallback?`; controller-stderr: ${fallback}`:'');
|
|
858
|
-
throw new Error(`remote controller '${startCommand}' failed exit=${launch.status}${detail}`);
|
|
859
|
-
}
|
|
860
|
-
const started=invokeController(ctx,'status',{capture:true,echoCaptured:false,timeoutMs:REMOTE_STATUS_TIMEOUT_MS});
|
|
861
|
-
if(started.status!==0||!controllerRuntimeReady(started.stdout)) throw new Error(`remote controller ${recovery?'restart':'start'} returned without a verified ONLINE runtime`);
|
|
862
|
-
startText=started.stdout||'';
|
|
863
|
-
}
|
|
876
|
+
if(preflight.status!==0||!controllerRuntimeReady(preflight.stdout))
|
|
877
|
+
throw new Error('R175_FOREGROUND_RUNTIME_NOT_READY: monitoring cannot start or restart a runtime; use explicit remote start');
|
|
878
|
+
process.stdout.write('[BOOT] Runtime already ONLINE; reusing verified bridge/cloud/supervisor.\n');
|
|
864
879
|
process.stdout.write(`${formatRemoteLifecycle('bridge_connected',{displayMode})}\n`);
|
|
865
880
|
process.stdout.write(`${formatRemoteLifecycle('cloud_connected',{displayMode})}\n`);
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
for(const plane of ['host','desktop','autonomous','coding','process'].filter(name=>!desired[name]&&priorAuthority.authority[name].armed)){
|
|
869
|
-
const command=`disarm${plane}`;
|
|
870
|
-
writeRemote(process.stdout,`${remoteTag('auth',displayMode)} Revoking unwanted ${plane} authority...\n`,displayMode);
|
|
871
|
-
invokeController(ctx,command,{capture:true,echoCaptured:false,timeoutMs:REMOTE_STATUS_TIMEOUT_MS});
|
|
872
|
-
}
|
|
873
|
-
for(const plane of AUTHORITY_PLANES.filter(name=>desired[name])){
|
|
874
|
-
const command=`arm${plane}`;
|
|
875
|
-
writeRemote(process.stdout,`${remoteTag('auth',displayMode)} Arming ${command.slice(3)}...\n`,displayMode);
|
|
876
|
-
invokeController(ctx,command,{capture:true,echoCaptured:false,timeoutMs:REMOTE_STATUS_TIMEOUT_MS});
|
|
877
|
-
}
|
|
878
|
-
const url=startText.match(/^URL\s*:\s*(\S+)/m)?.[1]||'';
|
|
879
|
-
writeRemote(process.stdout,`${remoteTag('auth',displayMode)} Verifying exact ${mode} authority vector...\n`,displayMode);
|
|
881
|
+
// Observation is never a grant transition or an expectation that live
|
|
882
|
+
// owner grants remain frozen for the lifetime of this log session.
|
|
880
883
|
const current=await readActiveRuntimeBinding(ctx.root);
|
|
881
|
-
const
|
|
882
|
-
|
|
883
|
-
|
|
884
|
+
for(const field of ['release_id','manifest_sha256','generation_manifest_sha256','generation_root'])
|
|
885
|
+
if(current?.[field]!==active[field])throw new Error('R175_FOREGROUND_ACTIVE_DRIFT: observed release changed');
|
|
886
|
+
const authority=assertRuntimeReadyObservation(observedAuthority(ctx),{
|
|
887
|
+
coreRoot:active.release_root
|
|
888
|
+
});
|
|
889
|
+
observeOnlyAuthority(authority);
|
|
890
|
+
const url=startText.match(/^URL\s*:\s*(\S+)/m)?.[1]||'';
|
|
891
|
+
writeRemote(process.stdout,`${remoteTag('auth',displayMode)} Observing existing grants without ARM or DISARM...\n`,displayMode);
|
|
884
892
|
process.stdout.write('[BOOT] Verifying public signed MCP execution path...\n');
|
|
885
|
-
|
|
893
|
+
// Signed machine test is safe only for already-disarmed authority vectors.
|
|
894
|
+
// Do not test against a made-up all-disarmed expectation while owner
|
|
895
|
+
// grants are live; never revoke them as a way to make that test pass.
|
|
896
|
+
if(AUTHORITY_PLANES.every(plane=>!authority.authority[plane].armed))
|
|
897
|
+
invokeController(ctx,'machinetest',{capture:true,echoCaptured:false,timeoutMs:REMOTE_START_TIMEOUT_MS,probeMode:'revoked'});
|
|
898
|
+
else process.stdout.write('[VERIFY] pre-existing owner grants preserved; revoked-plane probe omitted\n');
|
|
886
899
|
ready=true;
|
|
887
900
|
writeRemote(process.stdout,`${remoteTag('auth',displayMode)} ${authorityBanner(authority)}\n`,displayMode);
|
|
888
901
|
process.stdout.write('[OK] ONLINE\n');
|
|
@@ -906,8 +919,8 @@ async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,fil
|
|
|
906
919
|
process.removeListener('SIGINT',abort);process.removeListener('SIGTERM',abort);
|
|
907
920
|
if(watchStdin){process.stdin.removeListener('end',abort);process.stdin.removeListener('close',abort);process.stdin.pause();}
|
|
908
921
|
process.stdout.write(`${formatRemoteLifecycle('cleanup',{displayMode})}\n`);
|
|
909
|
-
if(ready||!wasAborted) process.stdout.write('[INFO] Disconnecting;
|
|
910
|
-
try{await cleanupRemoteSession(ctx,{displayMode});}
|
|
922
|
+
if(ready||!wasAborted) process.stdout.write('[INFO] Disconnecting; preserving owner standing grants...\n');
|
|
923
|
+
try{await cleanupRemoteSession(ctx,{displayMode,marker});}
|
|
911
924
|
catch(cleanupError){
|
|
912
925
|
if(sessionError) throw new AggregateError([sessionError,cleanupError],
|
|
913
926
|
`remote activation failed: ${String(sessionError?.message||sessionError)}; cleanup failed, ownership marker retained: ${String(cleanupError?.message||cleanupError)}`);
|