deadbyte-mcp 0.15.67 → 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 (87) hide show
  1. package/MANIFEST.SHA256 +85 -40
  2. package/PROVENANCE.json +46 -24
  3. package/README.txt +46 -33
  4. package/RELEASE.json +44 -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 +3 -2
  13. package/scripts/install-r186-owner-trust.ps1 +59 -0
  14. package/scripts/r173-protected-preflight.mjs +42 -0
  15. package/scripts/r175-focused-runner.mjs +38 -0
  16. package/scripts/r176-log-preview.mjs +18 -0
  17. package/scripts/r177-owner-trust-inventory.ps1 +36 -0
  18. package/scripts/r178-full-regression.mjs +21 -0
  19. package/scripts/r178-npm-ci.mjs +10 -0
  20. package/scripts/r178-npm-preflight.mjs +16 -0
  21. package/scripts/r178-release-gates-readonly.mjs +9 -0
  22. package/scripts/r178-shard-regression.mjs +25 -0
  23. package/scripts/r179-predecessor-registry.mjs +24 -0
  24. package/scripts/r179-protected-inventory.ps1 +21 -0
  25. package/scripts/r186-focused-runner.mjs +22 -0
  26. package/scripts/r186-owner-anchor-inspect.mjs +6 -0
  27. package/scripts/r186-owner-consent-check.mjs +86 -0
  28. package/scripts/r186-owner-grant-cli.mjs +45 -0
  29. package/scripts/r186-owner-key-local.mjs +22 -0
  30. package/scripts/r186-owner-review-export.mjs +34 -0
  31. package/src/activation-authority-proof-r75.mjs +28 -4
  32. package/src/activation-preflight-r65.mjs +2 -1
  33. package/src/deadbyte-cli.mjs +69 -56
  34. package/src/r174-owner-webauthn.mjs +176 -0
  35. package/src/r175-foreground-boundary.mjs +28 -0
  36. package/src/r186-owner-anchor.mjs +12 -0
  37. package/src/r186-owner-consent-preflight.mjs +63 -0
  38. package/src/r186-owner-grant-broker.mjs +96 -0
  39. package/src/r186-owner-local-signing.mjs +25 -0
  40. package/src/r186-owner-request.mjs +40 -0
  41. package/src/r186-owner-review-export.mjs +49 -0
  42. package/src/remote-live-log.mjs +81 -5
  43. package/src/remote-session-guardian.mjs +23 -0
  44. package/src/version.mjs +1 -1
  45. package/test/helpers/r175-active-pointer.mjs +28 -0
  46. package/test/helpers/release-lock.mjs +12 -0
  47. package/test/r129-sdk-security-baseline.test.mjs +2 -1
  48. package/test/r130-release-security-contract.test.mjs +11 -10
  49. package/test/r132-release-identity.test.mjs +12 -11
  50. package/test/r153-prepublish-identity.test.mjs +1 -1
  51. package/test/r155-default-live-log.test.mjs +17 -16
  52. package/test/r160-package-decision-manifest.test.mjs +1 -1
  53. package/test/r160-release-contract.test.mjs +4 -5
  54. package/test/r163-isolated-slot-rollback-restore.test.mjs +6 -6
  55. package/test/r165-live-cli-skew.test.mjs +6 -6
  56. package/test/r167-release-contract.test.mjs +15 -13
  57. package/test/r168-protected-entrypoint.test.mjs +14 -11
  58. package/test/r168-release-contract.test.mjs +4 -3
  59. package/test/r168-remote-one-shot-coherence.test.mjs +2 -1
  60. package/test/r172-release-contract.test.mjs +8 -8
  61. package/test/r173-release-contract.test.mjs +33 -0
  62. package/test/r174-owner-webauthn.test.mjs +144 -0
  63. package/test/r175-cli-flags.test.mjs +23 -0
  64. package/test/r175-foreground-boundary.test.mjs +87 -0
  65. package/test/r175-guardian-owner-boundary.test.mjs +56 -0
  66. package/test/r176-pretty-live-log.test.mjs +54 -0
  67. package/test/r177-windows-absolute-live-path.test.mjs +37 -0
  68. package/test/r179-absolute-path-live-log.test.mjs +40 -0
  69. package/test/r179-release-contract.test.mjs +62 -0
  70. package/test/r186-controller-owner-grant.test.mjs +20 -0
  71. package/test/r186-owner-consent-preflight.test.mjs +98 -0
  72. package/test/r186-owner-enrollment.test.mjs +20 -0
  73. package/test/r186-owner-grant-broker.test.mjs +85 -0
  74. package/test/r186-owner-local-signing.test.mjs +30 -0
  75. package/test/r186-owner-request.test.mjs +28 -0
  76. package/test/r186-owner-review-export.test.mjs +52 -0
  77. package/test/r48-release-contract.test.mjs +2 -1
  78. package/test/r51-runtime-coherence.test.mjs +4 -2
  79. package/test/r58-cleanup-retention.test.mjs +5 -12
  80. package/test/r58-concurrent-recovery.test.mjs +5 -1
  81. package/test/r58-stale-owner.test.mjs +9 -3
  82. package/test/r59-tool-surface-compat.test.mjs +1 -1
  83. package/test/r75-authority-evidence.test.mjs +32 -0
  84. package/test/remote-live-log-r37.test.mjs +2 -2
  85. package/test/remote-live-log-recovery-r36.test.mjs +1 -1
  86. package/test/remote-session-cli.test.mjs +92 -126
  87. package/node_modules/which/CHANGELOG.md +0 -166
