deadbyte-mcp 0.15.48 → 0.15.49

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 (50) hide show
  1. package/MANIFEST.SHA256 +48 -22
  2. package/PROVENANCE.json +11 -22
  3. package/README.txt +18 -18
  4. package/RELEASE.json +368 -378
  5. package/SBOM.cdx.json +2 -2
  6. package/TEST-MATRIX.json +2 -2
  7. package/bench/r121-epoch-readhead.mjs +54 -0
  8. package/bench/r124-epoch-predecessor.mjs +53 -0
  9. package/controller/deadbyte-process-policy.json +4 -4
  10. package/docs/HARNESS2-ADAPTATION-20261020.md +24 -0
  11. package/docs/R128-RELEASE-CANDIDATE.md +26 -0
  12. package/package.json +1 -1
  13. package/scripts/npm-release-r48.mjs +0 -4
  14. package/scripts/verify-authority-epoch-history.mjs +38 -0
  15. package/src/activation-continuity-engine-r65.mjs +9 -15
  16. package/src/agent-eval-r42.mjs +30 -0
  17. package/src/authority-continuity-r65.mjs +182 -13
  18. package/src/autonomous-local-supervisor.mjs +77 -47
  19. package/src/deadbyte-cli.mjs +18 -28
  20. package/src/remote-live-log.mjs +15 -8
  21. package/src/remote-log-decode-r60.mjs +65 -0
  22. package/src/remote-session-guardian.mjs +4 -5
  23. package/src/version.mjs +1 -1
  24. package/test/autonomous-local-supervisor.test.mjs +1 -1
  25. package/test/harness2-eval-context-isolation.test.mjs +90 -0
  26. package/test/r108-remote-powershell-path.test.mjs +59 -0
  27. package/test/r110-supervisor-powershell-path.test.mjs +28 -0
  28. package/test/r111-supervisor-marker-cas.test.mjs +116 -0
  29. package/test/r112-supervisor-claim-cas.test.mjs +63 -0
  30. package/test/r113-supervisor-cleanup-terminality.test.mjs +100 -0
  31. package/test/r114-supervisor-absence-proof.test.mjs +45 -0
  32. package/test/r115-posix-continuity-entrypoint.test.mjs +17 -0
  33. package/test/r116-authority-crash-matrix.test.mjs +83 -0
  34. package/test/r117-concurrent-orphan-recovery.test.mjs +54 -0
  35. package/test/r118-cross-process-orphan-recovery.test.mjs +99 -0
  36. package/test/r119-recovery-crash-after-commit.test.mjs +91 -0
  37. package/test/r120-authority-history-audit.test.mjs +103 -0
  38. package/test/r121-signed-transition-invariant.test.mjs +132 -0
  39. package/test/r122-epoch-bounded-read.test.mjs +58 -0
  40. package/test/r123-recovery-bounded-read.test.mjs +64 -0
  41. package/test/r124-live-log-integrity.test.mjs +66 -0
  42. package/test/r124-live-log-tail-boundaries.test.mjs +57 -0
  43. package/test/r124-signed-predecessor-shape.test.mjs +66 -0
  44. package/test/r125-restore-binding-types.test.mjs +63 -0
  45. package/test/r126-restore-policy-envelope.test.mjs +89 -0
  46. package/test/r127-restore-append-policy-boundary.test.mjs +25 -0
  47. package/test/r59-tool-surface-compat.test.mjs +1 -1
  48. package/test/r65-authority-epoch-store.test.mjs +9 -6
  49. package/test/r74-prepublish-contract.test.mjs +3 -1
  50. package/scripts/npm-release-readiness-r108.mjs +0 -134
@@ -214,10 +214,63 @@ const durablePublicKeys=new Map();
214
214
  const RESTORE_BINDING_FIELDS=Object.freeze(['prior_grant_sha256','prior_grant_instance_nonce','predecessor_policy_envelope_sha256','successor_policy_envelope_sha256','candidate_manifest_sha256','continuity_subject_sha256']);
