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.
Files changed (83) hide show
  1. package/MANIFEST.SHA256 +81 -38
  2. package/PROVENANCE.json +35 -23
  3. package/README.txt +44 -31
  4. package/RELEASE.json +30 -17
  5. package/SBOM.cdx.json +2 -2
  6. package/TEST-MATRIX.json +25 -2
  7. package/bridge/package.json +1 -1
  8. package/controller/deadbyte-controller.ps1 +65 -1
  9. package/controller/deadbyte-process-policy.json +4 -4
  10. package/docs/R184-package-lock.json +97 -0
  11. package/docs/R186-OWNER-CONSENT-HANDOFF.txt +87 -0
  12. package/package.json +1 -1
  13. package/scripts/install-r186-owner-trust.ps1 +59 -0
  14. package/scripts/r175-focused-runner.mjs +38 -0
  15. package/scripts/r176-log-preview.mjs +18 -0
  16. package/scripts/r177-owner-trust-inventory.ps1 +36 -0
  17. package/scripts/r178-full-regression.mjs +21 -0
  18. package/scripts/r178-npm-ci.mjs +10 -0
  19. package/scripts/r178-npm-preflight.mjs +16 -0
  20. package/scripts/r178-release-gates-readonly.mjs +9 -0
  21. package/scripts/r178-shard-regression.mjs +25 -0
  22. package/scripts/r179-predecessor-registry.mjs +24 -0
  23. package/scripts/r179-protected-inventory.ps1 +21 -0
  24. package/scripts/r186-focused-runner.mjs +22 -0
  25. package/scripts/r186-owner-anchor-inspect.mjs +6 -0
  26. package/scripts/r186-owner-consent-check.mjs +86 -0
  27. package/scripts/r186-owner-grant-cli.mjs +45 -0
  28. package/scripts/r186-owner-key-local.mjs +22 -0
  29. package/scripts/r186-owner-review-export.mjs +34 -0
  30. package/src/deadbyte-cli.mjs +69 -56
  31. package/src/r174-owner-webauthn.mjs +176 -0
  32. package/src/r175-foreground-boundary.mjs +28 -0
  33. package/src/r186-owner-anchor.mjs +12 -0
  34. package/src/r186-owner-consent-preflight.mjs +63 -0
  35. package/src/r186-owner-grant-broker.mjs +96 -0
  36. package/src/r186-owner-local-signing.mjs +25 -0
  37. package/src/r186-owner-request.mjs +40 -0
  38. package/src/r186-owner-review-export.mjs +49 -0
  39. package/src/remote-live-log.mjs +81 -5
  40. package/src/remote-session-guardian.mjs +23 -0
  41. package/src/version.mjs +1 -1
  42. package/test/helpers/r175-active-pointer.mjs +28 -0
  43. package/test/helpers/release-lock.mjs +12 -0
  44. package/test/r129-sdk-security-baseline.test.mjs +2 -1
  45. package/test/r130-release-security-contract.test.mjs +11 -10
  46. package/test/r132-release-identity.test.mjs +12 -11
  47. package/test/r153-prepublish-identity.test.mjs +1 -1
  48. package/test/r155-default-live-log.test.mjs +17 -16
  49. package/test/r160-package-decision-manifest.test.mjs +1 -1
  50. package/test/r160-release-contract.test.mjs +4 -5
  51. package/test/r163-isolated-slot-rollback-restore.test.mjs +6 -6
  52. package/test/r165-live-cli-skew.test.mjs +6 -6
  53. package/test/r167-release-contract.test.mjs +15 -13
  54. package/test/r168-protected-entrypoint.test.mjs +5 -2
  55. package/test/r168-release-contract.test.mjs +4 -3
  56. package/test/r168-remote-one-shot-coherence.test.mjs +2 -1
  57. package/test/r172-release-contract.test.mjs +7 -7
  58. package/test/r173-release-contract.test.mjs +18 -17
  59. package/test/r174-owner-webauthn.test.mjs +144 -0
  60. package/test/r175-cli-flags.test.mjs +23 -0
  61. package/test/r175-foreground-boundary.test.mjs +87 -0
  62. package/test/r175-guardian-owner-boundary.test.mjs +56 -0
  63. package/test/r176-pretty-live-log.test.mjs +54 -0
  64. package/test/r177-windows-absolute-live-path.test.mjs +37 -0
  65. package/test/r179-absolute-path-live-log.test.mjs +40 -0
  66. package/test/r179-release-contract.test.mjs +62 -0
  67. package/test/r186-controller-owner-grant.test.mjs +20 -0
  68. package/test/r186-owner-consent-preflight.test.mjs +98 -0
  69. package/test/r186-owner-enrollment.test.mjs +20 -0
  70. package/test/r186-owner-grant-broker.test.mjs +85 -0
  71. package/test/r186-owner-local-signing.test.mjs +30 -0
  72. package/test/r186-owner-request.test.mjs +28 -0
  73. package/test/r186-owner-review-export.test.mjs +52 -0
  74. package/test/r48-release-contract.test.mjs +2 -1
  75. package/test/r51-runtime-coherence.test.mjs +4 -2
  76. package/test/r58-cleanup-retention.test.mjs +5 -12
  77. package/test/r58-concurrent-recovery.test.mjs +5 -1
  78. package/test/r58-stale-owner.test.mjs +9 -3
  79. package/test/r59-tool-surface-compat.test.mjs +1 -1
  80. package/test/remote-live-log-r37.test.mjs +2 -2
  81. package/test/remote-live-log-recovery-r36.test.mjs +1 -1
  82. package/test/remote-session-cli.test.mjs +92 -126
  83. 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;});
@@ -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 # process/coding/autonomous; explicit owner gates still apply`,
65
- ` npx -y ${exact} remote --desktop # session with Desktop authority; Host remains disarmed`,
66
- ` npx -y ${exact} remote --host # session with Host authority; Desktop remains disarmed`,
67
- ` npx -y ${exact} remote --full # explicit five-plane foreground session`,
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 and revokes authority.\n'
404
- : '[LOG] Live tool calls with individual START/DONE, sanitized arguments and results. Transport hidden; signed audit remains on disk. Ctrl+C disconnects and revokes authority.\n');
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
- // First installation without an active pointer retains the existing
801
- // unbound bootstrap path. A bound older release must never auto-upgrade.
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
- const migrationContext=await recoverRemoteSession(ctx,{displayMode});
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 synchronizeForegroundRuntime(ctx,{displayMode,migrationContext,hostCommandPins});
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===0&&controllerRuntimeReady(preflight.stdout)){
842
- process.stdout.write('[BOOT] Runtime already ONLINE; reusing verified bridge/cloud/supervisor.\n');
843
- }else{
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
- const desired=authorityVector(mode);
867
- const priorAuthority=observedAuthority(ctx);
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 authority=assertAuthorityVector(assertRuntimeReadyObservation(observedAuthority(ctx),{
882
- coreRoot:current?.release_root??null
883
- }),desired);
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
- invokeController(ctx,'machinetest',{capture:true,echoCaptured:false,timeoutMs:REMOTE_START_TIMEOUT_MS,probeMode:mode==='default'?'revoked':mode});
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; revoking authority...\n');
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)}`);