@@ -63,7 +63,8 @@ export function classifyActivationPreflight({pointer,expectedParent,candidate,ex
63
63
  return Object.freeze({state:'AUTHORITY_UNVERIFIED',safe_to_activate:false,reason:`${plane} authority proof missing`,...bound});
64
64
  if(proof&&(proof.policy_sha256!==observation?.policy_sha256?.[plane]||
65
65
  proof.state!==(authority[plane]?'VERIFIED_ARMED':'VERIFIED_DISARMED')||
66
- (!authority[plane]&&proof.grant_absent!==true)))
66
+ (!authority[plane]&&proof.grant_absent!==true&&
67
+ !(entry?.reason==='epoch-revoked'&&proof.transaction_revoked===true))))
67
68
  return Object.freeze({state:'AUTHORITY_UNVERIFIED',safe_to_activate:false,reason:`${plane} authority proof unverified`,...bound});
68
69
  }
69
70
  if(PLANES.some(plane=>!HEX64.test(String(observation?.policy_sha256?.[plane]))))
@@ -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)}`);
@@ -0,0 +1,176 @@
1
+ // R174 pure WebAuthn owner assertion verifier.
2
+ //
3
+ // This module does not enroll a credential, solicit owner consent, consume a
4
+ // challenge, write an authority epoch, or grant autonomous access. Those are
5
+ // separate protected-host boundaries and MUST NOT be supplied by an agent.
6
+ import {createHash,createPublicKey,verify,timingSafeEqual} from 'node:crypto';
7
+ import {lstat,realpath,open} from 'node:fs/promises';
8
+ import path from 'node:path';
9
+ import {canonicalJson} from './canonical-json.mjs';
10
+
11
+ const HEX64=/^[0-9a-f]{64}$/;
12
+ const B64URL=/^[A-Za-z0-9_-]+$/;
13
+ const sha256=x=>createHash('sha256').update(x).digest();
14
+ const canonicalHash=x=>sha256(Buffer.from(canonicalJson(x),'utf8')).toString('hex');
15
+ function requireRecord(x,label){
16
+ if(!x||typeof x!=='object'||Array.isArray(x))throw new Error('R174 '+label+' invalid');
17
+ return x;
18
+ }
19
+ function keys(x,names,label){
20
+ requireRecord(x,label);
21
+ if(Object.keys(x).length!==names.length||names.some(n=>!Object.hasOwn(x,n)))
22
+ throw new Error('R174 '+label+' fields invalid');
23
+ }
24
+ function base64url(value,label,minimum=1,maximum=16384){
25
+ if(typeof value!=='string'||value.length>maximum*2||!B64URL.test(value))
26
+ throw new Error('R174 '+label+' encoding invalid');
27
+ const decoded=Buffer.from(value,'base64url');
28
+ if(decoded.length<minimum||decoded.length>maximum||decoded.toString('base64url')!==value)
29
+ throw new Error('R174 '+label+' encoding invalid');
30
+ return decoded;
31
+ }
32
+ function matches(a,b){return a.length===b.length&&timingSafeEqual(a,b);}
33
+ function instant(value){
34
+ if(typeof value!=='string'||!/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d{3}Z$/.test(value))
35
+ throw new Error('R174 transaction UTC invalid');
36
+ const timestamp=Date.parse(value);
37
+ if(!Number.isFinite(timestamp)||new Date(timestamp).toISOString()!==value)
38
+ throw new Error('R174 transaction UTC invalid');
39
+ return timestamp;
40
+ }
41
+ function validateSubject(s){
42
+ keys(s,['schema','release_manifest_sha256','coding_policy_sha256',
43
+ 'autonomous_policy_sha256','root_id','project_root','effect','max_seconds'],'subject');
44
+ if(s.schema!=='deadbyte.r174-autonomous-owner-subject.v1'||
45
+ [s.release_manifest_sha256,s.coding_policy_sha256,s.autonomous_policy_sha256]
46
+ .some(x=>typeof x!=='string'||!HEX64.test(x))||
47
+ s.root_id!=='core'||typeof s.project_root!=='string'||
48
+ !path.isAbsolute(s.project_root)||s.effect!=='autonomous_goal_run'||
49
+ !Number.isSafeInteger(s.max_seconds)||s.max_seconds<1||s.max_seconds>3600)
50
+ throw new Error('R174 subject scope invalid');
51
+ }
52
+ export async function verifyR174OwnerAssertion({credential,subject,transaction,assertion,now}={}){
53
+ validateSubject(subject);
54
+ keys(transaction,['schema','challenge_b64url','subject_sha256','issued_at_utc','expires_at_utc'],'challenge');
55
+ if(transaction.schema!=='deadbyte.r174-owner-challenge.v1'||!HEX64.test(transaction.subject_sha256))
56
+ throw new Error('R174 challenge schema invalid');
57
+ const challenge=base64url(transaction.challenge_b64url,'challenge',32,32);
58
+ if(canonicalHash(subject)!==transaction.subject_sha256)
59
+ throw new Error('R174 subject hash mismatch');
60
+ const issued=instant(transaction.issued_at_utc),expires=instant(transaction.expires_at_utc);
61
+ if(expires<=issued||expires-issued>300000)
62
+ throw new Error('R174 challenge lifetime exceeds five minutes');
63
+ if(!(now instanceof Date)||!Number.isFinite(now.getTime())||
64
+ now.getTime()<issued||now.getTime()>=expires)
65
+ throw new Error('R174 challenge expired or not yet valid');
66
+ keys(credential,['schema','credential_id_b64url','rp_id','origin',
67
+ 'public_key_spki_pem','algorithm','sign_count'],'credential');
68
+ if(credential.schema!=='deadbyte.r174-owner-credential.v1'||credential.algorithm!=='ES256'||
69
+ typeof credential.rp_id!=='string'||!credential.rp_id||typeof credential.origin!=='string'||
70
+ !Number.isSafeInteger(credential.sign_count)||credential.sign_count<0)
71
+ throw new Error('R174 credential identity invalid');
72
+ if(credential.origin!=='https://'+credential.rp_id)
73
+ throw new Error('R174 RP ID and origin binding mismatch');
74
+ const credentialId=base64url(credential.credential_id_b64url,'credential id',16,1024);
75
+ keys(assertion,['credential_id_b64url','client_data_json_b64url',
76
+ 'authenticator_data_b64url','signature_b64url'],'assertion');
77
+ if(!matches(credentialId,base64url(assertion.credential_id_b64url,'credential id',16,1024)))
78
+ throw new Error('R174 credential id mismatch');
79
+ const rawClient=base64url(assertion.client_data_json_b64url,'client data',16,16384);
80
+ let client;
81
+ try{client=JSON.parse(new TextDecoder('utf-8',{fatal:true}).decode(rawClient));}
82
+ catch{throw new Error('R174 client data JSON invalid');}
83
+ if(!client||typeof client!=='object'||Array.isArray(client)||client.type!=='webauthn.get')
84
+ throw new Error('R174 client data type invalid');
85
+ if(client.challenge!==transaction.challenge_b64url||
86
+ !matches(base64url(client.challenge,'challenge',32,32),challenge))
87
+ throw new Error('R174 client challenge mismatch');
88
+ if(client.origin!==credential.origin)throw new Error('R174 client origin mismatch');
89
+ if(client.crossOrigin===true||client.topOrigin!==undefined)
90
+ throw new Error('R174 cross-origin assertion denied');
91
+ const authenticator=base64url(assertion.authenticator_data_b64url,'authenticator data',37,16384);
92
+ const rpIdHash=sha256(Buffer.from(credential.rp_id,'utf8'));
93
+ if(!matches(authenticator.subarray(0,32),rpIdHash))
94
+ throw new Error('R174 RP ID hash mismatch');
95
+ const flags=authenticator[32];
96
+ if((flags&0x01)!==0x01)throw new Error('R174 user presence required');
97
+ if((flags&0x04)!==0x04)throw new Error('R174 user verification required');
98
+ const signCount=authenticator.readUInt32BE(33);
99
+ // Zero counters are common with platform authenticators. Replay resistance
100
+ // for such devices MUST be enforced by a one-time protected challenge journal.
101
+ if(credential.sign_count>0&&signCount<=credential.sign_count)
102
+ throw new Error('R174 sign counter rollback');
103
+ let key;
104
+ try{key=createPublicKey(credential.public_key_spki_pem);}
105
+ catch{throw new Error('R174 owner key invalid');}
106
+ if(key.asymmetricKeyType!=='ec'||key.asymmetricKeyDetails?.namedCurve!=='prime256v1')
107
+ throw new Error('R174 owner key must be ES256 P-256');
108
+ const signed=Buffer.concat([authenticator,sha256(rawClient)]);
109
+ const signature=base64url(assertion.signature_b64url,'signature',8,256);
110
+ if(!verify('sha256',signed,key,signature))
111
+ throw new Error('R174 signature verification failed');
112
+ return Object.freeze({status:'verified',subject_sha256:transaction.subject_sha256,
113
+ challenge_b64url:transaction.challenge_b64url,credential_id_b64url:credential.credential_id_b64url,
114
+ sign_count:signCount,user_verified:true,
115
+ // NOT an authority grant. Protected challenge consumption still mandatory.
116
+ authority_granted:false});
117
+ }
118
+
119
+ // The protected-host use-once boundary is intentionally separate from signature verification.
120
+ export async function acceptR174OwnerProof({ledgerRoot,ownerTrust,...inputs}={}){
121
+ // A trusted caller MUST load ownerTrust from an independently pinned
122
+ // owner-controlled trust store, not copy it from request JSON. This
123
+ // primitive deliberately does not authenticate its caller or issue grants.
124
+ keys(ownerTrust,['schema','credential_id_b64url','public_key_spki_sha256',
125
+ 'rp_id','origin','subject_sha256'],'owner trust');
126
+ if(ownerTrust.schema!=='deadbyte.r174-owner-trust-binding.v1'||
127
+ typeof ownerTrust.public_key_spki_sha256!=='string'||
128
+ !HEX64.test(ownerTrust.public_key_spki_sha256)||
129
+ typeof ownerTrust.subject_sha256!=='string'||
130
+ !HEX64.test(ownerTrust.subject_sha256))
131
+ throw new Error('R174 owner trust metadata invalid');
132
+ const {credential,transaction,subject}=inputs;
133
+ const credentialKey=createPublicKey(credential?.public_key_spki_pem??'');
134
+ const keySha=sha256(credentialKey.export({type:'spki',format:'der'})).toString('hex');
135
+ if(keySha!==ownerTrust.public_key_spki_sha256 ||
136
+ ownerTrust.credential_id_b64url!==credential?.credential_id_b64url ||
137
+ ownerTrust.rp_id!==credential?.rp_id ||
138
+ ownerTrust.origin!==credential?.origin ||
139
+ ownerTrust.subject_sha256!==transaction?.subject_sha256 ||
140
+ ownerTrust.subject_sha256!==canonicalHash(subject))
141
+ throw new Error('R174 owner trust credential or reviewed subject binding mismatch');
142
+ // Then check signed owner assertion. Invalid assertions must never
143
+ // consume a challenge or produce a receipt.
144
+ const proof=await verifyR174OwnerAssertion(inputs);
145
+ if(typeof ledgerRoot!=='string'||!path.isAbsolute(ledgerRoot))
146
+ throw new Error('R174 replay ledger root must be absolute');
147
+ const dir=await lstat(ledgerRoot);
148
+ if(!dir.isDirectory()||dir.isSymbolicLink()||
149
+ path.resolve(await realpath(ledgerRoot)).toLowerCase()!==path.resolve(ledgerRoot).toLowerCase())
150
+ throw new Error('R174 replay ledger must be a real directory');
151
+ // Caller must independently attest protected ACLs and owner-only trust.
152
+ // This primitive does NOT establish those conditions or grant authority.
153
+ // A challenge is globally one-use within this protected journal regardless
154
+ // of owner credential or scoped subject. Identity is checked by the signature
155
+ // verifier; it must not widen the replay key.
156
+ const id=sha256(Buffer.concat([
157
+ Buffer.from('DEADBYTE-R174-OWNER-CHALLENGE-CONSUMPTION-v1','utf8'),
158
+ base64url(proof.challenge_b64url,'challenge',32,32)
159
+ ])).toString('hex');
160
+ const file=path.join(ledgerRoot,'used-'+id+'.json');
161
+ const receipt={schema:'deadbyte.r174-consumed-owner-challenge.v1',
162
+ id,subject_sha256:proof.subject_sha256,credential_id_b64url:proof.credential_id_b64url,
163
+ challenge_b64url:proof.challenge_b64url,sign_count:proof.sign_count,
164
+ authority_granted:false,consumed_at_utc:new Date().toISOString()};
165
+ let handle;
166
+ try{handle=await open(file,'wx',0o600);}
167
+ catch(error){
168
+ if(error?.code==='EEXIST')throw new Error('R174 owner challenge replay denied');
169
+ throw error;
170
+ }
171
+ try{
172
+ await handle.writeFile(JSON.stringify(receipt)+'\n');
173
+ await handle.sync();
174
+ }finally{await handle.close();}
175
+ return Object.freeze({...proof,status:'consumed',id,authority_granted:false});
176
+ }
@@ -0,0 +1,28 @@
1
+ // R175: foreground log sessions never own operator standing grants.
2
+ import {AUTHORITY_PLANES} from './remote-authority.mjs';
3
+ export function observeOnlyAuthority(snapshot){
4
+ const expected={};
5
+ if(snapshot?.state!=='ONLINE')throw new Error('R175 foreground requires ONLINE runtime');
6
+ for(const plane of AUTHORITY_PLANES){
7
+ const observed=snapshot.authority?.[plane];
8
+ if(!observed||typeof observed.armed!=='boolean'||typeof observed.configured!=='boolean')
9
+ throw new Error('R175 foreground authority observation invalid: '+plane);
10
+ expected[plane]=observed.armed;
11
+ }
12
+ return Object.freeze({authority_scope:'observe-only',expected:Object.freeze(expected),
13
+ commands:Object.freeze([])});
14
+ }
15
+ export function assertObserveOnlyMarker(marker){
16
+ return marker?.schema==='deadbyte.remote-session.v2'&&
17
+ marker.authority_scope==='observe-only'&&
18
+ Number.isInteger(marker.owner_pid)&&marker.owner_pid>0&&
19
+ typeof marker.session_nonce==='string'&&/^[0-9a-f]{64}$/.test(marker.session_nonce);
20
+ }
21
+
22
+ export function assertMatchingForegroundRelease(active,{version,manifest_sha256}={}){
23
+ if(!active||typeof version!=='string'||!/^[0-9]+\.[0-9]+\.[0-9]+$/.test(version)||
24
+ typeof manifest_sha256!=='string'||!/^[0-9a-f]{64}$/.test(manifest_sha256)||
25
+ active.version!==version||active.manifest_sha256!==manifest_sha256)
26
+ throw new Error('R175_EXACT_ACTIVE_RELEASE_REQUIRED: observation session will not activate, downgrade or upgrade runtime');
27
+ return active;
28
+ }
@@ -0,0 +1,12 @@
1
+ import {createHash,createPublicKey} from 'node:crypto';
2
+
3
+ export function createR186OwnerAnchor(publicKeyPem){
4
+ if(typeof publicKeyPem!=='string'||publicKeyPem.length<32||publicKeyPem.length>8192||/PRIVATE KEY/.test(publicKeyPem))
5
+ throw new Error('R186 owner enrollment requires a public PEM only');
6
+ let key;try{key=createPublicKey(publicKeyPem);}catch{throw new Error('R186 owner public key invalid');}
7
+ if(key.asymmetricKeyType!=='ed25519')throw new Error('R186 owner public key must be Ed25519');
8
+ const canonicalPem=key.export({type:'spki',format:'pem'}).toString();
9
+ const owner_key_id=createHash('sha256').update(key.export({type:'spki',format:'der'})).digest('hex');
10
+ return Object.freeze({anchor:Object.freeze({schema:'deadbyte.r186-owner-anchor.v1',owner_key_id}),
11
+ public_key_pem:canonicalPem});
12
+ }
@@ -0,0 +1,63 @@
1
+ // R186: only a read-only verifier for independently enrolled owner consent.
2
+ // A successful result MUST NOT be treated as an Autonomous grant. The caller
3
+ // must separately prove protected enrollment, single-use consumption and epoch.
4
+ import {createHash,createPublicKey} from 'node:crypto';
5
+ import path from 'node:path';
6
+ import {verifyProjectOwnerApproval} from './r70-owner-approval.mjs';
7
+
8
+ const SHA=/^[0-9a-f]{64}$/;
9
+ function exact(value,fields,label){
10
+ if(!value||typeof value!=='object'||Array.isArray(value)||
11
+ Object.keys(value).length!==fields.length||
12
+ fields.some(field=>!Object.hasOwn(value,field)))
13
+ throw new Error('R186 '+label+' invalid');
14
+ }
15
+ export async function verifyR186OwnerConsent({ownerTrust,ownerPublicKeyPem,approval,expected,now=new Date()}={}){
16
+ exact(ownerTrust,['schema','owner_key_id'],'owner anchor');
17
+ if(ownerTrust.schema!=='deadbyte.r186-owner-anchor.v1'||
18
+ typeof ownerTrust.owner_key_id!=='string'||!SHA.test(ownerTrust.owner_key_id))
19
+ throw new Error('R186 owner anchor schema or fingerprint invalid');
20
+ if(typeof ownerPublicKeyPem!=='string'||ownerPublicKeyPem.length>8192)
21
+ throw new Error('R186 owner anchor public key invalid');
22
+ let pub;
23
+ try{pub=createPublicKey(ownerPublicKeyPem);}catch{throw new Error('R186 owner anchor public key invalid');}
24
+ if(pub.asymmetricKeyType!=='ed25519')throw new Error('R186 owner anchor must be Ed25519');
25
+ const keySha=createHash('sha256').update(pub.export({type:'spki',format:'der'})).digest('hex');
26
+ if(ownerTrust.owner_key_id!==keySha)
27
+ throw new Error('R186 owner anchor public key mismatch');
28
+ const approved=await verifyProjectOwnerApproval({approval,ownerPublicKey:pub,expected,now});
29
+ return Object.freeze({status:'verified_readonly',authority_granted:false,
30
+ owner_key_id:keySha,subject_sha256:approved.subject_sha256,
31
+ root_id:approved.root_id,project_root:approved.project_root,
32
+ expires_at_utc:approved.expires_at_utc,
33
+ next_gate:'PROTECTED_OWNER_ENROLLMENT_AND_SINGLE_USE_EPOCH_ISSUANCE_REQUIRED'});
34
+ }
35
+
36
+ export function verifyR186PolicyRootBinding({
37
+ codingPolicy,autonomousPolicy,rootId,projectRoot,codingPolicySha256
38
+ }={}){
39
+ if(!codingPolicy||typeof codingPolicy!=='object'||
40
+ codingPolicy.schema!=='deadbyte.coding-policy.v1'||codingPolicy.enabled!==true)
41
+ throw new Error('R186 coding policy schema invalid');
42
+ if(!autonomousPolicy||typeof autonomousPolicy!=='object'||
43
+ autonomousPolicy.schema!=='deadbyte.autonomous-policy.v1'||autonomousPolicy.enabled!==true)
44
+ throw new Error('R186 autonomous policy schema invalid');
45
+ if(typeof rootId!=='string'||!/^[A-Za-z0-9._-]{1,64}$/.test(rootId)||
46
+ !Array.isArray(autonomousPolicy.root_ids)||
47
+ autonomousPolicy.root_ids.length!==1||autonomousPolicy.root_ids[0]!==rootId)
48
+ throw new Error('R186 Autonomous root vector exceeds owner-reviewed scope');
49
+ if(typeof codingPolicySha256!=='string'||!SHA.test(codingPolicySha256)||
50
+ autonomousPolicy.coding_policy_sha256!==codingPolicySha256)
51
+ throw new Error('R186 coding-policy digest binding mismatch');
52
+ const bound=codingPolicy.roots?.[rootId]?.path;
53
+ if(typeof bound!=='string'||typeof projectRoot!=='string'||
54
+ !path.win32.isAbsolute(bound)||!path.win32.isAbsolute(projectRoot)||
55
+ bound.startsWith('\\\\')||projectRoot.startsWith('\\\\'))
56
+ throw new Error('R186 coding project root is not a real absolute drive path');
57
+ const actual=path.win32.normalize(bound).toLowerCase();
58
+ const requested=path.win32.normalize(projectRoot).toLowerCase();
59
+ if(actual!==requested||
60
+ actual===path.win32.parse(actual).root.toLowerCase())
61
+ throw new Error('R186 reviewed root must match selected Coding project root');
62
+ return Object.freeze({root_id:rootId,project_root:bound});
63
+ }
@@ -0,0 +1,96 @@
1
+ // Single-use R186 Autonomous grant issuance. This module does not enroll owner
2
+ // keys and accepts no caller-selected authority scope: the policy, reviewed
3
+ // manifest and independently protected owner anchor are the complete subject.
4
+ import {createHash,createPrivateKey,createPublicKey,randomBytes,sign,verify} from 'node:crypto';
5
+ import {mkdir,open,readFile,writeFile} from 'node:fs/promises';
6
+ import path from 'node:path';
7
+ import {canonicalJson} from './canonical-json.mjs';
8
+ import {createAuthorityEpochStore} from './authority-continuity-r65.mjs';
9
+ import {verifyR186OwnerConsent,verifyR186PolicyRootBinding} from './r186-owner-consent-preflight.mjs';
10
+
11
+ const SHA=/^[0-9a-f]{64}$/;
12
+ const TERMINAL_DOMAIN=Buffer.from('DEADBYTE-R186-OWNER-GRANT-TERMINAL\0','utf8');
13
+ const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
14
+ const bytes=value=>Buffer.from(canonicalJson(value),'utf8');
15
+ async function writeExclusive(file,data){
16
+ await mkdir(path.dirname(file),{recursive:true});
17
+ const handle=await open(file,'wx',0o600);
18
+ try{await handle.writeFile(data);await handle.sync();}
19
+ finally{await handle.close();}
20
+ }
21
+ function insist(condition,message){if(!condition)throw new Error(`R186 owner grant blocked: ${message}`);}
22
+ async function signTerminal(runtimeDir,body){
23
+ const trustDir=path.join(path.dirname(path.resolve(runtimeDir)),'trust');
24
+ const [privatePem,publicPem]=await Promise.all([
25
+ readFile(path.join(trustDir,'ed25519-private.pem')),readFile(path.join(trustDir,'ed25519-public.pem'))]);
26
+ const privateKey=createPrivateKey(privatePem),publicKey=createPublicKey(publicPem);
27
+ insist(privateKey.asymmetricKeyType==='ed25519'&&publicKey.asymmetricKeyType==='ed25519','machine receipt key type invalid');
28
+ const privateKeyId=sha(createPublicKey(privateKey).export({type:'spki',format:'der'}));
29
+ const publicKeyId=sha(publicKey.export({type:'spki',format:'der'}));
30
+ insist(privateKeyId===publicKeyId,'machine receipt key pair mismatch');
31
+ const signature=sign(null,Buffer.concat([TERMINAL_DOMAIN,bytes(body)]),privateKey);
32
+ insist(verify(null,Buffer.concat([TERMINAL_DOMAIN,bytes(body)]),publicKey,signature),'machine terminal signature self-check failed');
33
+ return Object.freeze({algorithm:'ed25519',domain:'DEADBYTE-R186-OWNER-GRANT-TERMINAL',key_id:publicKeyId,signature_base64:signature.toString('base64')});
34
+ }
35
+
36
+ export async function issueR186AutonomousGrant({
37
+ runtimeDir,grantFile,codingPolicy,codingPolicyBytes,autonomousPolicy,autonomousPolicyBytes,
38
+ releaseManifestBytes,ownerTrust,ownerPublicKeyPem,approval,now=new Date(),epochStore=null
39
+ }={}){
40
+ insist(typeof runtimeDir==='string'&&path.isAbsolute(runtimeDir),'runtime directory invalid');
41
+ insist(typeof grantFile==='string'&&path.isAbsolute(grantFile),'grant path invalid');
42
+ for(const [name,value] of [['Coding policy',codingPolicyBytes],['Autonomous policy',autonomousPolicyBytes],['release manifest',releaseManifestBytes]])
43
+ insist(Buffer.isBuffer(value)&&value.length>0,`${name} bytes invalid`);
44
+ insist(autonomousPolicy?.schema==='deadbyte.autonomous-policy.v1'&&autonomousPolicy.enabled===true,'Autonomous policy invalid');
45
+ insist(path.resolve(autonomousPolicy.lease_file)===path.resolve(grantFile),'policy grant path mismatch');
46
+ const runtimePrefix=path.resolve(runtimeDir)+path.sep;
47
+ insist(path.resolve(grantFile).startsWith(runtimePrefix),'grant path outside runtime');
48
+ const codingSha=sha(codingPolicyBytes),autonomousSha=sha(autonomousPolicyBytes),manifestSha=sha(releaseManifestBytes);
49
+ const rootId=Array.isArray(autonomousPolicy.root_ids)&&autonomousPolicy.root_ids.length===1?autonomousPolicy.root_ids[0]:null;
50
+ const projectRoot=rootId?codingPolicy?.roots?.[rootId]?.path:null;
51
+ const binding=verifyR186PolicyRootBinding({codingPolicy,autonomousPolicy,rootId,projectRoot,codingPolicySha256:codingSha});
52
+ const consent=await verifyR186OwnerConsent({ownerTrust,ownerPublicKeyPem,approval,now,expected:{
53
+ rootId:binding.root_id,projectRoot:binding.project_root,codingPolicySha256:codingSha,
54
+ autonomousPolicySha256:autonomousSha,reviewedArtifactSha256:manifestSha
55
+ }});
56
+ const evidenceDir=path.join(runtimeDir,'owner-consent','r186');
57
+ const claimPath=path.join(evidenceDir,`${consent.subject_sha256}.claim.json`);
58
+ const terminalPath=path.join(evidenceDir,`${consent.subject_sha256}.terminal.json`);
59
+ try{await readFile(terminalPath);throw new Error('R186 owner approval already consumed; replay denied');}
60
+ catch(error){if(error?.code!=='ENOENT')throw error;}
61
+ const nonce=randomBytes(32).toString('hex');
62
+ const claim={schema:'deadbyte.r186-owner-grant-claim.v1',subject_sha256:consent.subject_sha256,
63
+ owner_key_id:consent.owner_key_id,policy_sha256:autonomousSha,coding_policy_sha256:codingSha,
64
+ release_manifest_sha256:manifestSha,root_id:binding.root_id,project_root:binding.project_root,
65
+ grant_instance_nonce:nonce,claimed_at_utc:now.toISOString()};
66
+ try{await writeExclusive(claimPath,bytes(claim));}
67
+ catch(error){if(error?.code==='EEXIST')throw new Error('R186 owner approval already consumed or in progress; replay denied');throw error;}
68
+ const grant={schema:'deadbyte.autonomous-grant.v2',policy_sha256:autonomousSha,
69
+ coding_policy_sha256:codingSha,instance_nonce:nonce,root_ids:[binding.root_id],
70
+ granted_at_utc:now.toISOString(),expires_at_utc:consent.expires_at_utc};
71
+ const grantBytes=bytes(grant);
72
+ try{await writeExclusive(grantFile,grantBytes);}
73
+ catch(error){throw Object.assign(new Error(`R186 grant write failed after durable claim: ${error?.message??error}`),{outcome:'OUTCOME_UNKNOWN'});}
74
+ const store=epochStore??createAuthorityEpochStore({runtimeDir,policyBytes:autonomousPolicyBytes});
75
+ let head;
76
+ try{head=await store.append({plane:'autonomous',action:'grant',grantBytes,grantInstanceNonce:nonce,updateId:null});}
77
+ catch(error){throw Object.assign(new Error(`R186 epoch issuance failed after grant write: ${error?.message??error}`),{outcome:'OUTCOME_UNKNOWN'});}
78
+ insist(SHA.test(String(head?.event_sha256)),'epoch identity invalid after issuance');
79
+ const verified=await store.verifyGrant({plane:'autonomous',grantBytes,grantInstanceNonce:nonce,policyBytes:autonomousPolicyBytes});
80
+ insist(verified?.trusted===true&&verified.event_sha256===head.event_sha256,'grant epoch read-back failed');
81
+ insist((await readFile(grantFile)).equals(grantBytes),'grant changed after epoch issuance');
82
+ const terminalBody={schema:'deadbyte.r186-owner-grant-terminal.v1',status:'GRANTED',
83
+ subject_sha256:consent.subject_sha256,owner_key_id:consent.owner_key_id,
84
+ claim_sha256:sha(bytes(claim)),grant_sha256:sha(grantBytes),grant_instance_nonce:nonce,
85
+ policy_sha256:autonomousSha,coding_policy_sha256:codingSha,release_manifest_sha256:manifestSha,
86
+ root_id:binding.root_id,project_root:binding.project_root,epoch_event_sha256:head.event_sha256,
87
+ epoch_seq:head.seq,expires_at_utc:consent.expires_at_utc,completed_at_utc:new Date().toISOString()};
88
+ const attestation=await signTerminal(runtimeDir,terminalBody);
89
+ const unsigned={body:terminalBody,attestation};
90
+ const terminal={...unsigned,receipt_sha256:sha(bytes(unsigned))};
91
+ try{await writeExclusive(terminalPath,bytes(terminal));}
92
+ catch(error){throw Object.assign(new Error(`R186 terminal receipt write failed: ${error?.message??error}`),{outcome:'OUTCOME_UNKNOWN'});}
93
+ return Object.freeze({status:'GRANTED',authority_granted:true,subject_sha256:consent.subject_sha256,
94
+ grant_sha256:terminalBody.grant_sha256,epoch_event_sha256:head.event_sha256,epoch_seq:head.seq,
95
+ terminal_path:terminalPath,receipt_sha256:terminal.receipt_sha256});
96
+ }