215
215
  function normalizeRestoreBinding(value,{required=false}={}){
216
216
  if(value===undefined||value===null){if(required)throw new Error('authority epoch transaction restore binding required');return null;}
217
- if(!plain(value)||Object.keys(value).length!==RESTORE_BINDING_FIELDS.length||RESTORE_BINDING_FIELDS.some(key=>!SHAPAT.test(String(value[key]))))
217
+ if(!plain(value)||Object.keys(value).length!==RESTORE_BINDING_FIELDS.length||
218
+ RESTORE_BINDING_FIELDS.some(key=>typeof value[key]!=='string'||!SHAPAT.test(value[key])))
218
219
  throw new Error('authority epoch transaction restore binding invalid');
219
220
  for(const key of Object.keys(value))if(!RESTORE_BINDING_FIELDS.includes(key))throw new Error('authority epoch transaction restore binding invalid');
220
- return Object.freeze(Object.fromEntries(RESTORE_BINDING_FIELDS.map(key=>[key,String(value[key])])));
221
+ return Object.freeze(Object.fromEntries(RESTORE_BINDING_FIELDS.map(key=>[key,value[key]])));
222
+ }
223
+
224
+ function assertEpochActionIdentity(current){
225
+ const action=current?.action;
226
+ if(!DURABLE_EPOCH_ACTIONS.has(action)||
227
+ (action.startsWith('transaction_')?
228
+ typeof current.update_id!=='string'||!/^[A-Za-z0-9._-]{1,96}$/.test(current.update_id):
229
+ current.update_id!==null))
230
+ throw new Error('authority epoch transition invalid');
231
+ if(action==='transaction_restore')normalizeRestoreBinding(current.restore_binding,{required:true});
232
+ else if(current.restore_binding!==null)throw new Error('authority epoch transition invalid');
233
+ }
234
+
235
+ function assertEpochPredecessorIdentity(event){
236
+ if(event.schema!==DURABLE_EPOCH_SCHEMA||
237
+ typeof event.grant_sha256!=='string'||!SHAPAT.test(event.grant_sha256)||
238
+ typeof event.grant_instance_nonce!=='string'||!SHAPAT.test(event.grant_instance_nonce)||
239
+ (event.seq===1?event.previous_event_sha256!==null:
240
+ typeof event.previous_event_sha256!=='string'||!SHAPAT.test(event.previous_event_sha256))||
241
+ ((event.policy_sha256!==undefined||event.policy_envelope_sha256!==undefined)&&
242
+ (typeof event.policy_sha256!=='string'||!SHAPAT.test(event.policy_sha256)||
243
+ typeof event.policy_envelope_sha256!=='string'||!SHAPAT.test(event.policy_envelope_sha256))))
244
+ throw new Error('authority epoch transition invalid');
245
+ assertEpochActionIdentity(event);
246
+ }
247
+
248
+ function assertEpochTransition(current,previous){
249
+ assertEpochActionIdentity(current);
250
+ const action=current.action;
251
+ if(!previous){
252
+ if(action.startsWith('transaction_'))throw new Error('authority epoch transition invalid');
253
+ return;
254
+ }
255
+ if(action==='transaction_revoke'&&
256
+ (!['grant','transaction_restore'].includes(previous.action)||
257
+ current.grant_sha256!==previous.grant_sha256||
258
+ current.grant_instance_nonce!==previous.grant_instance_nonce))
259
+ throw new Error('authority epoch transition invalid');
260
+ if(action==='transaction_restore'){
261
+ const binding=normalizeRestoreBinding(current.restore_binding,{required:true});
262
+ if(previous.action!=='transaction_revoke'||current.update_id!==previous.update_id||
263
+ binding.prior_grant_sha256!==previous.grant_sha256||
264
+ binding.prior_grant_instance_nonce!==previous.grant_instance_nonce||
265
+ current.grant_instance_nonce===previous.grant_instance_nonce||
266
+ (previous.policy_envelope_sha256!==undefined&&
267
+ binding.predecessor_policy_envelope_sha256!==previous.policy_envelope_sha256)||
268
+ (current.policy_envelope_sha256!==undefined&&
269
+ binding.successor_policy_envelope_sha256!==current.policy_envelope_sha256))
270
+ throw new Error('authority epoch transition invalid');
271
+ }
272
+ if(action==='grant'&&current.grant_instance_nonce===previous.grant_instance_nonce)
273
+ throw new Error('authority epoch transition invalid');
221
274
  }
222
275
 
223
276
  async function durableEpochKeys(runtimeDir,{privateRequired=false}={}){
@@ -431,12 +484,40 @@ async function acquireEpochLock(dir,{allowStaleRecovery=true,onLockDispositionSt
431
484
  }
432
485
  throw new Error('authority epoch mutation locked');
433
486
  }
487
+ const MAX_DURABLE_EPOCH_RECORD_BYTES=1048576;
488
+
489
+ async function readBoundedEpochHandle(handle,label){
490
+ const stat=await handle.stat();
491
+ if(!stat.isFile()||!Number.isSafeInteger(stat.size)||stat.size>MAX_DURABLE_EPOCH_RECORD_BYTES)
492
+ throw new Error(`authority epoch ${label} oversized`);
493
+ // Read at most the observed size plus one byte. A concurrent extension
494
+ // cannot turn this into an unbounded read, and is rejected rather than
495
+ // silently truncating the record that will be hashed or parsed.
496
+ const bytes=Buffer.alloc(stat.size+1);
497
+ let used=0;
498
+ while(used<bytes.length){
499
+ const {bytesRead}=await handle.read(bytes,used,bytes.length-used,used);
500
+ if(bytesRead===0)break;
501
+ used+=bytesRead;
502
+ }
503
+ if(used!==stat.size)throw new Error(`authority epoch ${label} changed during read`);
504
+ return bytes.subarray(0,used);
505
+ }
506
+
507
+ async function readBoundedEpochRecord(file,label){
508
+ const handle=await open(file,'r');
509
+ try{
510
+ return await readBoundedEpochHandle(handle,label);
511
+ }finally{await handle.close();}
512
+ }
513
+
434
514
  async function readDurableHead(runtimeDir,plane,{recoveryOrphanFile=null}={}){
435
515
  const dir=durableEpochDir(runtimeDir,plane),headPath=path.join(dir,'head.json');
436
516
  let head;
437
- try{head=JSON.parse(await readFile(headPath,'utf8'));}
517
+ try{head=JSON.parse(decodeUtf8Strict(await readBoundedEpochRecord(headPath,'head')));}
438
518
  catch(error){
439
519
  if(error?.code==='ENOENT')return null;
520
+ if(String(error?.message??'').includes('authority epoch head oversized'))throw error;
440
521
  throw new Error('authority epoch head invalid');
441
522
  }
442
523
  if(!plain(head)||head.schema!==DURABLE_HEAD_SCHEMA||head.plane!==plane||
@@ -455,7 +536,8 @@ async function readDurableHead(runtimeDir,plane,{recoveryOrphanFile=null}={}){
455
536
  // the head. The head remains freshly read on every authority decision.
456
537
  const [eventNames,eventRead,keys]=await Promise.all([
457
538
  readdir(path.join(dir,'events')),
458
- readFile(path.join(dir,'events',head.event_file)).then(bytes=>({bytes}),()=>({bytes:null})),
539
+ readBoundedEpochRecord(path.join(dir,'events',head.event_file),'event')
540
+ .then(bytes=>({bytes}),error=>({error})),
459
541
  durableEpochKeys(runtimeDir)
460
542
  ]);
461
543
  if(eventNames.length>10000)throw new Error('authority epoch event budget exceeded');
@@ -466,8 +548,11 @@ async function readDurableHead(runtimeDir,plane,{recoveryOrphanFile=null}={}){
466
548
  if(!Number.isSafeInteger(seq)||(seq>head.seq&&name!==recoveryOrphanFile)||(seq===head.seq&&name!==head.event_file))
467
549
  throw new Error('authority epoch head replay or incomplete write');
468
550
  }
551
+ if(eventRead.error){
552
+ if(eventRead.error.code==='ENOENT')throw new Error('authority epoch event missing');
553
+ throw eventRead.error;
554
+ }
469
555
  const eventBytes=eventRead.bytes;
470
- if(!eventBytes)throw new Error('authority epoch event missing');
471
556
  if(hashBytes(eventBytes)!==head.event_sha256)throw new Error('authority epoch event hash mismatch');
472
557
  let event;
473
558
  try{event=JSON.parse(decodeUtf8Strict(eventBytes));}catch{throw new Error('authority epoch event invalid');}
@@ -484,22 +569,92 @@ async function readDurableHead(runtimeDir,plane,{recoveryOrphanFile=null}={}){
484
569
  (!SHAPAT.test(String(head.policy_sha256))||!SHAPAT.test(String(head.policy_envelope_sha256))||
485
570
  head.policy_sha256!==event.policy_sha256||head.policy_envelope_sha256!==event.policy_envelope_sha256))
486
571
  throw new Error('authority epoch policy binding invalid');
487
- if(head.seq===1&&head.previous_event_sha256!==null)throw new Error('authority epoch genesis chain invalid');
572
+ if(head.seq===1){
573
+ if(head.previous_event_sha256!==null)throw new Error('authority epoch genesis chain invalid');
574
+ assertEpochTransition(head,null);
575
+ }
488
576
  if(head.seq>1){
489
577
  if(!SHAPAT.test(String(head.previous_event_sha256)))throw new Error('authority epoch predecessor missing');
490
578
  const priorName=durableEventName(head.seq-1,head.previous_event_sha256);
491
579
  let priorBytes;
492
- try{priorBytes=await readFile(path.join(dir,'events',priorName));}
493
- catch{throw new Error('authority epoch predecessor event missing');}
580
+ try{priorBytes=await readBoundedEpochRecord(path.join(dir,'events',priorName),'predecessor event');}
581
+ catch(error){
582
+ if(error?.code==='ENOENT')throw new Error('authority epoch predecessor event missing');
583
+ throw error;
584
+ }
494
585
  if(hashBytes(priorBytes)!==head.previous_event_sha256)throw new Error('authority epoch predecessor hash mismatch');
495
586
  let prior;
496
587
  try{prior=JSON.parse(decodeUtf8Strict(priorBytes));}catch{throw new Error('authority epoch predecessor invalid');}
497
588
  if(!verifyDurableEvent(prior,publicKey,keyId)||prior.seq!==head.seq-1||prior.plane!==plane)
498
589
  throw new Error('authority epoch predecessor signature invalid');
590
+ assertEpochPredecessorIdentity(prior);
591
+ if(prior.seq===1)assertEpochTransition(prior,null);
592
+ assertEpochTransition(head,prior);
499
593
  }
500
594
  return Object.freeze({...head});
501
595
  }
502
596
 
597
+ // Deliberately separate from the per-request authority decision. This is an
598
+ // independent, bounded audit of the complete signed chain, including events
599
+ // older than the immediate predecessor checked by readDurableHead().
600
+ async function auditDurableHistory(runtimeDir,plane){
601
+ const dir=durableEpochDir(runtimeDir,plane),events=path.join(dir,'events');
602
+ const head=await readDurableHead(runtimeDir,plane);
603
+ if(!head){
604
+ let names;
605
+ try{names=await readdir(events);}catch(error){if(error?.code==='ENOENT')return Object.freeze({status:'EMPTY',plane,verified_events:0});throw error;}
606
+ if(names.length)throw new Error('authority epoch history events exist without head');
607
+ return Object.freeze({status:'EMPTY',plane,verified_events:0});
608
+ }
609
+ if(head.seq>10000)throw new Error('authority epoch history event budget exceeded');
610
+ const headPath=path.join(dir,'head.json'),headBytes=await readBoundedEpochRecord(headPath,'head');
611
+ let pinnedHead;
612
+ try{pinnedHead=JSON.parse(decodeUtf8Strict(headBytes));}catch{throw new Error('authority epoch history head changed');}
613
+ if(canonicalJson(pinnedHead)!==canonicalJson(head))throw new Error('authority epoch history head changed');
614
+ const names=await readdir(events);
615
+ if(names.length!==head.seq)throw new Error('authority epoch history event count mismatch');
616
+ const namesSet=new Set(names);
617
+ const {publicKey,keyId}=await durableEpochKeys(runtimeDir);
618
+ let expectedSha=head.event_sha256,totalBytes=0,newer=null;
619
+ for(let seq=head.seq;seq>=1;seq--){
620
+ const name=durableEventName(seq,expectedSha);
621
+ if(!namesSet.has(name))throw new Error('authority epoch history event missing');
622
+ const file=path.join(events,name),st=await lstat(file);
623
+ if(!st.isFile()||st.isSymbolicLink()||st.size>1048576)
624
+ throw new Error('authority epoch history event identity invalid');
625
+ totalBytes+=st.size;
626
+ if(totalBytes>67108864)throw new Error('authority epoch history byte budget exceeded');
627
+ const bytes=await readBoundedEpochRecord(file,'event');
628
+ if(hashBytes(bytes)!==expectedSha)throw new Error('authority epoch history hash mismatch');
629
+ let event;
630
+ try{event=JSON.parse(decodeUtf8Strict(bytes));}catch{throw new Error('authority epoch history event invalid');}
631
+ if(!verifyDurableEvent(event,publicKey,keyId)||event.schema!==DURABLE_EPOCH_SCHEMA||
632
+ event.plane!==plane||event.seq!==seq||!DURABLE_EPOCH_ACTIONS.has(event.action)||
633
+ !SHAPAT.test(String(event.grant_sha256))||!SHAPAT.test(String(event.grant_instance_nonce)))
634
+ throw new Error('authority epoch history event signature or identity invalid');
635
+ normalizeRestoreBinding(event.restore_binding,{required:event.action==='transaction_restore'});
636
+ if((event.action.startsWith('transaction_')?!/^[A-Za-z0-9._-]{1,96}$/.test(String(event.update_id??'')):event.update_id!==null))
637
+ throw new Error('authority epoch history update identity invalid');
638
+ if(event.policy_sha256!==undefined||event.policy_envelope_sha256!==undefined){
639
+ if(!SHAPAT.test(String(event.policy_sha256))||!SHAPAT.test(String(event.policy_envelope_sha256)))
640
+ throw new Error('authority epoch history policy binding invalid');
641
+ }
642
+ if(seq===1){
643
+ if(event.previous_event_sha256!==null)throw new Error('authority epoch history genesis chain invalid');
644
+ }else if(!SHAPAT.test(String(event.previous_event_sha256)))
645
+ throw new Error('authority epoch history predecessor missing');
646
+ if(newer)assertEpochTransition(newer,event);
647
+ else if(seq===1)assertEpochTransition(event,null);
648
+ newer=event;
649
+ expectedSha=event.previous_event_sha256;
650
+ }
651
+ if(newer&&head.seq>1)assertEpochTransition(newer,null);
652
+ if(!(await readBoundedEpochRecord(headPath,'head')).equals(headBytes))throw new Error('authority epoch history head changed during audit');
653
+ if((await durableEpochKeys(runtimeDir)).keyId!==keyId)throw new Error('authority epoch history trust changed during audit');
654
+ return Object.freeze({status:'PASS',plane,seq:head.seq,event_sha256:head.event_sha256,
655
+ verified_events:head.seq,verified_bytes:totalBytes});
656
+ }
657
+
503
658
  // R79: An orphan event is not a committed head. Ordinary reads, verification
504
659
  // and appends MUST keep rejecting it. Only explicit revocation-only recovery
505
660
  // can use the bounded exception to inspect an older, still-signed head.
@@ -516,7 +671,7 @@ async function inspectOrphanRevocation(runtimeDir,plane,{expectedHeadSha256,expe
516
671
  const headPath=path.join(dir,'head.json');
517
672
  const headStat=await lstat(headPath);
518
673
  if(!headStat.isFile()||headStat.isSymbolicLink())throw new Error('authority epoch recovery head identity invalid');
519
- const rawHead=await readFile(headPath);
674
+ const rawHead=await readBoundedEpochRecord(headPath,'head');
520
675
  let untrusted;
521
676
  try{untrusted=JSON.parse(decodeUtf8Strict(rawHead));}catch{throw new Error('authority epoch recovery head invalid');}
522
677
  if(!Number.isSafeInteger(untrusted?.seq)||untrusted.seq<1)
@@ -539,7 +694,7 @@ async function inspectOrphanRevocation(runtimeDir,plane,{expectedHeadSha256,expe
539
694
  const orphanPath=path.join(events,orphanFile);
540
695
  const orphanStat=await lstat(orphanPath);
541
696
  if(!orphanStat.isFile()||orphanStat.isSymbolicLink())throw new Error('authority epoch recovery orphan file identity invalid');
542
- const orphanBytes=await readFile(orphanPath);
697
+ const orphanBytes=await readBoundedEpochRecord(orphanPath,'event');
543
698
  if(hashBytes(orphanBytes)!==expectedOrphanSha256)
544
699
  throw new Error('authority epoch recovery orphan bytes mismatch');
545
700
  let event;
@@ -559,7 +714,7 @@ async function inspectOrphanRevocation(runtimeDir,plane,{expectedHeadSha256,expe
559
714
  throw new Error('authority epoch recovery orphan revocation binding invalid');
560
715
  // Head cannot be promoted unless it is still exactly the same committed
561
716
  // predecessor. No authority-creating action is ever eligible.
562
- const replay=await readFile(path.join(dir,'head.json'));
717
+ const replay=await readBoundedEpochRecord(headPath,'head');
563
718
  if(!replay.equals(rawHead))throw new Error('authority epoch recovery head changed');
564
719
  const newHead={schema:DURABLE_HEAD_SCHEMA,plane,seq:event.seq,action:event.action,
565
720
  update_id:event.update_id,grant_sha256:event.grant_sha256,
@@ -583,6 +738,9 @@ export function createAuthorityEpochStore({runtimeDir,policyBytes=null,onDurabil
583
738
  async readHead({plane}){
584
739
  return readDurableHead(root,plane);
585
740
  },
741
+ async auditHistory({plane}){
742
+ return auditDurableHistory(root,plane);
743
+ },
586
744
  async reconcileOrphanedRevocation({plane,expectedHeadSha256,expectedOrphanSha256,operatorConfirmed=false}){
587
745
  // NOT called from grant verification, append, startup or automatic
588
746
  // recovery. The operator must supply two observed event identities.
@@ -610,12 +768,12 @@ export function createAuthorityEpochStore({runtimeDir,policyBytes=null,onDurabil
610
768
  const st=await fh.stat();
611
769
  if(!st.isFile()||st.size!==orphanBytes.length)
612
770
  throw new Error('authority epoch recovery pinned orphan identity invalid');
613
- const pinnedBytes=await fh.readFile();
771
+ const pinnedBytes=await readBoundedEpochHandle(fh,'event');
614
772
  if(!pinnedBytes.equals(orphanBytes)||hashBytes(pinnedBytes)!==expectedOrphanSha256)
615
773
  throw new Error('authority epoch recovery orphan changed before sync');
616
774
  await fh.sync();
617
775
  }finally{await fh.close();}
618
- if(!(await readFile(path.join(dir,'head.json'))).equals(headBytes))
776
+ if(!(await readBoundedEpochRecord(path.join(dir,'head.json'),'head')).equals(headBytes))
619
777
  throw new Error('authority epoch recovery head changed before commit');
620
778
  await release.verifyOwned();
621
779
  await atomicJsonWrite(path.join(dir,'head.json'),newHead,{stage});
@@ -650,6 +808,11 @@ export function createAuthorityEpochStore({runtimeDir,policyBytes=null,onDurabil
650
808
  if(parsedGrant?.policy_sha256!==policySha256)throw new Error('authority epoch grant policy digest mismatch');
651
809
  policyBinding={policy_sha256:policySha256,policy_envelope_sha256:deriveSecurityEnvelope(plane,parsedPolicy).sha256};
652
810
  }
811
+ // A mismatched successor envelope is known before acquiring a native
812
+ // lock or writing any signed event. Refuse it without side effects.
813
+ if(action==='transaction_restore'&&policyBinding?.policy_envelope_sha256!==undefined&&
814
+ normalizedRestoreBinding.successor_policy_envelope_sha256!==policyBinding.policy_envelope_sha256)
815
+ throw new Error('authority epoch transaction restore successor policy envelope mismatch');
653
816
  const dir=durableEpochDir(root,plane),events=path.join(dir,'events');
654
817
  const release=await acquireEpochLock(dir,{onLockDispositionStage});
655
818
  try{
@@ -674,6 +837,12 @@ export function createAuthorityEpochStore({runtimeDir,policyBytes=null,onDurabil
674
837
  throw new Error('authority epoch transaction restore predecessor grant binding mismatch');
675
838
  if(previous.grant_instance_nonce===grantInstanceNonce)
676
839
  throw new Error('authority epoch transaction restore requires fresh nonce; revoked nonce cannot be resurrected');
840
+ if(previous.policy_envelope_sha256!==undefined&&
841
+ normalizedRestoreBinding.predecessor_policy_envelope_sha256!==previous.policy_envelope_sha256)
842
+ throw new Error('authority epoch transaction restore predecessor policy envelope mismatch');
843
+ if(policyBinding?.policy_envelope_sha256!==undefined&&
844
+ normalizedRestoreBinding.successor_policy_envelope_sha256!==policyBinding.policy_envelope_sha256)
845
+ throw new Error('authority epoch transaction restore successor policy envelope mismatch');
677
846
  }
678
847
  if(action==='grant'&&previous?.grant_instance_nonce===grantInstanceNonce)
679
848
  throw new Error('authority epoch grant requires fresh nonce; prior nonce cannot be resurrected');
@@ -1,11 +1,15 @@
1
1
  import { createHash, randomBytes } from 'node:crypto';
2
2
  import { execFile } from 'node:child_process';
3
3
  import { promisify } from 'node:util';
4
- import { access, mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
4
+ import { access, readFile } from 'node:fs/promises';
5
5
  import path from 'node:path';
6
6
  import { startAutonomousSupervisor } from './autonomous-supervisor.mjs';
7
+ import { windowsPowerShellEnvironment } from './windows-powershell-environment.mjs';
8
+ import { readPinnedEpochLockSnapshot, createPinnedEpochLock, deletePinnedRemoteMarker } from './foreground-marker-cas-win32.mjs';
9
+ import { runNativeInspect } from './native-inspector-r67.mjs';
7
10
 
8
11
  const execFileAsync=promisify(execFile);
12
+ const TRUSTED_WINDOWS_ROOT='C:\\Windows';
9
13
  export const AUTONOMOUS_SUPERVISOR_OWNER_SCHEMA='deadbyte.autonomous-supervisor-owner.v1';
10
14
  const HEX64=/^[0-9a-f]{64}$/;
11
15
  const RECOVERY_OPERATIONS=Object.freeze(['status','resume','park','disarmautonomous','disarmcoding','disarmprocess','disarmhost']);
@@ -33,12 +37,12 @@ function validateMarker(marker){
33
37
  }
34
38
  async function powershellPath(){
35
39
  if(process.platform!=='win32') return null;
36
- const systemRoot=process.env.SystemRoot || process.env.WINDIR;
37
- if(systemRoot){
38
- const candidate=path.join(systemRoot,'System32','WindowsPowerShell','v1.0','powershell.exe');
39
- try { await access(candidate); return candidate; } catch {}
40
- }
41
- return 'powershell.exe';
40
+ const candidate=path.join(TRUSTED_WINDOWS_ROOT,'System32','WindowsPowerShell','v1.0','powershell.exe');
41
+ try { await access(candidate); return candidate; } catch { return null; }
42
+ }
43
+
44
+ function trustedPowerShellEnvironment(){
45
+ return windowsPowerShellEnvironment({...process.env,SystemRoot:TRUSTED_WINDOWS_ROOT,WINDIR:TRUSTED_WINDOWS_ROOT});
42
46
  }
43
47
 
44
48
  export async function observeCurrentProcessIdentity(pid=process.pid){
@@ -49,7 +53,7 @@ export async function observeCurrentProcessIdentity(pid=process.pid){
49
53
  let stdout;
50
54
  try {
51
55
  ({stdout}=await execFileAsync(shell,['-NoProfile','-NonInteractive','-ExecutionPolicy','Bypass','-Command',script],{
52
- windowsHide:true,timeout:10_000,maxBuffer:1024*1024,encoding:'utf8'
56
+ windowsHide:true,timeout:10_000,maxBuffer:1024*1024,encoding:'utf8',env:trustedPowerShellEnvironment()
53
57
  }));
54
58
  } catch { return null; }
55
59
  let parsed;
@@ -64,14 +68,36 @@ export async function observeCurrentProcessIdentity(pid=process.pid){
64
68
  });
65
69
  }
66
70
 
71
+ async function confirmProcessAbsent(pid){
72
+ const shell=await powershellPath();
73
+ if(!shell) return false;
74
+ const script=`$ErrorActionPreference='Stop'; $p=Get-CimInstance Win32_Process -Filter 'ProcessId=${pid}' -ErrorAction Stop; if($null -eq $p){[Console]::Out.WriteLine('ABSENT')}else{[Console]::Out.WriteLine('PRESENT')}`;
75
+ try {
76
+ const {stdout}=await execFileAsync(shell,['-NoProfile','-NonInteractive','-ExecutionPolicy','Bypass','-Command',script],{
77
+ windowsHide:true,timeout:10_000,maxBuffer:1024*1024,encoding:'utf8',env:trustedPowerShellEnvironment()
78
+ });
79
+ if(String(stdout).trim()!=='ABSENT') return false;
80
+ const native=await runNativeInspect(['identity',String(pid)]);
81
+ return native.status==='failed'&&native.error_code==='INVALID_PARAMETER'&&
82
+ native.win32_error===87&&native.error==='OpenProcess identity win32=87';
83
+ } catch { return false; }
84
+ }
85
+
67
86
  async function readMarker(markerPath){
68
- let raw;
69
- try { raw=await readFile(markerPath); }
70
- catch(error){ if(error?.code==='ENOENT') return {exists:false}; throw error; }
87
+ let raw, fileId=null;
88
+ if(process.platform==='win32'){
89
+ const snapshot=readPinnedEpochLockSnapshot(markerPath);
90
+ if(!snapshot) return {exists:false};
91
+ raw=snapshot.bytes;
92
+ fileId=snapshot.fileId;
93
+ } else {
94
+ try { raw=await readFile(markerPath); }
95
+ catch(error){ if(error?.code==='ENOENT') return {exists:false}; throw error; }
96
+ }
71
97
  let marker;
72
98
  try { marker=JSON.parse(raw.toString('utf8')); }
73
- catch { return {exists:true,raw,raw_sha256:sha256Bytes(raw),valid:false,marker:null}; }
74
- return {exists:true,raw,raw_sha256:sha256Bytes(raw),valid:validateMarker(marker),marker};
99
+ catch { return {exists:true,raw,raw_sha256:sha256Bytes(raw),file_id:fileId,valid:false,marker:null}; }
100
+ return {exists:true,raw,raw_sha256:sha256Bytes(raw),file_id:fileId,valid:validateMarker(marker),marker};
75
101
  }
76
102
  function markerFor(identity){
77
103
  return {
@@ -91,28 +117,28 @@ export async function observeSupervisor(options={}){
91
117
  const markerPath=assertOptions(options);
92
118
  const stored=await readMarker(markerPath);
93
119
  if(!stored.exists) return {status:'absent',live:false,reason:'no_owner',marker:null,marker_sha256:null};
94
- if(!stored.valid) return {status:'unverified',live:false,reason:'marker_invalid',marker:stored.marker,marker_sha256:stored.raw_sha256};
120
+ const binding={marker_sha256:stored.raw_sha256,marker_file_id:stored.file_id};
121
+ if(!stored.valid) return {status:'unverified',live:false,reason:'marker_invalid',marker:stored.marker,...binding};
95
122
  const observed=await observeCurrentProcessIdentity(stored.marker.pid);
96
- if(!observed) return {status:'stale',live:false,reason:'process_unobservable',marker:stored.marker,marker_sha256:stored.raw_sha256};
123
+ if(!observed){
124
+ if(await confirmProcessAbsent(stored.marker.pid))
125
+ return {status:'stale',live:false,reason:'process_absent',marker:stored.marker,...binding};
126
+ return {status:'unverified',live:false,reason:'process_unobservable',marker:stored.marker,...binding};
127
+ }
97
128
  if(observed.creation_time_utc!==new Date(stored.marker.creation_time_utc).toISOString()) {
98
- return {status:'stale',live:false,reason:'pid_reused',marker:stored.marker,observation:observed,marker_sha256:stored.raw_sha256};
129
+ return {status:'stale',live:false,reason:'pid_reused',marker:stored.marker,observation:observed,...binding};
99
130
  }
100
131
  if(!samePath(observed.image_path,stored.marker.image_path)||observed.image_sha256!==stored.marker.image_sha256) {
101
- return {status:'stale',live:false,reason:'identity_mismatch',marker:stored.marker,observation:observed,marker_sha256:stored.raw_sha256};
132
+ return {status:'unverified',live:false,reason:'identity_mismatch',marker:stored.marker,observation:observed,...binding};
102
133
  }
103
- return {status:'owned',live:true,reason:'live_verified_owner',marker:stored.marker,observation:observed,marker_sha256:stored.raw_sha256};
134
+ return {status:'owned',live:true,reason:'live_verified_owner',marker:stored.marker,observation:observed,...binding};
104
135
  }
105
136
 
106
137
  export async function claimSupervisor(options={}){
107
138
  const markerPath=assertOptions(options);
108
139
  const marker=await currentMarker();
109
- await mkdir(path.dirname(markerPath),{recursive:true});
110
- try {
111
- await writeFile(markerPath,`${JSON.stringify(marker)}\n`,{flag:'wx',mode:0o600});
112
- } catch(error){
113
- if(error?.code==='EEXIST') throw new Error('supervisor owner marker already exists; takeover required');
114
- throw error;
115
- }
140
+ const created=createPinnedEpochLock(markerPath,Buffer.from(`${JSON.stringify(marker)}\n`));
141
+ if(!created) throw new Error('supervisor owner marker already exists; takeover required');
116
142
  return {status:'claimed',instance_nonce:marker.instance_nonce,marker};
117
143
  }
118
144
 
@@ -121,17 +147,13 @@ export async function takeoverSupervisor(options={}){
121
147
  const prior=await observeSupervisor({markerPath});
122
148
  if(prior.live&&prior.reason==='live_verified_owner') throw new Error('supervisor takeover refused: live verified owner');
123
149
  if(prior.reason==='no_owner') return claimSupervisor({markerPath});
124
- const marker=await currentMarker();
125
- const before=await readMarker(markerPath);
126
- if(!before.exists||before.raw_sha256!==prior.marker_sha256) throw new Error('supervisor ownership changed during takeover');
127
- const temp=path.join(path.dirname(markerPath),`.${path.basename(markerPath)}.${process.pid}.${randomBytes(8).toString('hex')}.tmp`);
128
- try {
129
- await writeFile(temp,`${JSON.stringify(marker)}\n`,{flag:'wx',mode:0o600});
130
- const check=await readMarker(markerPath);
131
- if(!check.exists||check.raw_sha256!==prior.marker_sha256) throw new Error('supervisor ownership changed during takeover');
132
- await rename(temp,markerPath);
133
- } finally { await rm(temp,{force:true}).catch(()=>{}); }
134
- return {status:'claimed',instance_nonce:marker.instance_nonce,marker,taken_over_reason:prior.reason};
150
+ if(!['process_absent','pid_reused'].includes(prior.reason))
151
+ throw new Error(`supervisor takeover refused: ownership unverified (${prior.reason})`);
152
+ if(!prior.marker_file_id) throw new Error('supervisor ownership file identity unavailable for takeover');
153
+ const removed=deletePinnedRemoteMarker(markerPath,prior.marker_sha256,{expectedFileId:prior.marker_file_id});
154
+ if(removed!=='REMOVED') throw new Error(`supervisor ownership changed during takeover: ${removed}`);
155
+ const claimed=await claimSupervisor({markerPath});
156
+ return {...claimed,taken_over_reason:prior.reason};
135
157
  }
136
158
 
137
159
  export async function releaseSupervisor(options={}){
@@ -147,9 +169,9 @@ export async function releaseSupervisor(options={}){
147
169
  !samePath(current.image_path,observed.observation.image_path)||current.image_sha256!==observed.observation.image_sha256) {
148
170
  throw new Error('supervisor ownership current process identity mismatch');
149
171
  }
150
- const check=await readMarker(markerPath);
151
- if(!check.exists||check.raw_sha256!==observed.marker_sha256) throw new Error('supervisor ownership changed before release');
152
- await rm(markerPath,{force:false});
172
+ if(!observed.marker_file_id) throw new Error('supervisor ownership file identity unavailable for release');
173
+ const removed=deletePinnedRemoteMarker(markerPath,observed.marker_sha256,{expectedFileId:observed.marker_file_id});
174
+ if(removed!=='REMOVED') throw new Error(`supervisor ownership changed before release: ${removed}`);
153
175
  return {status:'released'};
154
176
  }
155
177
 
@@ -180,7 +202,7 @@ export async function runPinnedControllerOperation(options={}){
180
202
  if(!shell) throw new Error('supervisor controller requires Windows PowerShell');
181
203
  try {
182
204
  const {stdout,stderr}=await execFileAsync(shell,['-NoProfile','-NonInteractive','-ExecutionPolicy','Bypass','-File',controllerPath,operation],{
183
- windowsHide:true,timeout:120_000,maxBuffer:4*1024*1024,encoding:'utf8'
205
+ windowsHide:true,timeout:120_000,maxBuffer:4*1024*1024,encoding:'utf8',env:trustedPowerShellEnvironment()
184
206
  });
185
207
  return {exit_code:0,stdout:String(stdout??''),stderr:String(stderr??'')};
186
208
  } catch(error) {
@@ -228,20 +250,28 @@ export async function startLocalSupervisorHost({runtime,policy=runtime?.policy,m
228
250
  try {
229
251
  supervisor=startAutonomousSupervisor({runtime,policy,idleDelayMs,maxCycles,logger,continuous});
230
252
  } catch(error) {
231
- await releaseSupervisor({markerPath,instanceNonce:claim.instance_nonce}).catch(()=>{});
253
+ try { await releaseSupervisor({markerPath,instanceNonce:claim.instance_nonce}); }
254
+ catch(cleanupError) {
255
+ throw new AggregateError([error,cleanupError],
256
+ 'supervisor startup and ownership cleanup both failed',{cause:error});
257
+ }
232
258
  throw error;
233
259
  }
234
260
  let stopped=false;
261
+ let stopPromise=null;
235
262
  return Object.freeze({
236
263
  marker_path:path.resolve(markerPath),instance_nonce:claim.instance_nonce,supervisor,
237
- get running(){ return !stopped&&supervisor.running; },
238
- async scanNow(){ if(stopped) throw new Error('local supervisor host stopped'); return supervisor.scanNow(); },
264
+ get running(){ return !stopPromise&&supervisor.running; },
265
+ async scanNow(){ if(stopPromise) throw new Error('local supervisor host stopping or stopped'); return supervisor.scanNow(); },
239
266
  async stop(){
240
267
  if(stopped) return {status:'stopped'};
241
- stopped=true;
242
- await supervisor.stop();
243
- await releaseSupervisor({markerPath,instanceNonce:claim.instance_nonce});
244
- return {status:'stopped'};
268
+ if(!stopPromise) stopPromise=(async()=>{
269
+ await supervisor.stop();
270
+ await releaseSupervisor({markerPath,instanceNonce:claim.instance_nonce});
271
+ stopped=true;
272
+ return {status:'stopped'};
273
+ })();
274
+ return stopPromise;
245
275
  }
246
276
  });
247
277
  }
@@ -25,7 +25,7 @@ import {foregroundIdentityMatches,queryForegroundIdentity,validForegroundIdentit
25
25
  import {readRemoteMarkerSnapshot,deletePinnedRemoteMarker} from './foreground-marker-cas-win32.mjs';
26
26
  import {acquireForegroundSessionLock} from './foreground-session-lock.mjs';
27
27
  import { AUTHORITY_PLANES, authorityVector, authorityRestorePlan, parseAuthorityObservation, assertAuthorityVector, assertRuntimeReadyObservation, authorityBanner } from './remote-authority.mjs';
28
- import { decodeRemoteLogBytes, boundedRemoteDiagnostic, safeRemoteLogPrefixLength } from './remote-log-decode-r60.mjs';
28
+ import { decodeRemoteLogBytes, boundedRemoteDiagnostic, readRemoteLogAppend } from './remote-log-decode-r60.mjs';
29
29
 
30
30
  const REMOTE_COMMANDS=new Set([
31
31
  'menu','start','resume','park','stop','restart','status','url','test','fulltest','toggle','clean','machinetest',
@@ -43,6 +43,7 @@ const CURRENT_PACKAGE_ROOT=path.resolve(fileURLToPath(new URL('..',import.meta.u
43
43
  const CURRENT_DEPLOY_SCRIPT=path.join(CURRENT_PACKAGE_ROOT,'scripts','deploy-r48.mjs');
44
44
  const REMOTE_UPDATE_TIMEOUT_MS=900000;
45
45
  const LIVE_LOG_DISCOVERY_INTERVAL_MS=1000;
46
+ const TRUSTED_WINDOWS_ROOT='C:\\Windows';
46
47
 
47
48
  function stateRoot(){return resolveStateRoot();}
48
49
  async function requireRegular(file,label){const info=await lstat(file);if(!info.isFile()||info.isSymbolicLink()) throw new Error(`${label} must be a regular non-symlink file`);return file;}
@@ -79,8 +80,7 @@ async function remoteContext(){
79
80
  if(process.platform!=='win32') throw new Error('remote controller is currently Windows-only');
80
81
  const root=stateRoot();
81
82
  const launcher=await requireRegular(path.join(root,'bin','DEADBYTE-MCP.ps1'),'stable DEADBYTE launcher');
82
- const systemRoot=process.env.SystemRoot||process.env.WINDIR;if(!systemRoot) throw new Error('SystemRoot/WINDIR missing');
83
- const powershell=await requireRegular(path.join(systemRoot,'System32','WindowsPowerShell','v1.0','powershell.exe'),'Windows PowerShell');
83
+ const powershell=await requireRegular(path.join(TRUSTED_WINDOWS_ROOT,'System32','WindowsPowerShell','v1.0','powershell.exe'),'Windows PowerShell');
84
84
  return {root,launcher,powershell};
85
85
  }
86
86
  function boundedSingleLine(value,maxChars=2048){
@@ -203,7 +203,8 @@ function invokeController(ctx,command,{capture=false,allowFailure=false,echoCapt
203
203
  stdio=['ignore',stdoutFd,stderrFd];
204
204
  }else stdio=capture?['ignore','pipe','pipe']:'inherit';
205
205
  const options={stdio,encoding:capture?'utf8':undefined,windowsHide:false,shell:false,
206
- env:windowsPowerShellEnvironment(probeMode?{...process.env,DEADBYTE_REMOTE_PROBE_MODE:probeMode}:process.env)};
206
+ env:windowsPowerShellEnvironment({...process.env,SystemRoot:TRUSTED_WINDOWS_ROOT,WINDIR:TRUSTED_WINDOWS_ROOT,
207
+ ...(probeMode?{DEADBYTE_REMOTE_PROBE_MODE:probeMode}:{})})};
207
208
  if(Number.isInteger(timeoutMs)&&timeoutMs>0) options.timeout=timeoutMs;
208
209
  result=spawnSync(ctx.powershell,['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',ctx.launcher,command,...(json?['-Json']:[])],options);
209
210
  }finally{
@@ -309,32 +310,15 @@ async function replayRemoteResults(root,cursor,{displayMode='unicode'}={}){
309
310
  if(!page.has_more) return next;
310
311
  }
311
312
  }
312
- async function readAppend(file,offset){
313
- let info;try{info=await stat(file);}catch(error){if(error?.code==='ENOENT') return {offset:0,text:''};throw error;}
314
- let start=offset;
315
- const reset=info.size<start;
316
- if(reset) start=0;
317
- if(info.size===start) return {offset:start,text:''};
318
- const handle=await open(file,'r');
319
- let encoding=null,buffer,bytesRead=0;
320
- try{
321
- const header=Buffer.alloc(2);await handle.read(header,0,2,0);
322
- if(header[0]===0xff&&header[1]===0xfe&&/\.log$/i.test(file)){
323
- encoding='utf16le';if(start%2!==0)start=Math.max(0,start-1);
324
- }
325
- const length=Math.min(1024*1024,info.size-start);
326
- if(length===0)return {offset:start,text:''};
327
- buffer=Buffer.alloc(length);
328
- ({bytesRead}=await handle.read(buffer,0,length,start));
329
- }finally{await handle.close();}
330
- const complete=safeRemoteLogPrefixLength(buffer.subarray(0,bytesRead),encoding??'utf8');
331
- return {offset:start+complete,text:decodeRemoteLogBytes(file,buffer.subarray(0,complete),{encoding}),reset};
313
+ async function readAppend(file,cursor){
314
+ return readRemoteLogAppend(file,cursor);
332
315
  }
333
316
  async function snapshotRemoteLogOffsets(root){
334
317
  const offsets=new Map();
335
318
  const files=await listLogFiles(root);
336
319
  for(const file of files){
337
- try{offsets.set(file,(await stat(file)).size);}catch{}
320
+ const snapshot=await readRemoteLogAppend(file,null,{startAtEnd:true});
321
+ if(snapshot.cursor)offsets.set(file,snapshot.cursor);
338
322
  }
339
323
  return offsets;
340
324
  }
@@ -346,6 +330,7 @@ function timelineFilterEvent(timeline){
346
330
  return {
347
331
  event_type:'tool.result',request_id:timeline.request_id,tool:timeline.tool,
348
332
  outcome:{
333
+ status:timeline.side_effect_state==='unknown'?'interrupted':timeline.status,
349
334
  is_error:timeline.status==='error',
350
335
  ...(Number.isFinite(Number(timeline.duration_ms))?{duration_ms:Number(timeline.duration_ms)}:{}),
351
336
  ...(timeline.side_effect_state?{side_effect_state:timeline.side_effect_state}:{}),
@@ -368,10 +353,13 @@ async function tailRemoteLogs(root,signal,{journal=null,offsets=null,activityTra
368
353
  nextDiscoveryAt=now+LIVE_LOG_DISCOVERY_INTERVAL_MS;
369
354
  }
370
355
  for(const file of liveFiles){
371
- const prior=activeOffsets.has(file)?activeOffsets.get(file):0;
356
+ const prior=activeOffsets.has(file)?activeOffsets.get(file):null;
372
357
  const next=await readAppend(file,prior);
373
- activeOffsets.set(file,next.offset);
374
- if(next.reset)partialLines.delete(file);
358
+ activeOffsets.set(file,next.cursor);
359
+ if(next.reset){
360
+ partialLines.delete(file);
361
+ process.stderr.write('[LIVELOG] STREAM_RESET '+path.basename(file)+' — '+next.resetReason+'; check audit continuity before trusting coverage\n');
362
+ }
375
363
  if(!next.text)continue;
376
364
  const lines=((partialLines.get(file)??'')+next.text).split(/\r?\n/);
377
365
  partialLines.set(file,lines.pop()??'');
@@ -379,6 +367,8 @@ async function tailRemoteLogs(root,signal,{journal=null,offsets=null,activityTra
379
367
  for(const line of lines){
380
368
  if(Buffer.byteLength(line,'utf8')>1024*1024) throw new Error(`remote log line exceeds bounded decode limit: ${path.basename(file)}`);
381
369
  const auditEvent=parseAuditLogLine(file,line);
370
+ if(/^segment-\d{8}\.jsonl$/.test(path.basename(file))&&!auditEvent)
371
+ throw new Error('LIVELOG_AUDIT_RECORD_INVALID '+path.basename(file)+'; refusing to silently skip audit bytes');
382
372
  const timeline=auditTimelineRecord(file,line,auditEvent);
383
373
  let recorded=null;
384
374
  if(timeline&&journal) recorded=timeline.event_type==='tool.call'